diff --git a/src/analyze.rs b/src/analyze.rs index 66a2a75..a6cd68c 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -18,6 +18,7 @@ pub async fn run_analysis( sqlite_path: Option, run_id: Option, file_path: Option, + survivors_only: bool, ) -> Result<()> { // DB-based analysis mode: read mutants from DB and test them. if let (Some(ref path), Some(rid)) = (sqlite_path.as_ref(), run_id) { @@ -29,7 +30,15 @@ pub async fn run_analysis( let db = Database::open(path)?; db.ensure_schema()?; db.seed_projects()?; - return run_db_analysis(&db, rid, &command, timeout_secs, file_path.as_deref()).await; + return run_db_analysis( + &db, + rid, + &command, + timeout_secs, + file_path.as_deref(), + survivors_only, + ) + .await; } // Folder-based analysis mode (existing behaviour). @@ -55,20 +64,26 @@ pub async fn run_analysis( } /// Test all pending mutants in `run_id` from the database, optionally filtered by `file_path`. +/// When `survivors_only` is true, only previously survived mutants are analyzed. async fn run_db_analysis( db: &Database, run_id: i64, command: &str, timeout_secs: u64, file_path: Option<&str>, + survivors_only: bool, ) -> Result<()> { - let mutants = db.get_mutants_for_run(run_id, file_path)?; + let mutants = db.get_mutants_for_run(run_id, file_path, survivors_only)?; let total = mutants.len(); - if let Some(fp) = file_path { - println!("* {} MUTANTS in run_id={} (file: {}) *", total, run_id, fp); - } else { - println!("* {} MUTANTS in run_id={} *", total, run_id); + match (file_path, survivors_only) { + (Some(fp), true) => println!( + "* {} SURVIVING MUTANTS in run_id={} (file: {}) *", + total, run_id, fp + ), + (Some(fp), false) => println!("* {} MUTANTS in run_id={} (file: {}) *", total, run_id, fp), + (None, true) => println!("* {} SURVIVING MUTANTS in run_id={} *", total, run_id), + (None, false) => println!("* {} MUTANTS in run_id={} *", total, run_id), } if total == 0 { diff --git a/src/db.rs b/src/db.rs index 7a40c3f..5da2301 100644 --- a/src/db.rs +++ b/src/db.rs @@ -155,37 +155,56 @@ impl Database { } /// Return mutants belonging to `run_id`, optionally filtered by `file_path`. + /// When `survivors_only` is true, only mutants with status `'survived'` are returned. pub fn get_mutants_for_run( &self, run_id: i64, file_path: Option<&str>, + survivors_only: bool, ) -> Result> { - let sql = if file_path.is_some() { - "SELECT id, diff, file_path FROM mutants WHERE run_id = ?1 AND file_path = ?2" - } else { - "SELECT id, diff, file_path FROM mutants WHERE run_id = ?1" + let map_row = |row: &rusqlite::Row<'_>| { + Ok(MutantRow { + id: row.get(0)?, + diff: row.get(1)?, + file_path: row.get(2)?, + }) }; - let mut stmt = self.conn.prepare(sql)?; - - let rows: Vec = if let Some(fp) = file_path { - stmt.query_map(params![run_id, fp], |row| { - Ok(MutantRow { - id: row.get(0)?, - diff: row.get(1)?, - file_path: row.get(2)?, - }) - })? - .collect::>()? - } else { - stmt.query_map(params![run_id], |row| { - Ok(MutantRow { - id: row.get(0)?, - diff: row.get(1)?, - file_path: row.get(2)?, - }) - })? - .collect::>()? + let rows: Vec = match (file_path, survivors_only) { + (Some(fp), false) => { + let mut stmt = self.conn.prepare( + "SELECT id, diff, file_path FROM mutants WHERE run_id = ?1 AND file_path = ?2", + )?; + let rows = stmt.query_map(params![run_id, fp], map_row)? + .collect::>()?; + rows + } + (Some(fp), true) => { + let mut stmt = self.conn.prepare( + "SELECT id, diff, file_path FROM mutants \ + WHERE run_id = ?1 AND file_path = ?2 AND status = 'survived'", + )?; + let rows = stmt.query_map(params![run_id, fp], map_row)? + .collect::>()?; + rows + } + (None, false) => { + let mut stmt = self.conn.prepare( + "SELECT id, diff, file_path FROM mutants WHERE run_id = ?1", + )?; + let rows = stmt.query_map(params![run_id], map_row)? + .collect::>()?; + rows + } + (None, true) => { + let mut stmt = self.conn.prepare( + "SELECT id, diff, file_path FROM mutants \ + WHERE run_id = ?1 AND status = 'survived'", + )?; + let rows = stmt.query_map(params![run_id], map_row)? + .collect::>()?; + rows + } }; Ok(rows) diff --git a/src/main.rs b/src/main.rs index 5cdc607..9a5fcc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,6 +103,10 @@ enum Commands { /// Only analyze mutants for this file path (requires --run_id) #[arg(long)] file_path: Option, + + /// Only analyze mutants that survived a previous run (requires --run_id) + #[arg(long)] + survivors_only: bool, }, } @@ -186,6 +190,7 @@ async fn main() -> Result<()> { sqlite, run_id, file_path, + survivors_only, } => { if run_id.is_some() && sqlite.is_none() { return Err(MutationError::InvalidInput( @@ -199,7 +204,7 @@ async fn main() -> Result<()> { )); } - analyze::run_analysis(folder, command, jobs, timeout, survival_threshold, sqlite, run_id, file_path) + analyze::run_analysis(folder, command, jobs, timeout, survival_threshold, sqlite, run_id, file_path, survivors_only) .await?; } }