Improve support for gpui tests that need multiple contexts

Max Brunsfeld created

If a test function takes multiple contexts, pass it however many
distinct contexts are needed. Construct each one with a different
starting entity id so that they do not share any entity ids.

Change summary

Cargo.lock             |  1 +
gpui/src/app.rs        | 27 +++++++++------------------
gpui/src/lib.rs        |  1 +
gpui_macros/Cargo.toml |  1 +
gpui_macros/src/lib.rs | 20 ++++++++++++++------
5 files changed, 26 insertions(+), 24 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1635,6 +1635,7 @@ dependencies = [
 name = "gpui_macros"
 version = "0.1.0"
 dependencies = [
+ "proc-macro2",
  "quote",
  "syn",
 ]

gpui/src/app.rs 🔗

@@ -128,17 +128,6 @@ impl App {
         f(&mut *cx)
     }
 
-    pub fn test_async<T, F, Fn>(f: Fn) -> T
-    where
-        Fn: FnOnce(TestAppContext) -> F,
-        F: Future<Output = T>,
-    {
-        let foreground = Rc::new(executor::Foreground::test());
-        let cx = TestAppContext::new(foreground.clone());
-        let future = f(cx);
-        smol::block_on(foreground.run(future))
-    }
-
     pub fn new(asset_source: impl AssetSource) -> Result<Self> {
         let platform = platform::current::platform();
         let foreground_platform = platform::current::foreground_platform();
@@ -248,16 +237,18 @@ impl App {
 }
 
 impl TestAppContext {
-    pub fn new(foreground: Rc<executor::Foreground>) -> Self {
+    pub fn new(foreground: Rc<executor::Foreground>, first_entity_id: usize) -> Self {
         let platform = Arc::new(platform::test::platform());
         let foreground_platform = Rc::new(platform::test::foreground_platform());
+        let mut cx = MutableAppContext::new(
+            foreground.clone(),
+            platform,
+            foreground_platform.clone(),
+            (),
+        );
+        cx.next_entity_id = first_entity_id;
         let cx = TestAppContext {
-            cx: Rc::new(RefCell::new(MutableAppContext::new(
-                foreground.clone(),
-                platform,
-                foreground_platform.clone(),
-                (),
-            ))),
+            cx: Rc::new(RefCell::new(cx)),
             foreground_platform,
         };
         cx.cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx.cx));

gpui/src/lib.rs 🔗

@@ -31,3 +31,4 @@ pub use presenter::{
     SizeConstraint, Vector2FExt,
 };
 pub use scoped_pool;
+pub use smol::block_on;

gpui_macros/src/lib.rs 🔗

@@ -38,6 +38,16 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
     let inner_fn_attributes = mem::take(&mut inner_fn.attrs);
     let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident);
     let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone());
+
+    // Pass to the test function the number of app contexts that it needs,
+    // based on its parameter list.
+    let inner_fn_args = (0..inner_fn.sig.inputs.len())
+        .map(|i| {
+            let first_entity_id = i * 100_000;
+            quote!(#namespace::TestAppContext::new(foreground.clone(), #first_entity_id),)
+        })
+        .collect::<proc_macro2::TokenStream>();
+
     let mut outer_fn: ItemFn = if inner_fn.sig.asyncness.is_some() {
         parse_quote! {
             #[test]
@@ -48,9 +58,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     let mut retries = 0;
                     loop {
                         let result = std::panic::catch_unwind(|| {
-                            #namespace::App::test_async(move |cx| async {
-                                #inner_fn_name(cx).await;
-                            });
+                            let foreground = ::std::rc::Rc::new(#namespace::executor::Foreground::test());
+                            #namespace::block_on(foreground.run(#inner_fn_name(#inner_fn_args)));
                         });
 
                         match result {
@@ -66,9 +75,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                         }
                     }
                 } else {
-                    #namespace::App::test_async(move |cx| async {
-                        #inner_fn_name(cx).await;
-                    });
+                    let foreground = ::std::rc::Rc::new(#namespace::executor::Foreground::test());
+                    #namespace::block_on(foreground.run(#inner_fn_name(#inner_fn_args)));
                 }
             }
         }