mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
gpui: Add property_test macro (#50935)
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
66de3d9c00
commit
33e5301946
15 changed files with 656 additions and 111 deletions
313
Cargo.lock
generated
313
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -650,6 +650,9 @@ postage = { version = "0.5", features = ["futures-traits"] }
|
|||
pretty_assertions = { version = "1.3.0", features = ["unstable"] }
|
||||
proc-macro2 = "1.0.93"
|
||||
profiling = "1"
|
||||
# replace this with main when #635 is merged
|
||||
proptest = { git = "https://github.com/proptest-rs/proptest", rev = "3dca198a8fef1b32e3a66f1e1897c955b4dc5b5b", features = ["attr-macro"] }
|
||||
proptest-derive = "0.8.0"
|
||||
prost = "0.9"
|
||||
prost-build = "0.9"
|
||||
prost-types = "0.9"
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ test-support = [
|
|||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-html",
|
||||
"proptest",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
|
|
@ -63,6 +64,8 @@ ordered-float.workspace = true
|
|||
parking_lot.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
project.workspace = true
|
||||
proptest = { workspace = true, optional = true }
|
||||
proptest-derive = { workspace = true, optional = true }
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
rpc.workspace = true
|
||||
|
|
@ -110,6 +113,8 @@ lsp = { workspace = true, features = ["test-support"] }
|
|||
markdown = { workspace = true, features = ["test-support"] }
|
||||
multi_buffer = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
proptest.workspace = true
|
||||
proptest-derive.workspace = true
|
||||
release_channel.workspace = true
|
||||
rand.workspace = true
|
||||
semver.workspace = true
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<Di
|
|||
.display_ranges(&editor.display_snapshot(cx))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod property_test;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_events(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
|
|
|||
85
crates/editor/src/editor_tests/property_test.rs
Normal file
85
crates/editor/src/editor_tests/property_test.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use proptest::prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, proptest_derive::Arbitrary)]
|
||||
pub enum Direction {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, proptest_derive::Arbitrary)]
|
||||
pub enum TestAction {
|
||||
#[proptest(weight = 4)]
|
||||
Type(String),
|
||||
Backspace {
|
||||
#[proptest(strategy = "1usize..100")]
|
||||
count: usize,
|
||||
},
|
||||
Move {
|
||||
#[proptest(strategy = "1usize..100")]
|
||||
count: usize,
|
||||
direction: Direction,
|
||||
},
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn apply_test_action(
|
||||
&mut self,
|
||||
action: &TestAction,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match action {
|
||||
TestAction::Type(text) => self.insert(&text, window, cx),
|
||||
TestAction::Backspace { count } => {
|
||||
for _ in 0..*count {
|
||||
self.delete(&Default::default(), window, cx);
|
||||
}
|
||||
}
|
||||
TestAction::Move { count, direction } => {
|
||||
for _ in 0..*count {
|
||||
match direction {
|
||||
Direction::Up => self.move_up(&Default::default(), window, cx),
|
||||
Direction::Down => self.move_down(&Default::default(), window, cx),
|
||||
Direction::Left => self.move_left(&Default::default(), window, cx),
|
||||
Direction::Right => self.move_right(&Default::default(), window, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_actions() -> impl Strategy<Value = Vec<TestAction>> {
|
||||
proptest::collection::vec(any::<TestAction>(), 1..10)
|
||||
}
|
||||
|
||||
#[gpui::property_test(config = ProptestConfig {cases: 100, ..Default::default()})]
|
||||
fn editor_property_test(
|
||||
cx: &mut TestAppContext,
|
||||
#[strategy = test_actions()] actions: Vec<TestAction>,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let group_interval = Duration::from_millis(1);
|
||||
|
||||
let buffer = cx.new(|cx| {
|
||||
let mut buf = language::Buffer::local("123456", cx);
|
||||
buf.set_group_interval(group_interval);
|
||||
buf
|
||||
});
|
||||
|
||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||
let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
|
||||
|
||||
editor
|
||||
.update(cx, |editor, window, cx| {
|
||||
for action in actions {
|
||||
editor.apply_test_action(&action, window, cx);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ test-support = [
|
|||
"http_client/test-support",
|
||||
"wayland",
|
||||
"x11",
|
||||
"proptest",
|
||||
]
|
||||
inspector = ["gpui_macros/inspector"]
|
||||
leak-detection = ["backtrace"]
|
||||
|
|
@ -64,6 +65,7 @@ num_cpus = "1.13"
|
|||
parking = "2.0.0"
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
proptest = { workspace = true, optional = true }
|
||||
chrono.workspace = true
|
||||
profiling.workspace = true
|
||||
rand.workspace = true
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ mod util;
|
|||
mod view;
|
||||
mod window;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use proptest;
|
||||
|
||||
#[cfg(doc)]
|
||||
pub mod _ownership_and_data_flow;
|
||||
|
||||
|
|
@ -86,7 +89,9 @@ pub use elements::*;
|
|||
pub use executor::*;
|
||||
pub use geometry::*;
|
||||
pub use global::*;
|
||||
pub use gpui_macros::{AppContext, IntoElement, Render, VisualContext, register_action, test};
|
||||
pub use gpui_macros::{
|
||||
AppContext, IntoElement, Render, VisualContext, property_test, register_action, test,
|
||||
};
|
||||
pub use gpui_util::arc_cow::ArcCow;
|
||||
pub use http_client;
|
||||
pub use input::*;
|
||||
|
|
|
|||
|
|
@ -27,12 +27,43 @@
|
|||
//! ```
|
||||
use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
|
||||
use futures::StreamExt as _;
|
||||
use proptest::prelude::{Just, Strategy, any};
|
||||
use std::{
|
||||
env,
|
||||
panic::{self, RefUnwindSafe},
|
||||
panic::{self, RefUnwindSafe, UnwindSafe},
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
/// Strategy injected into `#[gpui::property_test]` tests to control the seed
|
||||
/// given to the scheduler. Doesn't shrink, since all scheduler seeds are
|
||||
/// equivalent in complexity. If `$SEED` is set, it always uses that value.
|
||||
pub fn seed_strategy() -> impl Strategy<Value = u64> {
|
||||
match std::env::var("SEED") {
|
||||
Ok(val) => Just(val.parse().unwrap()).boxed(),
|
||||
Err(_) => any::<u64>().no_shrink().boxed(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Similar to [`run_test`], but only runs the callback once, allowing
|
||||
/// [`FnOnce`] callbacks. This is intended for use with the
|
||||
/// `gpui::property_test` macro and generally should not be used directly.
|
||||
///
|
||||
/// Doesn't support many features of [`run_test`], since these are provided by
|
||||
/// proptest.
|
||||
pub fn run_test_once(seed: u64, test_fn: Box<dyn UnwindSafe + FnOnce(TestDispatcher)>) {
|
||||
let result = panic::catch_unwind(|| {
|
||||
let dispatcher = TestDispatcher::new(seed);
|
||||
let scheduler = dispatcher.scheduler().clone();
|
||||
test_fn(dispatcher);
|
||||
scheduler.end_test();
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Err(e) => panic::resume_unwind(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the given test function with the configured parameters.
|
||||
/// This is intended for use with the `gpui::test` macro
|
||||
/// and generally should not be used directly.
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ quote.workspace = true
|
|||
syn.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["inspector"] }
|
||||
gpui = { workspace = true, features = ["inspector"] }
|
||||
|
|
@ -3,6 +3,7 @@ mod derive_app_context;
|
|||
mod derive_into_element;
|
||||
mod derive_render;
|
||||
mod derive_visual_context;
|
||||
mod property_test;
|
||||
mod register_action;
|
||||
mod styles;
|
||||
mod test;
|
||||
|
|
@ -188,6 +189,79 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
|
|||
test::test(args, function)
|
||||
}
|
||||
|
||||
/// A variant of `#[gpui::test]` that supports property-based testing.
|
||||
///
|
||||
/// A property test, much like a standard GPUI randomized test, allows testing
|
||||
/// claims of the form "for any possible X, Y should hold". For example:
|
||||
/// ```
|
||||
/// #[gpui::property_test]
|
||||
/// fn test_arithmetic(x: i32, y: i32) {
|
||||
/// assert!(x == y || x < y || x > y);
|
||||
/// }
|
||||
/// ```
|
||||
/// Standard GPUI randomized tests provide you with an instance of `StdRng` to
|
||||
/// generate random data in a controlled manner. Property-based tests have some
|
||||
/// advantages, however:
|
||||
/// - Shrinking - the harness also understands a notion of the "complexity" of a
|
||||
/// particular value. This allows it to find the "simplest possible value that
|
||||
/// causes the test to fail".
|
||||
/// - Ergonomics/clarity - the property-testing harness will automatically
|
||||
/// generate values, removing the need to fill the test body with generation
|
||||
/// logic.
|
||||
/// - Failure persistence - if a failing seed is identified, it is stored in a
|
||||
/// file, which can be checked in, and future runs will check these cases before
|
||||
/// future cases.
|
||||
///
|
||||
/// Property tests work best when all inputs can be generated up-front and kept
|
||||
/// in a simple data structure. Sometimes, this isn't possible - for example, if
|
||||
/// a test needs to make a random decision based on the current state of some
|
||||
/// structure. In this case, a standard GPUI randomized test may be more
|
||||
/// suitable.
|
||||
///
|
||||
/// ## Customizing random values
|
||||
///
|
||||
/// This macro is based on the [`#[proptest::property_test]`] macro, but handles
|
||||
/// some of the same GPUI-specific arguments as `#[gpui::test]`. Specifically,
|
||||
/// `&{mut,} TestAppContext` and `BackgroundExecutor` work as normal. `StdRng`
|
||||
/// arguments are **explicitly forbidden**, since they break shrinking, and are
|
||||
/// a common footgun.
|
||||
///
|
||||
/// All other arguments are forwarded to the underlying proptest macro.
|
||||
///
|
||||
/// Note: much of the following is copied from the proptest docs, specifically the
|
||||
/// [`#[proptest::property_test]`] macro docs.
|
||||
///
|
||||
/// Random values of type `T` are generated by a `Strategy<Value = T>` object.
|
||||
/// Some types have a canonical `Strategy` - these types also implement
|
||||
/// `Arbitrary`. Parameters to a `#[gpui::property_test]`, by default, use a
|
||||
/// type's `Arbitrary` implementation. If you'd like to provide a custom
|
||||
/// strategy, you can use `#[strategy = ...]` on the argument:
|
||||
/// ```
|
||||
/// #[gpui::property_test]
|
||||
/// fn int_test(#[strategy = 1..10] x: i32, #[strategy = "[a-zA-Z0-9]{20}"] s: String) {
|
||||
/// assert!(s.len() > (x as usize));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// For more information on writing custom `Strategy` and `Arbitrary`
|
||||
/// implementations, see [the proptest book][book], and the [`Strategy`] trait.
|
||||
///
|
||||
/// ## Scheduler
|
||||
///
|
||||
/// Similar to `#[gpui::test]`, this macro will choose random seeds for the test
|
||||
/// scheduler. It uses `.no_shrink()` to tell proptest that all seeds are
|
||||
/// roughly equivalent in terms of "complexity". If `$SEED` is set, it will
|
||||
/// affect **ONLY** the seed passed to the scheduler. To control other values,
|
||||
/// use custom `Strategy`s.
|
||||
///
|
||||
/// [`#[proptest::property_test]`]: https://docs.rs/proptest/latest/proptest/attr.property_test.html
|
||||
/// [book]: https://proptest-rs.github.io/proptest/intro.html
|
||||
/// [`Strategy`]: https://docs.rs/proptest/latest/proptest/strategy/trait.Strategy.html
|
||||
#[proc_macro_attribute]
|
||||
pub fn property_test(args: TokenStream, function: TokenStream) -> TokenStream {
|
||||
property_test::test(args.into(), function.into()).into()
|
||||
}
|
||||
|
||||
/// When added to a trait, `#[derive_inspector_reflection]` generates a module which provides
|
||||
/// enumeration and lookup by name of all methods that have the shape `fn method(self) -> Self`.
|
||||
/// This is used by the inspector so that it can use the builder methods in `Styled` and
|
||||
|
|
|
|||
199
crates/gpui_macros/src/property_test.rs
Normal file
199
crates/gpui_macros/src/property_test.rs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{
|
||||
FnArg, Ident, ItemFn, Type, parse2, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
||||
};
|
||||
|
||||
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let item_span = item.span();
|
||||
let Ok(func) = parse2::<ItemFn>(item) else {
|
||||
return quote_spanned! { item_span =>
|
||||
compile_error!("#[gpui::property_test] must be placed on a function");
|
||||
};
|
||||
};
|
||||
|
||||
let test_name = func.sig.ident.clone();
|
||||
let inner_fn_name = format_ident!("__{test_name}");
|
||||
|
||||
let parsed_args = parse_args(func.sig.inputs, &test_name);
|
||||
|
||||
let inner_body = func.block;
|
||||
let inner_arg_decls = parsed_args.inner_fn_decl_args;
|
||||
let asyncness = func.sig.asyncness;
|
||||
|
||||
let inner_fn = quote! {
|
||||
let #inner_fn_name = #asyncness move |#inner_arg_decls| #inner_body;
|
||||
};
|
||||
|
||||
let arg_errors = parsed_args.errors;
|
||||
let proptest_args = parsed_args.proptest_args;
|
||||
let inner_args = parsed_args.inner_fn_args;
|
||||
let cx_vars = parsed_args.cx_vars;
|
||||
let cx_teardowns = parsed_args.cx_teardowns;
|
||||
|
||||
let proptest_args = quote! {
|
||||
#[strategy = ::gpui::seed_strategy()] __seed: u64,
|
||||
#proptest_args
|
||||
};
|
||||
|
||||
let run_test_body = match &asyncness {
|
||||
None => quote! {
|
||||
#cx_vars
|
||||
#inner_fn_name(#inner_args);
|
||||
#cx_teardowns
|
||||
},
|
||||
Some(_) => quote! {
|
||||
let foreground_executor = gpui::ForegroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
|
||||
#cx_vars
|
||||
foreground_executor.block_test(#inner_fn_name(#inner_args));
|
||||
#cx_teardowns
|
||||
},
|
||||
};
|
||||
|
||||
quote! {
|
||||
#arg_errors
|
||||
|
||||
#[::gpui::proptest::property_test(proptest_path = "::gpui::proptest", #args)]
|
||||
fn #test_name(#proptest_args) {
|
||||
#inner_fn
|
||||
|
||||
::gpui::run_test_once(
|
||||
__seed,
|
||||
Box::new(move |dispatcher| {
|
||||
#run_test_body
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ParsedArgs {
|
||||
cx_vars: TokenStream,
|
||||
cx_teardowns: TokenStream,
|
||||
proptest_args: TokenStream,
|
||||
errors: TokenStream,
|
||||
|
||||
// exprs passed at the call-site
|
||||
inner_fn_args: TokenStream,
|
||||
// args in the declaration
|
||||
inner_fn_decl_args: TokenStream,
|
||||
}
|
||||
|
||||
fn parse_args(args: Punctuated<FnArg, Comma>, test_name: &Ident) -> ParsedArgs {
|
||||
let mut parsed = ParsedArgs::default();
|
||||
let mut args = args.into_iter().collect();
|
||||
|
||||
remove_cxs(&mut parsed, &mut args, test_name);
|
||||
remove_std_rng(&mut parsed, &mut args);
|
||||
remove_background_executor(&mut parsed, &mut args);
|
||||
|
||||
// all remaining args forwarded to proptest's macro
|
||||
parsed.proptest_args = quote!( #(#args),* );
|
||||
|
||||
parsed
|
||||
}
|
||||
|
||||
fn remove_cxs(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>, test_name: &Ident) {
|
||||
let mut ix = 0;
|
||||
args.retain_mut(|arg| {
|
||||
if !is_test_cx(arg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let cx_varname = format_ident!("cx_{ix}");
|
||||
ix += 1;
|
||||
|
||||
parsed.cx_vars.extend(quote!(
|
||||
let mut #cx_varname = gpui::TestAppContext::build(
|
||||
dispatcher.clone(),
|
||||
Some(stringify!(#test_name)),
|
||||
);
|
||||
));
|
||||
parsed.cx_teardowns.extend(quote!(
|
||||
dispatcher.run_until_parked();
|
||||
#cx_varname.executor().forbid_parking();
|
||||
#cx_varname.quit();
|
||||
dispatcher.run_until_parked();
|
||||
));
|
||||
|
||||
parsed.inner_fn_decl_args.extend(quote!(#arg,));
|
||||
parsed.inner_fn_args.extend(quote!(&mut #cx_varname,));
|
||||
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_std_rng(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>) {
|
||||
args.retain_mut(|arg| {
|
||||
if !is_std_rng(arg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
parsed.errors.extend(quote_spanned! { arg.span() =>
|
||||
compile_error!("`StdRng` is not allowed in a property test. Consider implementing `Arbitrary`, or implementing a custom `Strategy`. https://altsysrq.github.io/proptest-book/proptest/tutorial/strategy-basics.html");
|
||||
});
|
||||
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_background_executor(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>) {
|
||||
args.retain_mut(|arg| {
|
||||
if !is_background_executor(arg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
parsed.inner_fn_decl_args.extend(quote!(#arg,));
|
||||
parsed
|
||||
.inner_fn_args
|
||||
.extend(quote!(gpui::BackgroundExecutor::new(std::sync::Arc::new(
|
||||
dispatcher.clone()
|
||||
)),));
|
||||
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
// Matches `&TestAppContext` or `&foo::bar::baz::TestAppContext`
|
||||
fn is_test_cx(arg: &FnArg) -> bool {
|
||||
let FnArg::Typed(arg) = arg else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Type::Reference(ty) = &*arg.ty else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Type::Path(ty) = &*ty.elem else {
|
||||
return false;
|
||||
};
|
||||
|
||||
ty.path
|
||||
.segments
|
||||
.last()
|
||||
.is_some_and(|seg| seg.ident == "TestAppContext")
|
||||
}
|
||||
|
||||
fn is_std_rng(arg: &FnArg) -> bool {
|
||||
is_path_with_last_segment(arg, "StdRng")
|
||||
}
|
||||
|
||||
fn is_background_executor(arg: &FnArg) -> bool {
|
||||
is_path_with_last_segment(arg, "BackgroundExecutor")
|
||||
}
|
||||
|
||||
fn is_path_with_last_segment(arg: &FnArg, last_segment: &str) -> bool {
|
||||
let FnArg::Typed(arg) = arg else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Type::Path(ty) = &*arg.ty else {
|
||||
return false;
|
||||
};
|
||||
|
||||
ty.path
|
||||
.segments
|
||||
.last()
|
||||
.is_some_and(|seg| seg.ident == last_segment)
|
||||
}
|
||||
|
|
@ -19,11 +19,17 @@ rayon.workspace = true
|
|||
log.workspace = true
|
||||
ztracing.workspace = true
|
||||
tracing.workspace = true
|
||||
proptest = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
rand.workspace = true
|
||||
proptest.workspace = true
|
||||
zlog.workspace = true
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["tracing"]
|
||||
|
||||
[features]
|
||||
test-support = ["proptest"]
|
||||
32
crates/sum_tree/src/property_test.rs
Normal file
32
crates/sum_tree/src/property_test.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use core::fmt::Debug;
|
||||
|
||||
use proptest::{prelude::*, sample::SizeRange};
|
||||
|
||||
use crate::{Item, SumTree, Summary};
|
||||
|
||||
impl<T> Arbitrary for SumTree<T>
|
||||
where
|
||||
T: Debug + Arbitrary + Item + 'static,
|
||||
T::Summary: Debug + Summary<Context<'static> = ()>,
|
||||
{
|
||||
type Parameters = ();
|
||||
type Strategy = BoxedStrategy<Self>;
|
||||
|
||||
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
|
||||
any::<Vec<T>>()
|
||||
.prop_map(|vec| SumTree::from_iter(vec, ()))
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// A strategy for producing a [`SumTree`] with a given size.
|
||||
///
|
||||
/// Equivalent to [`proptest::collection::vec`].
|
||||
pub fn sum_tree<S, T>(values: S, size: impl Into<SizeRange>) -> impl Strategy<Value = SumTree<T>>
|
||||
where
|
||||
T: Debug + Arbitrary + Item + 'static,
|
||||
T::Summary: Debug + Summary<Context<'static> = ()>,
|
||||
S: Strategy<Value = T>,
|
||||
{
|
||||
proptest::collection::vec(values, size).prop_map(|vec| SumTree::from_iter(vec, ()))
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
mod cursor;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod property_test;
|
||||
mod tree_map;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
|
|
|
|||
|
|
@ -37,3 +37,4 @@ rand.workspace = true
|
|||
util = { workspace = true, features = ["test-support"] }
|
||||
http_client = { workspace = true, features = ["test-support"] }
|
||||
zlog.workspace = true
|
||||
proptest.workspace = true
|
||||
|
|
|
|||
Loading…
Reference in a new issue