Skip to content

Commit

Permalink
Add --file
Browse files Browse the repository at this point in the history
  • Loading branch information
sourcefrog committed Mar 29, 2022
1 parent 6d8276f commit 5b638e0
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 11 deletions.
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ similar = "2.0"
subprocess = "0.2.8"
tempfile = "3.2"
walkdir = "2.3"
globset = "0.4.8"

[dependencies.cp_r]
version = "0.5.1"
Expand Down
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- New `--file` command line option to mutate only functions in source files
matching a glob.

- Improved: Don't attempt to mutate functions called `new` or implementations of
`Default`. cargo-mutants can not yet generate good mutations for these so they
are generally false positives.
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ but not adequately tested.

`-d`, `--dir`: Test the Rust tree in the given directory, rather than the default directory.

`-f`, `--file FILE`: Mutate only functions in files matching the given name or
glob. If the glob contains `/` it matches against the path from the source tree
root; otherwise it matches only against the file name.

`--list`: Show what mutants could be generated, without running them.

`--diff`: With `--list`, also include a diff of the source change for each mutant.
Expand Down
2 changes: 1 addition & 1 deletion src/lab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub fn test_unmutated_then_all_mutants(
}
}

let mut mutants = source_tree.mutants()?;
let mut mutants = source_tree.mutants(&options)?;
if options.shuffle {
mutants.shuffle(&mut rand::thread_rng());
}
Expand Down
10 changes: 8 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod source;
mod textedit;
mod visit;

use std::convert::TryFrom;
use std::env;
use std::io;
use std::path::PathBuf;
Expand Down Expand Up @@ -62,6 +63,11 @@ struct Args {
#[argh(option, short = 'd', default = r#"PathBuf::from(".")"#)]
dir: PathBuf,

/// glob for files to examine; with no glob, all files are examined; globs containing
/// slash match the entire path.
#[argh(option, short = 'f')]
file: Vec<String>,

/// output json (only for --list).
#[argh(switch)]
json: bool,
Expand Down Expand Up @@ -112,11 +118,11 @@ fn main() -> Result<()> {
exit(exit_code::USAGE);
}
let args: Args = argh::cargo_from_env();
let options = Options::try_from(&args)?;
let source_tree = SourceTree::new(&args.dir)?;
let options = Options::from(&args);
interrupt::install_handler();
if args.list {
let mutants = source_tree.mutants()?;
let mutants = source_tree.mutants(&options)?;
if args.json {
if args.diff {
eprintln!("--list --diff --json is not (yet) supported");
Expand Down
31 changes: 27 additions & 4 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

//! Global in-process options for experimenting on mutants.
use std::convert::TryFrom;
use std::time::Duration;

use globset::{Glob, GlobSet, GlobSetBuilder};

use crate::*;

/// Options for running experiments.
Expand Down Expand Up @@ -36,6 +39,9 @@ pub struct Options {

/// Build the source directory before copying it.
pub build_source: bool,

/// Files to examine.
pub globset: Option<GlobSet>,
}

impl Options {
Expand All @@ -55,12 +61,29 @@ impl Options {
}
}

impl From<&Args> for Options {
fn from(args: &Args) -> Options {
Options {
impl TryFrom<&Args> for Options {
type Error = anyhow::Error;

fn try_from(args: &Args) -> std::result::Result<Options, anyhow::Error> {
let globset = if args.file.is_empty() {
None
} else {
let mut builder = GlobSetBuilder::new();
for glob_str in &args.file {
if glob_str.contains('/') {
builder.add(Glob::new(glob_str)?);
} else {
builder.add(Glob::new(&format!("**/{}", glob_str))?);
}
}
Some(builder.build()?)
};

Ok(Options {
build_source: !args.no_copy_target,
check_only: args.check,
copy_target: !args.no_copy_target,
globset,
print_caught: args.caught,
print_unviable: args.unviable,
shuffle: !args.no_shuffle,
Expand All @@ -71,6 +94,6 @@ impl From<&Args> for Options {
.map(Duration::from_secs_f64)
.unwrap_or(Duration::MAX),
additional_cargo_test_args: args.cargo_test_args.clone(),
}
})
}
}
15 changes: 11 additions & 4 deletions src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,19 @@ impl SourceTree {
}

/// Return all the mutations that could possibly be applied to this tree.
pub fn mutants(&self) -> Result<Vec<Mutant>> {
pub fn mutants(&self, options: &Options) -> Result<Vec<Mutant>> {
let mut r = Vec::new();
for sf in self.source_files() {
for sf in self.source_files(options) {
check_interrupted()?;
r.extend(discover_mutants(sf.into())?);
}
Ok(r)
}

/// Return an iterator of `src/**/*.rs` paths relative to the root.
pub fn source_files(&self) -> impl Iterator<Item = SourceFile> + '_ {
pub fn source_files(&self, options: &Options) -> impl Iterator<Item = SourceFile> + '_ {
let globset = options.globset.clone();
let root_path = self.root.clone();
walkdir::WalkDir::new(self.root.join("src"))
.sort_by_file_name()
.into_iter()
Expand All @@ -96,6 +98,11 @@ impl SourceTree {
path.extension()
.map_or(false, |p| p.eq_ignore_ascii_case("rs"))
})
.filter(move |path| {
globset.as_ref().map_or(true, |gs| {
gs.is_match(path.strip_prefix(&root_path).expect("strip path prefix"))
})
})
.filter_map(move |full_path| {
let tree_relative = full_path.strip_prefix(&self.root).unwrap();
SourceFile::new(&self.root, tree_relative)
Expand Down Expand Up @@ -129,7 +136,7 @@ mod test {
fn source_files_in_testdata_factorial() {
let source_paths = SourceTree::new(Path::new("testdata/tree/factorial"))
.unwrap()
.source_files()
.source_files(&Options::default())
.collect::<Vec<SourceFile>>();
assert_eq!(source_paths.len(), 1);
assert_eq!(
Expand Down
9 changes: 9 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ fn list_mutants_well_tested() {
.assert_insta("list_mutants_well_tested");
}

#[test]
fn list_mutants_well_tested_name_filter() {
run()
.arg("mutants")
.args(["--list", "--file", "nested_function.rs"])
.current_dir("testdata/tree/well_tested")
.assert_insta("list_mutants_well_tested_name_filter");
}

#[test]
fn list_mutants_json_well_tested() {
run()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: tests/cli.rs
assertion_line: 41
expression: "String::from_utf8_lossy(&output.stdout)"
---
src/nested_function.rs:1: replace has_nested -> u32 with Default::default()
src/nested_function.rs:2: replace has_nested::inner -> u32 with Default::default()

0 comments on commit 5b638e0

Please sign in to comment.