Skip to content

Commit 5918cc3

Browse files
committed
wiring up test database
1 parent f25b1ac commit 5918cc3

File tree

9 files changed

+219
-5
lines changed

9 files changed

+219
-5
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
key: linux
3434

3535
- name: Run unit tests
36-
run: cargo test --all
36+
run: make test
3737

3838
- name: Lint check
3939
run: cargo clippy --all -- -D warnings

Cargo.lock

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

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.PHONY: test start-postgres
2+
3+
# Helper to spin up postgres
4+
start-postgres:
5+
docker compose up -d pg_test
6+
7+
# Helper to stop postgres & destroy the volume (ensures a clean rebuild on `start`)
8+
stop-postgres:
9+
docker compose down -v
10+
11+
# Run the tests with the database purring along in the background
12+
test: start-postgres
13+
TEST_DB_URL="postgres://postgres:testpass@localhost/postgres" cargo test

database/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ csv = "1"
2626
x509-cert = { version = "0.2.5", features = ["pem"] }
2727

2828
intern = { path = "../intern" }
29+
uuid = "1.16.0"

database/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ pub mod metric;
1414
pub mod pool;
1515
pub mod selector;
1616

17+
#[cfg(test)]
18+
mod tests;
19+
1720
pub use pool::{Connection, Pool};
1821

1922
intern!(pub struct Metric);

database/src/pool.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,92 @@ impl Pool {
294294
}
295295
}
296296
}
297+
298+
#[cfg(test)]
299+
mod tests {
300+
use chrono::Utc;
301+
use std::str::FromStr;
302+
303+
use super::*;
304+
use crate::{tests::run_db_test, Commit, CommitType, Date};
305+
306+
/// Create a Commit
307+
fn create_commit(commit_sha: &str, time: chrono::DateTime<Utc>, r#type: CommitType) -> Commit {
308+
Commit {
309+
sha: commit_sha.into(),
310+
date: Date(time),
311+
r#type,
312+
}
313+
}
314+
315+
#[tokio::test]
316+
async fn pstat_returns_empty_vector_when_empty() {
317+
run_db_test(|ctx| async {
318+
// This is essentially testing the database testing framework is
319+
// wired up correctly. Though makes sense that there should be
320+
// an empty vector returned if there are no pstats.
321+
let db = ctx.db_client();
322+
let result = db.connection().await.get_pstats(&vec![], &vec![]).await;
323+
let expected: Vec<Vec<Option<f64>>> = vec![];
324+
325+
assert_eq!(result, expected);
326+
Ok(ctx)
327+
})
328+
.await;
329+
}
330+
331+
#[tokio::test]
332+
async fn artifact_storage() {
333+
run_db_test(|ctx| async {
334+
let db = ctx.db_client();
335+
let time = chrono::DateTime::from_str("2021-09-01T00:00:00.000Z").unwrap();
336+
337+
let artifact_one = ArtifactId::from(create_commit("abc", time, CommitType::Master));
338+
let artifact_two = ArtifactId::Tag("nightly-2025-05-14".to_string());
339+
340+
let artifact_one_id_number = db.connection().await.artifact_id(&artifact_one).await;
341+
let artifact_two_id_number = db.connection().await.artifact_id(&artifact_two).await;
342+
343+
// We cannot arbitrarily add random sizes to the artifact size
344+
// table, as their is a constraint that the artifact must actually
345+
// exist before attaching something to it.
346+
347+
// Artifact one inserts
348+
db.connection()
349+
.await
350+
.record_artifact_size(artifact_one_id_number, "llvm.so", 32)
351+
.await;
352+
db.connection()
353+
.await
354+
.record_artifact_size(artifact_one_id_number, "llvm.a", 64)
355+
.await;
356+
357+
// Artifact two inserts
358+
db.connection()
359+
.await
360+
.record_artifact_size(artifact_two_id_number, "another-llvm.a", 128)
361+
.await;
362+
363+
let result_one = db
364+
.connection()
365+
.await
366+
.get_artifact_size(artifact_one_id_number)
367+
.await;
368+
let result_two = db
369+
.connection()
370+
.await
371+
.get_artifact_size(artifact_two_id_number)
372+
.await;
373+
374+
// artifact one
375+
assert_eq!(Some(32 as u64), result_one.get("llvm.so").copied());
376+
assert_eq!(Some(64 as u64), result_one.get("llvm.a").copied());
377+
assert_eq!(None, result_one.get("another-llvm.a").copied());
378+
379+
// artifact two
380+
assert_eq!(Some(128), result_two.get("another-llvm.a").copied());
381+
Ok(ctx)
382+
})
383+
.await;
384+
}
385+
}

