From 54550c37e6ebe26ab38efa3bc038d5d6703029c8 Mon Sep 17 00:00:00 2001 From: Andrew Aylett Date: Fri, 3 Jan 2025 23:19:05 +0000 Subject: [PATCH] test: Use testcontainers for integration tests requiring Postgres Rather than requiring the environment to supply a suitable database, we may use TestContainers to provision a fresh database for each test. This allows contributors to run the entire test suite without project-specific setup, although it does require a working Docker environment. --- .github/workflows/rust.yml | 12 -- Cargo.lock | 257 ++++++++++++++++++++++++++++++- crates/atuin/Cargo.toml | 1 + crates/atuin/tests/common/mod.rs | 31 +++- crates/atuin/tests/sync.rs | 3 +- crates/atuin/tests/users.rs | 9 +- 6 files changed, 289 insertions(+), 24 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e0f8ee4c81e..c45d8669ee1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -155,16 +155,6 @@ jobs: integration-test: runs-on: ubuntu-latest - services: - postgres: - image: postgres - env: - POSTGRES_USER: atuin - POSTGRES_PASSWORD: pass - POSTGRES_DB: atuin - ports: - - 5432:5432 - steps: - uses: actions/checkout@v4 @@ -188,8 +178,6 @@ jobs: - name: Run cargo test run: cargo nextest run --test '*' - env: - ATUIN_DB_URI: postgres://atuin:pass@localhost:5432/atuin clippy: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index f94713845a3..080a7e5bbdd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,6 +256,7 @@ dependencies = [ "serde", "serde_json", "sysinfo", + "testcontainers-modules", "time", "tiny-bip39", "tokio", @@ -647,6 +648,56 @@ dependencies = [ "objc2", ] +[[package]] +name = "bollard" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41711ad46fda47cd701f6908e59d1bd6b9a2b7464c0d0aeab95c6d37096ff8a" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.2.0", + "http-body-util", + "hyper 1.5.2", + "hyper-named-pipe", + "hyper-rustls 0.27.5", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls 0.23.20", + "rustls-native-certs 0.7.3", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + +[[package]] +name = "bollard-stubs" +version = "1.45.0-rc.26.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7c5415e3a6bc6d3e99eff6268e488fd4ee25e7b28c10f08fa6760bd9de16e4" +dependencies = [ + "serde", + "serde_repr", + "serde_with", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1267,6 +1318,17 @@ dependencies = [ "libloading", ] +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -1442,6 +1504,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1923,6 +1997,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.5.2", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1987,6 +2076,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.5.2", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -2339,6 +2443,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.8", ] [[package]] @@ -2922,11 +3027,36 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax 0.8.5", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax 0.8.5", + "structmeta", + "syn", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -3434,6 +3564,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -3528,7 +3667,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -3742,6 +3881,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -3966,6 +4118,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4414,6 +4577,29 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "strum" version = "0.26.3" @@ -4538,6 +4724,44 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "testcontainers" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f40cc2bd72e17f328faf8ca7687fe337e61bccd8acf9674fa78dd3792b045e1" +dependencies = [ + "async-trait", + "bollard", + "bollard-stubs", + "bytes", + "docker_credential", + "either", + "etcetera", + "futures", + "log", + "memchr", + "parse-display", + "pin-project-lite", + "serde", + "serde_json", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tokio-tar", + "tokio-util", + "url", +] + +[[package]] +name = "testcontainers-modules" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064a2677e164cad39ef3c1abddb044d5a25c49d27005804563d8c4227aac8bd0" +dependencies = [ + "testcontainers", +] + [[package]] name = "testing_logger" version = "0.1.1" @@ -4745,6 +4969,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5714c010ca3e5c27114c1cdeb9d14641ace49874aa5626d7149e47aedace75" +dependencies = [ + "filetime", + "futures-core", + "libc", + "redox_syscall 0.3.5", + "tokio", + "tokio-stream", + "xattr", +] + [[package]] name = "tokio-util" version = "0.7.13" @@ -5093,6 +5332,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -5349,7 +5589,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall", + "redox_syscall 0.5.8", "wasite", "web-sys", ] @@ -5632,6 +5872,17 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "yansi" version = "1.0.1" diff --git a/crates/atuin/Cargo.toml b/crates/atuin/Cargo.toml index a43bf563d7f..d788c6a0a2a 100644 --- a/crates/atuin/Cargo.toml +++ b/crates/atuin/Cargo.toml @@ -94,3 +94,4 @@ arboard = { version = "3.4", optional = true, features = ["wayland-data-control" [dev-dependencies] tracing-tree = "0.4" +testcontainers-modules = { version = "0.11.4", features = ["postgres"] } diff --git a/crates/atuin/tests/common/mod.rs b/crates/atuin/tests/common/mod.rs index 015098e96ff..464ff958e49 100644 --- a/crates/atuin/tests/common/mod.rs +++ b/crates/atuin/tests/common/mod.rs @@ -1,15 +1,25 @@ -use std::{env, time::Duration}; +use std::time::Duration; use atuin_client::api_client; use atuin_common::utils::uuid_v7; use atuin_server::{launch_with_tcp_listener, Settings as ServerSettings}; use atuin_server_postgres::{Postgres, PostgresSettings}; use futures_util::TryFutureExt; +use testcontainers_modules::postgres::Postgres as PostgresContainer; +use testcontainers_modules::testcontainers::runners::AsyncRunner; +use testcontainers_modules::testcontainers::{ContainerAsync, ImageExt}; use tokio::{net::TcpListener, sync::oneshot, task::JoinHandle}; use tracing::{dispatcher, Dispatch}; use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; -pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandle<()>) { +pub async fn start_server( + path: &str, +) -> ( + String, + oneshot::Sender<()>, + JoinHandle<()>, + ContainerAsync, +) { let formatting_layer = tracing_tree::HierarchicalLayer::default() .with_writer(tracing_subscriber::fmt::TestWriter::new()) .with_indent_lines(true) @@ -22,8 +32,14 @@ pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandl .with(EnvFilter::new("atuin_server=debug,atuin_client=debug,info")) .into(); - let db_uri = env::var("ATUIN_DB_URI") - .unwrap_or_else(|_| "postgres://atuin:pass@localhost:5432/atuin".to_owned()); + let container = PostgresContainer::default() + .with_tag("17-alpine") + .start() + .await + .unwrap(); + let host_port = container.get_host_port_ipv4(5432).await.unwrap(); + let host = container.get_host().await.unwrap(); + let db_uri = format!("postgres://postgres:postgres@{host}:{host_port}/postgres",); let server_settings = ServerSettings { host: "127.0.0.1".to_owned(), @@ -63,7 +79,12 @@ pub async fn start_server(path: &str) -> (String, oneshot::Sender<()>, JoinHandl // let the server come online tokio::time::sleep(Duration::from_millis(200)).await; - (format!("http://{addr}{path}"), shutdown_tx, server) + ( + format!("http://{addr}{path}"), + shutdown_tx, + server, + container, + ) } pub async fn register_inner<'a>( diff --git a/crates/atuin/tests/sync.rs b/crates/atuin/tests/sync.rs index 7e25d1c2927..299b611df80 100644 --- a/crates/atuin/tests/sync.rs +++ b/crates/atuin/tests/sync.rs @@ -6,7 +6,7 @@ mod common; #[tokio::test] async fn sync() { let path = format!("/{}", uuid_v7().as_simple()); - let (address, shutdown, server) = common::start_server(&path).await; + let (address, shutdown, server, postgres) = common::start_server(&path).await; let client = common::register(&address).await; let hostname = uuid_v7().as_simple().to_string(); @@ -42,4 +42,5 @@ async fn sync() { shutdown.send(()).unwrap(); server.await.unwrap(); + postgres.stop().await.unwrap(); } diff --git a/crates/atuin/tests/users.rs b/crates/atuin/tests/users.rs index 95fb533b539..565b2499816 100644 --- a/crates/atuin/tests/users.rs +++ b/crates/atuin/tests/users.rs @@ -5,7 +5,7 @@ mod common; #[tokio::test] async fn registration() { let path = format!("/{}", uuid_v7().as_simple()); - let (address, shutdown, server) = common::start_server(&path).await; + let (address, shutdown, server, postgres) = common::start_server(&path).await; dbg!(&address); // -- REGISTRATION -- @@ -28,12 +28,13 @@ async fn registration() { shutdown.send(()).unwrap(); server.await.unwrap(); + postgres.stop().await.unwrap(); } #[tokio::test] async fn change_password() { let path = format!("/{}", uuid_v7().as_simple()); - let (address, shutdown, server) = common::start_server(&path).await; + let (address, shutdown, server, postgres) = common::start_server(&path).await; // -- REGISTRATION -- @@ -66,12 +67,13 @@ async fn change_password() { shutdown.send(()).unwrap(); server.await.unwrap(); + postgres.stop().await.unwrap(); } #[tokio::test] async fn multi_user_test() { let path = format!("/{}", uuid_v7().as_simple()); - let (address, shutdown, server) = common::start_server(&path).await; + let (address, shutdown, server, postgres) = common::start_server(&path).await; dbg!(&address); // -- REGISTRATION -- @@ -118,4 +120,5 @@ async fn multi_user_test() { shutdown.send(()).unwrap(); server.await.unwrap(); + postgres.stop().await.unwrap(); }