From cfac1290c0c57b2ee46782a54062a5f7375a3b41 Mon Sep 17 00:00:00 2001 From: Rain Date: Sat, 16 Sep 2023 12:24:43 -0700 Subject: [PATCH] Use PathParams --- proptest/src/arbitrary/_std/path.rs | 73 ++++++++++++++++++++++++----- proptest/src/lib.rs | 3 ++ proptest/src/path.rs | 55 ++++++++++++++++++++++ proptest/src/string.rs | 2 +- 4 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 proptest/src/path.rs diff --git a/proptest/src/arbitrary/_std/path.rs b/proptest/src/arbitrary/_std/path.rs index 53c00dd2..5c7689e8 100644 --- a/proptest/src/arbitrary/_std/path.rs +++ b/proptest/src/arbitrary/_std/path.rs @@ -9,19 +9,59 @@ //! Arbitrary implementations for `std::path`. -use std::{ffi::OsString, path::*}; +use std::path::*; use crate::{ arbitrary::{SMapped, StrategyFor}, + path::PathParams, prelude::{any, any_with, Arbitrary, Strategy}, - std_facade::{string::ToString, Arc, Box, Rc, Vec}, + std_facade::{string::ToString, Arc, Box, Rc, String, Vec}, strategy::{statics::static_map, MapInto}, }; arbitrary!(StripPrefixError; Path::new("").strip_prefix("a").unwrap_err()); -/// This implementation takes two parameters: a range for the number of components, and a regex to -/// determine the string. It generates either a relative or an absolute path with equal probability. +/// A private type (not actually pub) representing the output of [`PathParams`] that can't be +/// referred to by API users. +/// +/// The goal of this type is to encapsulate the output of `PathParams`. If this layer weren't +/// present, the type of `::Strategy` would be `SMapped<(bool, Vec), +/// Self>`. This is a problem because it exposes the internal representation of `PathParams` as an +/// API. For example, if an additional parameter of randomness (e.g. another bool) were added, the +/// type of `Strategy` would change. +/// +/// With `PathParamsOutput`, the type of `Strategy` is `SMapped`, which is a +/// type that can't be named directly---only via `::Strategy`. The internal +/// representation of `PathParams` can be changed without affecting the API. +#[derive(Debug)] +pub struct PathParamsOutput { + is_absolute: bool, + components: Vec, +} + +impl Arbitrary for PathParamsOutput { + type Parameters = PathParams; + type Strategy = SMapped<(bool, Vec), Self>; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + static_map( + ( + any::(), + any_with::>(( + args.components(), + args.component_regex(), + )), + ), + |(is_absolute, components)| Self { + is_absolute, + components, + }, + ) + } +} + +/// This implementation accepts as its argument a [`PathParams`] struct. It generates either a +/// relative or an absolute path with equal probability. /// /// Currently, this implementation does not generate: /// @@ -29,20 +69,31 @@ arbitrary!(StripPrefixError; Path::new("").strip_prefix("a").unwrap_err()); /// * Paths with a [`PrefixComponent`](std::path::PrefixComponent) on Windows, e.g. `C:\` (this may /// change in the future) impl Arbitrary for PathBuf { - type Parameters = as Arbitrary>::Parameters; - type Strategy = SMapped<(bool, Vec), Self>; + type Parameters = PathParams; + type Strategy = SMapped; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { static_map( - (any::(), any_with::>(args)), - |(is_absolute, parts)| { + any_with::(args), + |PathParamsOutput { + is_absolute, + components, + }| { let mut out = PathBuf::new(); if is_absolute { out.push(&MAIN_SEPARATOR.to_string()); } - for part in parts { - out.push(&part); + + for component in components { + // If a component has an embedded / (or \ on Windows), remove it from the + // string. + let component = component + .chars() + .filter(|&c| std::path::is_separator(c)) + .collect::(); + out.push(&component); } + out }, ) @@ -55,7 +106,7 @@ macro_rules! dst_wrapped { /// This implementation is identical to [the `Arbitrary` implementation for /// `PathBuf`](trait.Arbitrary.html#impl-Arbitrary-for-PathBuf). impl Arbitrary for $w { - type Parameters = as Arbitrary>::Parameters; + type Parameters = PathParams; type Strategy = MapInto, Self>; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { diff --git a/proptest/src/lib.rs b/proptest/src/lib.rs index 9cc3c77e..72b90ffb 100644 --- a/proptest/src/lib.rs +++ b/proptest/src/lib.rs @@ -86,6 +86,9 @@ pub mod test_runner; pub mod tuple; pub mod option; +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +pub mod path; pub mod result; pub mod sample; #[cfg(feature = "std")] diff --git a/proptest/src/path.rs b/proptest/src/path.rs new file mode 100644 index 00000000..76242817 --- /dev/null +++ b/proptest/src/path.rs @@ -0,0 +1,55 @@ +//! Strategies for generating [`PathBuf`] and related path types. +//! +//! [`PathParams`] in this module is used as the argument to the +//! [`Arbitrary`](crate::arbitrary::Arbitrary) implementation for [`PathBuf`]. + +use crate::{collection::SizeRange, string::StringParam}; + +/// Parameters for the [`Arbitrary`] implementation for [`PathBuf`]. +/// +/// By default, this generates paths with 0 to 8 components uniformly at random, each of which is a +/// default [`StringParam`]. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct PathParams { + /// The number of components in the path. + components: SizeRange, + /// The regular expression to generate individual components. + component_regex: StringParam, +} + +impl PathParams { + /// Gets the number of components in the path. + pub fn components(&self) -> SizeRange { + self.components.clone() + } + + /// Sets the number of components in the path. + pub fn with_components(mut self, components: impl Into) -> Self { + self.components = components.into(); + self + } + + /// Gets the regular expression to generate individual components. + pub fn component_regex(&self) -> StringParam { + self.component_regex + } + + /// Sets the regular expression to generate individual components. + pub fn with_component_regex( + mut self, + component_regex: impl Into, + ) -> Self { + self.component_regex = component_regex.into(); + self + } +} + +impl Default for PathParams { + fn default() -> Self { + Self { + components: (0..8).into(), + // This is the default regex for `any::()`. + component_regex: StringParam::default(), + } + } +} diff --git a/proptest/src/string.rs b/proptest/src/string.rs index 5e24f825..55666cb6 100644 --- a/proptest/src/string.rs +++ b/proptest/src/string.rs @@ -27,7 +27,7 @@ use crate::test_runner::*; /// Wraps the regex that forms the `Strategy` for `String` so that a sensible /// `Default` can be given. The default is a string of non-control characters. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct StringParam(&'static str); impl From for &'static str {