@@ -37,6 +37,9 @@ use std::{
/// 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.
+///
+/// Note: this function is not intended to be used directly. Rather, it is
+/// public so that it can be used from the `property_test` macro.
pub fn seed_strategy() -> impl Strategy<Value = u64> {
match std::env::var("SEED") {
Ok(val) => Just(val.parse().unwrap()).boxed(),
@@ -44,6 +47,24 @@ pub fn seed_strategy() -> impl Strategy<Value = u64> {
}
}
+/// Applies a fixed RNG seed to a proptest config so that case generation
+/// is deterministic. Uses `$SEED` if set, otherwise defaults to `0`.
+/// This bridges the GPUI `SEED` env var to proptest's RNG seed, so that
+/// a single variable controls both the scheduler seed and case generation.
+///
+/// Note: this function is not intended to be used directly. Rather, it is
+/// public so that it can be used from the `property_test` macro.
+pub fn apply_seed_to_proptest_config(
+ mut config: proptest::test_runner::Config,
+) -> proptest::test_runner::Config {
+ let seed = env::var("SEED")
+ .ok()
+ .and_then(|val| val.parse::<u64>().ok())
+ .unwrap_or(0);
+ config.rng_seed = proptest::test_runner::RngSeed::Fixed(seed);
+ config
+}
+
/// 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.
@@ -1,7 +1,12 @@
use proc_macro2::TokenStream;
-use quote::{format_ident, quote, quote_spanned};
+use quote::{ToTokens, format_ident, quote, quote_spanned};
use syn::{
- FnArg, Ident, ItemFn, Type, parse2, punctuated::Punctuated, spanned::Spanned, token::Comma,
+ Expr, FnArg, Ident, ItemFn, MetaNameValue, Token, Type,
+ parse::{Parse, ParseStream},
+ parse2,
+ punctuated::Punctuated,
+ spanned::Spanned,
+ token::Comma,
};
pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
@@ -12,6 +17,11 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
};
};
+ let args = match parse2::<Args>(args) {
+ Ok(args) => args,
+ Err(e) => return e.to_compile_error(),
+ };
+
let test_name = func.sig.ident.clone();
let inner_fn_name = format_ident!("__{test_name}");
let outer_fn_attributes = &func.attrs;
@@ -51,10 +61,12 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
},
};
+ let fixed_macro_invocation = args.render();
+
quote! {
#arg_errors
- #[::gpui::proptest::property_test(proptest_path = "::gpui::proptest", #args)]
+ #fixed_macro_invocation
#(#outer_fn_attributes)*
fn #test_name(#proptest_args) {
#inner_fn
@@ -69,6 +81,63 @@ pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
}
}
+struct Args {
+ config: Option<Expr>,
+ remaining_args: Vec<MetaNameValue>,
+ errors: TokenStream,
+}
+
+impl Args {
+ /// By default, proptest uses random seeds unless `$PROPTEST_SEED` is set.
+ /// Rather than managing both `$SEED` and `$PROPTEST_SEED`, we intercept
+ /// `config = ...` tokens and add a call to `gpui::apply_seed_to_config`.
+ fn render(&self) -> TokenStream {
+ let user_provided_config = match &self.config {
+ None => quote! { ::gpui::proptest::prelude::ProptestConfig::default() },
+ Some(config) => config.into_token_stream(),
+ };
+
+ let fixed_config = quote!(::gpui::apply_seed_to_proptest_config(#user_provided_config));
+ let remaining_args = &self.remaining_args;
+ let errors = &self.errors;
+
+ quote! {
+ #errors
+ #[::gpui::proptest::property_test(
+ proptest_path = "::gpui::proptest",
+ config = #fixed_config,
+ #(#remaining_args,)*
+ )]
+ }
+ }
+}
+
+impl Parse for Args {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
+ let pairs = Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?;
+
+ let mut config = None;
+ let mut remaining_args = vec![];
+ let mut errors = quote!();
+
+ for pair in pairs {
+ match pair.path.get_ident().map(Ident::to_string).as_deref() {
+ Some("config") => config = Some(pair.value),
+ Some("proptest_path") => errors.extend(quote_spanned! {pair.span() =>
+ compile_error!("`gpui::property_test` overrides the `proptest_path` parameter")
+ }),
+ _ => remaining_args.push(pair),
+ }
+ }
+
+ Ok(Self {
+ config,
+ remaining_args,
+ errors,
+ })
+ }
+}
+
#[derive(Default)]
struct ParsedArgs {
cx_vars: TokenStream,