Skip to content

Commit 6953e06

Browse files
committed
move TestDatabase into database subcrate
1 parent c8dd877 commit 6953e06

File tree

10 files changed

+138
-123
lines changed

10 files changed

+138
-123
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/lib/docs_rs_database/Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,19 @@ docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" }
1111
futures-util = { workspace = true }
1212
hex = "0.4.3"
1313
opentelemetry = { workspace = true }
14+
rand = { workspace = true, optional = true }
1415
sqlx = { workspace = true }
1516
thiserror = { workspace = true }
1617
tokio = { workspace = true }
1718
tracing = { workspace = true }
1819

20+
[dev-dependencies]
21+
rand = { workspace = true }
22+
docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry", features = ["testing"] }
23+
test-case = { workspace = true }
24+
1925
[features]
20-
testing = []
26+
testing = [
27+
"dep:rand",
28+
"docs_rs_opentelemetry/testing",
29+
]

crates/lib/docs_rs_database/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ mod errors;
33
mod metrics;
44
mod migrations;
55
mod pool;
6+
#[cfg(any(test, feature = "testing"))]
7+
pub mod testing;
68

79
pub use config::Config;
810
pub use errors::PoolError;

crates/lib/docs_rs_database/src/pool.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl Pool {
3030
Self::new_inner(config, DEFAULT_SCHEMA, otel_meter_provider).await
3131
}
3232

