1#![cfg_attr(fuzzing_random, allow(dead_code))]
2
3use bolero_engine::{
4 driver::{self, exhaustive, object::Object},
5 rng, Engine, Failure, Seed, TargetLocation, Test,
6};
7use core::{fmt, mem::size_of, time::Duration};
8use std::path::PathBuf;
9
10type ExhastiveDriver = Box<Object<exhaustive::Driver>>;
11
12mod input;
13mod outcome;
14mod report;
15
16#[derive(Debug)]
20pub struct TestEngine {
21 location: TargetLocation,
22 rng_cfg: rng::Options,
23}
24
25struct NamedTest {
26 name: String,
27 data: input::Test,
28}
29
30impl fmt::Display for NamedTest {
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 if let input::Test::Rng(test) = &self.data {
33 write!(f, "[BOLERO_RANDOM_SEED={}]", test.seed)
34 } else {
35 write!(f, "{}", self.name)
36 }
37 }
38}
39
40impl From<input::RngTest> for NamedTest {
41 #[inline]
42 fn from(value: input::RngTest) -> Self {
43 Self {
44 name: String::new(),
45 data: input::Test::Rng(value),
46 }
47 }
48}
49
50impl TestEngine {
51 #[allow(dead_code)]
52 pub fn new(location: TargetLocation) -> Self {
53 Self {
54 location,
55 rng_cfg: Default::default(),
56 }
57 }
58
59 pub fn with_test_time(&mut self, test_time: Duration) -> &mut Self {
60 self.rng_cfg.test_time = self.rng_cfg.test_time.or(Some(test_time));
61 self
62 }
63
64 pub fn with_iterations(&mut self, iterations: usize) -> &mut Self {
65 self.rng_cfg.iterations = self.rng_cfg.iterations.or(Some(iterations));
66 self
67 }
68
69 pub fn with_max_len(&mut self, max_len: usize) -> &mut Self {
70 self.rng_cfg.max_len = self.rng_cfg.max_len.or(Some(max_len));
71 self
72 }
73
74 fn sub_dir<'a, D: Iterator<Item = &'a str>>(&self, dirs: D) -> PathBuf {
75 let mut fuzz_target_path = self
76 .location
77 .work_dir()
78 .expect("could not resolve target work dir");
79
80 fuzz_target_path.extend(dirs);
81
82 fuzz_target_path
83 }
84
85 fn file_tests<'a, D: Iterator<Item = &'a str> + std::panic::UnwindSafe>(
86 &self,
87 sub_dirs: D,
88 ) -> impl Iterator<Item = NamedTest> {
89 std::fs::read_dir(self.sub_dir(sub_dirs))
90 .ok()
91 .into_iter()
92 .flat_map(move |dir| {
93 dir.filter_map(Result::ok)
94 .map(|item| item.path())
95 .filter(|path| path.is_file())
96 .filter(|path| !path.file_name().unwrap().to_str().unwrap().starts_with('.'))
97 .map(move |path| NamedTest {
98 name: format!("{}", path.display()),
99 data: input::Test::File(input::FileTest { path }),
100 })
101 })
102 }
103
104 fn seed_tests(&self) -> impl Iterator<Item = input::RngTest> {
105 self.rng_cfg
106 .seed
107 .into_iter()
108 .map(move |seed| input::RngTest { seed })
109 }
110
111 fn rng_tests(&self) -> impl Iterator<Item = input::RngTest> {
112 use rand::{rngs::StdRng, RngCore, SeedableRng};
113
114 let iterations = self.rng_cfg.iterations_or_default();
115 let mut seed_rng = StdRng::from_os_rng();
117
118 (0..iterations).map(move |_| {
119 let mut seed = [0; size_of::<Seed>()];
120 seed_rng.fill_bytes(&mut seed);
121 let seed = Seed::from_le_bytes(seed);
122 input::RngTest { seed }
123 })
124 }
125
126 #[cfg(fuzzing_random)]
127 fn tests(&self) -> impl Iterator<Item = NamedTest> {
128 self.seed_tests()
129 .map(|t| t.into())
130 .chain(self.rng_tests().map(|t| t.into()))
131 }
132
133 #[cfg(not(fuzzing_random))]
134 fn tests(&self) -> impl Iterator<Item = NamedTest> {
135 self.seed_tests()
136 .map(|t| t.into())
137 .chain(self.file_tests(["crashes"].iter().cloned()))
138 .chain(self.file_tests(["afl_state", "crashes"].iter().cloned()))
139 .chain(self.file_tests(["afl_state", "hangs"].iter().cloned()))
140 .chain(self.file_tests(["corpus"].iter().cloned()))
141 .chain(self.file_tests(["afl_state", "queue"].iter().cloned()))
142 .chain(self.rng_tests().map(|t| t.into()))
143 }
144
145 fn run_with_value<T>(self, test: T, options: driver::Options) -> bolero_engine::Never
146 where
147 T: Test,
148 T::Value: core::fmt::Debug,
149 {
150 if options.exhaustive() {
151 let mut buffer = vec![];
152
153 assert!(!options.replay_on_fail(), "replay_on_fail is not supported with run_with_value");
154 let testfn = |mut driver: Box<Object<exhaustive::Driver>>, _is_replay: bool, test: &mut T| {
155 let mut input = input::ExhastiveInput {
156 driver: &mut driver,
157 buffer: &mut buffer,
158 };
159
160 let result = match test.test(&mut input) {
161 Ok(is_valid) => Ok(is_valid),
162 Err(error) => {
163 input.driver.replay();
165 let input = test.generate_value(&mut input);
166 let error = Failure {
167 seed: None,
168 error,
169 hide_error: false,
170 input,
171 };
172 Err(error.to_string())
173 }
174 };
175
176 (driver, result)
177 };
178
179 return self.run_exhaustive(test, testfn, options);
180 }
181
182 let file_options = options.clone();
183 let rng_options = options;
184
185 let file_options = &file_options;
186 let rng_options = &rng_options;
187
188 let mut buffer = vec![];
189 let mut cache = driver::cache::Cache::default();
190
191 assert!(!rng_options.replay_on_fail(), "replay_on_fail is not supported with run_with_value");
192 let testfn = |test: &mut T, _is_replay: bool, data: &input::Test| {
193 buffer.clear();
194 match data {
195 input::Test::File(file) => {
196 file.read_into(&mut buffer);
197
198 let mut input = input::Bytes::new(&buffer, file_options);
199 test.test(&mut input).map_err(|error| {
200 let shrunken = test.shrink(buffer.clone(), data.seed(), file_options);
201
202 if let Some((_, shrunken)) = shrunken {
203 format!("{shrunken:#}")
204 } else {
205 format!(
206 "{:#}",
207 Failure {
208 seed: data.seed(),
209 error,
210 hide_error: false,
211 input: buffer.clone()
212 }
213 )
214 }
215 })
216 }
217 input::Test::Rng(conf) => {
218 let mut input = conf.input(&mut buffer, &mut cache, rng_options);
219 test.test(&mut input).map_err(|error| {
220 let shrunken = if rng_options.shrink_time_or_default().is_zero() {
221 None
222 } else {
223 let mut input = conf.buffered_input(&mut buffer, rng_options);
225 let _ = test.generate_value(&mut input);
226
227 test.shrink(buffer.clone(), data.seed(), rng_options)
228 };
229
230 if let Some((_, shrunken)) = shrunken {
231 format!("{shrunken:#}")
232 } else {
233 buffer.clear();
234 let mut input = conf.input(&mut buffer, &mut cache, rng_options);
235 let input = test.generate_value(&mut input);
236 format!(
237 "{:#}",
238 Failure {
239 seed: data.seed(),
240 error,
241 hide_error: false,
242 input
243 }
244 )
245 }
246 })
247 }
248 }
249 };
250
251 self.run_tests(test, testfn, rng_options.replay_on_fail())
252 }
253
254 #[cfg(feature = "std")]
255 fn run_with_scope<T, R>(self, test: T, options: driver::Options)
256 where
257 T: FnMut(bool) -> R + core::panic::RefUnwindSafe,
258 R: bolero_engine::IntoResult,
259 {
260 if options.exhaustive() {
261 let replay_on_fail = options.replay_on_fail();
262 let testfn = |driver: ExhastiveDriver, is_replay: bool, test: &mut T| {
263 if is_replay {
264 bolero_engine::any::scope::with(driver, || {
265 test(true);
266 });
267
268 unreachable!("Did not crash when replaying, is it deterministic?");
269 } else {
270 let (driver, result) = bolero_engine::any::run(driver, || test(is_replay));
271 let result = result.map_err(|error| {
272 Failure {
273 seed: None,
274 error,
275 hide_error: replay_on_fail,
276 input: driver.serialize(),
277 }
278 .to_string()
279 });
280 (driver, result)
281 }
282 };
283
284 return self.run_exhaustive(test, testfn, options);
285 }
286
287 let file_options = options.clone();
288 let rng_options = options;
289
290 let file_options = &file_options;
291 let rng_options = &rng_options;
292
293 let mut buffer = vec![];
294 let file_driver = bolero_engine::driver::bytes::Driver::new(vec![], file_options);
297 let file_driver = bolero_engine::driver::object::Object(file_driver);
298 let file_driver = Box::new(file_driver);
299 let mut file_driver = Some(file_driver);
300
301 let testfn = |test: &mut T, is_replay: bool, data: &input::Test| {
302 buffer.clear();
303 match data {
304 input::Test::File(file) => {
305 let mut driver = file_driver.take().unwrap();
306
307 let mut buf = core::mem::take(&mut buffer);
308 file.read_into(&mut buf);
309 driver.reset(buf, file_options);
310
311 if is_replay {
312 bolero_engine::any::scope::with(driver, || {
313 test(true);
314 });
315
316 unreachable!("Did not crash when replaying, is it deterministic?");
317 } else {
318 let (mut driver, result) = bolero_engine::any::run(driver, || test(false));
319 buffer = driver.reset(vec![], file_options);
320 file_driver = Some(driver);
321
322 result.map_err(|error| {
325 Failure {
326 seed: None,
327 error,
328 hide_error: false,
329 input: (), }
331 .to_string()
332 })
333 }
334 }
335 input::Test::Rng(conf) => {
336 let seed = conf.seed;
337 let driver = conf.driver(rng_options);
338 let driver = Box::new(Object(driver));
339
340 if is_replay {
341 bolero_engine::any::scope::with(driver, || {
342 test(true);
343 });
344
345 unreachable!("Did not crash when replaying, is it deterministic?");
346 } else {
347 let (_driver, result) = bolero_engine::any::run(driver, || test(is_replay));
348
349 result.map_err(|error| {
352 Failure {
353 seed: Some(seed),
354 error,
355 hide_error: false,
356 input: (), }
358 .to_string()
359 })
360 }
361 }
362 }
363 };
364
365 self.run_tests(test, testfn, rng_options.replay_on_fail())
366 }
367
368 fn run_tests<S, T>(mut self, mut state: S, mut testfn: T, replay_on_fail: bool)
369 where
370 T: FnMut(&mut S, bool, &input::Test) -> Result<bool, String>,
371 {
372 if cfg!(fuzzing_random) && self.rng_cfg.iterations.is_none() {
375 self.rng_cfg.iterations = Some(usize::MAX);
376 }
377
378 let tests = self.tests();
379
380 let start_time = std::time::Instant::now();
381 let test_time = if cfg!(fuzzing_random) {
382 self.rng_cfg.test_time
383 } else {
384 Some(self.rng_cfg.test_time_or_default()).filter(|v| *v < Duration::MAX)
385 };
386
387 let mut report = report::Report::default();
388 if cfg!(fuzzing_random) {
389 report.spawn_timer();
390 }
391
392 let mut outcome = outcome::Outcome::new(&self.location, start_time);
393
394 bolero_engine::panic::set_hook();
395 bolero_engine::panic::forward_panic(false);
396
397 for input in tests {
398 if let Some(test_time) = test_time {
399 if start_time.elapsed() > test_time {
400 outcome.on_exit(outcome::ExitReason::MaxDurationExceeded {
401 limit: test_time,
402 default: self.rng_cfg.test_time.is_none(),
403 });
404 break;
405 }
406 }
407
408 outcome.on_named_test(&input.data);
409
410 match testfn(&mut state, false, &input.data) {
411 Ok(is_valid) => {
412 report.on_result(is_valid);
413 }
414 Err(err) => {
415 bolero_engine::panic::forward_panic(true);
416 outcome.on_exit(outcome::ExitReason::TestFailure);
417 drop(outcome);
418 eprintln!("{err}");
419
420 if replay_on_fail {
421 let _ = testfn(&mut state, true, &input.data).expect_err("Did not crash when replaying, is it deterministic?");
422 }
423 panic!("test failed");
424 }
425 }
426 }
427 }
428
429 fn run_exhaustive<S, F>(self, mut state: S, mut testfn: F, options: driver::Options)
430 where
431 F: FnMut(ExhastiveDriver, bool, &mut S) -> (ExhastiveDriver, Result<bool, String>),
432 {
433 bolero_engine::panic::set_hook();
434 bolero_engine::panic::forward_panic(false);
435
436 let driver = exhaustive::Driver::new(&options);
437 let mut driver = Box::new(Object(driver));
438 let test_time = self.rng_cfg.test_time;
439 let start_time = std::time::Instant::now();
440
441 let mut report = report::Report::default();
442 report.spawn_timer();
444
445 let mut outcome = outcome::Outcome::new(&self.location, start_time);
446
447 while driver.step().is_continue() {
448 if let Some(test_time) = test_time {
449 if start_time.elapsed() > test_time {
450 outcome.on_exit(outcome::ExitReason::MaxDurationExceeded {
451 limit: test_time,
452 default: false,
453 });
454 break;
455 }
456 }
457
458 outcome.on_exhaustive_input();
459
460 let (drvr, result) = testfn(driver, false, &mut state);
461 driver = drvr;
462
463 match result {
464 Ok(is_valid) => {
465 report.on_estimate(driver.estimate());
466 report.on_result(is_valid);
467 }
468 Err(error) => {
469 bolero_engine::panic::forward_panic(true);
470 outcome.on_exit(outcome::ExitReason::TestFailure);
471 drop(outcome);
472 eprintln!("{error}");
473
474 if options.replay_on_fail() {
475 driver.replay();
476 let _ = testfn(driver, true, &mut state).1.expect_err("Did not crash when replaying, is it deterministic?");
477 }
478
479 panic!("test failed");
480 }
481 }
482 }
483 }
484}
485
486impl<T> Engine<T> for TestEngine
487where
488 T: Test,
489 T::Value: core::fmt::Debug,
490{
491 type Output = ();
492
493 fn run(self, test: T, options: driver::Options) -> Self::Output {
494 self.run_with_value(test, options);
495 bolero_engine::panic::forward_panic(true);
496 }
497}
498
499#[cfg(feature = "std")]
500impl bolero_engine::ScopedEngine for TestEngine {
501 type Output = ();
502
503 fn run<F, R>(self, test: F, options: driver::Options) -> Self::Output
504 where
505 F: FnMut(bool) -> R + core::panic::RefUnwindSafe,
506 R: bolero_engine::IntoResult,
507 {
508 self.run_with_scope(test, options);
509 bolero_engine::panic::forward_panic(true);
510 }
511}