Skip to content

Commit

Permalink
Merge pull request #1 from paxosglobal/jard-inc-runs
Browse files Browse the repository at this point in the history
read positive outcomes file and filter mutants
  • Loading branch information
cl3joly authored Dec 1, 2023
2 parents d1e8b50 + 41810c1 commit 0f8c89c
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 67 deletions.
47 changes: 37 additions & 10 deletions src/incremental.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
// Copyright 2023 Paxos

//! Logic for incremantal runs
use crate::{
mutate::{Mutant, MutantHash},
options::Options,
output::{PositiveOutcome, OUTDIR_NAME, POSITIVE_OUTCOMES_FILE},
};
use anyhow::{anyhow, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::{collections::HashSet, fs};

use crate::{mutate::Mutant, output::PositiveOutcomes};

pub fn filter(mutants: Vec<Mutant>) -> (Option<PositiveOutcomes>, Vec<Mutant>) {
let last_positive_outcomes = read_last_positive_outcomes();
// TODO Exclude mutants whose hash is in the last positive outcomes
let mutants = mutants.into_iter().filter(|m| todo!());

(last_positive_outcomes, mutants.collect())
pub fn filter_by_last_positive_outcomes(
mutants: Vec<Mutant>,
dir: &Utf8PathBuf,
options: &Options,
) -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) {
let read_path: &Utf8Path = options.output_in_dir.as_ref().map_or(dir, |p| p.as_path());
// TODO: add logging here for error cases
let last_positive_outcomes = match read_last_positive_outcomes(read_path) {
Ok(outcomes) => Some(outcomes),
Err(_) => None,
};
// if last_positive_outcomes is none the hash set will be empty thereby allowing all mutants to be considered
let existing_mutants: HashSet<MutantHash> = last_positive_outcomes
.iter()
.flatten()
.map(|o| o.mutant_hash())
.collect();
let mutants = mutants
.into_iter()
.filter(|m| !existing_mutants.contains(&m.calculate_hash()))
.collect();
(last_positive_outcomes, mutants)
}

fn read_last_positive_outcomes() -> Option<PositiveOutcomes> {
todo!()
fn read_last_positive_outcomes(read_path: &Utf8Path) -> Result<Vec<PositiveOutcome>> {
let path = read_path.join(OUTDIR_NAME).join(POSITIVE_OUTCOMES_FILE);
let json_str = fs::read_to_string(path.clone());
match json_str {
Ok(contents) => serde_json::from_str(&contents).map_err(|e| anyhow!("{}", e)),
Err(e) => Err(anyhow!("{}", e)),
}
}
4 changes: 2 additions & 2 deletions src/lab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tracing::{debug, debug_span, error, info, trace};
use crate::cargo::run_cargo;
use crate::console::Console;
use crate::outcome::{LabOutcome, Phase, ScenarioOutcome};
use crate::output::{OutputDir, PositiveOutcomes};
use crate::output::{OutputDir, PositiveOutcome};
use crate::package::Package;
use crate::*;

Expand All @@ -29,7 +29,7 @@ pub fn test_mutants(
workspace_dir: &Utf8Path,
options: Options,
console: &Console,
last_positive_outcomes: Option<PositiveOutcomes>,
last_positive_outcomes: Option<Vec<PositiveOutcome>>,
) -> Result<LabOutcome> {
let start_time = Instant::now();
let output_in_dir: &Utf8Path = options
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,8 @@ fn main() -> Result<()> {
}
let mut last_positive_outcomes = None;
if args.incremental {
(last_positive_outcomes, mutants) = incremental::filter(mutants);
(last_positive_outcomes, mutants) =
incremental::filter_by_last_positive_outcomes(mutants, &workspace.dir, &options);
}
if args.list {
list_mutants(FmtToIoWrite::new(io::stdout()), &mutants, &options)?;
Expand Down
1 change: 0 additions & 1 deletion src/mutate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

//! Mutations to source files, and inference of interesting mutations to apply.
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::fs;
use std::hash::Hash;
Expand Down
76 changes: 23 additions & 53 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use std::time::Duration;
use anyhow::{Context, Result};
use camino::Utf8Path;
use fs2::FileExt;
use itertools::Itertools;
use path_slash::PathExt;
use serde::{Deserialize, Serialize};
use time::format_description::well_known::Rfc3339;
Expand All @@ -22,8 +21,10 @@ use crate::mutate::MutantHash;
use crate::outcome::{LabOutcome, SummaryOutcome};
use crate::*;

const OUTDIR_NAME: &str = "mutants.out";
const ROTATED_NAME: &str = "mutants.out.old";
pub const OUTDIR_NAME: &str = "mutants.out";
pub const ROTATED_NAME: &str = "mutants.out.old";
pub const OUTCOMES_FILE: &str = "outcomes.json";
pub const POSITIVE_OUTCOMES_FILE: &str = "positive_outcomes.json";
const LOCK_JSON: &str = "lock.json";
const LOCK_POLL: Duration = Duration::from_millis(100);

Expand Down Expand Up @@ -100,7 +101,7 @@ pub struct OutputDir {
/// A file holding a list of mutants where testing timed out, as text, one per line.
timeout_list: File,
unviable_list: File,
last_positive_outcomes: Option<PositiveOutcomes>,
last_positive_outcomes: Option<Vec<PositiveOutcome>>,
/// The accumulated overall lab outcome.
pub lab_outcome: LabOutcome,
}
Expand All @@ -118,7 +119,7 @@ impl OutputDir {
/// the lock to be released. The returned `OutputDir` holds a lock for its lifetime.
pub fn new(
in_dir: &Utf8Path,
last_positive_outcomes: Option<PositiveOutcomes>,
last_positive_outcomes: Option<Vec<PositiveOutcome>>,
) -> Result<OutputDir> {
if !in_dir.exists() {
fs::create_dir(in_dir).context("create output parent directory {in_dir:?}")?;
Expand Down Expand Up @@ -189,20 +190,20 @@ impl OutputDir {
///
/// Called multiple times as the lab runs.
pub fn maybe_write_positive_outcomes(&self) -> Result<()> {
if let Some(last_positive_outcomes) = self.last_positive_outcomes {
if let Some(last_positive_outcomes) = &self.last_positive_outcomes {
let positive_outcomes = self
.lab_outcome
.outcomes
.iter()
.filter_map(|o: &ScenarioOutcome| &PositiveOutcome::try_from(o).ok())
.chain(last_positive_outcomes.iter())
.collect::<&PositiveOutcomes>();
.filter_map(|o: &ScenarioOutcome| PositiveOutcome::try_from(o).ok())
.chain(last_positive_outcomes.iter().cloned())
.collect::<Vec<PositiveOutcome>>();

serde_json::to_writer_pretty(
BufWriter::new(File::create(self.path.join("positive_outcomes.json"))?),
BufWriter::new(File::create(self.path.join(POSITIVE_OUTCOMES_FILE))?),
&positive_outcomes,
)
.context("write positive_outcomes.json")
.context(format!("write {}", POSITIVE_OUTCOMES_FILE))
} else {
Ok(())
}
Expand All @@ -213,10 +214,10 @@ impl OutputDir {
/// Called multiple times as the lab runs.
pub fn write_lab_outcome(&self) -> Result<()> {
serde_json::to_writer_pretty(
BufWriter::new(File::create(self.path.join("outcomes.json"))?),
BufWriter::new(File::create(self.path.join(OUTCOMES_FILE))?),
&self.lab_outcome,
)
.context("write outcomes.json")
.context(format!("write {}", OUTCOMES_FILE))
}

/// Add the result of testing one scenario.
Expand Down Expand Up @@ -312,7 +313,7 @@ mod test {
let tmp = minimal_source_tree();
let tmp_path: &Utf8Path = tmp.path().try_into().unwrap();
let workspace = Workspace::open(tmp_path).unwrap();
let output_dir = OutputDir::new(&workspace.dir).unwrap();
let output_dir = OutputDir::new(&workspace.dir, None).unwrap();
assert_eq!(
list_recursive(tmp.path()),
&[
Expand Down Expand Up @@ -340,7 +341,7 @@ mod test {
let temp_dir_path = Utf8Path::from_path(temp_dir.path()).unwrap();

// Create an initial output dir with one log.
let output_dir = OutputDir::new(temp_dir_path).unwrap();
let output_dir = OutputDir::new(temp_dir_path, None).unwrap();
output_dir.create_log(&Scenario::Baseline).unwrap();
assert!(temp_dir
.path()
Expand All @@ -349,7 +350,7 @@ mod test {
drop(output_dir); // release the lock.

// The second time we create it in the same directory, the old one is moved away.
let output_dir = OutputDir::new(temp_dir_path).unwrap();
let output_dir = OutputDir::new(temp_dir_path, None).unwrap();
output_dir.create_log(&Scenario::Baseline).unwrap();
assert!(temp_dir
.path()
Expand All @@ -362,7 +363,7 @@ mod test {
drop(output_dir);

// The third time (and later), the .old directory is removed.
let output_dir = OutputDir::new(temp_dir_path).unwrap();
let output_dir = OutputDir::new(temp_dir_path, None).unwrap();
output_dir.create_log(&Scenario::Baseline).unwrap();
assert!(temp_dir
.path()
Expand All @@ -379,33 +380,7 @@ mod test {
}
}

/// Caught and unviable scenario outcomes
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct PositiveOutcomes {
/// Caught and unviable scenario outcomes
pub outcomes: Vec<PositiveOutcome>,
}

impl PositiveOutcomes {
fn new() -> Self {
Self {
outcomes: Vec::new(),
}
}

pub fn iter(&self) -> impl Iterator<Item = &PositiveOutcome> {
self.outcomes.iter()
}
}

impl FromIterator<&PositiveOutcome> for PositiveOutcomes {
fn from_iter<T: IntoIterator<Item = PositiveOutcome>>(iter: T) -> Self {
Self {
outcomes: iter.into_iter().collect(),
}
}
}

/// Caught and unviable scenario outcome
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct PositiveOutcome {
pub mutant_hash: MutantHash,
Expand All @@ -420,12 +395,10 @@ impl PositiveOutcome {
mutant_hash: mutant.calculate_hash(),
summary,
}),
_ => {
return Err(anyhow::anyhow!(
"summary {:?} does not belong to a positive outcome",
summary
))
}
_ => Err(anyhow::anyhow!(
"outcome {:?} isn’t a positive outcome",
summary
)),
}
}

Expand All @@ -447,9 +420,6 @@ impl TryFrom<&ScenarioOutcome> for PositiveOutcome {
))
}
};
let summary = scenario_outcome.summary();
Self::new(mutant, scenario_outcome.summary())
}
}

impl PositiveOutcome {}
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(0,
src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(0, "xyzzy")]
src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(1, "")]
src/in_diff.rs: replace partial_new_file -> Vec<(usize, &'d str)> with vec![(1, "xyzzy")]
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (None, vec![])
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (None, vec![Default::default()])
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (Some(vec![]), vec![])
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (Some(vec![]), vec![Default::default()])
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (Some(vec![Default::default()]), vec![])
src/incremental.rs: replace filter_by_last_positive_outcomes -> (Option<Vec<PositiveOutcome>>, Vec<Mutant>) with (Some(vec![Default::default()]), vec![Default::default()])
src/incremental.rs: replace read_last_positive_outcomes -> Result<Vec<PositiveOutcome>> with Ok(vec![])
src/incremental.rs: replace read_last_positive_outcomes -> Result<Vec<PositiveOutcome>> with Ok(vec![Default::default()])
src/incremental.rs: replace read_last_positive_outcomes -> Result<Vec<PositiveOutcome>> with Err(::anyhow::anyhow!("mutated!"))
src/interrupt.rs: replace install_handler with ()
src/lab.rs: replace test_mutants -> Result<LabOutcome> with Ok(Default::default())
src/lab.rs: replace test_mutants -> Result<LabOutcome> with Err(::anyhow::anyhow!("mutated!"))
Expand Down Expand Up @@ -208,6 +217,7 @@ src/mutate.rs: replace Mutant::write_in_dir -> Result<()> with Ok(())
src/mutate.rs: replace Mutant::write_in_dir -> Result<()> with Err(::anyhow::anyhow!("mutated!"))
src/mutate.rs: replace Mutant::log_file_name_base -> String with String::new()
src/mutate.rs: replace Mutant::log_file_name_base -> String with "xyzzy".into()
src/mutate.rs: replace Mutant::calculate_hash -> MutantHash with Default::default()
src/mutate.rs: replace <impl Debug for Mutant>::fmt -> fmt::Result with Ok(Default::default())
src/mutate.rs: replace <impl Debug for Mutant>::fmt -> fmt::Result with Err(::anyhow::anyhow!("mutated!"))
src/mutate.rs: replace <impl Display for Mutant>::fmt -> fmt::Result with Ok(Default::default())
Expand Down Expand Up @@ -264,6 +274,8 @@ src/output.rs: replace LockFile::acquire_lock -> Result<File> with Err(::anyhow:
src/output.rs: replace OutputDir::create_log -> Result<LogFile> with Ok(Default::default())
src/output.rs: replace OutputDir::create_log -> Result<LogFile> with Err(::anyhow::anyhow!("mutated!"))
src/output.rs: replace OutputDir::path -> &Utf8Path with &Default::default()
src/output.rs: replace OutputDir::maybe_write_positive_outcomes -> Result<()> with Ok(())
src/output.rs: replace OutputDir::maybe_write_positive_outcomes -> Result<()> with Err(::anyhow::anyhow!("mutated!"))
src/output.rs: replace OutputDir::write_lab_outcome -> Result<()> with Ok(())
src/output.rs: replace OutputDir::write_lab_outcome -> Result<()> with Err(::anyhow::anyhow!("mutated!"))
src/output.rs: replace OutputDir::add_scenario_outcome -> Result<()> with Ok(())
Expand All @@ -273,6 +285,9 @@ src/output.rs: replace OutputDir::open_debug_log -> Result<File> with Err(::anyh
src/output.rs: replace OutputDir::write_mutants_list -> Result<()> with Ok(())
src/output.rs: replace OutputDir::write_mutants_list -> Result<()> with Err(::anyhow::anyhow!("mutated!"))
src/output.rs: replace OutputDir::take_lab_outcome -> LabOutcome with Default::default()
src/output.rs: replace PositiveOutcome::mutant_hash -> MutantHash with Default::default()
src/output.rs: replace <impl TryFrom for PositiveOutcome>::try_from -> Result<Self> with Ok(Default::default())
src/output.rs: replace <impl TryFrom for PositiveOutcome>::try_from -> Result<Self> with Err(::anyhow::anyhow!("mutated!"))
src/path.rs: replace ascent -> isize with 0
src/path.rs: replace ascent -> isize with 1
src/path.rs: replace ascent -> isize with -1
Expand Down

0 comments on commit 0f8c89c

Please sign in to comment.