database/src/pool/postgres.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ impl Postgres {
2525

2626
const CERT_URL: &str = "https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem";
2727

28-
async fn make_client(db_url: &str) -> anyhow::Result<tokio_postgres::Client> {
28+
pub async fn make_client(db_url: &str) -> anyhow::Result<tokio_postgres::Client> {
2929
if db_url.contains("rds.amazonaws.com") {
3030
let mut builder = TlsConnector::builder();
3131
for cert in make_certificates().await {

database/src/tests/mod.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::future::Future;
2+
use tokio_postgres::config::Host;
3+
use tokio_postgres::Config;
4+
5+
use crate::pool::postgres::make_client;
6+
use crate::Pool;
7+
8+
/// Represents a connection to a Postgres database that can be
9+
/// used in integration tests to test logic that interacts with
10+
/// a database.
11+
pub(crate) struct TestContext {
12+
db_name: String,
13+
original_db_url: String,
14+
// Pre-cached client to avoid creating unnecessary connections in tests
15+
client: Pool,
16+
}
17+
18+
impl TestContext {
19+
async fn new(db_url: &str) -> Self {
20+
let config: Config = db_url.parse().expect("Cannot parse connection string");
21+
22+
// Create a new database that will be used for this specific test
23+
let client = make_client(&db_url)
24+
.await
25+
.expect("Cannot connect to database");
26+
let db_name = format!("db{}", uuid::Uuid::new_v4().to_string().replace("-", ""));
27+
client
28+
.execute(&format!("CREATE DATABASE {db_name}"), &[])
29+
.await
30+
.expect("Cannot create database");
31+
drop(client);
32+
33+
// We need to connect to the database against, because Postgres doesn't allow
34+
// changing the active database mid-connection.
35+
// There does not seem to be a way to turn the config back into a connection
36+
// string, so construct it manually.
37+
let test_db_url = format!(
38+
"postgresql://{}:{}@{}:{}/{}",
39+
config.get_user().unwrap(),
40+
String::from_utf8(config.get_password().unwrap().to_vec()).unwrap(),
41+
match &config.get_hosts()[0] {
42+
Host::Tcp(host) => host,
43+
Host::Unix(_) =>
44+
panic!("Unix sockets in Postgres connection string are not supported"),
45+
},
46+
&config.get_ports()[0],
47+
db_name
48+
);
49+
let pool = Pool::open(test_db_url.as_str());
50+
51+
Self {
52+
db_name,
53+
original_db_url: db_url.to_string(),
54+
client: pool,
55+
}
56+
}
57+
58+
pub(crate) fn db_client(&self) -> &Pool {
59+
&self.client
60+
}
61+
62+
async fn finish(self) {
63+
// Cleanup the test database
64+
// First, we need to stop using the database
65+
drop(self.client);
66+
67+
// Then we need to connect to the default database and drop our test DB
68+
let client = make_client(&self.original_db_url)
69+
.await
70+
.expect("Cannot connect to database");
71+
client
72+
.execute(&format!("DROP DATABASE {}", self.db_name), &[])
73+
.await
74+
.unwrap();
75+
}
76+
}
77+
78+
pub(crate) async fn run_db_test<F, Fut>(f: F)
79+
where
80+
F: FnOnce(TestContext) -> Fut,
81+
Fut: Future<Output = anyhow::Result<TestContext>>,
82+
{
83+
if let Ok(db_url) = std::env::var("TEST_DB_URL") {
84+
let ctx = TestContext::new(&db_url).await;
85+
let ctx = f(ctx).await.expect("Test failed");
86+
ctx.finish().await;
87+
} else {
88+
eprintln!("Skipping test because TEST_DB_URL was not passed");
89+
}
90+
}

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
pg_test:
3+
image: postgres:16-alpine
4+
environment:
5+
POSTGRES_USER: postgres
6+
POSTGRES_PASSWORD: testpass
7+
POSTGRES_DB: postgres
8+
ports:
9+
- "5432:5432" # expose to host for tests
10+
healthcheck: # wait until the DB is ready
11+
test: ["CMD-SHELL", "pg_isready -U postgres"]
12+
interval: 2s
13+
timeout: 2s
14+
retries: 15
15+
tmpfs:
16+
- /var/lib/postgresql/data # store data in RAM
17+

0 commit comments

Comments
 (0)