Skip to content
This repository has been archived by the owner on Aug 26, 2023. It is now read-only.

Commit

Permalink
Add mutant sampling functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
lwagner94 committed Apr 18, 2022
1 parent 932a7b5 commit 69d2689
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ log = "0.4.0"
env_logger = "0.9.0"
num_cpus = "1.13.1"
indicatif = {version = "0.16.2", features = ["rayon"]}
rand = "0.8.4"
rand = "0.8.5"
syntect = "4.6.0"
handlebars = "4.2.0"
md5 = "0.7.0"
Expand Down
4 changes: 4 additions & 0 deletions src/cliarguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ pub enum CLICommand {
#[clap(short, long, default_value = "wasmut-report")]
output: String,

/// The percentage of all mutants which should be executed
#[clap(short, long, default_value_t = 100)]
sample_threshold: i32,

/// Path to the wasm module
wasmfile: String,
},
Expand Down
6 changes: 4 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ fn mutate(
config: &Config,
report_type: &Output,
output_directory: &str,
sample_threshold: i32,
) -> Result<()> {
let start = Instant::now();

let module = WasmModule::from_file(wasmfile)?;
let mutator = MutationEngine::new(config)?;
let mutator = MutationEngine::new(config, sample_threshold)?;
let mutations = mutator.discover_mutation_positions(&module)?;

let executor = Executor::new(config);
Expand Down Expand Up @@ -234,12 +235,13 @@ fn run_main(cli: CLIArguments) -> Result<()> {
wasmfile,
threads,
config_samedir,
sample_threshold,
report,
output,
} => {
let config = load_config(config.as_deref(), Some(&wasmfile), config_samedir)?;
init_rayon(threads);
mutate(&wasmfile, &config, &report, &output)?;
mutate(&wasmfile, &config, &report, &output, sample_threshold)?;
}
CLICommand::NewConfig { path } => {
new_config(path)?;
Expand Down
38 changes: 34 additions & 4 deletions src/mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{config::Config, policy::MutationPolicy, wasmmodule::WasmModule};
use anyhow::Result;
use atomic_counter::AtomicCounter;
use atomic_counter::RelaxedCounter;
use rand::distributions::{Distribution, Uniform};

/// Definition of a position where and how a module is mutated.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -41,14 +42,18 @@ pub struct MutationEngine {

/// A list of all operators that are to be enabled.
enabled_operators: Vec<String>,

/// Percentage of mutants that are to be executed
sample_threshold: i32,
}

impl MutationEngine {
/// Create a new `MutationEngine`, based on a configuration.
pub fn new(config: &Config) -> Result<Self> {
pub fn new(config: &Config, sample_threshold: i32) -> Result<Self> {
Ok(Self {
mutation_policy: MutationPolicy::from_config(config)?,
enabled_operators: config.operators().enabled_operators(),
sample_threshold,
})
}

Expand Down Expand Up @@ -80,6 +85,12 @@ impl MutationEngine {
let mutations: Vec<Mutation> = registry
.mutants_for_instruction(instruction, &context)
.into_iter()
.filter(|_| {
let mut rng = rand::thread_rng();
let die = Uniform::from(0..=100i32);
let roll = die.sample(&mut rng);
roll <= self.sample_threshold
})
.map(|operator| Mutation {
id: id_counter.inc() as i64,
operator,
Expand Down Expand Up @@ -166,7 +177,7 @@ mod tests {
let module = WasmModule::from_file("testdata/simple_add/test.wasm")?;

let config = Config::default();
let engine = MutationEngine::new(&config)?;
let engine = MutationEngine::new(&config, 100)?;
let positions = engine.discover_mutation_positions(&module).unwrap();

assert!(!positions.is_empty());
Expand All @@ -177,7 +188,7 @@ mod tests {
fn test_mutation() -> Result<()> {
let module = WasmModule::from_file("testdata/simple_add/test.wasm")?;
let config = Config::default();
let engine = MutationEngine::new(&config)?;
let engine = MutationEngine::new(&config, 100)?;

let locations = engine.discover_mutation_positions(&module).unwrap();
dbg!(&locations);
Expand All @@ -196,7 +207,7 @@ mod tests {
fn check_number_of_mutants(config: &str) -> usize {
let module = WasmModule::from_file("testdata/count_words/test.wasm").unwrap();
let config = Config::parse_file(format!("testdata/count_words/{config}")).unwrap();
let engine = MutationEngine::new(&config).unwrap();
let engine = MutationEngine::new(&config, 100).unwrap();
engine.discover_mutation_positions(&module).unwrap().len()
}

Expand All @@ -205,4 +216,23 @@ mod tests {
assert_eq!(check_number_of_mutants("wasmut.toml"), 23);
Ok(())
}

#[test]
fn test_sample_threshold() -> Result<()> {
fn check_number_of_mutants(threshold: i32) -> usize {
let module = WasmModule::from_file("testdata/count_words/test.wasm").unwrap();
let config = Config::parse_file("testdata/count_words/wasmut.toml").unwrap();
let engine = MutationEngine::new(&config, threshold).unwrap();
engine.discover_mutation_positions(&module).unwrap().len()
}

assert_eq!(check_number_of_mutants(100), 23);

// There is a extremely low probability that this might fail...
// but I don't really see a way to test this without having
// a seedable RNG - and this requires a bit of refactoring
// TODO !
assert!(check_number_of_mutants(50) < 23);
Ok(())
}
}

0 comments on commit 69d2689

Please sign in to comment.