Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:06:23.917Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

property_test.rs

274 lines · 7.8 KB · rust
1use proc_macro2::TokenStream;
2use quote::{ToTokens, format_ident, quote, quote_spanned};
3use syn::{
4    Expr, FnArg, Ident, ItemFn, MetaNameValue, Token, Type,
5    parse::{Parse, ParseStream},
6    parse2,
7    punctuated::Punctuated,
8    spanned::Spanned,
9    token::Comma,
10};
11
12pub fn test(args: TokenStream, item: TokenStream) -> TokenStream {
13    let item_span = item.span();
14    let Ok(func) = parse2::<ItemFn>(item) else {
15        return quote_spanned! { item_span =>
16            compile_error!("#[gpui::property_test] must be placed on a function");
17        };
18    };
19
20    let args = match parse2::<Args>(args) {
21        Ok(args) => args,
22        Err(e) => return e.to_compile_error(),
23    };
24
25    let test_name = func.sig.ident.clone();
26    let test_ret_ty = func.sig.output.clone();
27    let inner_fn_name = format_ident!("__{test_name}");
28    let outer_fn_attributes = &func.attrs;
29
30    let parsed_args = parse_args(func.sig.inputs, &test_name);
31
32    let inner_body = func.block;
33    let inner_arg_decls = parsed_args.inner_fn_decl_args;
34    let asyncness = func.sig.asyncness;
35
36    let inner_fn = quote! {
37        let #inner_fn_name = #asyncness move |#inner_arg_decls| #inner_body;
38    };
39
40    let arg_errors = parsed_args.errors;
41    let proptest_args = parsed_args.proptest_args;
42    let inner_args = parsed_args.inner_fn_args;
43    let cx_vars = parsed_args.cx_vars;
44    let cx_teardowns = parsed_args.cx_teardowns;
45
46    let proptest_args = quote! {
47        #[strategy = ::gpui::seed_strategy()] __seed: u64,
48        #proptest_args
49    };
50
51    let run_test_body = match &asyncness {
52        None => quote! {
53            #cx_vars
54            let result = #inner_fn_name(#inner_args);
55            #cx_teardowns
56            result
57        },
58        Some(_) => quote! {
59            let foreground_executor = gpui::ForegroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
60            #cx_vars
61            let result = foreground_executor.block_test(#inner_fn_name(#inner_args));
62            #cx_teardowns
63            result
64        },
65    };
66
67    let fixed_macro_invocation = args.render();
68
69    quote! {
70        #arg_errors
71
72        #fixed_macro_invocation
73        #(#outer_fn_attributes)*
74        fn #test_name(#proptest_args) #test_ret_ty {
75            #inner_fn
76
77            ::gpui::run_test_once(
78                __seed,
79                Box::new(move |dispatcher| #test_ret_ty {
80                    #run_test_body
81                }),
82            )
83        }
84    }
85}
86
87struct Args {
88    config: Option<Expr>,
89    remaining_args: Vec<MetaNameValue>,
90    errors: TokenStream,
91}
92
93impl Args {
94    /// By default, proptest uses random seeds unless `$PROPTEST_SEED` is set.
95    /// Rather than managing both `$SEED` and `$PROPTEST_SEED`, we intercept
96    /// `config = ...` tokens and add a call to `gpui::apply_seed_to_config`.
97    fn render(&self) -> TokenStream {
98        let user_provided_config = match &self.config {
99            None => quote! { ::gpui::proptest::prelude::ProptestConfig::default() },
100            Some(config) => config.into_token_stream(),
101        };
102
103        let fixed_config = quote!(::gpui::apply_seed_to_proptest_config(#user_provided_config));
104        let remaining_args = &self.remaining_args;
105        let errors = &self.errors;
106
107        quote! {
108            #errors
109            #[::gpui::proptest::property_test(
110                proptest_path = "::gpui::proptest",
111                config = #fixed_config,
112                #(#remaining_args,)*
113            )]
114        }
115    }
116}
117
118impl Parse for Args {
119    fn parse(input: ParseStream) -> syn::Result<Self> {
120        let pairs = Punctuated::<MetaNameValue, Token![,]>::parse_terminated(input)?;
121
122        let mut config = None;
123        let mut remaining_args = vec![];
124        let mut errors = quote!();
125
126        for pair in pairs {
127            match pair.path.get_ident().map(Ident::to_string).as_deref() {
128                Some("config") => config = Some(pair.value),
129                Some("proptest_path") => errors.extend(quote_spanned! {pair.span() =>
130                    compile_error!("`gpui::property_test` overrides the `proptest_path` parameter")
131                }),
132                _ => remaining_args.push(pair),
133            }
134        }
135
136        Ok(Self {
137            config,
138            remaining_args,
139            errors,
140        })
141    }
142}
143
144#[derive(Default)]
145struct ParsedArgs {
146    cx_vars: TokenStream,
147    cx_teardowns: TokenStream,
148    proptest_args: TokenStream,
149    errors: TokenStream,
150
151    // exprs passed at the call-site
152    inner_fn_args: TokenStream,
153    // args in the declaration
154    inner_fn_decl_args: TokenStream,
155}
156
157fn parse_args(args: Punctuated<FnArg, Comma>, test_name: &Ident) -> ParsedArgs {
158    let mut parsed = ParsedArgs::default();
159    let mut args = args.into_iter().collect();
160
161    remove_cxs(&mut parsed, &mut args, test_name);
162    remove_std_rng(&mut parsed, &mut args);
163    remove_background_executor(&mut parsed, &mut args);
164
165    // all remaining args forwarded to proptest's macro
166    parsed.proptest_args = quote!( #(#args),* );
167
168    parsed
169}
170
171fn remove_cxs(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>, test_name: &Ident) {
172    let mut ix = 0;
173    args.retain_mut(|arg| {
174        if !is_test_cx(arg) {
175            return true;
176        }
177
178        let cx_varname = format_ident!("cx_{ix}");
179        ix += 1;
180
181        parsed.cx_vars.extend(quote!(
182            let mut #cx_varname = gpui::TestAppContext::build(
183                dispatcher.clone(),
184                Some(stringify!(#test_name)),
185            );
186        ));
187        parsed.cx_teardowns.extend(quote!(
188            dispatcher.run_until_parked();
189            #cx_varname.executor().forbid_parking();
190            #cx_varname.quit();
191            dispatcher.run_until_parked();
192        ));
193
194        parsed.inner_fn_decl_args.extend(quote!(#arg,));
195        parsed.inner_fn_args.extend(quote!(&mut #cx_varname,));
196
197        false
198    });
199}
200
201fn remove_std_rng(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>) {
202    args.retain_mut(|arg| {
203        if !is_std_rng(arg) {
204            return true;
205        }
206
207        parsed.errors.extend(quote_spanned! { arg.span() =>
208            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");
209        });
210
211        false
212    });
213}
214
215fn remove_background_executor(parsed: &mut ParsedArgs, args: &mut Vec<FnArg>) {
216    args.retain_mut(|arg| {
217        if !is_background_executor(arg) {
218            return true;
219        }
220
221        parsed.inner_fn_decl_args.extend(quote!(#arg,));
222        parsed
223            .inner_fn_args
224            .extend(quote!(gpui::BackgroundExecutor::new(std::sync::Arc::new(
225                dispatcher.clone()
226            )),));
227
228        false
229    });
230}
231
232// Matches `&TestAppContext` or `&foo::bar::baz::TestAppContext`
233fn is_test_cx(arg: &FnArg) -> bool {
234    let FnArg::Typed(arg) = arg else {
235        return false;
236    };
237
238    let Type::Reference(ty) = &*arg.ty else {
239        return false;
240    };
241
242    let Type::Path(ty) = &*ty.elem else {
243        return false;
244    };
245
246    ty.path
247        .segments
248        .last()
249        .is_some_and(|seg| seg.ident == "TestAppContext")
250}
251
252fn is_std_rng(arg: &FnArg) -> bool {
253    is_path_with_last_segment(arg, "StdRng")
254}
255
256fn is_background_executor(arg: &FnArg) -> bool {
257    is_path_with_last_segment(arg, "BackgroundExecutor")
258}
259
260fn is_path_with_last_segment(arg: &FnArg, last_segment: &str) -> bool {
261    let FnArg::Typed(arg) = arg else {
262        return false;
263    };
264
265    let Type::Path(ty) = &*arg.ty else {
266        return false;
267    };
268
269    ty.path
270        .segments
271        .last()
272        .is_some_and(|seg| seg.ident == last_segment)
273}
274
Served at tenant.openagents/omega Member data and write actions are omitted.