Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub async fn run_analysis(
sqlite_path: Option<PathBuf>,
run_id: Option<i64>,
file_path: Option<String>,
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) {
Expand All @@ -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).
Expand All @@ -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 {
Expand Down
67 changes: 43 additions & 24 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<MutantRow>> {
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<MutantRow> = 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::<rusqlite::Result<_>>()?
} else {
stmt.query_map(params![run_id], |row| {
Ok(MutantRow {
id: row.get(0)?,
diff: row.get(1)?,
file_path: row.get(2)?,
})
})?
.collect::<rusqlite::Result<_>>()?
let rows: Vec<MutantRow> = 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::<rusqlite::Result<_>>()?;
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::<rusqlite::Result<_>>()?;
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::<rusqlite::Result<_>>()?;
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::<rusqlite::Result<_>>()?;
rows
}
};

Ok(rows)
Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ enum Commands {
/// Only analyze mutants for this file path (requires --run_id)
#[arg(long)]
file_path: Option<String>,

/// Only analyze mutants that survived a previous run (requires --run_id)
#[arg(long)]
survivors_only: bool,
},
}

Expand Down Expand Up @@ -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(
Expand All @@ -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?;
}
}
Expand Down
Loading