Reuse FontCache across randomized tests

Nathan Sobo and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

gpui/src/app.rs           |  25 ++++++---
gpui/src/lib.rs           |   2 
gpui/src/platform/test.rs |   8 +-
gpui_macros/src/lib.rs    | 101 +++++++++++++++++++++++++---------------
4 files changed, 83 insertions(+), 53 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -118,15 +118,19 @@ pub struct TestAppContext {
 }
 
 impl App {
-    pub fn test<T, F: FnOnce(&mut MutableAppContext) -> T>(f: F) -> T {
-        let foreground_platform = platform::test::foreground_platform();
-        let platform = platform::test::platform();
+    pub fn test<T, F: FnOnce(&mut MutableAppContext) -> T>(
+        foreground_platform: Rc<platform::test::ForegroundPlatform>,
+        platform: Arc<dyn Platform>,
+        font_cache: Arc<FontCache>,
+        f: F,
+    ) -> T {
         let foreground = Rc::new(executor::Foreground::test());
         let cx = Rc::new(RefCell::new(MutableAppContext::new(
             foreground,
             Arc::new(executor::Background::new()),
-            Arc::new(platform),
-            Rc::new(foreground_platform),
+            platform,
+            foreground_platform,
+            font_cache,
             (),
         )));
         cx.borrow_mut().weak_self = Some(Rc::downgrade(&cx));
@@ -143,6 +147,7 @@ impl App {
             Arc::new(executor::Background::new()),
             platform.clone(),
             foreground_platform.clone(),
+            Arc::new(FontCache::new(platform.fonts())),
             asset_source,
         ))));
 
