gpui: Add a timeout to `#[gpui::test]` tests (#41303)

Lukas Wirth created

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/extension/src/extension_builder.rs         | 14 ++++++++++--
crates/extension_host/src/extension_store_test.rs |  7 +----
crates/gpui/src/executor.rs                       |  9 +++++++
crates/zlog/src/zlog.rs                           | 18 +++++++++++-----
4 files changed, 33 insertions(+), 15 deletions(-)

Detailed changes

crates/extension/src/extension_builder.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
 use anyhow::{Context as _, Result, bail};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
-use futures::io::BufReader;
+use futures::{AsyncReadExt, io::Cursor};
 use heck::ToSnakeCase;
 use http_client::{self, AsyncBody, HttpClient};
 use serde::Deserialize;
@@ -411,6 +411,8 @@ impl ExtensionBuilder {
         let mut clang_path = wasi_sdk_dir.clone();
         clang_path.extend(["bin", &format!("clang{}", env::consts::EXE_SUFFIX)]);
 
+        log::info!("downloading wasi-sdk to {}", wasi_sdk_dir.display());
+
         if fs::metadata(&clang_path).is_ok_and(|metadata| metadata.is_file()) {
             return Ok(clang_path);
         }
@@ -423,13 +425,19 @@ impl ExtensionBuilder {
 
         log::info!("downloading wasi-sdk to {}", wasi_sdk_dir.display());
         let mut response = self.http.get(&url, AsyncBody::default(), true).await?;
-        let body = BufReader::new(response.body_mut());
-        let body = GzipDecoder::new(body);
+        let body = GzipDecoder::new({
+            // stream the entire request into memory at once as the artifact is quite big (100MB+)
+            let mut b = vec![];
+            response.body_mut().read_to_end(&mut b).await?;
+            Cursor::new(b)
+        });
         let tar = Archive::new(body);
 
+        log::info!("un-tarring wasi-sdk to {}", wasi_sdk_dir.display());
         tar.unpack(&tar_out_dir)
             .await
             .context("failed to unpack wasi-sdk archive")?;
+        log::info!("finished downloading wasi-sdk");
 
         let inner_dir = fs::read_dir(&tar_out_dir)?
             .next()

crates/extension_host/src/extension_store_test.rs 🔗

@@ -31,7 +31,8 @@ use util::test::TempTree;
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    zlog::init_test();
+    // show info logs while we debug the extension_store tests hanging.
+    zlog::init_test_with("info");
 }
 
 #[gpui::test]
@@ -529,10 +530,6 @@ async fn test_extension_store(cx: &mut TestAppContext) {
     });
 }
 
-// todo(windows)
-// Disable this test on Windows for now. Because this test hangs at
-// `let fake_server = fake_servers.next().await.unwrap();`.
-// Reenable this test when we figure out why.
 #[gpui::test]
 async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
     init_test(cx);

crates/gpui/src/executor.rs 🔗

@@ -281,6 +281,8 @@ impl BackgroundExecutor {
         });
         let mut cx = std::task::Context::from_waker(&waker);
 
+        let mut test_should_end_by = Instant::now() + Duration::from_secs(500);
+
         loop {
             match future.as_mut().poll(&mut cx) {
                 Poll::Ready(result) => return Ok(result),
@@ -313,7 +315,12 @@ impl BackgroundExecutor {
                             )
                         }
                         dispatcher.set_unparker(unparker.clone());
-                        parker.park();
+                        parker.park_timeout(
+                            test_should_end_by.saturating_duration_since(Instant::now()),
+                        );
+                        if Instant::now() > test_should_end_by {
+                            panic!("test timed out with allow_parking")
+                        }
                     }
                 }
             }

crates/zlog/src/zlog.rs 🔗

@@ -10,22 +10,28 @@ pub use sink::{flush, init_output_file, init_output_stderr, init_output_stdout};
 pub const SCOPE_DEPTH_MAX: usize = 4;
 
 pub fn init() {
-    if let Err(err) = try_init() {
+    if let Err(err) = try_init(None) {
         log::error!("{err}");
         eprintln!("{err}");
     }
 }
 
-pub fn try_init() -> anyhow::Result<()> {
+pub fn try_init(filter: Option<String>) -> anyhow::Result<()> {
     log::set_logger(&ZLOG)?;
     log::set_max_level(log::LevelFilter::max());
-    process_env();
+    process_env(filter);
     filter::refresh_from_settings(&std::collections::HashMap::default());
     Ok(())
 }
 
 pub fn init_test() {
-    if get_env_config().is_some() && try_init().is_ok() {
+    if get_env_config().is_some() && try_init(None).is_ok() {
+        init_output_stdout();
+    }
+}
+
+pub fn init_test_with(filter: &str) {
+    if try_init(Some(filter.to_owned())).is_ok() {
         init_output_stdout();
     }
 }
@@ -36,8 +42,8 @@ fn get_env_config() -> Option<String> {
         .ok()
 }
 
-pub fn process_env() {
-    let Some(env_config) = get_env_config() else {
+pub fn process_env(filter: Option<String>) {
+    let Some(env_config) = get_env_config().or(filter) else {
         return;
     };
     match env_config::parse(&env_config) {