diff options
Diffstat (limited to 'vendor/proptest/src/test_runner/failure_persistence')
4 files changed, 867 insertions, 0 deletions
diff --git a/vendor/proptest/src/test_runner/failure_persistence/file.rs b/vendor/proptest/src/test_runner/failure_persistence/file.rs new file mode 100644 index 000000000..61d7dcf6a --- /dev/null +++ b/vendor/proptest/src/test_runner/failure_persistence/file.rs @@ -0,0 +1,536 @@ +//- +// Copyright 2017, 2018, 2019 The proptest developers +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::any::Any; +use core::fmt::Debug; +use std::borrow::{Cow, ToOwned}; +use std::boxed::Box; +use std::env; +use std::fs; +use std::io::{self, BufRead, Write}; +use std::path::{Path, PathBuf}; +use std::string::{String, ToString}; +use std::sync::RwLock; +use std::vec::Vec; + +use self::FileFailurePersistence::*; +use crate::test_runner::failure_persistence::{ + FailurePersistence, PersistedSeed, +}; + +/// Describes how failing test cases are persisted. +/// +/// Note that file names in this enum are `&str` rather than `&Path` since +/// constant functions are not yet in Rust stable as of 2017-12-16. +/// +/// In all cases, if a derived path references a directory which does not yet +/// exist, proptest will attempt to create all necessary parent directories. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FileFailurePersistence { + /// Completely disables persistence of failing test cases. + /// + /// This is semantically equivalent to `Direct("/dev/null")` on Unix and + /// `Direct("NUL")` on Windows (though it is internally handled by simply + /// not doing any I/O). + Off, + /// The path given to `TestRunner::set_source_file()` is parsed. The path + /// is traversed up the directory tree until a directory containing a file + /// named `lib.rs` or `main.rs` is found. A sibling to that directory with + /// the name given by the string in this configuration is created, and a + /// file with the same name and path relative to the source directory, but + /// with the extension changed to `.txt`, is used. + /// + /// For example, given a source path of + /// `/home/jsmith/code/project/src/foo/bar.rs` and a configuration of + /// `SourceParallel("proptest-regressions")` (the default), assuming the + /// `src` directory has a `lib.rs` or `main.rs`, the resulting file would + /// be `/home/jsmith/code/project/proptest-regressions/foo/bar.txt`. + /// + /// If no `lib.rs` or `main.rs` can be found, a warning is printed and this + /// behaves like `WithSource`. + /// + /// If no source file has been configured, a warning is printed and this + /// behaves like `Off`. + SourceParallel(&'static str), + /// The path given to `TestRunner::set_source_file()` is parsed. The + /// extension of the path is changed to the string given in this + /// configuration, and that filename is used. + /// + /// For example, given a source path of + /// `/home/jsmith/code/project/src/foo/bar.rs` and a configuration of + /// `WithSource("regressions")`, the resulting path would be + /// `/home/jsmith/code/project/src/foo/bar.regressions`. + WithSource(&'static str), + /// The string given in this option is directly used as a file path without + /// any further processing. + Direct(&'static str), + #[doc(hidden)] + #[allow(missing_docs)] + _NonExhaustive, +} + +impl Default for FileFailurePersistence { + fn default() -> Self { + SourceParallel("proptest-regressions") + } +} + +impl FailurePersistence for FileFailurePersistence { + fn load_persisted_failures2( + &self, + source_file: Option<&'static str>, + ) -> Vec<PersistedSeed> { + let p = self.resolve( + source_file + .and_then(|s| absolutize_source_file(Path::new(s))) + .as_ref() + .map(|cow| &**cow), + ); + + let path: Option<&PathBuf> = p.as_ref(); + let result: io::Result<Vec<PersistedSeed>> = path.map_or_else( + || Ok(vec![]), + |path| { + // .ok() instead of .unwrap() so we don't propagate panics here + let _lock = PERSISTENCE_LOCK.read().ok(); + io::BufReader::new(fs::File::open(path)?) + .lines() + .enumerate() + .filter_map(|(lineno, line)| match line { + Err(err) => Some(Err(err)), + Ok(line) => parse_seed_line(line, path, lineno).map(Ok), + }) + .collect() + }, + ); + + unwrap_or!(result, err => { + if io::ErrorKind::NotFound != err.kind() { + eprintln!( + "proptest: failed to open {}: {}", + &path.map(|x| &**x) + .unwrap_or_else(|| Path::new("??")) + .display(), + err + ); + } + vec![] + }) + } + + fn save_persisted_failure2( + &mut self, + source_file: Option<&'static str>, + seed: PersistedSeed, + shrunken_value: &dyn Debug, + ) { + let path = self.resolve(source_file.map(Path::new)); + if let Some(path) = path { + // .ok() instead of .unwrap() so we don't propagate panics here + let _lock = PERSISTENCE_LOCK.write().ok(); + let is_new = !path.is_file(); + + let mut to_write = Vec::<u8>::new(); + if is_new { + write_header(&mut to_write) + .expect("proptest: couldn't write header."); + } + + write_seed_line(&mut to_write, &seed, shrunken_value) + .expect("proptest: couldn't write seed line."); + + if let Err(e) = write_seed_data_to_file(&path, &to_write) { + eprintln!( + "proptest: failed to append to {}: {}", + path.display(), + e + ); + } else if is_new { + eprintln!( + "proptest: Saving this and future failures in {}\n\ + proptest: If this test was run on a CI system, you may \ + wish to add the following line to your copy of the file.{}\n\ + {}", + path.display(), + if is_new { " (You may need to create it.)" } else { "" }, + seed); + } + } + } + + fn box_clone(&self) -> Box<dyn FailurePersistence> { + Box::new(*self) + } + + fn eq(&self, other: &dyn FailurePersistence) -> bool { + other + .as_any() + .downcast_ref::<Self>() + .map_or(false, |x| x == self) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +/// Ensure that the source file to use for resolving the location of the persisted +/// failing cases file is absolute. +/// +/// The source location can only be used if it is absolute. If `source` is +/// not an absolute path, an attempt will be made to determine the absolute +/// path based on the current working directory and its parents. If no +/// absolute path can be determined, a warning will be printed and proptest +/// will continue as if this function had never been called. +/// +/// See [`FileFailurePersistence`](enum.FileFailurePersistence.html) for details on +/// how this value is used once it is made absolute. +/// +/// This is normally called automatically by the `proptest!` macro, which +/// passes `file!()`. +/// +fn absolutize_source_file<'a>(source: &'a Path) -> Option<Cow<'a, Path>> { + absolutize_source_file_with_cwd(env::current_dir, source) +} + +fn absolutize_source_file_with_cwd<'a>( + getcwd: impl FnOnce() -> io::Result<PathBuf>, + source: &'a Path, +) -> Option<Cow<'a, Path>> { + if source.is_absolute() { + // On Unix, `file!()` is absolute. In these cases, we can use + // that path directly. + Some(Cow::Borrowed(source)) + } else { + // On Windows, `file!()` is relative to the crate root, but the + // test is not generally run with the crate root as the working + // directory, so the path is not directly usable. However, the + // working directory is almost always a subdirectory of the crate + // root, so pop directories off until pushing the source onto the + // directory results in a path that refers to an existing file. + // Once we find such a path, we can use that. + // + // If we can't figure out an absolute path, print a warning and act + // as if no source had been given. + match getcwd() { + Ok(mut cwd) => loop { + let joined = cwd.join(source); + if joined.is_file() { + break Some(Cow::Owned(joined)); + } + + if !cwd.pop() { + eprintln!( + "proptest: Failed to find absolute path of \ + source file '{:?}'. Ensure the test is \ + being run from somewhere within the crate \ + directory hierarchy.", + source + ); + break None; + } + }, + + Err(e) => { + eprintln!( + "proptest: Failed to determine current \ + directory, so the relative source path \ + '{:?}' cannot be resolved: {}", + source, e + ); + None + } + } + } +} + +fn parse_seed_line( + mut line: String, + path: &Path, + lineno: usize, +) -> Option<PersistedSeed> { + // Remove anything after and including '#': + if let Some(comment_start) = line.find('#') { + line.truncate(comment_start); + } + + if line.len() > 0 { + let ret = line.parse::<PersistedSeed>().ok(); + if !ret.is_some() { + eprintln!( + "proptest: {}:{}: unparsable line, ignoring", + path.display(), + lineno + 1 + ); + } + return ret; + } + + None +} + +fn write_seed_line( + buf: &mut Vec<u8>, + seed: &PersistedSeed, + shrunken_value: &dyn Debug, +) -> io::Result<()> { + // Write the seed itself + write!(buf, "{}", seed.to_string())?; + + // Write out comment: + let debug_start = buf.len(); + write!(buf, " # shrinks to {:?}", shrunken_value)?; + + // Ensure there are no newlines in the debug output + for byte in &mut buf[debug_start..] { + if b'\n' == *byte || b'\r' == *byte { + *byte = b' '; + } + } + + buf.push(b'\n'); + + Ok(()) +} + +fn write_header(buf: &mut Vec<u8>) -> io::Result<()> { + writeln!( + buf, + "\ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases." + ) +} + +fn write_seed_data_to_file(dst: &Path, data: &[u8]) -> io::Result<()> { + if let Some(parent) = dst.parent() { + fs::create_dir_all(parent)?; + } + + let mut options = fs::OpenOptions::new(); + options.append(true).create(true); + let mut out = options.open(dst)?; + out.write_all(data)?; + + Ok(()) +} + +impl FileFailurePersistence { + /// Given the nominal source path, determine the location of the failure + /// persistence file, if any. + pub(super) fn resolve(&self, source: Option<&Path>) -> Option<PathBuf> { + let source = source.and_then(absolutize_source_file); + + match *self { + Off => None, + + SourceParallel(sibling) => match source { + Some(source_path) => { + let mut dir = Cow::into_owned(source_path.clone()); + let mut found = false; + while dir.pop() { + if dir.join("lib.rs").is_file() + || dir.join("main.rs").is_file() + { + found = true; + break; + } + } + + if !found { + eprintln!( + "proptest: FileFailurePersistence::SourceParallel set, \ + but failed to find lib.rs or main.rs" + ); + WithSource(sibling).resolve(Some(&*source_path)) + } else { + let suffix = source_path + .strip_prefix(&dir) + .expect("parent of source is not a prefix of it?") + .to_owned(); + let mut result = dir; + // If we've somehow reached the root, or someone gave + // us a relative path that we've exhausted, just accept + // creating a subdirectory instead. + let _ = result.pop(); + result.push(sibling); + result.push(&suffix); + result.set_extension("txt"); + Some(result) + } + } + None => { + eprintln!( + "proptest: FileFailurePersistence::SourceParallel set, \ + but no source file known" + ); + None + } + }, + + WithSource(extension) => match source { + Some(source_path) => { + let mut result = Cow::into_owned(source_path); + result.set_extension(extension); + Some(result) + } + + None => { + eprintln!( + "proptest: FileFailurePersistence::WithSource set, \ + but no source file known" + ); + None + } + }, + + Direct(path) => Some(Path::new(path).to_owned()), + + _NonExhaustive => { + panic!("FailurePersistence set to _NonExhaustive") + } + } + } +} + +lazy_static! { + /// Used to guard access to the persistence file(s) so that a single + /// process will not step on its own toes. + /// + /// We don't have much protecting us should two separate process try to + /// write to the same file at once (depending on how atomic append mode is + /// on the OS), but this should be extremely rare. + static ref PERSISTENCE_LOCK: RwLock<()> = RwLock::new(()); +} + +#[cfg(test)] +mod tests { + use super::*; + + struct TestPaths { + crate_root: &'static Path, + src_file: PathBuf, + subdir_file: PathBuf, + misplaced_file: PathBuf, + } + + lazy_static! { + static ref TEST_PATHS: TestPaths = { + let crate_root = Path::new(env!("CARGO_MANIFEST_DIR")); + let lib_root = crate_root.join("src"); + let src_subdir = lib_root.join("strategy"); + let src_file = lib_root.join("foo.rs"); + let subdir_file = src_subdir.join("foo.rs"); + let misplaced_file = crate_root.join("foo.rs"); + TestPaths { + crate_root, + src_file, + subdir_file, + misplaced_file, + } + }; + } + + #[test] + fn persistence_file_location_resolved_correctly() { + // If off, there is never a file + assert_eq!(None, Off.resolve(None)); + assert_eq!(None, Off.resolve(Some(&TEST_PATHS.subdir_file))); + + // For direct, we don't care about the source file, and instead always + // use whatever is in the config. + assert_eq!( + Some(Path::new("bar.txt").to_owned()), + Direct("bar.txt").resolve(None) + ); + assert_eq!( + Some(Path::new("bar.txt").to_owned()), + Direct("bar.txt").resolve(Some(&TEST_PATHS.subdir_file)) + ); + + // For WithSource, only the extension changes, but we get nothing if no + // source file was configured. + // Accounting for the way absolute paths work on Windows would be more + // complex, so for now don't test that case. + #[cfg(unix)] + fn absolute_path_case() { + assert_eq!( + Some(Path::new("/foo/bar.ext").to_owned()), + WithSource("ext").resolve(Some(Path::new("/foo/bar.rs"))) + ); + } + #[cfg(not(unix))] + fn absolute_path_case() {} + absolute_path_case(); + assert_eq!(None, WithSource("ext").resolve(None)); + + // For SourceParallel, we make a sibling directory tree and change the + // extensions to .txt ... + assert_eq!( + Some(TEST_PATHS.crate_root.join("sib").join("foo.txt")), + SourceParallel("sib").resolve(Some(&TEST_PATHS.src_file)) + ); + assert_eq!( + Some( + TEST_PATHS + .crate_root + .join("sib") + .join("strategy") + .join("foo.txt") + ), + SourceParallel("sib").resolve(Some(&TEST_PATHS.subdir_file)) + ); + // ... but if we can't find lib.rs / main.rs, give up and set the + // extension instead ... + assert_eq!( + Some(TEST_PATHS.crate_root.join("foo.sib")), + SourceParallel("sib").resolve(Some(&TEST_PATHS.misplaced_file)) + ); + // ... and if no source is configured, we do nothing + assert_eq!(None, SourceParallel("ext").resolve(None)); + } + + #[test] + fn relative_source_files_absolutified() { + const TEST_RUNNER_PATH: &[&str] = &["src", "test_runner", "mod.rs"]; + lazy_static! { + static ref TEST_RUNNER_RELATIVE: PathBuf = + TEST_RUNNER_PATH.iter().collect(); + } + const CARGO_DIR: &str = env!("CARGO_MANIFEST_DIR"); + + let expected = ::std::iter::once(CARGO_DIR) + .chain(TEST_RUNNER_PATH.iter().map(|s| *s)) + .collect::<PathBuf>(); + + // Running from crate root + assert_eq!( + &*expected, + absolutize_source_file_with_cwd( + || Ok(Path::new(CARGO_DIR).to_owned()), + &TEST_RUNNER_RELATIVE + ) + .unwrap() + ); + + // Running from test subdirectory + assert_eq!( + &*expected, + absolutize_source_file_with_cwd( + || Ok(Path::new(CARGO_DIR).join("target")), + &TEST_RUNNER_RELATIVE + ) + .unwrap() + ); + } +} diff --git a/vendor/proptest/src/test_runner/failure_persistence/map.rs b/vendor/proptest/src/test_runner/failure_persistence/map.rs new file mode 100644 index 000000000..322e554c6 --- /dev/null +++ b/vendor/proptest/src/test_runner/failure_persistence/map.rs @@ -0,0 +1,99 @@ +//- +// Copyright 2017, 2018 The proptest developers +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::std_facade::{fmt, BTreeMap, BTreeSet, Box, Vec}; +use core::any::Any; + +use crate::test_runner::failure_persistence::FailurePersistence; +use crate::test_runner::failure_persistence::PersistedSeed; + +/// Failure persistence option that loads and saves seeds in memory +/// on the heap. This may be useful when accumulating test failures +/// across multiple `TestRunner` instances for external reporting +/// or batched persistence. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MapFailurePersistence { + /// Backing map, keyed by source_file. + pub map: BTreeMap<&'static str, BTreeSet<PersistedSeed>>, +} + +impl FailurePersistence for MapFailurePersistence { + fn load_persisted_failures2( + &self, + source_file: Option<&'static str>, + ) -> Vec<PersistedSeed> { + source_file + .and_then(|source| self.map.get(source)) + .map(|seeds| seeds.iter().cloned().collect::<Vec<_>>()) + .unwrap_or_default() + } + + fn save_persisted_failure2( + &mut self, + source_file: Option<&'static str>, + seed: PersistedSeed, + _shrunken_value: &dyn fmt::Debug, + ) { + let s = match source_file { + Some(sf) => sf, + None => return, + }; + let set = self.map.entry(s).or_insert_with(BTreeSet::new); + set.insert(seed); + } + + fn box_clone(&self) -> Box<dyn FailurePersistence> { + Box::new(self.clone()) + } + + fn eq(&self, other: &dyn FailurePersistence) -> bool { + other + .as_any() + .downcast_ref::<Self>() + .map_or(false, |x| x == self) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_runner::failure_persistence::tests::*; + + #[test] + fn initial_map_is_empty() { + assert!(MapFailurePersistence::default() + .load_persisted_failures2(HI_PATH) + .is_empty()) + } + + #[test] + fn seeds_recoverable() { + let mut p = MapFailurePersistence::default(); + p.save_persisted_failure2(HI_PATH, INC_SEED, &""); + let restored = p.load_persisted_failures2(HI_PATH); + assert_eq!(1, restored.len()); + assert_eq!(INC_SEED, *restored.first().unwrap()); + + assert!(p.load_persisted_failures2(None).is_empty()); + assert!(p.load_persisted_failures2(UNREL_PATH).is_empty()); + } + + #[test] + fn seeds_deduplicated() { + let mut p = MapFailurePersistence::default(); + p.save_persisted_failure2(HI_PATH, INC_SEED, &""); + p.save_persisted_failure2(HI_PATH, INC_SEED, &""); + let restored = p.load_persisted_failures2(HI_PATH); + assert_eq!(1, restored.len()); + } +} diff --git a/vendor/proptest/src/test_runner/failure_persistence/mod.rs b/vendor/proptest/src/test_runner/failure_persistence/mod.rs new file mode 100644 index 000000000..6d21c4002 --- /dev/null +++ b/vendor/proptest/src/test_runner/failure_persistence/mod.rs @@ -0,0 +1,156 @@ +//- +// Copyright 2017, 2018, 2019 The proptest developers +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::std_facade::{fmt, Box, Vec}; +use core::any::Any; +use core::fmt::Display; +use core::result::Result; +use core::str::FromStr; + +#[cfg(feature = "std")] +mod file; +mod map; +mod noop; + +#[cfg(feature = "std")] +pub use self::file::*; +pub use self::map::*; +pub use self::noop::*; + +use crate::test_runner::Seed; + +/// Opaque struct representing a seed which can be persisted. +/// +/// The `Display` and `FromStr` implementations go to and from the format +/// Proptest uses for its persistence file. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct PersistedSeed(pub(crate) Seed); + +impl Display for PersistedSeed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.to_persistence()) + } +} + +impl FromStr for PersistedSeed { + type Err = (); + + fn from_str(s: &str) -> Result<Self, ()> { + Seed::from_persistence(s).map(PersistedSeed).ok_or(()) + } +} + +/// Provides external persistence for historical test failures by storing seeds. +/// +/// **Note**: Implementing `load_persisted_failures` and +/// `save_persisted_failures` is **deprecated** and these methods will be +/// removed in proptest 0.10.0. Instead, implement `load_persisted_failures2` +/// and `save_persisted_failures2`. +pub trait FailurePersistence: Send + Sync + fmt::Debug { + /// Supply seeds associated with the given `source_file` that may be used + /// by a `TestRunner`'s random number generator in order to consistently + /// recreate a previously-failing `Strategy`-provided value. + /// + /// The default implementation is **for backwards compatibility**. It + /// delegates to `load_persisted_failures` and converts the results into + /// XorShift seeds. + #[allow(deprecated)] + fn load_persisted_failures2( + &self, + source_file: Option<&'static str>, + ) -> Vec<PersistedSeed> { + self.load_persisted_failures(source_file) + .into_iter() + .map(|seed| PersistedSeed(Seed::XorShift(seed))) + .collect() + } + + /// Use `load_persisted_failures2` instead. + /// + /// This function inadvertently exposes the implementation of seeds prior + /// to Proptest 0.9.1 and only works with XorShift seeds. + #[deprecated] + #[allow(unused_variables)] + fn load_persisted_failures( + &self, + source_file: Option<&'static str>, + ) -> Vec<[u8; 16]> { + panic!("load_persisted_failures2 not implemented"); + } + + /// Store a new failure-generating seed associated with the given `source_file`. + /// + /// The default implementation is **for backwards compatibility**. It + /// delegates to `save_persisted_failure` if `seed` is a XorShift seed. + #[allow(deprecated)] + fn save_persisted_failure2( + &mut self, + source_file: Option<&'static str>, + seed: PersistedSeed, + shrunken_value: &dyn fmt::Debug, + ) { + match seed.0 { + Seed::XorShift(seed) => { + self.save_persisted_failure(source_file, seed, shrunken_value) + } + _ => (), + } + } + + /// Use `save_persisted_failures2` instead. + /// + /// This function inadvertently exposes the implementation of seeds prior + /// to Proptest 0.9.1 and only works with XorShift seeds. + #[deprecated] + #[allow(unused_variables)] + fn save_persisted_failure( + &mut self, + source_file: Option<&'static str>, + seed: [u8; 16], + shrunken_value: &dyn fmt::Debug, + ) { + panic!("save_persisted_failure2 not implemented"); + } + + /// Delegate method for producing a trait object usable with `Clone` + fn box_clone(&self) -> Box<dyn FailurePersistence>; + + /// Equality testing delegate required due to constraints of trait objects. + fn eq(&self, other: &dyn FailurePersistence) -> bool; + + /// Assistant method for trait object comparison. + fn as_any(&self) -> &dyn Any; +} + +impl<'a, 'b> PartialEq<dyn FailurePersistence + 'b> + for dyn FailurePersistence + 'a +{ + fn eq(&self, other: &(dyn FailurePersistence + 'b)) -> bool { + FailurePersistence::eq(self, other) + } +} + +impl Clone for Box<dyn FailurePersistence> { + fn clone(&self) -> Box<dyn FailurePersistence> { + self.box_clone() + } +} + +#[cfg(test)] +mod tests { + use super::PersistedSeed; + use crate::test_runner::rng::Seed; + + pub const INC_SEED: PersistedSeed = PersistedSeed(Seed::XorShift([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + ])); + + pub const HI_PATH: Option<&str> = Some("hi"); + pub const UNREL_PATH: Option<&str> = Some("unrelated"); +} diff --git a/vendor/proptest/src/test_runner/failure_persistence/noop.rs b/vendor/proptest/src/test_runner/failure_persistence/noop.rs new file mode 100644 index 000000000..59538392a --- /dev/null +++ b/vendor/proptest/src/test_runner/failure_persistence/noop.rs @@ -0,0 +1,76 @@ +//- +// Copyright 2017, 2018 The proptest developers +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::std_facade::{fmt, Box, Vec}; +use core::any::Any; + +use crate::test_runner::failure_persistence::{ + FailurePersistence, PersistedSeed, +}; + +/// Failure persistence option that loads and saves nothing at all. +#[derive(Debug, Default, PartialEq)] +struct NoopFailurePersistence; + +impl FailurePersistence for NoopFailurePersistence { + fn load_persisted_failures2( + &self, + _source_file: Option<&'static str>, + ) -> Vec<PersistedSeed> { + Vec::new() + } + + fn save_persisted_failure2( + &mut self, + _source_file: Option<&'static str>, + _seed: PersistedSeed, + _shrunken_value: &dyn fmt::Debug, + ) { + } + + fn box_clone(&self) -> Box<dyn FailurePersistence> { + Box::new(NoopFailurePersistence) + } + + fn eq(&self, other: &dyn FailurePersistence) -> bool { + other + .as_any() + .downcast_ref::<Self>() + .map_or(false, |x| x == self) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_runner::failure_persistence::tests::*; + + #[test] + fn default_load_is_empty() { + assert!(NoopFailurePersistence::default() + .load_persisted_failures2(None) + .is_empty()); + assert!(NoopFailurePersistence::default() + .load_persisted_failures2(HI_PATH) + .is_empty()); + } + + #[test] + fn seeds_not_recoverable() { + let mut p = NoopFailurePersistence::default(); + p.save_persisted_failure2(HI_PATH, INC_SEED, &""); + assert!(p.load_persisted_failures2(HI_PATH).is_empty()); + assert!(p.load_persisted_failures2(None).is_empty()); + assert!(p.load_persisted_failures2(UNREL_PATH).is_empty()); + } +} |