Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 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 tooling/ast_fuzzer/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ noir_ast_fuzzer = { path = ".." }
[dev-dependencies]
arbtest.workspace = true
env_logger.workspace = true
proptest.workspace = true


[[bin]]
Expand Down
85 changes: 68 additions & 17 deletions tooling/ast_fuzzer/fuzz/src/targets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ pub mod pass_vs_prev;

#[cfg(test)]
mod tests {
use std::time::Duration;

const TIMEOUT: Duration = Duration::from_secs(20);
const MIN_SIZE: u32 = 1 << 12;
const MAX_SIZE: u32 = 1 << 20;

use std::time::{Duration, Instant};

use arbitrary::Unstructured;
use color_eyre::eyre;
use proptest::prelude::*;

pub fn seed_from_env() -> Option<u64> {
let Ok(seed) = std::env::var("NOIR_ARBTEST_SEED") else { return None };
Expand All @@ -18,14 +24,7 @@ mod tests {
Some(seed)
}

/// We can use this to disable the proptests on CI until we fix known bugs.
///
/// The tests should always be enabled locally. They can be run with:
///
/// ```ignore
/// cargo test -p noir_ast_fuzzer_fuzz
/// ```
#[allow(unused)]
/// Check if we are running on CI.
pub fn is_running_in_ci() -> bool {
std::env::var("CI").is_ok()
}
Expand All @@ -42,18 +41,70 @@ mod tests {
pub fn fuzz_with_arbtest(f: impl Fn(&mut Unstructured) -> eyre::Result<()>) {
let _ = env_logger::try_init();

let mut prop = arbtest::arbtest(|u| {
if let Some(seed) = seed_from_env() {
run_reproduce(f, seed);
} else if is_running_in_ci() {
run_deterministic(f);
} else {
run_nondeterministic(f);
}
}

/// Reproduce the result of a single seed.
fn run_reproduce(f: impl Fn(&mut Unstructured) -> eyre::Result<()>, seed: u64) {
arbtest::arbtest(|u| {
f(u).unwrap();
Ok(())
})
.budget(Duration::from_secs(20))
.size_min(1 << 12)
.size_max(1 << 20);
.seed(seed)
.run();
}

if let Some(seed) = seed_from_env() {
prop = prop.seed(seed);
}
/// Run the tests non-deterministically until the timeout.
///
/// This is the local behavior.
fn run_nondeterministic(f: impl Fn(&mut Unstructured) -> eyre::Result<()>) {
arbtest::arbtest(|u| {
f(u).unwrap();
Ok(())
})
.size_min(MIN_SIZE)
.size_max(MAX_SIZE)
.budget(TIMEOUT)
.run();
}

/// Run multiple tests with a deterministic RNG.
///
/// This is the behavior on CI.
fn run_deterministic(f: impl Fn(&mut Unstructured) -> eyre::Result<()>) {
// Comptime tests run slower than others.
let start = Instant::now();

let config = proptest::test_runner::Config {
cases: 1000,
failure_persistence: None,
..Default::default()
};
let rng = proptest::test_runner::TestRng::deterministic_rng(config.rng_algorithm);
let mut runner = proptest::test_runner::TestRunner::new_with_rng(config, rng);

runner
.run(&seed_strategy(), |seed| {
if start.elapsed() < TIMEOUT {
run_reproduce(&f, seed);
}
Ok(())
})
.unwrap();
}

prop.run();
/// Generate seeds for `arbtest` where the top 32 bits are random and the lower 32 bits represent the input size.
fn seed_strategy() -> proptest::strategy::BoxedStrategy<u64> {
(MIN_SIZE..MAX_SIZE)
.prop_flat_map(move |size| {
any::<u64>().prop_map(move |raw| (size as u64) | (raw << u32::BITS))
})
.boxed()
}
}
Loading