fix(forge): preserve mutation workspace paths#15630
Conversation
| return Ok(()); | ||
| } | ||
| let is_external = rel.is_absolute(); | ||
| let target = if is_external || is_covered_by_handled_root(&rel, handled_roots) { |
There was a problem hiding this comment.
This uses the broader handled-roots set, so a corpus under src/test/script can get copied into .foundry_mutable even though rebase_mutable_project_path still points at the normal temp copy. Correctness looks fine; I'll handle the classifier cleanup separately as a follow-up.
mattsse
left a comment
There was a problem hiding this comment.
One more mutable workspace path looks like it should be isolated before this lands.
failure_persist_dir is mutable too, so preserving external values can still let isolated mutation/brutalize workspaces write into the user's real external failure cache. Fuzz writes counterexamples under test_paths(... failure_persist_dir ...), and invariant writes under invariant_failure_dir / record_invariant_failure, so a config like failure_persist_dir = "../shared-failures" can escape the temp workspace.
I think these two fields should use the same mutable-path helper as corpus/frontier:
if let Some(path) = &config.fuzz.failure_persist_dir {
temp_config.fuzz.failure_persist_dir =
Some(rebase_mutable_project_path(config, temp_path, path));
}
if let Some(path) = &config.invariant.failure_persist_dir {
temp_config.invariant.failure_persist_dir =
Some(rebase_mutable_project_path(config, temp_path, path));
}Test suggestion: add a workspace unit test with fuzz.failure_persist_dir = Some(PathBuf::from("../shared-fuzz-failures")) and invariant.failure_persist_dir = Some(PathBuf::from("../shared-invariant-failures")), then assert both rebased paths start with workspace.join(".foundry_mutable"). I would keep this as rebase-only for the minimal fix; project-local failure dirs are not copied today, so this closes the write escape without changing persisted-failure replay semantics more broadly.
Mutation workspaces now preserve external read-only compiler paths instead of trying to copy them into each temporary project. This lets monorepo-style projects with remappings or allow paths pointing at sibling Solidity directories run mutation testing without every mutant becoming invalid during workspace setup.
The workspace rebasing logic now resolves project-relative paths against the original root, rebases project-local paths into the temp workspace, and keeps external paths external. Project-local fuzz and invariant corpus/frontier directories are also copied and rebased into each mutant workspace so mutation runs do not share those mutable paths with the original project. Paths already covered by handled dependency roots are skipped to avoid interfering with dependency symlinks.