Merge pull request #438 from zed-industries/speed-up-integration-tests

Max Brunsfeld created

Always run multiple iterations of all integration tests

Change summary

Cargo.lock                   |   1 
crates/editor/src/test.rs    |   5 
crates/gpui/src/test.rs      |   6 
crates/language/src/tests.rs |   5 
crates/lsp/src/lsp.rs        |   2 
crates/server/Cargo.toml     |   1 
crates/server/src/db.rs      | 122 +++++++++++++++++++++++++++----------
crates/server/src/rpc.rs     |  37 +++++-----
crates/text/src/tests.rs     |   5 
crates/zed/src/test.rs       |   4 
10 files changed, 124 insertions(+), 64 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5850,6 +5850,7 @@ dependencies = [
  "tide-compress",
  "time 0.2.25",
  "toml",
+ "util",
  "zed",
 ]
 

crates/editor/src/test.rs 🔗

@@ -1,6 +1,7 @@
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    // std::env::set_var("RUST_LOG", "info");
-    env_logger::init();
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
 }

crates/gpui/src/test.rs 🔗

@@ -18,9 +18,9 @@ use crate::{
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    env_logger::builder()
-        .filter_level(log::LevelFilter::Info)
-        .init();
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
 }
 
 pub fn run_test(

crates/language/src/tests.rs 🔗

@@ -17,8 +17,9 @@ use util::test::Network;
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    // std::env::set_var("RUST_LOG", "info");
-    env_logger::init();
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
 }
 
 #[test]

crates/lsp/src/lsp.rs 🔗