@@ -245,17 +250,19 @@ impl App {
 
 impl TestAppContext {
     pub fn new(
+        foreground_platform: Rc<platform::test::ForegroundPlatform>,
+        platform: Arc<dyn Platform>,
         foreground: Rc<executor::Foreground>,
         background: Arc<executor::Background>,
+        font_cache: Arc<FontCache>,
         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(),
             background,
             platform,
             foreground_platform.clone(),
+            font_cache,
             (),
         );
         cx.next_entity_id = first_entity_id;
@@ -593,9 +600,9 @@ impl MutableAppContext {
         background: Arc<executor::Background>,
         platform: Arc<dyn platform::Platform>,
         foreground_platform: Rc<dyn platform::ForegroundPlatform>,
+        font_cache: Arc<FontCache>,
         asset_source: impl AssetSource,
     ) -> Self {
-        let fonts = platform.fonts();
         Self {
             weak_self: None,
             foreground_platform,
@@ -607,7 +614,7 @@ impl MutableAppContext {
                 values: Default::default(),
                 ref_counts: Arc::new(Mutex::new(RefCounts::default())),
                 background,
-                font_cache: Arc::new(FontCache::new(fonts)),
+                font_cache,
                 platform,
             },
             actions: HashMap::new(),

gpui/src/lib.rs 🔗

@@ -23,7 +23,7 @@ pub use executor::Task;
 pub mod color;
 pub mod json;
 pub mod keymap;
-mod platform;
+pub mod platform;
 pub use gpui_macros::test;
 pub use platform::FontSystem;
 pub use platform::{Event, PathPromptOptions, Platform, PromptLevel};

gpui/src/platform/test.rs 🔗

@@ -9,14 +9,14 @@ use std::{
     sync::Arc,
 };
 
-pub(crate) struct Platform {
+pub struct Platform {
     dispatcher: Arc<dyn super::Dispatcher>,
     fonts: Arc<dyn super::FontSystem>,
     current_clipboard_item: Mutex<Option<ClipboardItem>>,
 }
 
 #[derive(Default)]
-pub(crate) struct ForegroundPlatform {
+pub struct ForegroundPlatform {
     last_prompt_for_new_path_args: RefCell<Option<(PathBuf, Box<dyn FnOnce(Option<PathBuf>)>)>>,
 }
 
@@ -191,10 +191,10 @@ impl super::Window for Window {
     }
 }
 
-pub(crate) fn platform() -> Platform {
+pub fn platform() -> Platform {
     Platform::new()
 }
 
-pub(crate) fn foreground_platform() -> ForegroundPlatform {
+pub fn foreground_platform() -> ForegroundPlatform {
     ForegroundPlatform::default()
 }

gpui_macros/src/lib.rs 🔗

@@ -67,9 +67,14 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     match last_segment.map(|s| s.ident.to_string()).as_deref() {
                         Some("TestAppContext") => {
                             let first_entity_id = ix * 100_000;
-                            inner_fn_args.extend(
-                            quote!(#namespace::TestAppContext::new(foreground.clone(), background.clone(), #first_entity_id),)
-                        );
+                            inner_fn_args.extend(quote!(#namespace::TestAppContext::new(
+                                foreground_platform.clone(),
+                                platform.clone(),
+                                foreground.clone(),
+                                background.clone(),
+                                font_cache.clone(),
+                                #first_entity_id),
+                            ));
                         }
                         Some("StdRng") => {
                             inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed)));
@@ -99,8 +104,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                 #inner_fn
 
                 let is_randomized = #num_iterations > 1;
-                let mut num_iterations = #num_iterations;
-                let mut starting_seed = #starting_seed;
+                let mut num_iterations = #num_iterations as u64;
+                let mut starting_seed = #starting_seed as u64;
                 if is_randomized {
                     if let Ok(value) = std::env::var("SEED") {
                         starting_seed = value.parse().expect("invalid SEED variable");
@@ -110,34 +115,43 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     }
                 }
 
+                let mut atomic_seed = std::sync::atomic::AtomicU64::new(starting_seed as u64);
                 let mut retries = 0;
-                let mut i = 0;
-                loop {
-                    let seed = (starting_seed + i) as u64;
-                    if is_randomized {
-                        dbg!(seed);
-                    }
 
+                loop {
                     let result = std::panic::catch_unwind(|| {
-                        let (foreground, background) = #namespace::executor::deterministic(seed);
-                        foreground.run(#inner_fn_name(#inner_fn_args));
+                        let foreground_platform = std::rc::Rc::new(#namespace::platform::test::foreground_platform());
+                        let platform = std::sync::Arc::new(#namespace::platform::test::platform());
+                        let font_system = #namespace::Platform::fonts(platform.as_ref());
+                        let font_cache = std::sync::Arc::new(#namespace::FontCache::new(font_system));
+
+                        loop {
+                            let seed = atomic_seed.load(std::sync::atomic::Ordering::SeqCst);
+                            if seed >= starting_seed + num_iterations {
+                                break;
+                            }
+
+                            if is_randomized {
+                                dbg!(seed);
+                            }
+
+                            let (foreground, background) = #namespace::executor::deterministic(seed);
+                            foreground.run(#inner_fn_name(#inner_fn_args));
+                            atomic_seed.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+                        }
                     });
 
                     match result {
                         Ok(result) => {
-                            retries = 0;
-                            i += 1;
-                            if i == num_iterations {
-                                return result
-                            }
+                            break;
                         }
                         Err(error) => {
                             if retries < #max_retries {
                                 retries += 1;
                                 println!("retrying: attempt {}", retries);
                             } else {
-                                if num_iterations > 1 {
-                                    eprintln!("failing seed: {}", seed);
+                                if is_randomized {
+                                    eprintln!("failing seed: {}", atomic_seed.load(std::sync::atomic::Ordering::SeqCst));
                                 }
                                 std::panic::resume_unwind(error);
                             }
@@ -171,8 +185,8 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                 #inner_fn
 
                 let is_randomized = #num_iterations > 1;
-                let mut num_iterations = #num_iterations;
-                let mut starting_seed = #starting_seed;
+                let mut num_iterations = #num_iterations as u64;
+                let mut starting_seed = #starting_seed as u64;
                 if is_randomized {
                     if let Ok(value) = std::env::var("SEED") {
                         starting_seed = value.parse().expect("invalid SEED variable");
@@ -182,35 +196,44 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     }
                 }
 
+                let mut atomic_seed = std::sync::atomic::AtomicU64::new(starting_seed as u64);
                 let mut retries = 0;
-                let mut i = 0;
-                loop {
-                    let seed = (starting_seed + i) as u64;
-                    if is_randomized {
-                        dbg!(seed);
-                    }
 
+                loop {
                     let result = std::panic::catch_unwind(|| {
-                        #namespace::App::test(|cx| {
-                            #inner_fn_name(#inner_fn_args);
-                        });
+                        let foreground_platform = std::rc::Rc::new(#namespace::platform::test::foreground_platform());
+                        let platform = std::sync::Arc::new(#namespace::platform::test::platform());
+                        let font_system = #namespace::Platform::fonts(platform.as_ref());
+                        let font_cache = std::sync::Arc::new(#namespace::FontCache::new(font_system));
+
+                        loop {
+                            let seed = atomic_seed.load(std::sync::atomic::Ordering::SeqCst);
+                            if seed >= starting_seed + num_iterations {
+                                break;
+                            }
+
+                            if is_randomized {
+                                dbg!(seed);
+                            }
+
+                            #namespace::App::test(foreground_platform.clone(), platform.clone(), font_cache.clone(), |cx| {
+                                #inner_fn_name(#inner_fn_args);
+                            });
+                            atomic_seed.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
+                        }
                     });
 
                     match result {
-                        Ok(result) => {
-                            retries = 0;
-                            i += 1;
-                            if i == num_iterations {
-                                return result
-                            }
+                        Ok(_) => {
+                            break;
                         }
                         Err(error) => {
                             if retries < #max_retries {
                                 retries += 1;
                                 println!("retrying: attempt {}", retries);
                             } else {
-                                if #num_iterations > 1 {
-                                    eprintln!("failing seed: {}", seed);
+                                if is_randomized {
+                                    eprintln!("failing seed: {}", atomic_seed.load(std::sync::atomic::Ordering::SeqCst));
                                 }
                                 std::panic::resume_unwind(error);
                             }