33-
#[cfg(feature = "testing")]
33+
#[cfg(any(test, feature = "testing"))]
3434
pub async fn new_with_schema(
3535
config: &Config,
3636
schema: &str,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod test_env;
2+
3+
pub use test_env::TestDatabase;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use crate::{AsyncPoolClient, Config, Pool, migrations};
2+
use anyhow::{Context as _, Result};
3+
use docs_rs_opentelemetry::AnyMeterProvider;
4+
use futures_util::TryStreamExt as _;
5+
use sqlx::Connection as _;
6+
use tokio::{runtime, task::block_in_place};
7+
use tracing::error;
8+
9+
#[derive(Debug)]
10+
pub struct TestDatabase {
11+
pool: Pool,
12+
schema: String,
13+
runtime: runtime::Handle,
14+
}
15+
16+
impl TestDatabase {
17+
pub async fn new(config: &Config, otel_meter_provider: &AnyMeterProvider) -> Result<Self> {
18+
// A random schema name is generated and used for the current connection. This allows each
19+
// test to create a fresh instance of the database to run within.
20+
let schema = format!("docs_rs_test_schema_{}", rand::random::<u64>());
21+
22+
let pool = Pool::new_with_schema(config, &schema, otel_meter_provider).await?;
23+
24+
let mut conn = sqlx::PgConnection::connect(&config.database_url).await?;
25+
sqlx::query(&format!("CREATE SCHEMA {schema}"))
26+
.execute(&mut conn)
27+
.await
28+
.context("error creating schema")?;
29+
sqlx::query(&format!("SET search_path TO {schema}, public"))
30+
.execute(&mut conn)
31+
.await
32+
.context("error setting search path")?;
33+
migrations::migrate(&mut conn, None)
34+
.await
35+
.context("error running migrations")?;
36+
37+
// Move all sequence start positions 10000 apart to avoid overlapping primary keys
38+
let sequence_names: Vec<_> = sqlx::query!(
39+
"SELECT relname
40+
FROM pg_class
41+
INNER JOIN pg_namespace ON
42+
pg_class.relnamespace = pg_namespace.oid
43+
WHERE pg_class.relkind = 'S'
44+
AND pg_namespace.nspname = $1
45+
",
46+
schema,
47+
)
48+
.fetch(&mut conn)
49+
.map_ok(|row| row.relname)
50+
.try_collect()
51+
.await?;
52+
53+
for (i, sequence) in sequence_names.into_iter().enumerate() {
54+
let offset = (i + 1) * 10000;
55+
sqlx::query(&format!(
56+
r#"ALTER SEQUENCE "{sequence}" RESTART WITH {offset};"#
57+
))
58+
.execute(&mut conn)
59+
.await?;
60+
}
61+
62+
Ok(TestDatabase {
63+
pool,
64+
schema,
65+
runtime: runtime::Handle::current(),
66+
})
67+
}
68+
69+
pub fn pool(&self) -> &Pool {
70+
&self.pool
71+
}
72+
73+
pub async fn async_conn(&self) -> AsyncPoolClient {
74+
self.pool
75+
.get_async()
76+
.await
77+
.expect("failed to get a connection out of the pool")
78+
}
79+
}
80+
81+
impl Drop for TestDatabase {
82+
fn drop(&mut self) {
83+
let pool = self.pool.clone();
84+
let schema = self.schema.clone();
85+
let runtime = self.runtime.clone();
86+
87+
block_in_place(move || {
88+
runtime.block_on(async move {
89+
let Ok(mut conn) = pool.get_async().await else {
90+
error!("error in drop impl");
91+
return;
92+
};
93+
94+
let migration_result = migrations::migrate(&mut conn, Some(0)).await;
95+
96+
if let Err(e) = sqlx::query(format!("DROP SCHEMA {} CASCADE;", schema).as_str())
97+
.execute(&mut *conn)
98+
.await
99+
{
100+
error!("failed to drop test schema {}: {}", schema, e);
101+
return;
102+
}
103+
104+
if let Err(err) = migration_result {
105+
error!(?err, "error reverting migrations");
106+
}
107+
})
108+
});
109+
}
110+
}

src/test/fakes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use super::TestDatabase;
21
use crate::{
32
db::{initialize_build, initialize_crate, initialize_release, update_build_status},
43
docbuilder::{DocCoverage, RUSTDOC_JSON_COMPRESSION_ALGORITHMS},
@@ -8,6 +7,7 @@ use anyhow::{Context, bail};
87
use base64::{Engine, engine::general_purpose::STANDARD as b64};
98
use chrono::{DateTime, Utc};
109
use docs_rs_cargo_metadata::{Dependency, MetadataPackage, Target};
10+
use docs_rs_database::testing::TestDatabase;
1111
use docs_rs_registry_api::{CrateData, CrateOwner, ReleaseData};
1212
use docs_rs_storage::{
1313
AsyncStorage, CompressionAlgorithm, FileEntry, RustdocJsonFormatVersion,

src/test/mod.rs

Lines changed: 4 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,22 @@ use anyhow::{Context as _, anyhow};
1212
use axum::body::Bytes;
1313
use axum::{Router, body::Body, http::Request, response::Response as AxumResponse};
1414
use axum_extra::headers::{ETag, HeaderMapExt as _};
15-
use docs_rs_database::{AsyncPoolClient, Pool};
15+
use docs_rs_database::testing::TestDatabase;
1616
use docs_rs_fastly::Cdn;
1717
use docs_rs_headers::{IfNoneMatch, SURROGATE_CONTROL, SurrogateKeys};
18-
use docs_rs_opentelemetry::{
19-
AnyMeterProvider,
20-
testing::{CollectedMetrics, TestMetrics},
21-
};
18+
use docs_rs_opentelemetry::testing::{CollectedMetrics, TestMetrics};
2219
use docs_rs_storage::{AsyncStorage, Storage, StorageKind, testing::TestStorage};
2320
use docs_rs_types::Version;
2421
use fn_error_context::context;
25-
use futures_util::stream::TryStreamExt;
2622
use http::{
2723
HeaderMap, HeaderName, HeaderValue, StatusCode,
2824
header::{CACHE_CONTROL, CONTENT_TYPE},
2925
};
3026
use http_body_util::BodyExt;
3127
use serde::de::DeserializeOwned;
32-
use sqlx::Connection as _;
3328
use std::{collections::HashMap, fs, future::Future, panic, rc::Rc, sync::Arc};
34-
use tokio::{runtime, task::block_in_place};
29+
use tokio::runtime;
3530
use tower::ServiceExt;
36-
use tracing::error;
3731

3832
// testing krate name constants
3933
pub(crate) const KRATE: &str = "krate";
@@ -452,8 +446,7 @@ impl TestEnvironment {
452446
fs::create_dir_all(config.registry_index_path.clone())?;
453447

454448
let test_metrics = TestMetrics::new();
455-
456-
let test_db = TestDatabase::new(&config, test_metrics.provider())
449+
let test_db = TestDatabase::new(&config.database, test_metrics.provider())
457450
.await
458451
.context("can't initialize test database")?;
459452

@@ -542,108 +535,3 @@ impl TestEnvironment {
542535
fakes::FakeRelease::new(self.async_db(), self.context.async_storage.clone())
543536
}
544537
}
545-
546-
#[derive(Debug)]
547-
pub(crate) struct TestDatabase {
548-
pool: Pool,
549-
schema: String,
550-
runtime: runtime::Handle,
551-
}
552-
553-
impl TestDatabase {
554-
async fn new(config: &Config, otel_meter_provider: &AnyMeterProvider) -> Result<Self> {
555-
// A random schema name is generated and used for the current connection. This allows each
556-
// test to create a fresh instance of the database to run within.
557-
let schema = format!("docs_rs_test_schema_{}", rand::random::<u64>());
558-
559-
let config = &config.database;
560-
561-
let pool = Pool::new_with_schema(config, &schema, otel_meter_provider).await?;
562-
563-
let mut conn = sqlx::PgConnection::connect(&config.database_url).await?;
564-
sqlx::query(&format!("CREATE SCHEMA {schema}"))
565-
.execute(&mut conn)
566-
.await
567-
.context("error creating schema")?;
568-
sqlx::query(&format!("SET search_path TO {schema}, public"))
569-
.execute(&mut conn)
570-
.await
571-
.context("error setting search path")?;
572-
docs_rs_database::migrate(&mut conn, None)
573-
.await
574-
.context("error running migrations")?;
575-
576-
// Move all sequence start positions 10000 apart to avoid overlapping primary keys
577-
let sequence_names: Vec<_> = sqlx::query!(
578-
"SELECT relname
579-
FROM pg_class
580-
INNER JOIN pg_namespace ON
581-
pg_class.relnamespace = pg_namespace.oid
582-
WHERE pg_class.relkind = 'S'
583-
AND pg_namespace.nspname = $1
584-
",
585-
schema,
586-
)
587-
.fetch(&mut conn)
588-
.map_ok(|row| row.relname)
589-
.try_collect()
590-
.await?;
591-
592-
for (i, sequence) in sequence_names.into_iter().enumerate() {
593-
let offset = (i + 1) * 10000;
594-
sqlx::query(&format!(
595-
r#"ALTER SEQUENCE "{sequence}" RESTART WITH {offset};"#
596-
))
597-
.execute(&mut conn)
598-
.await?;
599-
}
600-
601-
Ok(TestDatabase {
602-
pool,
603-
schema,
604-
runtime: runtime::Handle::current(),
605-
})
606-
}
607-
608-
pub(crate) fn pool(&self) -> &Pool {
609-
&self.pool
610-
}
611-
612-
pub(crate) async fn async_conn(&self) -> AsyncPoolClient {
613-
self.pool
614-
.get_async()
615-
.await
616-
.expect("failed to get a connection out of the pool")
617-
}
618-
}
619-
620-
impl Drop for TestDatabase {
621-
fn drop(&mut self) {
622-
let pool = self.pool.clone();
623-
let schema = self.schema.clone();
624-
let runtime = self.runtime.clone();
625-
626-
block_in_place(move || {
627-
runtime.block_on(async move {
628-
let Ok(mut conn) = pool.get_async().await else {
629-
error!("error in drop impl");
630-
return;
631-
};
632-
633-
let migration_result = docs_rs_database::migrate(&mut conn, Some(0)).await;
634-
635-
if let Err(e) = sqlx::query(format!("DROP SCHEMA {} CASCADE;", schema).as_str())
636-
.execute(&mut *conn)
637-
.await
638-
{
639-
error!("failed to drop test schema {}: {}", schema, e);
640-
return;
641-
}
642-
643-
if let Err(err) = migration_result {
644-
error!(?err, "error reverting migrations");
645-
}
646-
})
647-
});
648-
}
649-
}

src/web/crate_details.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -723,10 +723,11 @@ mod tests {
723723
use super::*;
724724
use crate::db::update_build_status;
725725
use crate::test::{
726-
AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment,
727-
async_wrapper, fake_release_that_failed_before_build,
726+
AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, async_wrapper,
727+
fake_release_that_failed_before_build,
728728
};
729729
use anyhow::Error;
730+
use docs_rs_database::testing::TestDatabase;
730731
use docs_rs_registry_api::CrateOwner;
731732
use docs_rs_types::KrateName;
732733
use http::StatusCode;

src/web/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,9 +628,9 @@ mod test {
628628
use super::*;
629629
use crate::docbuilder::DocCoverage;
630630
use crate::test::{
631-
AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment,
632-
async_wrapper,
631+
AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, async_wrapper,
633632
};
633+
use docs_rs_database::testing::TestDatabase;
634634
use docs_rs_types::ReleaseId;
635635
use kuchikiki::traits::TendrilSink;
636636
use pretty_assertions::assert_eq;

0 commit comments

Comments
 (0)