Skip to content

Commit 73585b6

Browse files
committed
add support for running tests with the real database pool
Some tests need access to a "real" production database pool, but that means we can't rely on begin_test_transaction(), as that only works inside a single connection. This commit adds support creating a brand new schema for each test, and adds a way for tests to opt into that. This is not enabled for all tests, as creating a new schema and running migrations on it takes around 1.5 seconds on my computer.
1 parent 192d948 commit 73585b6

File tree

4 files changed

+91
-56
lines changed

4 files changed

+91
-56
lines changed

src/tests/dump_db.rs

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1+
use crate::util::FreshSchema;
12
use cargo_registry::tasks::dump_db;
2-
use diesel::{
3-
connection::{Connection, SimpleConnection},
4-
pg::PgConnection,
5-
};
63

74
#[test]
85
fn dump_db_and_reimport_dump() {
@@ -13,59 +10,10 @@ fn dump_db_and_reimport_dump() {
1310
let directory = dump_db::DumpDirectory::create().unwrap();
1411
directory.populate(&database_url).unwrap();
1512

16-
let schema = TemporarySchema::create(database_url, "test_db_dump");
17-
schema.run_migrations();
13+
let schema = FreshSchema::new(&database_url);
1814

1915
let import_script = directory.export_dir.join("import.sql");
20-
dump_db::run_psql(&import_script, &schema.database_url).unwrap();
16+
dump_db::run_psql(&import_script, schema.database_url()).unwrap();
2117

2218
// TODO: Consistency checks on the re-imported data?
2319
}
24-
25-
struct TemporarySchema {
26-
pub database_url: String,
27-
pub schema_name: String,
28-
pub connection: PgConnection,
29-
}
30-
31-
impl TemporarySchema {
32-
pub fn create(database_url: String, schema_name: &str) -> Self {
33-
let params = &[("options", format!("--search_path={},public", schema_name))];
34-
let database_url = url::Url::parse_with_params(&database_url, params)
35-
.unwrap()
36-
.into_string();
37-
let schema_name = schema_name.to_owned();
38-
let connection = PgConnection::establish(&database_url).unwrap();
39-
connection
40-
.batch_execute(&format!(
41-
r#"DROP SCHEMA IF EXISTS "{schema_name}" CASCADE;
42-
CREATE SCHEMA "{schema_name}";"#,
43-
schema_name = schema_name,
44-
))
45-
.unwrap();
46-
Self {
47-
database_url,
48-
schema_name,
49-
connection,
50-
}
51-
}
52-
53-
pub fn run_migrations(&self) {
54-
use diesel_migrations::{find_migrations_directory, run_pending_migrations_in_directory};
55-
let migrations_dir = find_migrations_directory().unwrap();
56-
run_pending_migrations_in_directory(
57-
&self.connection,
58-
&migrations_dir,
59-
&mut std::io::sink(),
60-
)
61-
.unwrap();
62-
}
63-
}
64-
65-
impl Drop for TemporarySchema {
66-
fn drop(&mut self) {
67-
self.connection
68-
.batch_execute(&format!(r#"DROP SCHEMA "{}" CASCADE;"#, self.schema_name))
69-
.unwrap();
70-
}
71-
}

src/tests/util.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ use conduit::header;
3333
use cookie::Cookie;
3434
use std::collections::HashMap;
3535

36+
mod fresh_schema;
3637
mod response;
3738
mod test_app;
3839

40+
pub(crate) use fresh_schema::FreshSchema;
3941
pub use response::Response;
4042
pub use test_app::TestApp;
4143

src/tests/util/fresh_schema.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
use diesel::connection::SimpleConnection;
2+
use diesel::prelude::*;
3+
use diesel_migrations::{find_migrations_directory, run_pending_migrations_in_directory};
4+
use rand::Rng;
5+
6+
pub(crate) struct FreshSchema {
7+
database_url: String,
8+
schema_name: String,
9+
management_conn: PgConnection,
10+
}
11+
12+
impl FreshSchema {
13+
pub(crate) fn new(database_url: &str) -> Self {
14+
let schema_name = generate_schema_name();
15+
16+
let conn = PgConnection::establish(&database_url).expect("can't connect to the test db");
17+
conn.batch_execute(&format!(
18+
"
19+
DROP SCHEMA IF EXISTS {schema_name} CASCADE;
20+
CREATE SCHEMA {schema_name};
21+
SET search_path TO {schema_name}, public;
22+
",
23+
schema_name = schema_name
24+
))
25+
.expect("failed to initialize schema");
26+
27+
let migrations_dir = find_migrations_directory().unwrap();
28+
run_pending_migrations_in_directory(&conn, &migrations_dir, &mut std::io::sink())
29+
.expect("failed to run migrations on the test schema");
30+
31+
let database_url = url::Url::parse_with_params(
32+
database_url,
33+
&[("options", format!("--search_path={},public", schema_name))],
34+
)
35+
.unwrap()
36+
.to_string();
37+
38+
Self {
39+
database_url,
40+
schema_name,
41+
management_conn: conn,
42+
}
43+
}
44+
45+
pub(crate) fn database_url(&self) -> &str {
46+
&self.database_url
47+
}
48+
}
49+
50+
impl Drop for FreshSchema {
51+
fn drop(&mut self) {
52+
self.management_conn
53+
.batch_execute(&format!("DROP SCHEMA {} CASCADE;", self.schema_name))
54+
.expect("failed to drop the test schema");
55+
}
56+
}
57+
58+
fn generate_schema_name() -> String {
59+
let mut rng = rand::thread_rng();
60+
let random_string: String = std::iter::repeat(())
61+
.map(|_| rng.sample(rand::distributions::Alphanumeric) as char)
62+
.take(16)
63+
.collect();
64+
format!("cratesio_test_{}", random_string)
65+
}

src/tests/util/test_app.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::{MockAnonymousUser, MockCookieUser, MockTokenUser};
2+
use crate::util::fresh_schema::FreshSchema;
23
use crate::{env, record};
34
use cargo_registry::{
45
background_jobs::Environment,
@@ -22,6 +23,9 @@ struct TestAppInner {
2223
middle: conduit_middleware::MiddlewareBuilder,
2324
index: Option<UpstreamRepository>,
2425
runner: Option<Runner<Environment, DieselPool>>,
26+
27+
// Must be the last field of the struct!
28+
_fresh_schema: Option<FreshSchema>,
2529
}
2630

2731
impl Drop for TestAppInner {
@@ -179,9 +183,19 @@ pub struct TestAppBuilder {
179183

180184
impl TestAppBuilder {
181185
/// Create a `TestApp` with an empty database
182-
pub fn empty(self) -> (TestApp, MockAnonymousUser) {
186+
pub fn empty(mut self) -> (TestApp, MockAnonymousUser) {
183187
use crate::git;
184188

189+
// Run each test inside a fresh database schema, deleted at the end of the test,
190+
// The schema will be cleared up once the app is dropped.
191+
let fresh_schema = if !self.config.use_test_database_pool {
192+
let fresh_schema = FreshSchema::new(&self.config.db_primary_config.url);
193+
self.config.db_primary_config.url = fresh_schema.database_url().into();
194+
Some(fresh_schema)
195+
} else {
196+
None
197+
};
198+
185199
let (app, middle) = build_app(self.config, self.proxy);
186200

187201
let runner = if self.build_job_runner {
@@ -211,6 +225,7 @@ impl TestAppBuilder {
211225

212226
let test_app_inner = TestAppInner {
213227
app,
228+
_fresh_schema: fresh_schema,
214229
_bomb: self.bomb,
215230
middle,
216231
index: self.index,
@@ -272,6 +287,11 @@ impl TestAppBuilder {
272287
self.build_job_runner = true;
273288
self
274289
}
290+
291+
pub fn with_slow_real_db_pool(mut self) -> Self {
292+
self.config.use_test_database_pool = false;
293+
self
294+
}
275295
}
276296

277297
pub fn init_logger() {

0 commit comments

Comments
 (0)