From 830fc38503d37face328fd686905dd66e527c537 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 14:42:10 +0200 Subject: [PATCH 1/7] Increase `condition` timeout on CI and remove `condition_with_duration` `condition_with_duration` wasn't really being used, as the default timeout was `500ms` and the only places that did use it specified `500ms` as well. --- Cargo.lock | 1 + gpui/Cargo.toml | 1 + gpui/src/app.rs | 19 ++++++++++--------- zed/src/workspace.rs | 14 ++++---------- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21a08332c5e08e81c35c0d9d4db343a38983d0d6..91d3b95f47c521bbcc8445247f00f58a58650af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,6 +1181,7 @@ dependencies = [ "font-kit", "foreign-types", "gpui_macros", + "lazy_static", "log", "metal", "num_cpus", diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 1eba8612bfa9c2474ca5a0e389bf0824cba6479f..0661ef903c14be8e040ecf0a55911e461c0272c4 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -9,6 +9,7 @@ async-task = "4.0.3" ctor = "0.1" etagere = "0.2" gpui_macros = {path = "../gpui_macros"} +lazy_static = "1.4" log = "0.4" num_cpus = "1.13" ordered-float = "2.1.1" diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 099bf2ec6c958e4bbe1aec4733ec99abf89b108c..a2f556e6deb12eeff41bc2ec219cb09865683315 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -10,6 +10,7 @@ use crate::{ use anyhow::{anyhow, Result}; use async_task::Task; use keymap::MatchResult; +use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use pathfinder_geometry::{rect::RectF, vector::vec2f}; use platform::Event; @@ -2290,17 +2291,12 @@ impl ViewHandle { pub fn condition( &self, ctx: &TestAppContext, - predicate: impl FnMut(&T, &AppContext) -> bool, - ) -> impl Future { - self.condition_with_duration(Duration::from_millis(500), ctx, predicate) - } - - pub fn condition_with_duration( - &self, - duration: Duration, - ctx: &TestAppContext, mut predicate: impl FnMut(&T, &AppContext) -> bool, ) -> impl Future { + lazy_static! { + static ref CI: bool = std::env::var("CI").is_ok(); + } + let (tx, mut rx) = mpsc::channel(1024); let mut ctx = ctx.0.borrow_mut(); @@ -2322,6 +2318,11 @@ impl ViewHandle { let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap(); let handle = self.downgrade(); + let duration = if *CI { + Duration::from_secs(1) + } else { + Duration::from_millis(500) + }; async move { timeout(duration, async move { diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 833a74d112dc18d328f8a8ca8310cd3b79477d23..328ebf30e8413f637f7f486f52dca51e0ca5191a 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -759,7 +759,7 @@ mod tests { use super::*; use crate::{editor::BufferView, settings, test::temp_tree}; use serde_json::json; - use std::{collections::HashSet, fs, time::Duration}; + use std::{collections::HashSet, fs}; use tempdir::TempDir; #[gpui::test] @@ -1031,18 +1031,14 @@ mod tests { app.update(|ctx| editor.update(ctx, |editor, ctx| editor.insert(&"x".to_string(), ctx))); fs::write(dir.path().join("a.txt"), "changed").unwrap(); editor - .condition_with_duration(Duration::from_millis(500), &app, |editor, ctx| { - editor.has_conflict(ctx) - }) + .condition(&app, |editor, ctx| editor.has_conflict(ctx)) .await; app.read(|ctx| assert!(editor.is_dirty(ctx))); app.update(|ctx| workspace.update(ctx, |w, ctx| w.save_active_item(&(), ctx))); app.simulate_prompt_answer(window_id, 0); editor - .condition_with_duration(Duration::from_millis(500), &app, |editor, ctx| { - !editor.is_dirty(ctx) - }) + .condition(&app, |editor, ctx| !editor.is_dirty(ctx)) .await; app.read(|ctx| assert!(!editor.has_conflict(ctx))); } @@ -1099,9 +1095,7 @@ mod tests { // When the save completes, the buffer's title is updated. editor - .condition_with_duration(Duration::from_millis(500), &app, |editor, ctx| { - !editor.is_dirty(ctx) - }) + .condition(&app, |editor, ctx| !editor.is_dirty(ctx)) .await; app.read(|ctx| { assert!(!editor.is_dirty(ctx)); From 65cf9b7c1d960a6516b3b629e87a582650807de4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 14:50:28 +0200 Subject: [PATCH 2/7] Remove `condition_with_duration` for `ModelHandle` Also, use a 2s timeout on CI for both `ModelHandle::condition` and `ViewHandle::condition`. --- Cargo.lock | 1 - gpui/Cargo.toml | 1 - gpui/src/app.rs | 23 +++++++---------------- zed/src/editor/buffer/mod.rs | 16 ++++------------ zed/src/worktree.rs | 12 ++++-------- 5 files changed, 15 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91d3b95f47c521bbcc8445247f00f58a58650af5..21a08332c5e08e81c35c0d9d4db343a38983d0d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,6 @@ dependencies = [ "font-kit", "foreign-types", "gpui_macros", - "lazy_static", "log", "metal", "num_cpus", diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index 0661ef903c14be8e040ecf0a55911e461c0272c4..1eba8612bfa9c2474ca5a0e389bf0824cba6479f 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -9,7 +9,6 @@ async-task = "4.0.3" ctor = "0.1" etagere = "0.2" gpui_macros = {path = "../gpui_macros"} -lazy_static = "1.4" log = "0.4" num_cpus = "1.13" ordered-float = "2.1.1" diff --git a/gpui/src/app.rs b/gpui/src/app.rs index a2f556e6deb12eeff41bc2ec219cb09865683315..efaf410dbb4c59407c776cd5b92fdf6cd826c803 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -10,7 +10,6 @@ use crate::{ use anyhow::{anyhow, Result}; use async_task::Task; use keymap::MatchResult; -use lazy_static::lazy_static; use parking_lot::{Mutex, RwLock}; use pathfinder_geometry::{rect::RectF, vector::vec2f}; use platform::Event; @@ -2083,15 +2082,6 @@ impl ModelHandle { pub fn condition( &self, ctx: &TestAppContext, - predicate: impl FnMut(&T, &AppContext) -> bool, - ) -> impl Future { - self.condition_with_duration(Duration::from_millis(100), ctx, predicate) - } - - pub fn condition_with_duration( - &self, - duration: Duration, - ctx: &TestAppContext, mut predicate: impl FnMut(&T, &AppContext) -> bool, ) -> impl Future { let (tx, mut rx) = mpsc::channel(1024); @@ -2114,6 +2104,11 @@ impl ModelHandle { let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap(); let handle = self.downgrade(); + let duration = if std::env::var("CI").is_ok() { + Duration::from_secs(2) + } else { + Duration::from_millis(500) + }; async move { timeout(duration, async move { @@ -2293,10 +2288,6 @@ impl ViewHandle { ctx: &TestAppContext, mut predicate: impl FnMut(&T, &AppContext) -> bool, ) -> impl Future { - lazy_static! { - static ref CI: bool = std::env::var("CI").is_ok(); - } - let (tx, mut rx) = mpsc::channel(1024); let mut ctx = ctx.0.borrow_mut(); @@ -2318,8 +2309,8 @@ impl ViewHandle { let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap(); let handle = self.downgrade(); - let duration = if *CI { - Duration::from_secs(1) + let duration = if std::env::var("CI").is_ok() { + Duration::from_secs(2) } else { Duration::from_millis(500) }; diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 27044e1dfe2b63b72e904a6e39f7f816e891fcde..8a57201ccff3d5c549f5e9caafd60606806614be 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -3011,9 +3011,7 @@ mod tests { }); fs::remove_file(dir.path().join("file2")).unwrap(); - buffer2 - .condition_with_duration(Duration::from_millis(500), &app, |b, _| b.is_dirty()) - .await; + buffer2.condition(&app, |b, _| b.is_dirty()).await; assert_eq!( *events.borrow(), &[Event::Dirtied, Event::FileHandleChanged] @@ -3038,9 +3036,7 @@ mod tests { events.borrow_mut().clear(); fs::remove_file(dir.path().join("file3")).unwrap(); buffer3 - .condition_with_duration(Duration::from_millis(500), &app, |_, _| { - !events.borrow().is_empty() - }) + .condition(&app, |_, _| !events.borrow().is_empty()) .await; assert_eq!(*events.borrow(), &[Event::FileHandleChanged]); app.read(|ctx| assert!(buffer3.read(ctx).is_dirty())); @@ -3095,9 +3091,7 @@ mod tests { // contents are edited according to the diff between the old and new // file contents. buffer - .condition_with_duration(Duration::from_millis(500), &app, |buffer, _| { - buffer.text() != initial_contents - }) + .condition(&app, |buffer, _| buffer.text() != initial_contents) .await; buffer.update(&mut app, |buffer, _| { @@ -3131,9 +3125,7 @@ mod tests { // Becaues the buffer is modified, it doesn't reload from disk, but is // marked as having a conflict. buffer - .condition_with_duration(Duration::from_millis(500), &app, |buffer, _| { - buffer.has_conflict() - }) + .condition(&app, |buffer, _| buffer.has_conflict()) .await; } diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index cc9ced3b711f3c9850ed9dfd685891033476d02b..33b23c8d698102f699529183f187466b9fe92c47 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -1276,16 +1276,12 @@ impl WorktreeHandle for ModelHandle { let tree = self.clone(); async move { fs::write(root_path.join(filename), "").unwrap(); - tree.condition_with_duration(Duration::from_secs(5), &app, |tree, _| { - tree.entry_for_path(filename).is_some() - }) - .await; + tree.condition(&app, |tree, _| tree.entry_for_path(filename).is_some()) + .await; fs::remove_file(root_path.join(filename)).unwrap(); - tree.condition_with_duration(Duration::from_secs(5), &app, |tree, _| { - tree.entry_for_path(filename).is_none() - }) - .await; + tree.condition(&app, |tree, _| tree.entry_for_path(filename).is_none()) + .await; app.read(|ctx| tree.read(ctx).scan_complete()).await; } From 1cf5413173888fbb24b51120dfbf2dd4c1ccf74a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 15:01:11 +0200 Subject: [PATCH 3/7] Increase timeout to 2s for fsevents tests --- fsevent/src/lib.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/fsevent/src/lib.rs b/fsevent/src/lib.rs index 44af8f1ba61bb04e1914a3d40ce4b6541c36c783..e6f8bcfe45450e997ac157965a60c905b1222552 100644 --- a/fsevent/src/lib.rs +++ b/fsevent/src/lib.rs @@ -313,16 +313,16 @@ mod tests { thread::spawn(move || stream.run(move |events| tx.send(events.to_vec()).is_ok())); // Flush any historical events. - rx.recv_timeout(Duration::from_millis(500)).ok(); + rx.recv_timeout(Duration::from_secs(2)).ok(); fs::write(path.join("new-file"), "").unwrap(); - let events = rx.recv_timeout(Duration::from_millis(500)).unwrap(); + let events = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let event = events.last().unwrap(); assert_eq!(event.path, path.join("new-file")); assert!(event.flags.contains(StreamFlags::ITEM_CREATED)); fs::remove_file(path.join("existing-file-5")).unwrap(); - let events = rx.recv_timeout(Duration::from_millis(500)).unwrap(); + let events = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let event = events.last().unwrap(); assert_eq!(event.path, path.join("existing-file-5")); assert!(event.flags.contains(StreamFlags::ITEM_REMOVED)); @@ -356,7 +356,7 @@ mod tests { assert!(event.flags.contains(StreamFlags::ITEM_CREATED)); fs::remove_file(path.join("existing-file-5")).unwrap(); - let events = rx.recv_timeout(Duration::from_millis(500)).unwrap(); + let events = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let event = events.last().unwrap(); assert_eq!(event.path, path.join("existing-file-5")); assert!(event.flags.contains(StreamFlags::ITEM_REMOVED)); @@ -383,17 +383,11 @@ mod tests { }); fs::write(path.join("new-file"), "").unwrap(); - assert_eq!( - rx.recv_timeout(Duration::from_millis(500)).unwrap(), - "running" - ); + assert_eq!(rx.recv_timeout(Duration::from_secs(2)).unwrap(), "running"); // Dropping the handle causes `EventStream::run` to return. drop(handle); - assert_eq!( - rx.recv_timeout(Duration::from_millis(500)).unwrap(), - "stopped" - ); + assert_eq!(rx.recv_timeout(Duration::from_secs(2)).unwrap(), "stopped"); } #[test] From 074dc55ab7e6eb3c1e9b29c2f634d3d811230349 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 15:17:01 +0200 Subject: [PATCH 4/7] Temporary: ensure CI env variable is being set --- gpui/src/app.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index efaf410dbb4c59407c776cd5b92fdf6cd826c803..1b01635eca57088b7bf9783c697f4136c1c53ceb 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -2315,6 +2315,8 @@ impl ViewHandle { Duration::from_millis(500) }; + log::error!("Duration is {:?}", duration); + async move { timeout(duration, async move { loop { From d7f5587d8467d73484bb5fcd71374da0b2aff110 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 15:20:33 +0200 Subject: [PATCH 5/7] Revert "Temporary: ensure CI env variable is being set" This reverts commit 074dc55ab7e6eb3c1e9b29c2f634d3d811230349. --- gpui/src/app.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 1b01635eca57088b7bf9783c697f4136c1c53ceb..efaf410dbb4c59407c776cd5b92fdf6cd826c803 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -2315,8 +2315,6 @@ impl ViewHandle { Duration::from_millis(500) }; - log::error!("Duration is {:?}", duration); - async move { timeout(duration, async move { loop { From 64d361fdb2dfbd080b9f86560fd1f023f98a4a99 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 15:36:24 +0200 Subject: [PATCH 6/7] Don't use `condition` to wait for `spawn_search` to complete Instead, return a `Task` from `spawn_search` that can either be awaited or detached. --- zed/src/file_finder.rs | 55 ++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/zed/src/file_finder.rs b/zed/src/file_finder.rs index 691bf4ae3f60a47fc923047a8a625dbfed4c9d62..7e57057e7215dee4a6ff67fc244aa2e45b99c34e 100644 --- a/zed/src/file_finder.rs +++ b/zed/src/file_finder.rs @@ -11,7 +11,7 @@ use gpui::{ fonts::{Properties, Weight}, geometry::vector::vec2f, keymap::{self, Binding}, - AppContext, Axis, Border, Entity, MutableAppContext, View, ViewContext, ViewHandle, + AppContext, Axis, Border, Entity, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, }; use postage::watch; @@ -310,7 +310,9 @@ impl FileFinder { } fn workspace_updated(&mut self, _: ViewHandle, ctx: &mut ViewContext) { - self.spawn_search(self.query_buffer.read(ctx).text(ctx.as_ref()), ctx); + if let Some(task) = self.spawn_search(self.query_buffer.read(ctx).text(ctx.as_ref()), ctx) { + task.detach(); + } } fn on_query_buffer_event( @@ -328,7 +330,9 @@ impl FileFinder { self.matches.clear(); ctx.notify(); } else { - self.spawn_search(query, ctx); + if let Some(task) = self.spawn_search(query, ctx) { + task.detach(); + } } } Blurred => ctx.emit(Event::Dismissed), @@ -385,7 +389,8 @@ impl FileFinder { ctx.emit(Event::Selected(*tree_id, path.clone())); } - fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) -> Option<()> { + #[must_use] + fn spawn_search(&mut self, query: String, ctx: &mut ViewContext) -> Option> { let snapshots = self .workspace .upgrade(&ctx)? @@ -415,13 +420,10 @@ impl FileFinder { (search_id, did_cancel, query, matches) }); - ctx.spawn(|this, mut ctx| async move { + Some(ctx.spawn(|this, mut ctx| async move { let matches = background_task.await; this.update(&mut ctx, |this, ctx| this.update_matches(matches, ctx)); - }) - .detach(); - - Some(()) + })) } fn update_matches( @@ -550,15 +552,18 @@ mod tests { let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); let query = "hi".to_string(); - finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx)); - finder.condition(&app, |f, _| f.matches.len() == 5).await; + finder + .update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx)) + .unwrap() + .await; + finder.read_with(&app, |f, _| assert_eq!(f.matches.len(), 5)); finder.update(&mut app, |finder, ctx| { let matches = finder.matches.clone(); // Simulate a search being cancelled after the time limit, // returning only a subset of the matches that would have been found. - finder.spawn_search(query.clone(), ctx); + finder.spawn_search(query.clone(), ctx).unwrap().detach(); finder.update_matches( ( finder.latest_search_id, @@ -570,7 +575,7 @@ mod tests { ); // Simulate another cancellation. - finder.spawn_search(query.clone(), ctx); + finder.spawn_search(query.clone(), ctx).unwrap().detach(); finder.update_matches( ( finder.latest_search_id, @@ -605,14 +610,16 @@ mod tests { // Even though there is only one worktree, that worktree's filename // is included in the matching, because the worktree is a single file. - finder.update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx)); - finder.condition(&app, |f, _| f.matches.len() == 1).await; - + finder + .update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx)) + .unwrap() + .await; app.read(|ctx| { let finder = finder.read(ctx); + assert_eq!(finder.matches.len(), 1); + let (file_name, file_name_positions, full_path, full_path_positions) = finder.labels_for_match(&finder.matches[0], ctx).unwrap(); - assert_eq!(file_name, "the-file"); assert_eq!(file_name_positions, &[0, 1, 4]); assert_eq!(full_path, "the-file"); @@ -621,8 +628,11 @@ mod tests { // Since the worktree root is a file, searching for its name followed by a slash does // not match anything. - finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx)); - finder.condition(&app, |f, _| f.matches.len() == 0).await; + finder + .update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx)) + .unwrap() + .await; + finder.read_with(&app, |f, _| assert_eq!(f.matches.len(), 0)); } #[gpui::test] @@ -649,11 +659,14 @@ mod tests { let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx)); // Run a search that matches two files with the same relative path. - finder.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx)); - finder.condition(&app, |f, _| f.matches.len() == 2).await; + finder + .update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx)) + .unwrap() + .await; // Can switch between different matches with the same relative path. finder.update(&mut app, |f, ctx| { + assert_eq!(f.matches.len(), 2); assert_eq!(f.selected_index(), 0); f.select_next(&(), ctx); assert_eq!(f.selected_index(), 1); From 1517ccf80b97bd7083b2f62b3167d4378e895f1c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 19 May 2021 16:03:30 +0200 Subject: [PATCH 7/7] Always flush historical events in `fsevent` tests --- fsevent/src/lib.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/fsevent/src/lib.rs b/fsevent/src/lib.rs index e6f8bcfe45450e997ac157965a60c905b1222552..95171f835c7d4adef52dece7b740cdbfb9e0f4cd 100644 --- a/fsevent/src/lib.rs +++ b/fsevent/src/lib.rs @@ -307,14 +307,12 @@ mod tests { for i in 0..10 { fs::write(path.join(format!("existing-file-{}", i)), "").unwrap(); } + flush_historical_events(); let (tx, rx) = mpsc::channel(); let (stream, handle) = EventStream::new(&[&path], Duration::from_millis(50)); thread::spawn(move || stream.run(move |events| tx.send(events.to_vec()).is_ok())); - // Flush any historical events. - rx.recv_timeout(Duration::from_secs(2)).ok(); - fs::write(path.join("new-file"), "").unwrap(); let events = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let event = events.last().unwrap(); @@ -338,6 +336,7 @@ mod tests { for i in 0..10 { fs::write(path.join(format!("existing-file-{}", i)), "").unwrap(); } + flush_historical_events(); let (tx, rx) = mpsc::channel(); let (stream, handle) = EventStream::new(&[&path], Duration::from_millis(50)); @@ -350,7 +349,7 @@ mod tests { }); fs::write(path.join("new-file"), "").unwrap(); - let events = rx.recv_timeout(Duration::from_millis(800)).unwrap(); + let events = rx.recv_timeout(Duration::from_secs(2)).unwrap(); let event = events.last().unwrap(); assert_eq!(event.path, path.join("new-file")); assert!(event.flags.contains(StreamFlags::ITEM_CREATED)); @@ -368,6 +367,7 @@ mod tests { fn test_event_stream_shutdown_by_dropping_handle() { let dir = TempDir::new("test-event-stream").unwrap(); let path = dir.path().canonicalize().unwrap(); + flush_historical_events(); let (tx, rx) = mpsc::channel(); let (stream, handle) = EventStream::new(&[&path], Duration::from_millis(50)); @@ -401,4 +401,13 @@ mod tests { // This returns immediately because the handle was already dropped. stream.run(|_| true); } + + fn flush_historical_events() { + let duration = if std::env::var("CI").is_ok() { + Duration::from_secs(2) + } else { + Duration::from_millis(500) + }; + thread::sleep(duration); + } }