@@ -547,7 +547,7 @@ impl FakeLanguageServer {
                     request.params,
                 );
             } else {
-                println!(
+                log::info!(
                     "skipping message in fake language server {:?}",
                     std::str::from_utf8(&self.buffer)
                 );

crates/server/Cargo.toml 🔗

@@ -61,6 +61,7 @@ gpui = { path = "../gpui" }
 zed = { path = "../zed", features = ["test-support"] }
 ctor = "0.1"
 env_logger = "0.8"
+util = { path = "../util" }
 
 lazy_static = "1.4"
 serde_json = { version = "1.0.64", features = ["preserve_order"] }

crates/server/src/db.rs 🔗

@@ -526,68 +526,120 @@ pub struct ChannelMessage {
 #[cfg(test)]
 pub mod tests {
     use super::*;
+    use lazy_static::lazy_static;
+    use parking_lot::Mutex;
     use rand::prelude::*;
     use sqlx::{
         migrate::{MigrateDatabase, Migrator},
         Postgres,
     };
-    use std::path::Path;
+    use std::{
+        mem,
+        path::Path,
+        sync::atomic::{AtomicUsize, Ordering::SeqCst},
+    };
+    use util::ResultExt as _;
 
     pub struct TestDb {
-        pub db: Db,
+        pub db: Option<Db>,
         pub name: String,
         pub url: String,
     }
 
+    lazy_static! {
+        static ref DB_POOL: Mutex<Vec<TestDb>> = Default::default();
+        static ref DB_COUNT: AtomicUsize = Default::default();
+    }
+
     impl TestDb {
         pub fn new() -> Self {
-            // Enable tests to run in parallel by serializing the creation of each test database.
-            lazy_static::lazy_static! {
-                static ref DB_CREATION: std::sync::Mutex<()> = std::sync::Mutex::new(());
-            }
-
-            let mut rng = StdRng::from_entropy();
-            let name = format!("zed-test-{}", rng.gen::<u128>());
-            let url = format!("postgres://postgres@localhost/{}", name);
-            let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations"));
-            let db = block_on(async {
-                {
-                    let _lock = DB_CREATION.lock();
+            DB_COUNT.fetch_add(1, SeqCst);
+            let mut pool = DB_POOL.lock();
+            if let Some(db) = pool.pop() {
+                db.truncate();
+                db
+            } else {
+                let mut rng = StdRng::from_entropy();
+                let name = format!("zed-test-{}", rng.gen::<u128>());
+                let url = format!("postgres://postgres@localhost/{}", name);
+                let migrations_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/migrations"));
+                let db = block_on(async {
                     Postgres::create_database(&url)
                         .await
                         .expect("failed to create test db");
+                    let mut db = Db::new(&url, 5).await.unwrap();
+                    db.test_mode = true;
+                    let migrator = Migrator::new(migrations_path).await.unwrap();
+                    migrator.run(&db.pool).await.unwrap();
+                    db
+                });
+
+                Self {
+                    db: Some(db),
+                    name,
+                    url,
                 }
-                let mut db = Db::new(&url, 5).await.unwrap();
-                db.test_mode = true;
-                let migrator = Migrator::new(migrations_path).await.unwrap();
-                migrator.run(&db.pool).await.unwrap();
-                db
-            });
-
-            Self { db, name, url }
+            }
         }
 
         pub fn db(&self) -> &Db {
-            &self.db
+            self.db.as_ref().unwrap()
         }
-    }
 
-    impl Drop for TestDb {
-        fn drop(&mut self) {
+        fn truncate(&self) {
             block_on(async {
                 let query = "
-                    SELECT pg_terminate_backend(pg_stat_activity.pid)
-                    FROM pg_stat_activity
-                    WHERE pg_stat_activity.datname = '{}' AND pid <> pg_backend_pid();
+                    SELECT tablename FROM pg_tables
+                    WHERE schemaname = 'public';
                 ";
-                sqlx::query(query)
-                    .bind(&self.name)
-                    .execute(&self.db.pool)
+                let table_names = sqlx::query_scalar::<_, String>(query)
+                    .fetch_all(&self.db().pool)
                     .await
                     .unwrap();
-                self.db.pool.close().await;
-                Postgres::drop_database(&self.url).await.unwrap();
-            });
+                sqlx::query(&format!(
+                    "TRUNCATE TABLE {} RESTART IDENTITY",
+                    table_names.join(", ")
+                ))
+                .execute(&self.db().pool)
+                .await
+                .unwrap();
+            })
+        }
+
+        async fn teardown(mut self) -> Result<()> {
+            let db = self.db.take().unwrap();
+            let query = "
+                SELECT pg_terminate_backend(pg_stat_activity.pid)
+                FROM pg_stat_activity
+                WHERE pg_stat_activity.datname = '{}' AND pid <> pg_backend_pid();
+            ";
+            sqlx::query(query)
+                .bind(&self.name)
+                .execute(&db.pool)
+                .await?;
+            db.pool.close().await;
+            Postgres::drop_database(&self.url).await?;
+            Ok(())
+        }
+    }
+
+    impl Drop for TestDb {
+        fn drop(&mut self) {
+            if let Some(db) = self.db.take() {
+                DB_POOL.lock().push(TestDb {
+                    db: Some(db),
+                    name: mem::take(&mut self.name),
+                    url: mem::take(&mut self.url),
+                });
+                if DB_COUNT.fetch_sub(1, SeqCst) == 1 {
+                    block_on(async move {
+                        let mut pool = DB_POOL.lock();
+                        for db in pool.drain(..) {
+                            db.teardown().await.log_err();
+                        }
+                    });
+                }
+            }
         }
     }
 

crates/server/src/rpc.rs 🔗

@@ -1196,11 +1196,12 @@ mod tests {
     #[cfg(test)]
     #[ctor::ctor]
     fn init_logger() {
-        // std::env::set_var("RUST_LOG", "info");
-        env_logger::init();
+        if std::env::var("RUST_LOG").is_ok() {
+            env_logger::init();
+        }
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_share_project(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         let (window_b, _) = cx_b.add_window(|_| EmptyView);
         let lang_registry = Arc::new(LanguageRegistry::new());
@@ -1339,7 +1340,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_unshare_project(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         let lang_registry = Arc::new(LanguageRegistry::new());
         let fs = Arc::new(FakeFs::new(cx_a.background()));
@@ -1436,7 +1437,7 @@ mod tests {
             .unwrap();
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_propagate_saves_and_fs_changes(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,
@@ -1622,7 +1623,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_buffer_conflict_after_save(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
         let lang_registry = Arc::new(LanguageRegistry::new());
@@ -1715,7 +1716,7 @@ mod tests {
         });
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_buffer_reloading(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
         let lang_registry = Arc::new(LanguageRegistry::new());
@@ -1877,7 +1878,7 @@ mod tests {
         buffer_b.condition(&cx_b, |buf, _| buf.text() == text).await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_leaving_worktree_while_opening_buffer(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,
@@ -1955,7 +1956,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_peer_disconnection(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
         let lang_registry = Arc::new(LanguageRegistry::new());
@@ -2026,7 +2027,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_collaborating_with_diagnostics(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,
@@ -2250,7 +2251,7 @@ mod tests {
         });
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_collaborating_with_completion(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,
@@ -2475,7 +2476,7 @@ mod tests {
         );
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_formatting_buffer(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
         let mut lang_registry = Arc::new(LanguageRegistry::new());
@@ -2579,7 +2580,7 @@ mod tests {
         );
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_definition(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
         let mut lang_registry = Arc::new(LanguageRegistry::new());
@@ -2736,7 +2737,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_open_buffer_while_getting_definition_pointing_to_it(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,
@@ -2850,7 +2851,7 @@ mod tests {
         assert_eq!(definitions[0].target_buffer, buffer_b2);
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_basic_chat(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
 
@@ -2990,7 +2991,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_chat_message_validation(mut cx_a: TestAppContext) {
         cx_a.foreground().forbid_parking();
 
@@ -3050,7 +3051,7 @@ mod tests {
         );
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_chat_reconnection(mut cx_a: TestAppContext, mut cx_b: TestAppContext) {
         cx_a.foreground().forbid_parking();
 
@@ -3262,7 +3263,7 @@ mod tests {
             .await;
     }
 
-    #[gpui::test]
+    #[gpui::test(iterations = 10)]
     async fn test_contacts(
         mut cx_a: TestAppContext,
         mut cx_b: TestAppContext,

crates/text/src/tests.rs 🔗

@@ -12,8 +12,9 @@ use util::test::Network;
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    // std::env::set_var("RUST_LOG", "info");
-    env_logger::init();
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
 }
 
 #[test]

crates/zed/src/test.rs 🔗

@@ -12,7 +12,9 @@ use workspace::Settings;
 #[cfg(test)]
 #[ctor::ctor]
 fn init_logger() {
-    env_logger::init();
+    if std::env::var("RUST_LOG").is_ok() {
+        env_logger::init();
+    }
 }
 
 pub fn test_app_state(cx: &mut MutableAppContext) -> Arc<AppState> {