Skip to content

Commit 95ed58b

Browse files
committed
fix(mutation): include execution inputs in cache key
1 parent 0d2a490 commit 95ed58b

1 file changed

Lines changed: 53 additions & 17 deletions

File tree

crates/forge/src/mutation/orchestrator.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{path::PathBuf, sync::Arc, time::Instant};
1111
use alloy_primitives::keccak256;
1212
use eyre::{Result, WrapErr};
1313
use foundry_cli::utils::FoundryPathExt;
14-
use foundry_common::sh_println;
14+
use foundry_common::{compile::ProjectCompiler, sh_println};
1515
use foundry_compilers::{
1616
Language, ProjectCompileOutput,
1717
compilers::multi::{MultiCompiler, MultiCompilerLanguage},
@@ -39,6 +39,7 @@ struct ExecutionCacheFingerprint<'a> {
3939
schema: &'static str,
4040
config: &'a Config,
4141
evm_opts: &'a EvmOpts,
42+
num_workers: usize,
4243
artifacts: &'a [ArtifactCacheFingerprint],
4344
}
4445

@@ -98,7 +99,12 @@ pub async fn run_mutation_testing(
9899

99100
// Determine which paths to mutate
100101
let mutate_paths = resolve_mutate_paths(&config, output, &mutation_config)?;
101-
let execution_cache_key = mutation_execution_cache_key(&config, output, &evm_opts)?;
102+
let execution_cache_output = ProjectCompiler::new()
103+
.dynamic_test_linking(config.dynamic_test_linking)
104+
.quiet(json_output)
105+
.compile(&config.project()?)?;
106+
let execution_cache_key =
107+
mutation_execution_cache_key(&config, &execution_cache_output, &evm_opts, num_workers)?;
102108

103109
if !mutation_config.show_progress && !json_output {
104110
sh_println!("Running mutation tests with {} parallel workers...", num_workers)?;
@@ -265,12 +271,15 @@ pub async fn run_mutation_testing(
265271
/// settings. Hashing the full serialized config intentionally includes fuzz /
266272
/// invariant settings, test filters, fs permissions, sender/balance/env values,
267273
/// and future config fields unless explicitly skipped by `Config` itself. The
268-
/// artifact fingerprint covers source and test build IDs, so changing a test
269-
/// file invalidates result caches even when the mutated source did not change.
274+
/// artifact fingerprint covers the unfiltered source and test build IDs, so
275+
/// changing a test file invalidates result caches even when the outer test run
276+
/// was filtered. Worker count is included because adaptive span skipping is
277+
/// concurrency-sensitive.
270278
fn mutation_execution_cache_key(
271279
config: &Config,
272280
output: &ProjectCompileOutput<MultiCompiler>,
273281
evm_opts: &EvmOpts,
282+
num_workers: usize,
274283
) -> Result<String> {
275284
let artifacts = output
276285
.artifact_ids()
@@ -282,19 +291,21 @@ fn mutation_execution_cache_key(
282291
profile: id.profile,
283292
})
284293
.collect::<Vec<_>>();
285-
mutation_execution_cache_key_from_parts(config, evm_opts, artifacts)
294+
mutation_execution_cache_key_from_parts(config, evm_opts, num_workers, artifacts)
286295
}
287296

288297
fn mutation_execution_cache_key_from_parts(
289298
config: &Config,
290299
evm_opts: &EvmOpts,
300+
num_workers: usize,
291301
mut artifacts: Vec<ArtifactCacheFingerprint>,
292302
) -> Result<String> {
293303
artifacts.sort();
294304
let fingerprint = ExecutionCacheFingerprint {
295305
schema: "mutation-results-v1",
296306
config,
297307
evm_opts,
308+
num_workers,
298309
artifacts: &artifacts,
299310
};
300311
let encoded = serde_json::to_vec(&fingerprint)
@@ -391,9 +402,10 @@ mod tests {
391402
let artifacts = vec![artifact("build-a")];
392403

393404
let first_key =
394-
mutation_execution_cache_key_from_parts(&first, &evm_opts, artifacts.clone()).unwrap();
405+
mutation_execution_cache_key_from_parts(&first, &evm_opts, 1, artifacts.clone())
406+
.unwrap();
395407
let second_key =
396-
mutation_execution_cache_key_from_parts(&second, &evm_opts, artifacts).unwrap();
408+
mutation_execution_cache_key_from_parts(&second, &evm_opts, 1, artifacts).unwrap();
397409

398410
assert_ne!(first_key, second_key);
399411
}
@@ -408,9 +420,9 @@ mod tests {
408420
let artifacts = vec![artifact("build-a")];
409421

410422
let first_key =
411-
mutation_execution_cache_key_from_parts(&config, &first, artifacts.clone()).unwrap();
423+
mutation_execution_cache_key_from_parts(&config, &first, 1, artifacts.clone()).unwrap();
412424
let second_key =
413-
mutation_execution_cache_key_from_parts(&config, &second, artifacts).unwrap();
425+
mutation_execution_cache_key_from_parts(&config, &second, 1, artifacts).unwrap();
414426

415427
assert_ne!(first_key, second_key);
416428
}
@@ -420,12 +432,20 @@ mod tests {
420432
let config = Config::default();
421433
let evm_opts = EvmOpts::default();
422434

423-
let first_key =
424-
mutation_execution_cache_key_from_parts(&config, &evm_opts, vec![artifact("build-a")])
425-
.unwrap();
426-
let second_key =
427-
mutation_execution_cache_key_from_parts(&config, &evm_opts, vec![artifact("build-b")])
428-
.unwrap();
435+
let first_key = mutation_execution_cache_key_from_parts(
436+
&config,
437+
&evm_opts,
438+
1,
439+
vec![artifact("build-a")],
440+
)
441+
.unwrap();
442+
let second_key = mutation_execution_cache_key_from_parts(
443+
&config,
444+
&evm_opts,
445+
1,
446+
vec![artifact("build-b")],
447+
)
448+
.unwrap();
429449

430450
assert_ne!(first_key, second_key);
431451
}
@@ -438,10 +458,26 @@ mod tests {
438458
let first = vec![artifact("build-a"), artifact("build-b")];
439459
let second = vec![artifact("build-b"), artifact("build-a")];
440460

441-
let first_key = mutation_execution_cache_key_from_parts(&config, &evm_opts, first).unwrap();
461+
let first_key =
462+
mutation_execution_cache_key_from_parts(&config, &evm_opts, 1, first).unwrap();
442463
let second_key =
443-
mutation_execution_cache_key_from_parts(&config, &evm_opts, second).unwrap();
464+
mutation_execution_cache_key_from_parts(&config, &evm_opts, 1, second).unwrap();
444465

445466
assert_eq!(first_key, second_key);
446467
}
468+
469+
#[test]
470+
fn execution_cache_key_changes_when_worker_count_changes() {
471+
let config = Config::default();
472+
let evm_opts = EvmOpts::default();
473+
let artifacts = vec![artifact("build-a")];
474+
475+
let first_key =
476+
mutation_execution_cache_key_from_parts(&config, &evm_opts, 1, artifacts.clone())
477+
.unwrap();
478+
let second_key =
479+
mutation_execution_cache_key_from_parts(&config, &evm_opts, 4, artifacts).unwrap();
480+
481+
assert_ne!(first_key, second_key);
482+
}
447483
}

0 commit comments

Comments
 (0)