Use a pool of databases to speed up integration tests

Max Brunsfeld and Nathan Sobo created

Also, use env_logger consistently in the tests for each crate.
Only initiallize the logger at all if some RUST_LOG env var is set.

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/test.rs    |   5 +
crates/gpui/src/test.rs      |   6 +-
crates/language/src/tests.rs |   5 +
crates/server/src/db.rs      | 102 +++++++++++++++++++++++++++----------
crates/server/src/rpc.rs     |   5 +
crates/text/src/tests.rs     |   5 +
crates/zed/src/test.rs       |   4 +
7 files changed, 92 insertions(+), 40 deletions(-)

Detailed changes

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/server/src/db.rs 🔗

@@ -526,54 +526,88 @@ 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};
 
     pub struct TestDb {
-        pub db: Db,
+        pub db: Option<Db>,
         pub name: String,
         pub url: String,
     }
 
+    lazy_static! {
+        static ref POOL: Mutex<Vec<TestDb>> = Default::default();
+    }
+
+    #[ctor::dtor]
+    fn clear_pool() {
+        for db in POOL.lock().drain(..) {
+            db.teardown();
+        }
+    }
+
     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();
+            let mut pool = 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 tablename FROM pg_tables
+                    WHERE schemaname = 'public';
+                ";
+                let table_names = sqlx::query_scalar::<_, String>(query)
+                    .fetch_all(&self.db().pool)
+                    .await
+                    .unwrap();
+                sqlx::query(&format!(
+                    "TRUNCATE TABLE {} RESTART IDENTITY",
+                    table_names.join(", ")
+                ))
+                .execute(&self.db().pool)
+                .await
+                .unwrap();
+            })
+        }
+
+        fn teardown(mut self) {
+            let db = self.db.take().unwrap();
             block_on(async {
                 let query = "
                     SELECT pg_terminate_backend(pg_stat_activity.pid)
@@ -582,15 +616,27 @@ pub mod tests {
                 ";
                 sqlx::query(query)
                     .bind(&self.name)
-                    .execute(&self.db.pool)
+                    .execute(&db.pool)
                     .await
                     .unwrap();
-                self.db.pool.close().await;
+                db.pool.close().await;
                 Postgres::drop_database(&self.url).await.unwrap();
             });
         }
     }
 
+    impl Drop for TestDb {
+        fn drop(&mut self) {
+            if let Some(db) = self.db.take() {
+                POOL.lock().push(TestDb {
+                    db: Some(db),
+                    name: mem::take(&mut self.name),
+                    url: mem::take(&mut self.url),
+                });
+            }
+        }
+    }
+
     #[gpui::test]
     async fn test_get_users_by_ids() {
         let test_db = TestDb::new();

crates/server/src/rpc.rs 🔗

@@ -1196,8 +1196,9 @@ 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]

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> {