From ee7c5e2f26fe58f28edfb89d81ac46b6c952b2c1 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 9 Jan 2026 07:47:44 +0100 Subject: [PATCH] Introduce new TestEnvironment usable by all binary crates --- Cargo.lock | 23 ++++ crates/bin/cratesfyi/Cargo.toml | 1 + crates/bin/cratesfyi/src/daemon.rs | 1 + crates/bin/docs_rs_admin/Cargo.toml | 2 + .../bin/docs_rs_admin/src/testing/test_env.rs | 77 +---------- ...29096357aa95c67c76905a6709a66247450ff.json | 15 ++ ...2e90e944d9009d75ad527e7442e312be98ea9.json | 26 ++++ ...c9973a3f45abc9cf73e171011a2ded4d97dfc.json | 15 ++ ...3889c846d64fa11463719e65496b950ae039f.json | 20 +++ crates/bin/docs_rs_builder/Cargo.toml | 6 +- crates/bin/docs_rs_builder/src/build_queue.rs | 12 +- crates/bin/docs_rs_builder/src/config.rs | 10 +- .../src/docbuilder/rustwide_builder.rs | 53 +++---- crates/bin/docs_rs_builder/src/main.rs | 1 + crates/bin/docs_rs_builder/src/testing/mod.rs | 2 +- .../docs_rs_builder/src/testing/test_env.rs | 97 ++----------- crates/bin/docs_rs_watcher/Cargo.toml | 3 + crates/bin/docs_rs_watcher/src/config.rs | 11 +- .../bin/docs_rs_watcher/src/consistency/db.rs | 2 +- .../docs_rs_watcher/src/consistency/mod.rs | 20 +-- .../bin/docs_rs_watcher/src/index_watcher.rs | 14 +- crates/bin/docs_rs_watcher/src/main.rs | 1 + crates/bin/docs_rs_watcher/src/rebuilds.rs | 7 +- .../docs_rs_watcher/src/testing/test_env.rs | 82 +---------- crates/bin/docs_rs_web/Cargo.toml | 2 + crates/bin/docs_rs_web/src/cache.rs | 1 + crates/bin/docs_rs_web/src/config.rs | 7 +- crates/bin/docs_rs_web/src/error.rs | 4 +- crates/bin/docs_rs_web/src/file.rs | 12 +- .../docs_rs_web/src/handlers/build_details.rs | 5 +- crates/bin/docs_rs_web/src/handlers/builds.rs | 9 +- .../docs_rs_web/src/handlers/crate_details.rs | 55 ++++---- .../bin/docs_rs_web/src/handlers/features.rs | 4 +- crates/bin/docs_rs_web/src/handlers/mod.rs | 5 +- .../bin/docs_rs_web/src/handlers/releases.rs | 20 ++- .../bin/docs_rs_web/src/handlers/rustdoc.rs | 2 +- .../bin/docs_rs_web/src/handlers/sitemap.rs | 4 +- crates/bin/docs_rs_web/src/handlers/source.rs | 17 ++- .../bin/docs_rs_web/src/handlers/statics.rs | 4 +- crates/bin/docs_rs_web/src/handlers/status.rs | 2 +- crates/bin/docs_rs_web/src/main.rs | 1 + crates/bin/docs_rs_web/src/match_release.rs | 18 +-- crates/bin/docs_rs_web/src/metrics.rs | 2 +- crates/bin/docs_rs_web/src/routes.rs | 4 +- crates/bin/docs_rs_web/src/testing/mod.rs | 2 +- .../bin/docs_rs_web/src/testing/test_env.rs | 119 +--------------- .../bin/docs_rs_web/src/utils/html_rewrite.rs | 4 +- crates/lib/docs_rs_build_limits/Cargo.toml | 1 + .../lib/docs_rs_build_limits/src/blacklist.rs | 1 + crates/lib/docs_rs_build_limits/src/config.rs | 6 +- crates/lib/docs_rs_build_limits/src/limits.rs | 1 + .../lib/docs_rs_build_limits/src/overrides.rs | 1 + crates/lib/docs_rs_build_queue/Cargo.toml | 2 + crates/lib/docs_rs_build_queue/src/config.rs | 6 +- .../lib/docs_rs_build_queue/src/priority.rs | 1 + .../docs_rs_build_queue/src/queue/blocking.rs | 7 +- .../src/queue/non_blocking.rs | 1 + crates/lib/docs_rs_config/Cargo.toml | 11 ++ crates/lib/docs_rs_config/src/lib.rs | 13 ++ ...29096357aa95c67c76905a6709a66247450ff.json | 15 ++ ...2e90e944d9009d75ad527e7442e312be98ea9.json | 26 ++++ ...c9973a3f45abc9cf73e171011a2ded4d97dfc.json | 15 ++ ...3889c846d64fa11463719e65496b950ae039f.json | 20 +++ ...a659e7fb431beb0017fbcfd21132f105ce722.json | 22 +++ crates/lib/docs_rs_context/Cargo.toml | 12 +- crates/lib/docs_rs_context/src/context.rs | 1 + crates/lib/docs_rs_context/src/lib.rs | 2 + crates/lib/docs_rs_context/src/testing/mod.rs | 3 + .../src/testing/test_env/blocking.rs | 54 ++++++++ .../src/testing/test_env/mod.rs | 2 + .../src/testing/test_env/non_blocking.rs | 130 ++++++++++++++++++ crates/lib/docs_rs_database/Cargo.toml | 3 + crates/lib/docs_rs_database/src/config.rs | 8 +- crates/lib/docs_rs_database/src/releases.rs | 1 + .../docs_rs_database/src/service_config.rs | 1 + crates/lib/docs_rs_fastly/Cargo.toml | 6 +- crates/lib/docs_rs_fastly/src/config.rs | 12 +- crates/lib/docs_rs_opentelemetry/Cargo.toml | 2 + .../lib/docs_rs_opentelemetry/src/config.rs | 6 +- crates/lib/docs_rs_registry_api/Cargo.toml | 1 + crates/lib/docs_rs_registry_api/src/config.rs | 5 +- .../lib/docs_rs_repository_stats/Cargo.toml | 1 + .../docs_rs_repository_stats/src/config.rs | 6 +- .../docs_rs_repository_stats/src/github.rs | 1 + .../docs_rs_repository_stats/src/updater.rs | 1 + crates/lib/docs_rs_storage/Cargo.toml | 3 + crates/lib/docs_rs_storage/src/config.rs | 30 ++-- .../docs_rs_storage/src/testing/test_env.rs | 6 +- crates/lib/docs_rs_utils/src/testing/mod.rs | 2 +- 89 files changed, 724 insertions(+), 528 deletions(-) create mode 100644 crates/bin/docs_rs_builder/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json create mode 100644 crates/bin/docs_rs_builder/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json create mode 100644 crates/bin/docs_rs_builder/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json create mode 100644 crates/bin/docs_rs_builder/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json create mode 100644 crates/lib/docs_rs_config/Cargo.toml create mode 100644 crates/lib/docs_rs_config/src/lib.rs create mode 100644 crates/lib/docs_rs_context/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json create mode 100644 crates/lib/docs_rs_context/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json create mode 100644 crates/lib/docs_rs_context/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json create mode 100644 crates/lib/docs_rs_context/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json create mode 100644 crates/lib/docs_rs_context/.sqlx/query-96b68919f9016705a1a36ef11a5a659e7fb431beb0017fbcfd21132f105ce722.json create mode 100644 crates/lib/docs_rs_context/src/testing/mod.rs create mode 100644 crates/lib/docs_rs_context/src/testing/test_env/blocking.rs create mode 100644 crates/lib/docs_rs_context/src/testing/test_env/mod.rs create mode 100644 crates/lib/docs_rs_context/src/testing/test_env/non_blocking.rs diff --git a/Cargo.lock b/Cargo.lock index bd1b6cb04..c5deb8060 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1469,6 +1469,7 @@ dependencies = [ "anyhow", "clap", "docs_rs_builder", + "docs_rs_config", "docs_rs_context", "docs_rs_logging", "docs_rs_utils", @@ -1902,6 +1903,7 @@ dependencies = [ "clap", "docs_rs_build_limits", "docs_rs_build_queue", + "docs_rs_config", "docs_rs_context", "docs_rs_database", "docs_rs_logging", @@ -1922,6 +1924,7 @@ name = "docs_rs_build_limits" version = "0.1.0" dependencies = [ "anyhow", + "docs_rs_config", "docs_rs_database", "docs_rs_env_vars", "docs_rs_opentelemetry", @@ -1941,6 +1944,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "docs_rs_config", "docs_rs_database", "docs_rs_env_vars", "docs_rs_opentelemetry", @@ -1963,6 +1967,7 @@ dependencies = [ "docs_rs_build_limits", "docs_rs_build_queue", "docs_rs_cargo_metadata", + "docs_rs_config", "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", @@ -2006,6 +2011,13 @@ dependencies = [ "test-case", ] +[[package]] +name = "docs_rs_config" +version = "0.1.0" +dependencies = [ + "anyhow", +] + [[package]] name = "docs_rs_context" version = "0.1.0" @@ -2014,12 +2026,15 @@ dependencies = [ "bon", "docs_rs_build_limits", "docs_rs_build_queue", + "docs_rs_config", "docs_rs_database", "docs_rs_fastly", + "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_registry_api", "docs_rs_repository_stats", "docs_rs_storage", + "docs_rs_test_fakes", "tokio", ] @@ -2030,6 +2045,7 @@ dependencies = [ "anyhow", "chrono", "docs_rs_cargo_metadata", + "docs_rs_config", "docs_rs_env_vars", "docs_rs_opentelemetry", "docs_rs_registry_api", @@ -2066,6 +2082,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "docs_rs_config", "docs_rs_env_vars", "docs_rs_headers", "docs_rs_opentelemetry", @@ -2128,6 +2145,7 @@ version = "0.1.0" dependencies = [ "anyhow", "derive_more 2.1.1", + "docs_rs_config", "docs_rs_env_vars", "opentelemetry", "opentelemetry-otlp", @@ -2144,6 +2162,7 @@ dependencies = [ "anyhow", "bon", "chrono", + "docs_rs_config", "docs_rs_env_vars", "docs_rs_types", "docs_rs_utils", @@ -2162,6 +2181,7 @@ dependencies = [ "async-trait", "chrono", "docs_rs_cargo_metadata", + "docs_rs_config", "docs_rs_database", "docs_rs_env_vars", "docs_rs_utils", @@ -2203,6 +2223,7 @@ dependencies = [ "chrono", "criterion", "dashmap", + "docs_rs_config", "docs_rs_env_vars", "docs_rs_headers", "docs_rs_logging", @@ -2302,6 +2323,7 @@ dependencies = [ "crates-index", "crates-index-diff", "docs_rs_build_queue", + "docs_rs_config", "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", @@ -2344,6 +2366,7 @@ dependencies = [ "docs_rs_build_limits", "docs_rs_build_queue", "docs_rs_cargo_metadata", + "docs_rs_config", "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", diff --git a/crates/bin/cratesfyi/Cargo.toml b/crates/bin/cratesfyi/Cargo.toml index 65effa3cc..d182c7df4 100644 --- a/crates/bin/cratesfyi/Cargo.toml +++ b/crates/bin/cratesfyi/Cargo.toml @@ -10,6 +10,7 @@ edition = "2024" anyhow = { workspace = true } clap = { workspace = true } docs_rs_builder = { path = "../docs_rs_builder" } +docs_rs_config = { path = "../../lib/docs_rs_config" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_logging = { path = "../../lib/docs_rs_logging" } docs_rs_utils = { path = "../../lib/docs_rs_utils" } diff --git a/crates/bin/cratesfyi/src/daemon.rs b/crates/bin/cratesfyi/src/daemon.rs index 4621c030c..2ea37bd6b 100644 --- a/crates/bin/cratesfyi/src/daemon.rs +++ b/crates/bin/cratesfyi/src/daemon.rs @@ -1,5 +1,6 @@ use anyhow::{Error, anyhow}; use docs_rs_builder::{RustwideBuilder, queue_builder}; +use docs_rs_config::AppConfig as _; use docs_rs_context::Context; use docs_rs_watcher::{ start_background_queue_rebuild, start_background_repository_stats_updater, diff --git a/crates/bin/docs_rs_admin/Cargo.toml b/crates/bin/docs_rs_admin/Cargo.toml index 857944a71..ca029ba7b 100644 --- a/crates/bin/docs_rs_admin/Cargo.toml +++ b/crates/bin/docs_rs_admin/Cargo.toml @@ -24,6 +24,8 @@ tracing = { workspace = true } [dev-dependencies] bon = { workspace = true } +docs_rs_config = { path = "../../lib/docs_rs_config", features = ["testing"] } +docs_rs_context = { path = "../../lib/docs_rs_context", features = ["testing"] } docs_rs_database = { path = "../../lib/docs_rs_database", features = ["testing"] } docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry", features = ["testing"] } docs_rs_storage = { path = "../../lib/docs_rs_storage", features = ["testing"] } diff --git a/crates/bin/docs_rs_admin/src/testing/test_env.rs b/crates/bin/docs_rs_admin/src/testing/test_env.rs index 2357dd396..6daf34786 100644 --- a/crates/bin/docs_rs_admin/src/testing/test_env.rs +++ b/crates/bin/docs_rs_admin/src/testing/test_env.rs @@ -1,75 +1,12 @@ use anyhow::Result; -use bon::bon; -use docs_rs_build_queue::AsyncBuildQueue; -use docs_rs_context::Context; -use docs_rs_database::{AsyncPoolClient, Config as DatabaseConfig, testing::TestDatabase}; -use docs_rs_opentelemetry::testing::TestMetrics; -use docs_rs_storage::{Config as StorageConfig, StorageKind, testing::TestStorage}; -use docs_rs_test_fakes::FakeRelease; -use std::sync::Arc; +use docs_rs_config::AppConfig; -pub(crate) struct TestEnvironment { - pub(crate) context: Arc, - // so we can allow asserting collected metrics later. - #[allow(dead_code)] // we need to keep the metrics so they can be collected & cleaned - pub(crate) metrics: TestMetrics, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) storage: TestStorage, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) db: TestDatabase, -} - -#[bon] -impl TestEnvironment { - pub(crate) async fn new() -> Result { - // NOTE: compiler crashes when I change the return to - // `Self::builder().build().await - #[allow(clippy::needless_question_mark)] - Ok(Self::builder().build().await?) - } - - #[builder(finish_fn = build)] - pub(crate) async fn builder() -> Result { - docs_rs_logging::testing::init(); - - let metrics = TestMetrics::new(); - - let db_config = DatabaseConfig::test_config()?; - let db = TestDatabase::new(&db_config, metrics.provider()).await?; - - let storage_config = Arc::new(StorageConfig::test_config(StorageKind::Memory)?); - - let test_storage = - TestStorage::from_config(storage_config.clone(), metrics.provider()).await?; +pub(crate) struct DummyConfig; - Ok(Self { - context: Context::builder() - .with_runtime() - .await? - .meter_provider(metrics.provider().clone()) - .pool(db_config.into(), db.pool().clone()) - .storage(storage_config.clone(), test_storage.storage()) - .with_build_queue()? - .build()? - .into(), - db, - storage: test_storage, - metrics, - }) - } - - pub(crate) fn build_queue(&self) -> Result<&Arc> { - self.context.build_queue() - } - - pub(crate) async fn async_conn(&self) -> Result { - self.context.pool()?.get_async().await.map_err(Into::into) - } - - pub async fn fake_release(&self) -> FakeRelease<'_> { - FakeRelease::new( - self.context.pool().unwrap().clone(), - self.context.storage().unwrap().clone(), - ) +impl AppConfig for DummyConfig { + fn from_environment() -> Result { + Ok(Self {}) } } + +pub(crate) type TestEnvironment = docs_rs_context::testing::TestEnvironment; diff --git a/crates/bin/docs_rs_builder/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json b/crates/bin/docs_rs_builder/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json new file mode 100644 index 000000000..f13d5b020 --- /dev/null +++ b/crates/bin/docs_rs_builder/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE builds SET output = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff" +} diff --git a/crates/bin/docs_rs_builder/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json b/crates/bin/docs_rs_builder/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json new file mode 100644 index 000000000..f1374e312 --- /dev/null +++ b/crates/bin/docs_rs_builder/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO repositories (host, host_id, name, description, last_commit, stars, forks, issues, updated_at)\n VALUES ('github.com', $1, $2, 'Fake description!', NOW(), $3, $4, $5, NOW())\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Int4", + "Int4", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9" +} diff --git a/crates/bin/docs_rs_builder/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json b/crates/bin/docs_rs_builder/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json new file mode 100644 index 000000000..60e1078e6 --- /dev/null +++ b/crates/bin/docs_rs_builder/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE builds\n SET\n build_status = 'failure',\n errors = $2\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc" +} diff --git a/crates/bin/docs_rs_builder/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json b/crates/bin/docs_rs_builder/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json new file mode 100644 index 000000000..80e54a1b7 --- /dev/null +++ b/crates/bin/docs_rs_builder/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM repositories", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f" +} diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index fe7bf4156..e63e6bb59 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -12,6 +12,7 @@ clap = { workspace = true } docs_rs_build_limits = { path = "../../lib/docs_rs_build_limits" } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_config = { path = "../../lib/docs_rs_config" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } @@ -41,6 +42,8 @@ tracing-log = "0.2.0" [dev-dependencies] docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue", features = ["testing"] } +docs_rs_config = { path = "../../lib/docs_rs_config", features = ["testing"] } +docs_rs_context = { path = "../../lib/docs_rs_context", features = ["testing"] } docs_rs_database = { path = "../../lib/docs_rs_database", features = ["testing"] } docs_rs_fastly= { path = "../../lib/docs_rs_fastly", features = ["testing"] } docs_rs_headers = { path = "../../lib/docs_rs_headers", features = ["testing"] } @@ -49,6 +52,3 @@ docs_rs_types = { path = "../../lib/docs_rs_types", features = ["testing"] } docs_rs_utils = { path = "../../lib/docs_rs_utils", features = ["testing"] } pretty_assertions = { workspace = true } test-case = { workspace = true } - -[features] -testing = [] diff --git a/crates/bin/docs_rs_builder/src/build_queue.rs b/crates/bin/docs_rs_builder/src/build_queue.rs index 2a8e23cbe..af9bd559c 100644 --- a/crates/bin/docs_rs_builder/src/build_queue.rs +++ b/crates/bin/docs_rs_builder/src/build_queue.rs @@ -91,16 +91,16 @@ mod tests { #[test] fn test_invalidate_cdn_after_error() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let queue = env.blocking_build_queue()?; - let builder_metrics = BuilderMetrics::new(env.context.meter_provider()); + let builder_metrics = BuilderMetrics::new(env.meter_provider()); const WILL_FAIL: KrateName = KrateName::from_static("will_fail"); queue.add_crate(&WILL_FAIL, &V1, 0, None)?; - process_next_crate(&env.context, &builder_metrics, |krate| { + process_next_crate(&env, &builder_metrics, |krate| { assert_eq!(WILL_FAIL, krate.name); anyhow::bail!("simulate a failure"); })?; @@ -115,14 +115,14 @@ mod tests { #[test] fn test_invalidate_cdn_after_build() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let queue = env.blocking_build_queue()?; - let builder_metrics = BuilderMetrics::new(env.context.meter_provider()); + let builder_metrics = BuilderMetrics::new(env.meter_provider()); const WILL_SUCCEED: KrateName = KrateName::from_static("will_succeed"); queue.add_crate(&WILL_SUCCEED, &V1, -1, None)?; - process_next_crate(&env.context, &builder_metrics, |krate| { + process_next_crate(&env, &builder_metrics, |krate| { assert_eq!(WILL_SUCCEED, krate.name); Ok(BuildPackageSummary::default()) })?; diff --git a/crates/bin/docs_rs_builder/src/config.rs b/crates/bin/docs_rs_builder/src/config.rs index fcdd20cdd..5f3b7b060 100644 --- a/crates/bin/docs_rs_builder/src/config.rs +++ b/crates/bin/docs_rs_builder/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, sync::Arc, time::Duration}; @@ -24,8 +26,8 @@ pub struct Config { pub build_limits: Arc, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; Ok(Self { temp_dir: prefix.join("tmp"), @@ -47,8 +49,8 @@ impl Config { }) } - #[cfg(any(feature = "testing", test))] - pub fn test_config() -> anyhow::Result { + #[cfg(test)] + fn test_config() -> Result { let mut config = Self::from_environment()?; config.include_default_targets = true; diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 9fe7f3c6a..ee3f052d5 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -1338,7 +1338,8 @@ pub(crate) struct BuildResult { #[cfg(test)] mod tests { use super::*; - use crate::testing::TestEnvironment; + use crate::testing::{TestEnvironment, TestEnvironmentExt as _}; + use docs_rs_config::AppConfig as _; use docs_rs_utils::block_on_async_with_conn; // use crate::test::{AxumRouterTestExt, TestEnvironment}; use docs_rs_registry_api::ReleaseData; @@ -1380,7 +1381,7 @@ mod tests { for path in paths { let full_path = env - .config + .config() .rustwide_workspace .join("cargo-home/registry") .join(path); @@ -1400,14 +1401,14 @@ mod tests { #[test] #[ignore] fn test_build_crate() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let crate_ = DUMMY_CRATE_NAME; let crate_path = crate_.replace('-', "_"); let version = DUMMY_CRATE_VERSION; let default_target = "x86_64-unknown-linux-gnu"; - let storage = env.context.blocking_storage()?; + let storage = env.blocking_storage()?; let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); storage.store_one(&old_rustdoc_file, Vec::new())?; @@ -1517,7 +1518,7 @@ mod tests { // Non-dist toolchains only have a single target, and of course // if include_default_targets is false we won't have this full list // of targets. - if builder.toolchain.as_dist().is_some() && env.config.include_default_targets { + if builder.toolchain.as_dist().is_some() && env.config().include_default_targets { assert_eq!( targets, vec![ @@ -1592,7 +1593,7 @@ mod tests { let mut config = Config::test_config()?; config.compiler_metrics_collection_path = Some(metrics_dir.clone()); config.include_default_targets = false; - let env = TestEnvironment::with_config_and_runtime(config)?; + let env = TestEnvironment::builder().config(config).build()?; let crate_ = DUMMY_CRATE_NAME; let version = DUMMY_CRATE_VERSION; @@ -1620,13 +1621,13 @@ mod tests { #[test] #[ignore] fn test_build_binary_crate() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; // some binary crate let crate_ = "heater"; let version = Version::new(0, 2, 3); - let storage = env.storage()?; + let storage = env.blocking_storage()?; let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); storage.store_one(&old_rustdoc_file, Vec::new())?; @@ -1682,7 +1683,7 @@ mod tests { #[test] #[ignore] fn test_failed_build_with_existing_successful_release() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; // rand 0.8.5 fails to build with recent nightly versions // https://github.com/rust-lang/docs.rs/issues/26750 @@ -1779,7 +1780,7 @@ mod tests { #[test_case("thiserror-impl", Version::new(1, 0, 26))] #[ignore] fn test_proc_macro(crate_: &str, version: Version) -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; @@ -1789,7 +1790,7 @@ mod tests { .successful ); - let storage = env.storage()?; + let storage = env.blocking_storage()?; // doc archive exists let doc_archive = rustdoc_archive_path(crate_, &version); @@ -1805,7 +1806,7 @@ mod tests { #[test] #[ignore] fn test_cross_compile_non_host_default() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let crate_ = "windows-win"; let version = Version::new(2, 4, 1); @@ -1820,7 +1821,7 @@ mod tests { .successful ); - let storage = env.storage()?; + let storage = env.blocking_storage()?; // doc archive exists let doc_archive = rustdoc_archive_path(crate_, &version); @@ -1855,7 +1856,7 @@ mod tests { fn test_locked_fails_unlocked_needs_new_deps() -> Result<()> { let mut config = Config::test_config()?; config.include_default_targets = false; - let env = TestEnvironment::with_config_and_runtime(config)?; + let env = TestEnvironment::builder().config(config).build()?; // if the corrected dependency of the crate was already downloaded we need to remove it remove_cache_files(&env, "rand_core", &Version::new(0, 5, 1))?; @@ -1883,7 +1884,7 @@ mod tests { fn test_locked_fails_unlocked_needs_new_unknown_deps() -> Result<()> { let mut config = Config::test_config()?; config.include_default_targets = false; - let env = TestEnvironment::with_config_and_runtime(config)?; + let env = TestEnvironment::builder().config(config).build()?; // if the corrected dependency of the crate was already downloaded we need to remove it remove_cache_files(&env, "value-bag-sval2", &Version::new(1, 4, 1))?; @@ -1906,7 +1907,7 @@ mod tests { #[test] #[ignore] fn test_rustflags_are_passed_to_build_script() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let crate_ = "proc-macro2"; let version = Version::new(1, 0, 95); @@ -1923,7 +1924,7 @@ mod tests { #[test] #[ignore] fn test_sources_are_added_even_for_build_failures_before_build() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; // https://github.com/rust-lang/docs.rs/issues/2523 // package with invalid cargo metadata. @@ -1944,7 +1945,7 @@ mod tests { // source archive exists let source_archive = source_archive_path(crate_, &version); assert!( - env.storage()?.exists(&source_archive)?, + env.blocking_storage()?.exists(&source_archive)?, "archive doesnt exist: {source_archive}" ); @@ -1954,7 +1955,7 @@ mod tests { #[test] #[ignore] fn test_build_failures_before_build() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; // https://github.com/rust-lang/docs.rs/issues/2491 // package without Cargo.toml, so fails directly in the fetch stage. @@ -2000,7 +2001,7 @@ mod tests { #[test] #[ignore] fn test_implicit_features_for_optional_dependencies() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let crate_ = "serde"; let version = Version::new(1, 0, 152); @@ -2025,7 +2026,7 @@ mod tests { #[test] #[ignore] fn test_no_implicit_features_for_optional_dependencies_with_dep_syntax() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; @@ -2054,7 +2055,7 @@ mod tests { #[test] #[ignore] fn test_build_std() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; @@ -2069,7 +2070,7 @@ mod tests { #[test] #[ignore] fn test_workspace_reinitialize_at_once() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; @@ -2087,7 +2088,7 @@ mod tests { fn test_workspace_reinitialize_after_interval() -> Result<()> { let mut config = Config::test_config()?; config.build_workspace_reinitialization_interval = Duration::from_secs(1); - let env = TestEnvironment::with_config_and_runtime(config)?; + let env = TestEnvironment::builder().config(config).build()?; use std::thread::sleep; use std::time::Duration; @@ -2112,7 +2113,7 @@ mod tests { #[test] #[ignore] fn test_new_builder_detects_existing_rustc() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; @@ -2162,7 +2163,7 @@ mod tests { ); } - let env = TestEnvironment::new_with_runtime()?; + let env = TestEnvironment::new()?; let mut builder = env.build_builder()?; builder.update_toolchain()?; diff --git a/crates/bin/docs_rs_builder/src/main.rs b/crates/bin/docs_rs_builder/src/main.rs index b28b38372..9ed3616e7 100644 --- a/crates/bin/docs_rs_builder/src/main.rs +++ b/crates/bin/docs_rs_builder/src/main.rs @@ -1,6 +1,7 @@ use anyhow::{Context as _, Result, anyhow, bail}; use clap::{Parser, Subcommand}; use docs_rs_builder::{Config, PackageKind, RustwideBuilder, queue_builder}; +use docs_rs_config::AppConfig as _; use docs_rs_context::Context; use docs_rs_database::service_config::{ConfigName, get_config}; use docs_rs_env_vars::maybe_env; diff --git a/crates/bin/docs_rs_builder/src/testing/mod.rs b/crates/bin/docs_rs_builder/src/testing/mod.rs index 19d3e6241..698fb4a6a 100644 --- a/crates/bin/docs_rs_builder/src/testing/mod.rs +++ b/crates/bin/docs_rs_builder/src/testing/mod.rs @@ -1,3 +1,3 @@ mod test_env; -pub(crate) use test_env::TestEnvironment; +pub(crate) use test_env::{TestEnvironment, TestEnvironmentExt}; diff --git a/crates/bin/docs_rs_builder/src/testing/test_env.rs b/crates/bin/docs_rs_builder/src/testing/test_env.rs index 8b74e20e1..006f329ed 100644 --- a/crates/bin/docs_rs_builder/src/testing/test_env.rs +++ b/crates/bin/docs_rs_builder/src/testing/test_env.rs @@ -1,92 +1,15 @@ -use crate::{Config as BuilderConfig, docbuilder::rustwide_builder::RustwideBuilder}; -use anyhow::{Context as _, Result}; -use docs_rs_build_queue::BuildQueue; -use docs_rs_context::Context; -use docs_rs_database::{Config as DatabaseConfig, testing::TestDatabase}; -use docs_rs_fastly::Cdn; -use docs_rs_opentelemetry::testing::TestMetrics; -use docs_rs_storage::{Config as StorageConfig, Storage, StorageKind, testing::TestStorage}; -use std::sync::Arc; -use tokio::runtime; +use crate::{Config as BuilderConfig, RustwideBuilder}; +use anyhow::Result; -pub(crate) struct TestEnvironment { - pub(crate) context: Context, - pub(crate) config: Arc, - #[allow(dead_code)] // so we can allow asserting collected metrics later. - pub(crate) metrics: TestMetrics, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) storage: TestStorage, - pub(crate) db: TestDatabase, - pub(crate) runtime: runtime::Runtime, -} - -impl TestEnvironment { - pub(crate) fn new_with_runtime() -> Result { - Self::with_config_and_runtime(BuilderConfig::test_config()?) - } - - pub(crate) fn with_config_and_runtime(config: BuilderConfig) -> Result { - crate::logging::init(); - docs_rs_logging::testing::init(); - - let runtime = runtime::Builder::new_multi_thread() - .enable_all() - .build() - .context("failed to initialize runtime")?; - - let metrics = TestMetrics::new(); - - let db_config = DatabaseConfig::test_config()?; - let db = runtime.block_on(TestDatabase::new(&db_config, metrics.provider()))?; - - let storage_config = Arc::new(StorageConfig::test_config(StorageKind::Memory)?); - let test_storage = runtime.block_on(TestStorage::from_config( - storage_config.clone(), - metrics.provider(), - ))?; - - Ok(Self { - config: Arc::new(config), - context: runtime.block_on(async { - Context::builder() - .with_runtime() - .await? - .with_meter_provider()? - .pool(db_config.into(), db.pool().clone()) - .storage(storage_config.clone(), test_storage.storage()) - .with_build_queue()? - .with_registry_api()? - .with_repository_stats()? - .maybe_cdn( - Arc::new(docs_rs_fastly::Config::test_config()), - Some(Cdn::mock().into()), - ) - .build() - })?, - db, - storage: test_storage, - metrics, - runtime, - }) - } - - pub(crate) fn runtime(&self) -> &runtime::Runtime { - &self.runtime - } - - pub(crate) fn storage(&self) -> Result<&Arc> { - self.context.blocking_storage() - } +pub(crate) type TestEnvironment = docs_rs_context::testing::BlockingTestEnvironment; - pub(crate) fn cdn(&self) -> &Arc { - self.context.cdn().expect("we always have a CDN in tests") - } - - pub(crate) fn blocking_build_queue(&self) -> Result<&Arc> { - self.context.blocking_build_queue() - } +pub(crate) trait TestEnvironmentExt { + fn build_builder(&self) -> Result; +} - pub(crate) fn build_builder(&self) -> Result { - RustwideBuilder::init(self.config.clone(), &self.context) +impl TestEnvironmentExt for TestEnvironment { + fn build_builder(&self) -> Result { + crate::logging::init(); // initialize rustwide logging + RustwideBuilder::init(self.config().clone(), self) } } diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 00c83d879..488eb0084 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -12,6 +12,7 @@ clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "29.0.0", features = [ "max-performance" ]} docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } +docs_rs_config = { path = "../../lib/docs_rs_config" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } @@ -31,6 +32,8 @@ tokio = { workspace = true } tracing = { workspace = true } [dev-dependencies] +docs_rs_config = { path = "../../lib/docs_rs_config", features = ["testing"] } +docs_rs_context = { path = "../../lib/docs_rs_context", features = ["testing"] } docs_rs_database = { path = "../../lib/docs_rs_database", features = ["testing"] } docs_rs_fastly = { path = "../../lib/docs_rs_fastly", features = ["testing"] } docs_rs_rustdoc_json = { path = "../../lib/docs_rs_rustdoc_json" } diff --git a/crates/bin/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs index ba4c07707..da8317e29 100644 --- a/crates/bin/docs_rs_watcher/src/config.rs +++ b/crates/bin/docs_rs_watcher/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, time::Duration}; @@ -18,8 +20,8 @@ pub struct Config { pub repository: docs_rs_repository_stats::Config, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; Ok(Self { registry_index_path: env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?, @@ -33,9 +35,4 @@ impl Config { repository: docs_rs_repository_stats::Config::from_environment()?, }) } - - #[cfg(test)] - pub fn test_config() -> anyhow::Result { - Self::from_environment() - } } diff --git a/crates/bin/docs_rs_watcher/src/consistency/db.rs b/crates/bin/docs_rs_watcher/src/consistency/db.rs index 316dd5a7a..6a63cd130 100644 --- a/crates/bin/docs_rs_watcher/src/consistency/db.rs +++ b/crates/bin/docs_rs_watcher/src/consistency/db.rs @@ -126,7 +126,7 @@ mod tests { .await?; let mut conn = env.async_conn().await?; - let result = load(&mut conn, env.context.config().build_queue()?).await?; + let result = load(&mut conn, env.context().config().build_queue()?).await?; assert_eq!( result, diff --git a/crates/bin/docs_rs_watcher/src/consistency/mod.rs b/crates/bin/docs_rs_watcher/src/consistency/mod.rs index 8e48a54ea..694d4ba63 100644 --- a/crates/bin/docs_rs_watcher/src/consistency/mod.rs +++ b/crates/bin/docs_rs_watcher/src/consistency/mod.rs @@ -185,7 +185,7 @@ mod tests { let diff = [Difference::CrateNotInIndex(KRATE)]; // calling with dry-run leads to no change - handle_diff(&env.context, diff.iter(), true).await?; + handle_diff(&env, diff.iter(), true).await?; assert_eq!( count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, @@ -193,7 +193,7 @@ mod tests { ); // without dry-run the crate will be deleted - handle_diff(&env.context, diff.iter(), false).await?; + handle_diff(&env, diff.iter(), false).await?; assert_eq!( count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, @@ -223,11 +223,11 @@ mod tests { assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); - handle_diff(&env.context, diff.iter(), true).await?; + handle_diff(&env, diff.iter(), true).await?; assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); - handle_diff(&env.context, diff.iter(), false).await?; + handle_diff(&env, diff.iter(), false).await?; assert_eq!( single_row::( @@ -254,14 +254,14 @@ mod tests { let diff = [Difference::ReleaseYank(KRATE, V1, false)]; - handle_diff(&env.context, diff.iter(), true).await?; + handle_diff(&env, diff.iter(), true).await?; assert_eq!( single_row::(&env, "SELECT yanked FROM releases").await?, vec![true] ); - handle_diff(&env.context, diff.iter(), false).await?; + handle_diff(&env, diff.iter(), false).await?; assert_eq!( single_row::(&env, "SELECT yanked FROM releases").await?, @@ -276,13 +276,13 @@ mod tests { let env = TestEnvironment::new().await?; let diff = [Difference::ReleaseNotInDb(KRATE, V1)]; - handle_diff(&env.context, diff.iter(), true).await?; + handle_diff(&env, diff.iter(), true).await?; let build_queue = env.build_queue()?; assert!(build_queue.queued_crates().await?.is_empty()); - handle_diff(&env.context, diff.iter(), false).await?; + handle_diff(&env, diff.iter(), false).await?; assert_eq!( build_queue @@ -301,13 +301,13 @@ mod tests { let env = TestEnvironment::new().await?; let diff = [Difference::CrateNotInDb(KRATE, vec![V1, V2])]; - handle_diff(&env.context, diff.iter(), true).await?; + handle_diff(&env, diff.iter(), true).await?; let build_queue = env.build_queue()?; assert!(build_queue.queued_crates().await?.is_empty()); - handle_diff(&env.context, diff.iter(), false).await?; + handle_diff(&env, diff.iter(), false).await?; assert_eq!( build_queue diff --git a/crates/bin/docs_rs_watcher/src/index_watcher.rs b/crates/bin/docs_rs_watcher/src/index_watcher.rs index 2b83c3507..45de7db60 100644 --- a/crates/bin/docs_rs_watcher/src/index_watcher.rs +++ b/crates/bin/docs_rs_watcher/src/index_watcher.rs @@ -344,7 +344,7 @@ mod tests { ..Default::default() }; - process_version_added(&env.context, &krate, None).await?; + process_version_added(&env, &krate, None).await?; let queue = build_queue.queued_crates().await?; assert_eq!(queue.len(), 1); assert_eq!(queue[0].priority, PRIORITY_DEFAULT); @@ -355,7 +355,7 @@ mod tests { ..Default::default() }; - process_version_added(&env.context, &krate, None).await?; + process_version_added(&env, &krate, None).await?; let queue = build_queue.queued_crates().await?; assert_eq!(queue.len(), 2); // The other queued version should be deprioritized @@ -387,7 +387,7 @@ mod tests { version: V1, yanked: true, }; - process_version_yank_status(&env.context, &krate).await?; + process_version_yank_status(&env, &krate).await?; // And verify it's actually marked as yanked let row = sqlx::query!( @@ -406,7 +406,7 @@ mod tests { version: V1, yanked: false, }; - process_version_yank_status(&env.context, &krate).await?; + process_version_yank_status(&env, &krate).await?; let row = sqlx::query!( "SELECT yanked @@ -433,7 +433,7 @@ mod tests { .create() .await?; - process_crate_deleted(&env.context, &KRATE).await?; + process_crate_deleted(&env, &KRATE).await?; let row = sqlx::query!( "SELECT id @@ -471,7 +471,7 @@ mod tests { version: V2, ..Default::default() }; - process_version_deleted(&env.context, &krate).await?; + process_version_deleted(&env, &krate).await?; let row = sqlx::query!( "SELECT id @@ -517,7 +517,7 @@ mod tests { ..Default::default() }; let added = process_changes( - &env.context, + &env, &vec![ // Should be added correctly Change::Added(krate1.into()), diff --git a/crates/bin/docs_rs_watcher/src/main.rs b/crates/bin/docs_rs_watcher/src/main.rs index d4659781d..beb11516b 100644 --- a/crates/bin/docs_rs_watcher/src/main.rs +++ b/crates/bin/docs_rs_watcher/src/main.rs @@ -1,5 +1,6 @@ use anyhow::{Context as _, Result}; use clap::{Parser, Subcommand}; +use docs_rs_config::AppConfig as _; use docs_rs_context::Context; use docs_rs_types::{KrateName, Version}; use docs_rs_watcher::{Config, Index, index_watcher}; diff --git a/crates/bin/docs_rs_watcher/src/rebuilds.rs b/crates/bin/docs_rs_watcher/src/rebuilds.rs index f84b7a18d..8c52d43a1 100644 --- a/crates/bin/docs_rs_watcher/src/rebuilds.rs +++ b/crates/bin/docs_rs_watcher/src/rebuilds.rs @@ -80,6 +80,7 @@ pub async fn queue_rebuilds( mod tests { use super::*; use crate::testing::TestEnvironment; + use docs_rs_config::AppConfig as _; use docs_rs_test_fakes::FakeBuild; use docs_rs_types::testing::{BAR, BAZ, FOO, V1}; use pretty_assertions::assert_eq; @@ -88,7 +89,7 @@ mod tests { async fn test_rebuild_when_old() -> Result<()> { let mut config = Config::test_config()?; config.max_queued_rebuilds = Some(100); - let env = TestEnvironment::with_config(config).await?; + let env = TestEnvironment::builder().config(config).build().await?; env.fake_release() .await @@ -119,7 +120,7 @@ mod tests { async fn test_still_rebuild_when_full_with_failed() -> Result<()> { let mut config = Config::test_config()?; config.max_queued_rebuilds = Some(1); - let env = TestEnvironment::with_config(config).await?; + let env = TestEnvironment::builder().config(config).build().await?; let build_queue = env.build_queue()?; build_queue @@ -157,7 +158,7 @@ mod tests { async fn test_dont_rebuild_when_full() -> Result<()> { let mut config = Config::test_config()?; config.max_queued_rebuilds = Some(1); - let env = TestEnvironment::with_config(config).await?; + let env = TestEnvironment::builder().config(config).build().await?; let build_queue = env.build_queue()?; build_queue diff --git a/crates/bin/docs_rs_watcher/src/testing/test_env.rs b/crates/bin/docs_rs_watcher/src/testing/test_env.rs index fbe314b26..139d50f06 100644 --- a/crates/bin/docs_rs_watcher/src/testing/test_env.rs +++ b/crates/bin/docs_rs_watcher/src/testing/test_env.rs @@ -1,83 +1,3 @@ use crate::Config as WatcherConfig; -use anyhow::Result; -use docs_rs_build_queue::AsyncBuildQueue; -use docs_rs_context::Context; -use docs_rs_database::{AsyncPoolClient, Config as DatabaseConfig, testing::TestDatabase}; -use docs_rs_fastly::Cdn; -use docs_rs_opentelemetry::testing::TestMetrics; -use docs_rs_storage::{AsyncStorage, Config as StorageConfig, StorageKind, testing::TestStorage}; -use docs_rs_test_fakes::FakeRelease; -use std::sync::Arc; -pub(crate) struct TestEnvironment { - pub(crate) context: Context, - pub(crate) config: Arc, - #[allow(dead_code)] // so we can allow asserting collected metrics later. - pub(crate) metrics: TestMetrics, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) storage: TestStorage, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) db: TestDatabase, -} - -impl TestEnvironment { - pub(crate) async fn new() -> Result { - Self::with_config(WatcherConfig::test_config()?).await - } - - pub(crate) async fn with_config(config: WatcherConfig) -> Result { - docs_rs_logging::testing::init(); - - let metrics = TestMetrics::new(); - - let db_config = DatabaseConfig::test_config()?; - let db = TestDatabase::new(&db_config, metrics.provider()).await?; - - let storage_config = Arc::new(StorageConfig::test_config(StorageKind::Memory)?); - let test_storage = - TestStorage::from_config(storage_config.clone(), metrics.provider()).await?; - - Ok(Self { - config: Arc::new(config), - context: Context::builder() - .with_runtime() - .await? - .with_meter_provider()? - .pool(db_config.into(), db.pool().clone()) - .storage(storage_config.clone(), test_storage.storage()) - .with_build_queue()? - .maybe_cdn( - docs_rs_fastly::Config::from_environment()?.into(), - Some(Cdn::mock().into()), - ) - .with_repository_stats()? - .build()?, - db, - storage: test_storage, - metrics, - }) - } - - pub(crate) fn config(&self) -> &WatcherConfig { - &self.config - } - - pub(crate) fn build_queue(&self) -> Result<&Arc> { - self.context.build_queue() - } - - pub(crate) async fn async_conn(&self) -> Result { - self.context.pool()?.get_async().await.map_err(Into::into) - } - - pub(crate) fn storage(&self) -> Result<&Arc> { - self.context.storage() - } - - pub async fn fake_release(&self) -> FakeRelease<'_> { - FakeRelease::new( - self.context.pool().unwrap().clone(), - self.context.storage().unwrap().clone(), - ) - } -} +pub(crate) type TestEnvironment = docs_rs_context::testing::TestEnvironment; diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index c677a6f28..d210beb18 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -23,6 +23,7 @@ derive_more = { workspace = true } docs_rs_build_limits = { path = "../../lib/docs_rs_build_limits" } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_config = { path = "../../lib/docs_rs_config" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } @@ -76,6 +77,7 @@ time = "0.3" walkdir = { workspace = true } [dev-dependencies] +docs_rs_config = { path = "../../lib/docs_rs_config", features = ["testing"] } docs_rs_context = { path = "../../lib/docs_rs_context", features = ["testing"] } docs_rs_database = { path = "../../lib/docs_rs_database", features = ["testing"] } docs_rs_headers = { path = "../../lib/docs_rs_headers", features = ["testing"] } diff --git a/crates/bin/docs_rs_web/src/cache.rs b/crates/bin/docs_rs_web/src/cache.rs index 2ee857840..e8ee61031 100644 --- a/crates/bin/docs_rs_web/src/cache.rs +++ b/crates/bin/docs_rs_web/src/cache.rs @@ -257,6 +257,7 @@ mod tests { use anyhow::{Context as _, Result}; use axum::{Router, body::Body, routing::get}; use axum_extra::headers::CacheControl; + use docs_rs_config::AppConfig as _; use http::Request; use test_case::{test_case, test_matrix}; use tower::{ServiceBuilder, ServiceExt as _}; diff --git a/crates/bin/docs_rs_web/src/config.rs b/crates/bin/docs_rs_web/src/config.rs index 9ce19d1e3..82d77c049 100644 --- a/crates/bin/docs_rs_web/src/config.rs +++ b/crates/bin/docs_rs_web/src/config.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::maybe_env; use std::time::Duration; @@ -82,13 +83,13 @@ impl ConfigBuilder { } } -impl Config { - pub fn from_environment() -> Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self::builder().load_environment()?.build()) } #[cfg(test)] - pub fn test_config() -> Result { + fn test_config() -> Result { Ok(Self::builder().test_config()?.build()) } } diff --git a/crates/bin/docs_rs_web/src/error.rs b/crates/bin/docs_rs_web/src/error.rs index eed4df308..080bb3e5e 100644 --- a/crates/bin/docs_rs_web/src/error.rs +++ b/crates/bin/docs_rs_web/src/error.rs @@ -247,7 +247,9 @@ pub(crate) type JsonAxumResult = Result; mod tests { use super::{AxumNope, EscapedURI, IntoResponse}; use crate::cache::CachePolicy; - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper, + }; use kuchikiki::traits::TendrilSink; #[test] diff --git a/crates/bin/docs_rs_web/src/file.rs b/crates/bin/docs_rs_web/src/file.rs index 00aee22b1..ab886070c 100644 --- a/crates/bin/docs_rs_web/src/file.rs +++ b/crates/bin/docs_rs_web/src/file.rs @@ -249,11 +249,13 @@ mod tests { let env = Rc::new( TestEnvironment::builder() .storage_config( - docs_rs_storage::Config::test_config(StorageKind::Memory)?.set(|mut cfg| { - cfg.max_file_size = MAX_SIZE; - cfg.max_file_size_html = MAX_HTML_SIZE; - cfg - }), + docs_rs_storage::Config::test_config_with_kind(StorageKind::Memory)?.set( + |mut cfg| { + cfg.max_file_size = MAX_SIZE; + cfg.max_file_size_html = MAX_HTML_SIZE; + cfg + }, + ), ) .build() .await?, diff --git a/crates/bin/docs_rs_web/src/handlers/build_details.rs b/crates/bin/docs_rs_web/src/handlers/build_details.rs index 17fc4907b..86c235d4a 100644 --- a/crates/bin/docs_rs_web/src/handlers/build_details.rs +++ b/crates/bin/docs_rs_web/src/handlers/build_details.rs @@ -182,7 +182,10 @@ pub(crate) async fn build_details_handler( #[cfg(test)] mod tests { - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, async_wrapper}; + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, TestEnvironmentExt as _, + async_wrapper, + }; use docs_rs_test_fakes::{FakeBuild, fake_release_that_failed_before_build}; use docs_rs_types::{BuildId, ReleaseId, testing::V0_1}; use kuchikiki::traits::TendrilSink; diff --git a/crates/bin/docs_rs_web/src/handlers/builds.rs b/crates/bin/docs_rs_web/src/handlers/builds.rs index 6b96de34a..70d3aba22 100644 --- a/crates/bin/docs_rs_web/src/handlers/builds.rs +++ b/crates/bin/docs_rs_web/src/handlers/builds.rs @@ -215,7 +215,10 @@ mod tests { use crate::{ Config, cache::CachePolicy, - testing::{AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, async_wrapper}, + testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, TestEnvironmentExt as _, + async_wrapper, + }, }; use anyhow::Result; use axum::{body::Body, http::Request}; @@ -308,7 +311,7 @@ mod tests { #[tokio::test(flavor = "multi_thread")] async fn build_trigger_rebuild_missing_config() -> Result<()> { let env = TestEnvironment::builder() - .web_config( + .config( Config::builder() .test_config()? .maybe_cratesio_token(None) @@ -358,7 +361,7 @@ mod tests { async fn build_trigger_rebuild_with_config() -> Result<()> { let correct_token = "foo137"; let env = TestEnvironment::builder() - .web_config( + .config( Config::builder() .test_config()? .cratesio_token(correct_token.into()) diff --git a/crates/bin/docs_rs_web/src/handlers/crate_details.rs b/crates/bin/docs_rs_web/src/handlers/crate_details.rs index 288d46775..5e7884661 100644 --- a/crates/bin/docs_rs_web/src/handlers/crate_details.rs +++ b/crates/bin/docs_rs_web/src/handlers/crate_details.rs @@ -634,11 +634,13 @@ pub(crate) async fn get_all_platforms( #[cfg(test)] mod tests { use super::*; - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, async_wrapper}; - use anyhow::Error; - use docs_rs_database::{ - crate_details::releases_for_crate, releases::update_build_status, testing::TestDatabase, + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, TestEnvironmentExt as _, + async_wrapper, }; + use anyhow::Error; + use docs_rs_database::Pool; + use docs_rs_database::{crate_details::releases_for_crate, releases::update_build_status}; use docs_rs_registry_api::CrateOwner; use docs_rs_test_fakes::{FakeBuild, fake_release_that_failed_before_build}; use docs_rs_types::KrateName; @@ -709,13 +711,13 @@ mod tests { } async fn assert_last_successful_build_equals( - db: &TestDatabase, + pool: &Pool, package: &str, version: &str, expected_last_successful_build: Option, ) -> Result<(), Error> { let version = version.parse::()?; - let mut conn = db.async_conn().await?; + let mut conn = pool.get_async().await?; let details = crate_details(&mut conn, package, version, None).await; anyhow::ensure!( @@ -730,8 +732,7 @@ mod tests { #[test] fn test_crate_details_documentation_url_is_none_when_url_is_docs_rs() { async_wrapper(|env| async move { - let db = &env.db; - let mut conn = db.async_conn().await?; + let mut conn = env.async_conn().await?; env.fake_release() .await @@ -773,7 +774,7 @@ mod tests { #[test] fn test_last_successful_build_when_last_releases_failed_or_yanked() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -824,7 +825,7 @@ mod tests { #[test] fn test_last_successful_build_when_all_releases_failed_or_yanked() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -858,7 +859,7 @@ mod tests { #[test] fn test_last_successful_build_with_intermittent_releases_failed_or_yanked() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -899,7 +900,7 @@ mod tests { #[test] fn test_releases_should_be_sorted() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; // Add new releases of 'foo' out-of-order since CrateDetails should sort them descending env.fake_release() @@ -955,7 +956,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; let mut details = crate_details(&mut conn, "foo", "0.2.0", None).await; for detail in &mut details.releases { detail.release_time = None; @@ -1108,7 +1109,7 @@ mod tests { #[test] fn test_latest_version() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1129,7 +1130,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; for version in &["0.0.1", "0.0.2", "0.0.3"] { let details = crate_details(&mut conn, "foo", *version, None).await; assert_eq!( @@ -1145,7 +1146,7 @@ mod tests { #[test] fn test_latest_version_ignores_prerelease() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1166,7 +1167,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; for &version in &["0.0.1", "0.0.2", "0.0.3-pre.1"] { let details = crate_details(&mut conn, "foo", version, None).await; assert_eq!( @@ -1182,7 +1183,7 @@ mod tests { #[test] fn test_latest_version_ignores_yanked() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1204,7 +1205,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; for &version in &["0.0.1", "0.0.2", "0.0.3"] { let details = crate_details(&mut conn, "foo", version, None).await; assert_eq!( @@ -1220,7 +1221,7 @@ mod tests { #[test] fn test_latest_version_only_yanked() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1244,7 +1245,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; for &version in &["0.0.1", "0.0.2", "0.0.3"] { let details = crate_details(&mut conn, "foo", version, None).await; assert_eq!( @@ -1260,7 +1261,7 @@ mod tests { #[test] fn test_latest_version_in_progress() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1278,7 +1279,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; for &version in &["0.0.1", "0.0.2"] { let details = crate_details(&mut conn, "foo", version, None).await; assert_eq!( @@ -1367,7 +1368,7 @@ mod tests { #[test] fn test_updating_owners() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; env.fake_release() .await @@ -1381,7 +1382,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; let details = crate_details(&mut conn, "foo", "0.0.1", None).await; assert_eq!( details.owners, @@ -1442,7 +1443,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; let details = crate_details(&mut conn, "foo", "0.0.1", None).await; assert_eq!( details.owners, @@ -1466,7 +1467,7 @@ mod tests { .create() .await?; - let mut conn = db.async_conn().await?; + let mut conn = db.get_async().await?; let details = crate_details(&mut conn, "foo", "0.0.1", None).await; assert_eq!( details.owners, diff --git a/crates/bin/docs_rs_web/src/handlers/features.rs b/crates/bin/docs_rs_web/src/handlers/features.rs index ebf753f5e..255fe4c73 100644 --- a/crates/bin/docs_rs_web/src/handlers/features.rs +++ b/crates/bin/docs_rs_web/src/handlers/features.rs @@ -274,7 +274,9 @@ fn get_sorted_features(raw_features: Vec) -> (Vec, HashSet Result<()> { let env = TestEnvironment::builder() - .web_config( + .config( Config::builder() .test_config()? .cache_control_stale_while_revalidate(2592000) diff --git a/crates/bin/docs_rs_web/src/handlers/sitemap.rs b/crates/bin/docs_rs_web/src/handlers/sitemap.rs index f45ba88f4..7c60e485b 100644 --- a/crates/bin/docs_rs_web/src/handlers/sitemap.rs +++ b/crates/bin/docs_rs_web/src/handlers/sitemap.rs @@ -220,7 +220,9 @@ pub(crate) async fn about_handler(subpage: Option>) -> AxumResult Result<()> { let env = TestEnvironment::builder() .storage_config( - docs_rs_storage::Config::test_config(StorageKind::Memory)?.set(|mut cfg| { - cfg.max_file_size = 1; - cfg.max_file_size_html = 1; - cfg - }), + docs_rs_storage::Config::test_config_with_kind(StorageKind::Memory)?.set( + |mut cfg| { + cfg.max_file_size = 1; + cfg.max_file_size_html = 1; + cfg + }, + ), ) .build() .await?; diff --git a/crates/bin/docs_rs_web/src/handlers/statics.rs b/crates/bin/docs_rs_web/src/handlers/statics.rs index df8aefcfe..0c4abbb6a 100644 --- a/crates/bin/docs_rs_web/src/handlers/statics.rs +++ b/crates/bin/docs_rs_web/src/handlers/statics.rs @@ -132,7 +132,9 @@ pub(crate) fn build_static_router() -> AxumRouter { #[cfg(test)] mod tests { use super::*; - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper, + }; use axum::{Router, body::Body}; use docs_rs_headers::compute_etag; use http::{ diff --git a/crates/bin/docs_rs_web/src/handlers/status.rs b/crates/bin/docs_rs_web/src/handlers/status.rs index 085db4409..ac560a4c2 100644 --- a/crates/bin/docs_rs_web/src/handlers/status.rs +++ b/crates/bin/docs_rs_web/src/handlers/status.rs @@ -52,7 +52,7 @@ pub(crate) async fn status_handler( mod tests { use crate::{ cache::CachePolicy, - testing::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}, + testing::{AxumResponseTestExt, AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper}, }; use docs_rs_types::ReqVersion; use reqwest::StatusCode; diff --git a/crates/bin/docs_rs_web/src/main.rs b/crates/bin/docs_rs_web/src/main.rs index 64a9c4fe4..7e604ad4f 100644 --- a/crates/bin/docs_rs_web/src/main.rs +++ b/crates/bin/docs_rs_web/src/main.rs @@ -1,5 +1,6 @@ use anyhow::Context as _; use clap::Parser; +use docs_rs_config::AppConfig as _; use docs_rs_web::{Config, run_web_server}; use std::{net::SocketAddr, sync::Arc}; diff --git a/crates/bin/docs_rs_web/src/match_release.rs b/crates/bin/docs_rs_web/src/match_release.rs index 3e8036d6c..984055fdb 100644 --- a/crates/bin/docs_rs_web/src/match_release.rs +++ b/crates/bin/docs_rs_web/src/match_release.rs @@ -252,7 +252,7 @@ pub(crate) async fn match_version( mod tests { use super::*; use crate::testing::{TestEnvironment, async_wrapper}; - use docs_rs_database::testing::TestDatabase; + use docs_rs_database::Pool; use docs_rs_test_fakes::FakeBuild; use docs_rs_types::ReleaseId; use std::str::FromStr as _; @@ -268,8 +268,8 @@ mod tests { .unwrap() } - async fn version(v: Option<&str>, db: &TestDatabase) -> Option { - let mut conn = db.async_conn().await.unwrap(); + async fn version(v: Option<&str>, pool: &Pool) -> Option { + let mut conn = pool.get_async().await.unwrap(); let version = match_version( &mut conn, "foo", @@ -297,7 +297,7 @@ mod tests { // https://github.com/rust-lang/docs.rs/issues/223 fn prereleases_are_not_considered_for_semver() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; let version = |v| version(v, db); let release = |v| release(v, &env); @@ -325,7 +325,7 @@ mod tests { // https://github.com/rust-lang/docs.rs/issues/1682 fn prereleases_are_considered_when_others_dont_match() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; // normal release release("1.0.0", &env).await; @@ -350,7 +350,7 @@ mod tests { // vaguely related to https://github.com/rust-lang/docs.rs/issues/395 fn metadata_has_no_effect() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; release("0.1.0+4.1", &env).await; release("0.1.1", &env).await; @@ -370,7 +370,7 @@ mod tests { #[test] fn in_progress_releases_are_ignored_when_others_match() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; // normal release release("1.0.0", &env).await; @@ -400,7 +400,7 @@ mod tests { // https://github.com/rust-lang/docs.rs/issues/221 fn yanked_crates_are_not_considered() { async_wrapper(|env| async move { - let db = &env.db; + let db = env.pool()?; let release_id = release("0.3.0", &env).await; @@ -408,7 +408,7 @@ mod tests { "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'", release_id.0 ) - .execute(&mut *db.async_conn().await?) + .execute(&mut *db.get_async().await?) .await?; assert_eq!(version(None, db).await, None); diff --git a/crates/bin/docs_rs_web/src/metrics.rs b/crates/bin/docs_rs_web/src/metrics.rs index 62c003ce9..6913395d3 100644 --- a/crates/bin/docs_rs_web/src/metrics.rs +++ b/crates/bin/docs_rs_web/src/metrics.rs @@ -118,7 +118,7 @@ pub(crate) async fn request_recorder( #[cfg(test)] mod tests { - use crate::testing::{AxumRouterTestExt, async_wrapper}; + use crate::testing::{AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper}; use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; use pretty_assertions::assert_eq; use std::collections::HashMap; diff --git a/crates/bin/docs_rs_web/src/routes.rs b/crates/bin/docs_rs_web/src/routes.rs index d6ee259a8..35725c366 100644 --- a/crates/bin/docs_rs_web/src/routes.rs +++ b/crates/bin/docs_rs_web/src/routes.rs @@ -355,7 +355,9 @@ async fn fallback() -> impl IntoResponse { #[cfg(test)] mod tests { use crate::cache::CachePolicy; - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper, + }; use reqwest::StatusCode; #[test] diff --git a/crates/bin/docs_rs_web/src/testing/mod.rs b/crates/bin/docs_rs_web/src/testing/mod.rs index b547cdccf..66f9ccc34 100644 --- a/crates/bin/docs_rs_web/src/testing/mod.rs +++ b/crates/bin/docs_rs_web/src/testing/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod headers; mod test_env; pub(crate) use axum_helpers::{AxumResponseTestExt, AxumRouterTestExt, assert_cache_headers_eq}; -pub(crate) use test_env::TestEnvironment; +pub(crate) use test_env::{TestEnvironment, TestEnvironmentExt}; use std::rc::Rc; use tokio::runtime; diff --git a/crates/bin/docs_rs_web/src/testing/test_env.rs b/crates/bin/docs_rs_web/src/testing/test_env.rs index 725e884d2..aada5c291 100644 --- a/crates/bin/docs_rs_web/src/testing/test_env.rs +++ b/crates/bin/docs_rs_web/src/testing/test_env.rs @@ -1,123 +1,18 @@ use crate::{Config as WebConfig, handlers::build_axum_app, page::TemplateData}; -use anyhow::Result; use axum::Router; -use bon::bon; -use docs_rs_build_queue::AsyncBuildQueue; -use docs_rs_context::Context; -use docs_rs_database::{AsyncPoolClient, Config as DatabaseConfig, testing::TestDatabase}; -use docs_rs_opentelemetry::testing::{CollectedMetrics, TestMetrics}; -use docs_rs_registry_api::RegistryApi; -use docs_rs_storage::{AsyncStorage, Config as StorageConfig, StorageKind, testing::TestStorage}; -use docs_rs_test_fakes::FakeRelease; use std::sync::Arc; -pub(crate) struct TestEnvironment { - pub(crate) context: Arc, - pub(crate) config: Arc, - // so we can allow asserting collected metrics later. - pub(crate) metrics: TestMetrics, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) storage: TestStorage, - #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. - pub(crate) db: TestDatabase, -} - -#[bon] -impl TestEnvironment { - pub(crate) async fn new() -> Result { - // NOTE: compiler crashes when I change the return to - // `Self::builder().build().await - #[allow(clippy::needless_question_mark)] - Ok(Self::builder().build().await?) - } - - #[builder(finish_fn = build)] - pub(crate) async fn builder( - web_config: Option, - registry_api_config: Option, - storage_config: Option, - ) -> Result { - docs_rs_logging::testing::init(); - - let web_config = Arc::new(if let Some(web_config) = web_config { - web_config - } else { - WebConfig::test_config()? - }); - - let registry_api_config = - Arc::new(if let Some(registry_api_config) = registry_api_config { - registry_api_config - } else { - docs_rs_registry_api::Config::from_environment()? - }); - - let registry_api = RegistryApi::from_config(®istry_api_config)?; - - let metrics = TestMetrics::new(); - - let db_config = DatabaseConfig::test_config()?; - let db = TestDatabase::new(&db_config, metrics.provider()).await?; - - let storage_config = Arc::new(if let Some(storage_config) = storage_config { - storage_config - } else { - StorageConfig::test_config(StorageKind::Memory)? - }); - - let test_storage = - TestStorage::from_config(storage_config.clone(), metrics.provider()).await?; - - Ok(Self { - config: web_config, - context: Context::builder() - .with_runtime() - .await? - .meter_provider(metrics.provider().clone()) - .pool(db_config.into(), db.pool().clone()) - .storage(storage_config.clone(), test_storage.storage()) - .with_build_queue()? - .registry_api(registry_api_config, registry_api.into()) - .with_build_limits()? - .build()? - .into(), - db, - storage: test_storage, - metrics, - }) - } +pub(crate) type TestEnvironment = docs_rs_context::testing::TestEnvironment; - pub(crate) fn config(&self) -> &WebConfig { - &self.config - } - - pub(crate) fn build_queue(&self) -> Result<&Arc> { - self.context.build_queue() - } - - pub(crate) async fn async_conn(&self) -> Result { - self.context.pool()?.get_async().await.map_err(Into::into) - } - - pub(crate) fn storage(&self) -> Result<&Arc> { - self.context.storage() - } +pub(crate) trait TestEnvironmentExt { + async fn web_app(&self) -> Router; +} - pub(crate) async fn web_app(&self) -> Router { +impl TestEnvironmentExt for TestEnvironment { + async fn web_app(&self) -> Router { let template_data = Arc::new(TemplateData::new(1).unwrap()); - build_axum_app(self.config.clone(), self.context.clone(), template_data) + build_axum_app(self.config().clone(), self.context().clone(), template_data) .await .expect("could not build axum app") } - - pub async fn fake_release(&self) -> FakeRelease<'_> { - FakeRelease::new( - self.context.pool().unwrap().clone(), - self.context.storage().unwrap().clone(), - ) - } - - pub fn collected_metrics(&self) -> CollectedMetrics { - self.metrics.collected_metrics() - } } diff --git a/crates/bin/docs_rs_web/src/utils/html_rewrite.rs b/crates/bin/docs_rs_web/src/utils/html_rewrite.rs index 969d76b0e..307f10a58 100644 --- a/crates/bin/docs_rs_web/src/utils/html_rewrite.rs +++ b/crates/bin/docs_rs_web/src/utils/html_rewrite.rs @@ -229,7 +229,9 @@ where #[cfg(test)] mod test { - use crate::testing::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; + use crate::testing::{ + AxumResponseTestExt, AxumRouterTestExt, TestEnvironmentExt as _, async_wrapper, + }; use docs_rs_types::testing::V1; #[test] diff --git a/crates/lib/docs_rs_build_limits/Cargo.toml b/crates/lib/docs_rs_build_limits/Cargo.toml index 5bc092a05..d05f2db5b 100644 --- a/crates/lib/docs_rs_build_limits/Cargo.toml +++ b/crates/lib/docs_rs_build_limits/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_types = { path = "../docs_rs_types" } futures-util = { workspace = true } diff --git a/crates/lib/docs_rs_build_limits/src/blacklist.rs b/crates/lib/docs_rs_build_limits/src/blacklist.rs index da81aba50..445e8021b 100644 --- a/crates/lib/docs_rs_build_limits/src/blacklist.rs +++ b/crates/lib/docs_rs_build_limits/src/blacklist.rs @@ -78,6 +78,7 @@ pub async fn remove_crate(conn: &mut sqlx::PgConnection, name: &KrateName) -> Re mod tests { use super::*; use anyhow::Result; + use docs_rs_config::AppConfig as _; use docs_rs_database::testing::TestDatabase; use docs_rs_opentelemetry::testing::TestMetrics; use docs_rs_types::testing::{BAR, BAZ, FOO}; diff --git a/crates/lib/docs_rs_build_limits/src/config.rs b/crates/lib/docs_rs_build_limits/src/config.rs index 3ff6f7857..b8b71bbe5 100644 --- a/crates/lib/docs_rs_build_limits/src/config.rs +++ b/crates/lib/docs_rs_build_limits/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::maybe_env; #[derive(Debug, Default)] @@ -5,8 +7,8 @@ pub struct Config { pub(crate) build_default_memory_limit: Option, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self { build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, }) diff --git a/crates/lib/docs_rs_build_limits/src/limits.rs b/crates/lib/docs_rs_build_limits/src/limits.rs index 384b01e25..e2ba418aa 100644 --- a/crates/lib/docs_rs_build_limits/src/limits.rs +++ b/crates/lib/docs_rs_build_limits/src/limits.rs @@ -73,6 +73,7 @@ impl Limits { #[cfg(test)] mod test { use super::*; + use docs_rs_config::AppConfig as _; use docs_rs_database::testing::TestDatabase; use docs_rs_opentelemetry::testing::TestMetrics; use docs_rs_types::testing::KRATE; diff --git a/crates/lib/docs_rs_build_limits/src/overrides.rs b/crates/lib/docs_rs_build_limits/src/overrides.rs index 3ede7b147..661550706 100644 --- a/crates/lib/docs_rs_build_limits/src/overrides.rs +++ b/crates/lib/docs_rs_build_limits/src/overrides.rs @@ -106,6 +106,7 @@ impl Overrides { #[cfg(test)] mod test { use super::*; + use docs_rs_config::AppConfig as _; use docs_rs_database::testing::TestDatabase; use docs_rs_opentelemetry::testing::TestMetrics; use std::time::Duration; diff --git a/crates/lib/docs_rs_build_queue/Cargo.toml b/crates/lib/docs_rs_build_queue/Cargo.toml index bceb7e1d7..5e33623e6 100644 --- a/crates/lib/docs_rs_build_queue/Cargo.toml +++ b/crates/lib/docs_rs_build_queue/Cargo.toml @@ -8,6 +8,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } chrono = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } @@ -27,6 +28,7 @@ pretty_assertions = { workspace = true } [features] testing = [ + "docs_rs_config/testing", "docs_rs_database/testing", "docs_rs_opentelemetry/testing", "docs_rs_types/testing", diff --git a/crates/lib/docs_rs_build_queue/src/config.rs b/crates/lib/docs_rs_build_queue/src/config.rs index e3ec73c6d..4263df6db 100644 --- a/crates/lib/docs_rs_build_queue/src/config.rs +++ b/crates/lib/docs_rs_build_queue/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::maybe_env; use std::time::Duration; @@ -16,8 +18,8 @@ impl Default for Config { } } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { let mut config = Self::default(); if let Some(attempts) = maybe_env::("DOCSRS_BUILD_ATTEMPTS")? { diff --git a/crates/lib/docs_rs_build_queue/src/priority.rs b/crates/lib/docs_rs_build_queue/src/priority.rs index bd35aadf0..ab26c11d2 100644 --- a/crates/lib/docs_rs_build_queue/src/priority.rs +++ b/crates/lib/docs_rs_build_queue/src/priority.rs @@ -73,6 +73,7 @@ pub async fn remove_crate_priority( #[cfg(test)] mod tests { use super::*; + use docs_rs_config::AppConfig as _; use docs_rs_database::{Config, testing::TestDatabase}; use docs_rs_opentelemetry::testing::TestMetrics; diff --git a/crates/lib/docs_rs_build_queue/src/queue/blocking.rs b/crates/lib/docs_rs_build_queue/src/queue/blocking.rs index 3f5656dc5..992376bd1 100644 --- a/crates/lib/docs_rs_build_queue/src/queue/blocking.rs +++ b/crates/lib/docs_rs_build_queue/src/queue/blocking.rs @@ -164,7 +164,8 @@ mod tests { use super::*; use chrono::Utc; - use docs_rs_database::testing::TestDatabase; + use docs_rs_config::AppConfig as _; + use docs_rs_database::{AsyncPoolClient, testing::TestDatabase}; use docs_rs_opentelemetry::testing::TestMetrics; use docs_rs_types::testing::{KRATE, V1, V2}; use docs_rs_utils::block_on_async_with_conn; @@ -189,6 +190,10 @@ mod tests { pub(crate) fn runtime(&self) -> &runtime::Runtime { &self.runtime } + + pub async fn async_conn(&self) -> Result { + self.db.async_conn().await + } } fn test_queue(config: Config) -> Result { diff --git a/crates/lib/docs_rs_build_queue/src/queue/non_blocking.rs b/crates/lib/docs_rs_build_queue/src/queue/non_blocking.rs index b1cd3d8cc..765f9208e 100644 --- a/crates/lib/docs_rs_build_queue/src/queue/non_blocking.rs +++ b/crates/lib/docs_rs_build_queue/src/queue/non_blocking.rs @@ -238,6 +238,7 @@ impl AsyncBuildQueue { #[cfg(test)] mod tests { use super::*; + use docs_rs_config::AppConfig as _; use docs_rs_database::testing::TestDatabase; use docs_rs_opentelemetry::testing::TestMetrics; use docs_rs_types::testing::{KRATE, V1, V2}; diff --git a/crates/lib/docs_rs_config/Cargo.toml b/crates/lib/docs_rs_config/Cargo.toml new file mode 100644 index 000000000..753249bff --- /dev/null +++ b/crates/lib/docs_rs_config/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "docs_rs_config" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } + +[features] +testing = [] + diff --git a/crates/lib/docs_rs_config/src/lib.rs b/crates/lib/docs_rs_config/src/lib.rs new file mode 100644 index 000000000..17dac70c8 --- /dev/null +++ b/crates/lib/docs_rs_config/src/lib.rs @@ -0,0 +1,13 @@ +use anyhow::Result; + +/// The main config trait for an application or library config. +/// +/// Used across our various binary or library crates. +pub trait AppConfig: Sized { + fn from_environment() -> Result; + + #[cfg(feature = "testing")] + fn test_config() -> Result { + Self::from_environment() + } +} diff --git a/crates/lib/docs_rs_context/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json b/crates/lib/docs_rs_context/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json new file mode 100644 index 000000000..f13d5b020 --- /dev/null +++ b/crates/lib/docs_rs_context/.sqlx/query-007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE builds SET output = $2 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "007a6d355f893370b544dda0d6129096357aa95c67c76905a6709a66247450ff" +} diff --git a/crates/lib/docs_rs_context/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json b/crates/lib/docs_rs_context/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json new file mode 100644 index 000000000..f1374e312 --- /dev/null +++ b/crates/lib/docs_rs_context/.sqlx/query-1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO repositories (host, host_id, name, description, last_commit, stars, forks, issues, updated_at)\n VALUES ('github.com', $1, $2, 'Fake description!', NOW(), $3, $4, $5, NOW())\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Int4", + "Int4", + "Int4" + ] + }, + "nullable": [ + false + ] + }, + "hash": "1002ada46a8b06269d7aa42acc52e90e944d9009d75ad527e7442e312be98ea9" +} diff --git a/crates/lib/docs_rs_context/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json b/crates/lib/docs_rs_context/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json new file mode 100644 index 000000000..60e1078e6 --- /dev/null +++ b/crates/lib/docs_rs_context/.sqlx/query-75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE builds\n SET\n build_status = 'failure',\n errors = $2\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Text" + ] + }, + "nullable": [] + }, + "hash": "75aa15d4c8898403835cdaf3d27c9973a3f45abc9cf73e171011a2ded4d97dfc" +} diff --git a/crates/lib/docs_rs_context/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json b/crates/lib/docs_rs_context/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json new file mode 100644 index 000000000..80e54a1b7 --- /dev/null +++ b/crates/lib/docs_rs_context/.sqlx/query-915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM repositories", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "915642dcaea4c90bb15b7ad4c6b3889c846d64fa11463719e65496b950ae039f" +} diff --git a/crates/lib/docs_rs_context/.sqlx/query-96b68919f9016705a1a36ef11a5a659e7fb431beb0017fbcfd21132f105ce722.json b/crates/lib/docs_rs_context/.sqlx/query-96b68919f9016705a1a36ef11a5a659e7fb431beb0017fbcfd21132f105ce722.json new file mode 100644 index 000000000..984eff3ac --- /dev/null +++ b/crates/lib/docs_rs_context/.sqlx/query-96b68919f9016705a1a36ef11a5a659e7fb431beb0017fbcfd21132f105ce722.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT relname\n FROM pg_class\n INNER JOIN pg_namespace ON\n pg_class.relnamespace = pg_namespace.oid\n WHERE pg_class.relkind = 'S'\n AND pg_namespace.nspname = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "relname", + "type_info": "Name" + } + ], + "parameters": { + "Left": [ + "Name" + ] + }, + "nullable": [ + false + ] + }, + "hash": "96b68919f9016705a1a36ef11a5a659e7fb431beb0017fbcfd21132f105ce722" +} diff --git a/crates/lib/docs_rs_context/Cargo.toml b/crates/lib/docs_rs_context/Cargo.toml index 7c5c1f759..f2a8346a6 100644 --- a/crates/lib/docs_rs_context/Cargo.toml +++ b/crates/lib/docs_rs_context/Cargo.toml @@ -10,13 +10,23 @@ anyhow = { workspace = true } bon = { workspace = true } docs_rs_build_limits = { path = "../docs_rs_build_limits" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_fastly = { path = "../docs_rs_fastly" } +docs_rs_logging = { path = "../docs_rs_logging", optional = true } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_registry_api = { path = "../docs_rs_registry_api" } docs_rs_repository_stats = { path = "../docs_rs_repository_stats" } docs_rs_storage = { path = "../docs_rs_storage" } +docs_rs_test_fakes = { path = "../docs_rs_test_fakes", optional = true } tokio = { workspace = true } [features] -testing = [] +testing = [ + "dep:docs_rs_logging", + "dep:docs_rs_test_fakes", + "docs_rs_database/testing", + "docs_rs_fastly/testing", + "docs_rs_logging/testing", + "docs_rs_storage/testing", +] diff --git a/crates/lib/docs_rs_context/src/context.rs b/crates/lib/docs_rs_context/src/context.rs index ee7e4036f..e2d7d6c51 100644 --- a/crates/lib/docs_rs_context/src/context.rs +++ b/crates/lib/docs_rs_context/src/context.rs @@ -1,6 +1,7 @@ use crate::config::Config; use anyhow::{Result, anyhow, bail}; use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; +use docs_rs_config::AppConfig as _; use docs_rs_database::Pool; use docs_rs_fastly::Cdn; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; diff --git a/crates/lib/docs_rs_context/src/lib.rs b/crates/lib/docs_rs_context/src/lib.rs index f5cab6ee7..7bb0204cf 100644 --- a/crates/lib/docs_rs_context/src/lib.rs +++ b/crates/lib/docs_rs_context/src/lib.rs @@ -1,5 +1,7 @@ mod config; mod context; +#[cfg(feature = "testing")] +pub mod testing; pub use config::Config; pub use context::Context; diff --git a/crates/lib/docs_rs_context/src/testing/mod.rs b/crates/lib/docs_rs_context/src/testing/mod.rs new file mode 100644 index 000000000..c0d275453 --- /dev/null +++ b/crates/lib/docs_rs_context/src/testing/mod.rs @@ -0,0 +1,3 @@ +mod test_env; + +pub use test_env::{blocking::BlockingTestEnvironment, non_blocking::TestEnvironment}; diff --git a/crates/lib/docs_rs_context/src/testing/test_env/blocking.rs b/crates/lib/docs_rs_context/src/testing/test_env/blocking.rs new file mode 100644 index 000000000..ac1a80672 --- /dev/null +++ b/crates/lib/docs_rs_context/src/testing/test_env/blocking.rs @@ -0,0 +1,54 @@ +use super::non_blocking::TestEnvironment; +use anyhow::{Context as _, Result}; +use bon::bon; +use docs_rs_config::AppConfig; +use docs_rs_storage::Config as StorageConfig; +use std::ops::Deref; +use tokio::runtime; + +pub struct BlockingTestEnvironment { + inner: TestEnvironment, + #[allow(dead_code)] // we need to keep the runtime alive while using the inner environment + runtime: runtime::Runtime, +} + +impl Deref for BlockingTestEnvironment { + type Target = TestEnvironment; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[bon] +impl BlockingTestEnvironment { + pub fn new() -> Result { + // NOTE: compiler crashes when I change the return to + // `Self::builder().build()` + #[allow(clippy::needless_question_mark)] + Ok(Self::builder().build()?) + } + + #[builder(finish_fn = build)] + pub fn builder( + config: Option, + registry_api_config: Option, + storage_config: Option, + ) -> Result { + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .context("failed to initialize runtime")?; + + Ok(Self { + inner: runtime.block_on( + TestEnvironment::builder() + .maybe_config(config) + .maybe_registry_api_config(registry_api_config) + .maybe_storage_config(storage_config) + .build(), + )?, + runtime, + }) + } +} diff --git a/crates/lib/docs_rs_context/src/testing/test_env/mod.rs b/crates/lib/docs_rs_context/src/testing/test_env/mod.rs new file mode 100644 index 000000000..ef1d7b54f --- /dev/null +++ b/crates/lib/docs_rs_context/src/testing/test_env/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod blocking; +pub(crate) mod non_blocking; diff --git a/crates/lib/docs_rs_context/src/testing/test_env/non_blocking.rs b/crates/lib/docs_rs_context/src/testing/test_env/non_blocking.rs new file mode 100644 index 000000000..4b96d6724 --- /dev/null +++ b/crates/lib/docs_rs_context/src/testing/test_env/non_blocking.rs @@ -0,0 +1,130 @@ +use crate::Context; +use anyhow::Result; +use bon::bon; +use docs_rs_config::AppConfig; +use docs_rs_database::{AsyncPoolClient, Config as DatabaseConfig, testing::TestDatabase}; +use docs_rs_fastly::Cdn; +use docs_rs_opentelemetry::testing::{CollectedMetrics, TestMetrics}; +use docs_rs_registry_api::RegistryApi; +use docs_rs_storage::{Config as StorageConfig, testing::TestStorage}; +use docs_rs_test_fakes::FakeRelease; +use std::{ops::Deref, sync::Arc}; + +pub struct TestEnvironment { + context: Arc, + config: Arc, + // so we can allow asserting collected metrics later. + metrics: TestMetrics, + #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. + storage: TestStorage, + #[allow(dead_code)] // we need to keep the storage so it can be cleaned up. + db: TestDatabase, +} + +impl Deref for TestEnvironment { + type Target = Context; + + fn deref(&self) -> &Self::Target { + &self.context + } +} + +#[bon] +impl TestEnvironment { + pub async fn new() -> Result { + // NOTE: compiler crashes when I change the return to + // `Self::builder().build().await + #[allow(clippy::needless_question_mark)] + Ok(Self::builder().build().await?) + } + + #[builder(finish_fn = build)] + pub async fn builder( + config: Option, + registry_api_config: Option, + storage_config: Option, + ) -> Result { + docs_rs_logging::testing::init(); + + let app_config = Arc::new(if let Some(web_config) = config { + web_config + } else { + C::test_config()? + }); + + let registry_api_config = + Arc::new(if let Some(registry_api_config) = registry_api_config { + registry_api_config + } else { + docs_rs_registry_api::Config::from_environment()? + }); + + let registry_api = RegistryApi::from_config(®istry_api_config)?; + + let metrics = TestMetrics::new(); + + let db_config = DatabaseConfig::test_config()?; + let db = TestDatabase::new(&db_config, metrics.provider()).await?; + + let storage_config = Arc::new(if let Some(storage_config) = storage_config { + storage_config + } else { + StorageConfig::test_config()? + }); + + let test_storage = + TestStorage::from_config(storage_config.clone(), metrics.provider()).await?; + + Ok(Self { + config: app_config, + context: Context::builder() + .with_runtime() + .await? + .meter_provider(metrics.provider().clone()) + .pool(db_config.into(), db.pool().clone()) + .storage(storage_config.clone(), test_storage.storage()) + .with_build_queue()? + .registry_api(registry_api_config, registry_api.into()) + .with_repository_stats()? + .maybe_cdn( + Arc::new(docs_rs_fastly::Config::test_config()?), + Some(Cdn::mock().into()), + ) + .with_build_limits()? + .build()? + .into(), + db, + storage: test_storage, + metrics, + }) + } + + pub fn config(&self) -> &Arc { + &self.config + } + + pub fn context(&self) -> &Arc { + &self.context + } + + pub fn cdn(&self) -> &Arc { + self.context + .cdn() + .expect("we always have a CDN in test environments") + } + + pub async fn async_conn(&self) -> Result { + self.context.pool()?.get_async().await.map_err(Into::into) + } + + pub async fn fake_release(&self) -> FakeRelease<'_> { + FakeRelease::new( + self.context.pool().unwrap().clone(), + self.context.storage().unwrap().clone(), + ) + } + + pub fn collected_metrics(&self) -> CollectedMetrics { + self.metrics.collected_metrics() + } +} diff --git a/crates/lib/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml index f898d2d67..cf6782390 100644 --- a/crates/lib/docs_rs_database/Cargo.toml +++ b/crates/lib/docs_rs_database/Cargo.toml @@ -9,6 +9,7 @@ build = "build.rs" [dependencies] anyhow = { workspace = true } chrono = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } @@ -30,6 +31,7 @@ tokio = { workspace = true } tracing = { workspace = true } [dev-dependencies] +docs_rs_config = { path = "../docs_rs_config", features = ["testing"] } docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata", features = ["testing"] } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry", features = ["testing"] } docs_rs_types = { path = "../docs_rs_types", features = ["testing"] } @@ -40,5 +42,6 @@ test-case = { workspace = true } [features] testing = [ "dep:rand", + "docs_rs_config/testing", "docs_rs_opentelemetry/testing", ] diff --git a/crates/lib/docs_rs_database/src/config.rs b/crates/lib/docs_rs_database/src/config.rs index 1d11f1166..f5f41e749 100644 --- a/crates/lib/docs_rs_database/src/config.rs +++ b/crates/lib/docs_rs_database/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, require_env}; #[derive(Debug)] @@ -7,8 +9,8 @@ pub struct Config { pub min_pool_idle: u32, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self { database_url: require_env("DOCSRS_DATABASE_URL")?, max_pool_size: env("DOCSRS_MAX_POOL_SIZE", 90u32)?, @@ -17,7 +19,7 @@ impl Config { } #[cfg(any(feature = "testing", test))] - pub fn test_config() -> anyhow::Result { + fn test_config() -> Result { let mut config = Self::from_environment()?; // Use less connections for each test compared to production. diff --git a/crates/lib/docs_rs_database/src/releases.rs b/crates/lib/docs_rs_database/src/releases.rs index e4ff815fb..8de5e3ac0 100644 --- a/crates/lib/docs_rs_database/src/releases.rs +++ b/crates/lib/docs_rs_database/src/releases.rs @@ -602,6 +602,7 @@ mod test { use crate::{Config, testing::TestDatabase}; use chrono::NaiveDate; use docs_rs_cargo_metadata::CargoMetadata; + use docs_rs_config::AppConfig as _; use docs_rs_opentelemetry::testing::TestMetrics; use docs_rs_registry_api::OwnerKind; use docs_rs_types::{ diff --git a/crates/lib/docs_rs_database/src/service_config.rs b/crates/lib/docs_rs_database/src/service_config.rs index 86e8cee86..93ac86c2e 100644 --- a/crates/lib/docs_rs_database/src/service_config.rs +++ b/crates/lib/docs_rs_database/src/service_config.rs @@ -48,6 +48,7 @@ where mod tests { use super::*; use crate::{Config, testing::TestDatabase}; + use docs_rs_config::AppConfig as _; use docs_rs_opentelemetry::testing::TestMetrics; use serde_json::Value; use test_case::test_case; diff --git a/crates/lib/docs_rs_fastly/Cargo.toml b/crates/lib/docs_rs_fastly/Cargo.toml index 0289ea15a..70e667479 100644 --- a/crates/lib/docs_rs_fastly/Cargo.toml +++ b/crates/lib/docs_rs_fastly/Cargo.toml @@ -8,20 +8,22 @@ edition = "2024" [dependencies] anyhow = { workspace = true } chrono = { workspace = true } -docs_rs_types = { path = "../docs_rs_types" } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_headers = { path = "../docs_rs_headers" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_types = { path = "../docs_rs_types" } docs_rs_utils = { path = "../docs_rs_utils" } http = { workspace = true } itertools = { workspace = true } opentelemetry = { workspace = true } reqwest = { workspace = true } +tokio = { workspace = true, optional = true } tracing = { workspace = true } url = { workspace = true } -tokio = { workspace = true, optional = true } [dev-dependencies] +docs_rs_config = { path = "../docs_rs_config", features = ["testing"] } docs_rs_headers = { path = "../docs_rs_headers", features = ["testing"] } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry", features = ["testing"] } mockito = { workspace = true } diff --git a/crates/lib/docs_rs_fastly/src/config.rs b/crates/lib/docs_rs_fastly/src/config.rs index c5756ccca..0df44c245 100644 --- a/crates/lib/docs_rs_fastly/src/config.rs +++ b/crates/lib/docs_rs_fastly/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env}; use url::Url; @@ -15,8 +17,8 @@ pub struct Config { pub service_sid: Option, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self { api_host: env("DOCSRS_FASTLY_API_HOST", FASTLY_API_HOST.parse().unwrap())?, api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, @@ -28,7 +30,7 @@ impl Config { /// assumes we're using the mock CDN, but generates a config where /// `is_valid` is true.` #[cfg(any(test, feature = "testing"))] - pub fn test_config() -> Self { + fn test_config() -> Result { let cfg = Self { api_host: FASTLY_API_HOST.parse().unwrap(), api_token: Some("some_token".into()), @@ -37,9 +39,11 @@ impl Config { debug_assert!(cfg.is_valid()); - cfg + Ok(cfg) } +} +impl Config { pub fn is_valid(&self) -> bool { self.api_token.is_some() && self.service_sid.is_some() } diff --git a/crates/lib/docs_rs_opentelemetry/Cargo.toml b/crates/lib/docs_rs_opentelemetry/Cargo.toml index 2bbd55a07..32c968a1c 100644 --- a/crates/lib/docs_rs_opentelemetry/Cargo.toml +++ b/crates/lib/docs_rs_opentelemetry/Cargo.toml @@ -8,6 +8,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } derive_more = { workspace = true, optional = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } opentelemetry = { workspace = true } opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"] } @@ -19,5 +20,6 @@ url = { workspace = true } [features] testing = [ "dep:derive_more", + "docs_rs_config/testing", "opentelemetry_sdk/testing", ] diff --git a/crates/lib/docs_rs_opentelemetry/src/config.rs b/crates/lib/docs_rs_opentelemetry/src/config.rs index d0e50c17d..dd4a32383 100644 --- a/crates/lib/docs_rs_opentelemetry/src/config.rs +++ b/crates/lib/docs_rs_opentelemetry/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::maybe_env; use url::Url; @@ -7,8 +9,8 @@ pub struct Config { pub endpoint: Option, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self { endpoint: maybe_env("OTEL_EXPORTER_OTLP_ENDPOINT")?, }) diff --git a/crates/lib/docs_rs_registry_api/Cargo.toml b/crates/lib/docs_rs_registry_api/Cargo.toml index f28d6ce78..dd80266be 100644 --- a/crates/lib/docs_rs_registry_api/Cargo.toml +++ b/crates/lib/docs_rs_registry_api/Cargo.toml @@ -9,6 +9,7 @@ edition = "2024" anyhow = { workspace = true } bon = { workspace = true } chrono = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_types = { path = "../docs_rs_types" } docs_rs_utils = { path = "../docs_rs_utils" } diff --git a/crates/lib/docs_rs_registry_api/src/config.rs b/crates/lib/docs_rs_registry_api/src/config.rs index 1044eed03..ef5f8b118 100644 --- a/crates/lib/docs_rs_registry_api/src/config.rs +++ b/crates/lib/docs_rs_registry_api/src/config.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::maybe_env; use url::Url; @@ -12,8 +13,8 @@ pub struct Config { pub crates_io_api_call_retries: u32, } -impl Config { - pub fn from_environment() -> Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self::builder() .maybe_crates_io_api_call_retries(maybe_env("DOCSRS_CRATESIO_API_CALL_RETRIES")?) .maybe_registry_api_host(maybe_env("DOCSRS_REGISTRY_API_HOST")?) diff --git a/crates/lib/docs_rs_repository_stats/Cargo.toml b/crates/lib/docs_rs_repository_stats/Cargo.toml index 4275ea135..ef3580c83 100644 --- a/crates/lib/docs_rs_repository_stats/Cargo.toml +++ b/crates/lib/docs_rs_repository_stats/Cargo.toml @@ -9,6 +9,7 @@ edition = "2024" anyhow = { workspace = true } async-trait = "0.1.89" chrono = { workspace = true } +docs_rs_config = { path = "../docs_rs_config" } docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } diff --git a/crates/lib/docs_rs_repository_stats/src/config.rs b/crates/lib/docs_rs_repository_stats/src/config.rs index 8ff361f4d..430bf8806 100644 --- a/crates/lib/docs_rs_repository_stats/src/config.rs +++ b/crates/lib/docs_rs_repository_stats/src/config.rs @@ -1,3 +1,5 @@ +use anyhow::Result; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env}; #[derive(Debug)] @@ -10,8 +12,8 @@ pub struct Config { pub(crate) gitlab_accesstoken: Option, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> Result { Ok(Self { github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?, github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?, diff --git a/crates/lib/docs_rs_repository_stats/src/github.rs b/crates/lib/docs_rs_repository_stats/src/github.rs index 56192ed0f..3d530a11f 100644 --- a/crates/lib/docs_rs_repository_stats/src/github.rs +++ b/crates/lib/docs_rs_repository_stats/src/github.rs @@ -267,6 +267,7 @@ mod tests { updater::{RepositoryForge, repository_name}, }; use anyhow::Result; + use docs_rs_config::AppConfig as _; const TEST_TOKEN: &str = "qsjdnfqdq"; diff --git a/crates/lib/docs_rs_repository_stats/src/updater.rs b/crates/lib/docs_rs_repository_stats/src/updater.rs index 438540089..1b1b96fb0 100644 --- a/crates/lib/docs_rs_repository_stats/src/updater.rs +++ b/crates/lib/docs_rs_repository_stats/src/updater.rs @@ -6,6 +6,7 @@ use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; use docs_rs_cargo_metadata::MetadataPackage; +use docs_rs_config::AppConfig as _; use docs_rs_database::Pool; use futures_util::stream::TryStreamExt; use regex::Regex; diff --git a/crates/lib/docs_rs_storage/Cargo.toml b/crates/lib/docs_rs_storage/Cargo.toml index d54976e32..fed84a5a4 100644 --- a/crates/lib/docs_rs_storage/Cargo.toml +++ b/crates/lib/docs_rs_storage/Cargo.toml @@ -15,6 +15,7 @@ aws-smithy-types-convert = { version = "0.60.0", features = ["convert-chrono"] } bzip2 = "0.6.0" chrono = { workspace = true } dashmap = "6.0.0" +docs_rs_config = { path = "../docs_rs_config" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_headers = { path = "../docs_rs_headers" } docs_rs_logging = { path = "../docs_rs_logging", optional = true } @@ -43,6 +44,7 @@ zstd = "0.13.0" [dev-dependencies] criterion = "0.8.0" +docs_rs_config = { path = "../docs_rs_config", features = ["testing"] } docs_rs_logging = { path = "../docs_rs_logging", features = ["testing"] } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry", features = ["testing"] } rand = { workspace = true } @@ -52,6 +54,7 @@ test-case = { workspace = true } testing = [ "dep:rand", "dep:docs_rs_logging", + "docs_rs_config/testing", "docs_rs_logging/testing", "docs_rs_opentelemetry/testing", ] diff --git a/crates/lib/docs_rs_storage/src/config.rs b/crates/lib/docs_rs_storage/src/config.rs index 6a6d7e364..0ae3fcfa2 100644 --- a/crates/lib/docs_rs_storage/src/config.rs +++ b/crates/lib/docs_rs_storage/src/config.rs @@ -1,4 +1,5 @@ use crate::types::StorageKind; +use docs_rs_config::AppConfig; use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{ io, @@ -58,8 +59,8 @@ pub struct Config { pub local_archive_cache_expected_count: usize, } -impl Config { - pub fn from_environment() -> anyhow::Result { +impl AppConfig for Config { + fn from_environment() -> anyhow::Result { let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; Ok(Self { @@ -84,6 +85,21 @@ impl Config { }) } + #[cfg(any(feature = "testing", test))] + fn test_config() -> anyhow::Result { + Self::test_config_with_kind(StorageKind::Memory) + } +} + +impl Config { + #[cfg(any(feature = "testing", test))] + pub fn set(self, f: F) -> Self + where + F: FnOnce(Self) -> Self, + { + f(self) + } + pub fn max_file_size_for(&self, path: impl AsRef) -> usize { static HTML: &str = "html"; @@ -97,7 +113,7 @@ impl Config { } #[cfg(any(feature = "testing", test))] - pub fn test_config(kind: StorageKind) -> anyhow::Result { + pub fn test_config_with_kind(kind: StorageKind) -> anyhow::Result { let mut config = Self::from_environment()?; config.storage_backend = kind; @@ -110,12 +126,4 @@ impl Config { Ok(config) } - - #[cfg(any(feature = "testing", test))] - pub fn set(self, f: F) -> Self - where - F: FnOnce(Self) -> Self, - { - f(self) - } } diff --git a/crates/lib/docs_rs_storage/src/testing/test_env.rs b/crates/lib/docs_rs_storage/src/testing/test_env.rs index ebb6e6925..884cc44ad 100644 --- a/crates/lib/docs_rs_storage/src/testing/test_env.rs +++ b/crates/lib/docs_rs_storage/src/testing/test_env.rs @@ -21,7 +21,11 @@ impl Deref for TestStorage { impl TestStorage { pub async fn from_kind(kind: StorageKind, meter_provider: &AnyMeterProvider) -> Result { docs_rs_logging::testing::init(); - Self::from_config(Arc::new(Config::test_config(kind)?), meter_provider).await + Self::from_config( + Arc::new(Config::test_config_with_kind(kind)?), + meter_provider, + ) + .await } pub async fn from_config( diff --git a/crates/lib/docs_rs_utils/src/testing/mod.rs b/crates/lib/docs_rs_utils/src/testing/mod.rs index 20c21e25b..b9198567a 100644 --- a/crates/lib/docs_rs_utils/src/testing/mod.rs +++ b/crates/lib/docs_rs_utils/src/testing/mod.rs @@ -2,7 +2,7 @@ macro_rules! block_on_async_with_conn { ($env:expr, |mut $conn:ident| async $body:block) => {{ $env.runtime().block_on(async { - let mut __conn = $env.db.async_conn().await?; + let mut __conn = $env.async_conn().await?; let $conn: &mut sqlx::PgConnection = &mut *__conn; // Force the async block to yield anyhow::Result<_> let __res: anyhow::Result<_> = (async $body).await;