diff --git a/tooling/nargo/src/ops/mod.rs b/tooling/nargo/src/ops/mod.rs index 02fe2d586a4..4632225bf49 100644 --- a/tooling/nargo/src/ops/mod.rs +++ b/tooling/nargo/src/ops/mod.rs @@ -11,7 +11,7 @@ pub use self::fuzz::{ FuzzExecutionConfig, FuzzFolderConfig, FuzzingRunStatus, run_fuzzing_harness, }; pub use self::test::{ - TestStatus, check_expected_failure_message, fuzz_test, run_or_fuzz_test, run_test, + FuzzConfig, TestStatus, check_expected_failure_message, fuzz_test, run_or_fuzz_test, run_test, test_status_program_compile_fail, test_status_program_compile_pass, }; diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index 9ec1a28ec98..8077f293c33 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -47,7 +47,12 @@ impl TestStatus { } } +pub struct FuzzConfig { + pub folder_config: FuzzFolderConfig, +} + /// Runs a test function. This will either run the test or fuzz it, depending on whether the function has arguments. +#[allow(clippy::too_many_arguments)] pub fn run_or_fuzz_test<'a, W, B, F, E>( blackbox_solver: &B, context: &mut Context, @@ -55,6 +60,7 @@ pub fn run_or_fuzz_test<'a, W, B, F, E>( output: W, package_name: String, config: &CompileOptions, + fuzz_config: FuzzConfig, build_foreign_call_executor: F, ) -> TestStatus where @@ -69,6 +75,7 @@ where test_function, package_name, config, + fuzz_config, build_foreign_call_executor, ) } else { @@ -186,6 +193,7 @@ pub fn fuzz_test<'a, B, F, E>( test_function: &TestFunction, package_name: String, config: &CompileOptions, + fuzz_config: FuzzConfig, build_foreign_call_executor: F, ) -> TestStatus where @@ -199,6 +207,7 @@ where test_function, package_name, config, + fuzz_config, build_foreign_call_executor, ), Err(err) => test_status_program_compile_fail(err, test_function), @@ -210,6 +219,7 @@ fn fuzz_test_impl<'a, B, F, E>( test_function: &TestFunction, package_name: String, config: &CompileOptions, + fuzz_config: FuzzConfig, build_foreign_call_executor: F, ) -> TestStatus where @@ -227,14 +237,26 @@ where let location = test_function.location; let fuzzing_harness = FuzzingHarness { id, scope, location }; - let corpus_dir = tempfile::tempdir().expect("Couldn't create temporary directory"); - let fuzzing_failure_dir = tempfile::tempdir().expect("Couldn't create temporary directory"); + let mut temporary_dirs_to_delete = Vec::new(); + + let mut config_or_temporary_dir = |dir: Option| match dir { + Some(ref dir) => PathBuf::from(dir), + None => { + let corpus_dir = tempfile::tempdir().expect("Couldn't create temporary directory"); + let corpus_dir = corpus_dir.into_path(); + temporary_dirs_to_delete.push(corpus_dir.clone()); + corpus_dir + } + }; + + let corpus_dir = config_or_temporary_dir(fuzz_config.folder_config.corpus_dir); + let fuzzing_failure_dir = + config_or_temporary_dir(fuzz_config.folder_config.fuzzing_failure_dir); - // TODO: allow configuring this. See https://github.com/noir-lang/noir/issues/8214 let fuzz_folder_config = FuzzFolderConfig { - corpus_dir: Some(corpus_dir.path().to_string_lossy().to_string()), - minimized_corpus_dir: None, - fuzzing_failure_dir: Some(fuzzing_failure_dir.path().to_string_lossy().to_string()), + corpus_dir: Some(corpus_dir.to_string_lossy().to_string()), + fuzzing_failure_dir: Some(fuzzing_failure_dir.to_string_lossy().to_string()), + minimized_corpus_dir: fuzz_config.folder_config.minimized_corpus_dir, }; // TODO: allow configuring this. See https://github.com/noir-lang/noir/issues/8214 let fuzz_execution_config = @@ -253,6 +275,11 @@ where build_foreign_call_executor, ); + for temporary_dir_to_delete in temporary_dirs_to_delete { + // Not a big deal if we can't delete a temporary directory + let _ = std::fs::remove_dir_all(temporary_dir_to_delete); + } + match fuzz_result { FuzzingRunStatus::ExecutionPass | FuzzingRunStatus::MinimizationPass => TestStatus::Pass, FuzzingRunStatus::CorpusFailure { message } => { diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 20a64f20276..220286ecf45 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -14,9 +14,10 @@ use clap::Args; use fm::FileManager; use formatters::{Formatter, JsonFormatter, PrettyFormatter, TerseFormatter}; use nargo::{ + FuzzFolderConfig, foreign_calls::DefaultForeignCallBuilder, insert_all_files_for_workspace_into_file_manager, - ops::{TestStatus, check_crate_and_report_errors}, + ops::{FuzzConfig, TestStatus, check_crate_and_report_errors}, package::Package, parse_all, prepare_package, workspace::Workspace, @@ -79,6 +80,18 @@ pub(crate) struct TestCommand { /// Only run fuzz tests (tests that have arguments) #[clap(long, conflicts_with("no_fuzz"))] only_fuzz: bool, + + /// If given, load/store fuzzer corpus from this folder + #[arg(long)] + corpus_dir: Option, + + /// If given, perform corpus minimization instead of fuzzing and store results in the given folder + #[arg(long)] + minimized_corpus_dir: Option, + + /// If given, store the failing input in the given folder + #[arg(long)] + fuzzing_failure_dir: Option, } impl WorkspaceCommand for TestCommand { @@ -542,6 +555,14 @@ impl<'a> TestRunner<'a> { let blackbox_solver = S::default(); let mut output_buffer = Vec::new(); + let fuzz_config = FuzzConfig { + folder_config: FuzzFolderConfig { + corpus_dir: self.args.corpus_dir.clone(), + minimized_corpus_dir: self.args.minimized_corpus_dir.clone(), + fuzzing_failure_dir: self.args.fuzzing_failure_dir.clone(), + }, + }; + let test_status = nargo::ops::run_or_fuzz_test( &blackbox_solver, &mut context, @@ -549,6 +570,7 @@ impl<'a> TestRunner<'a> { &mut output_buffer, package_name.clone(), &self.args.compile_options, + fuzz_config, |output, base| { DefaultForeignCallBuilder { output,