Skip to content

Commit 0f7f2c2

Browse files
authored
Setup framework to do integration testing (#50)
1 parent 011f9f8 commit 0f7f2c2

File tree

12 files changed

+316
-6
lines changed

12 files changed

+316
-6
lines changed

.github/workflows/tests.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
profile: minimal
1818
toolchain: stable
1919
override: true
20+
- uses: Swatinem/rust-cache@v1
2021
- uses: actions-rs/cargo@v1
2122
with:
2223
command: check
@@ -31,9 +32,12 @@ jobs:
3132
profile: minimal
3233
toolchain: stable
3334
override: true
35+
- uses: Swatinem/rust-cache@v1
36+
- run: cd compressor_integration_tests && docker-compose up -d
3437
- uses: actions-rs/cargo@v1
3538
with:
3639
command: test
40+
args: --workspace
3741

3842
fmt:
3943
name: Rustfmt
@@ -45,7 +49,8 @@ jobs:
4549
profile: minimal
4650
toolchain: stable
4751
override: true
48-
- run: rustup component add rustfmt
52+
components: rustfmt
53+
- uses: Swatinem/rust-cache@v1
4954
- uses: actions-rs/cargo@v1
5055
with:
5156
command: fmt
@@ -61,7 +66,8 @@ jobs:
6166
profile: minimal
6267
toolchain: stable
6368
override: true
64-
- run: rustup component add clippy
69+
components: clippy
70+
- uses: Swatinem/rust-cache@v1
6571
- uses: actions-rs/cargo@v1
6672
with:
6773
command: clippy

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
**/*.rs.bk
33
*.data
44
*.old
5-
out.sql
5+
**.sql
66
*.csv

Cargo.lock

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

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[workspace]
2+
members = ["compressor_integration_tests"]
3+
14
[package]
25
authors = ["Erik Johnston"]
36
description = "A tool to compress some state in a Synapse instance's database"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "compressor_integration_tests"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
string_cache = "0.8.0"
10+
serial_test = "0.5.1"
11+
openssl = "0.10.32"
12+
postgres = "0.19.0"
13+
postgres-openssl = "0.5.0"
14+
rand = "0.8.0"
15+
synapse_compress_state = { path = "../" }
16+
17+
[dependencies.state-map]
18+
git = "https://github.com/matrix-org/rust-matrix-state-map"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/sh
2+
3+
#N.B. the database setup comes from:
4+
#https://github.com/matrix-org/synapse/blob/develop/synapse/storage/schema/state/full_schemas/54/full.sql
5+
6+
# Setup the required tables for testing
7+
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<SQLCODE
8+
9+
CREATE TABLE state_groups (
10+
id BIGINT PRIMARY KEY,
11+
room_id TEXT NOT NULL,
12+
event_id TEXT NOT NULL
13+
);
14+
15+
CREATE TABLE state_groups_state (
16+
state_group BIGINT NOT NULL,
17+
room_id TEXT NOT NULL,
18+
type TEXT NOT NULL,
19+
state_key TEXT NOT NULL,
20+
event_id TEXT NOT NULL
21+
);
22+
23+
CREATE TABLE state_group_edges (
24+
state_group BIGINT NOT NULL,
25+
prev_state_group BIGINT NOT NULL
26+
);
27+
28+
SQLCODE
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: '3'
2+
services:
3+
postgres:
4+
image: "postgres:latest"
5+
6+
ports:
7+
# N.B. format is [port on machine]:[port to expose from container]
8+
- 5432:5432
9+
10+
environment:
11+
POSTGRES_USER: synapse_user
12+
POSTGRES_PASSWORD: synapse_pass
13+
POSTGRES_DB: synapse
14+
PGDATA: /tmp/data
15+
16+
volumes:
17+
- ./database_setup.sh:/docker-entrypoint-initdb.d/1_database_setup.sh
18+
19+
tmpfs:
20+
/tmp/data
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
2+
use postgres::Client;
3+
use postgres_openssl::MakeTlsConnector;
4+
use rand::{distributions::Alphanumeric, thread_rng, Rng};
5+
use std::{borrow::Cow, collections::BTreeMap, fmt};
6+
7+
use synapse_compress_state::StateGroupEntry;
8+
9+
pub mod map_builder;
10+
11+
pub static DB_URL: &str = "postgresql://synapse_user:synapse_pass@localhost/synapse";
12+
13+
/// Adds the contents of a state group map to the testing database
14+
pub fn add_contents_to_database(room_id: &str, state_group_map: &BTreeMap<i64, StateGroupEntry>) {
15+
// connect to the database
16+
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
17+
builder.set_verify(SslVerifyMode::NONE);
18+
let connector = MakeTlsConnector::new(builder.build());
19+
20+
let mut client = Client::connect(DB_URL, connector).unwrap();
21+
22+
// build up the query
23+
let mut sql = "".to_string();
24+
25+
for (sg, entry) in state_group_map {
26+
// create the entry for state_groups
27+
sql.push_str(&format!(
28+
"INSERT INTO state_groups (id, room_id, event_id) VALUES ({},{},{});\n",
29+
sg,
30+
PGEscape(room_id),
31+
PGEscape("left_blank")
32+
));
33+
34+
// create the entry in state_group_edges IF exists
35+
if let Some(prev_sg) = entry.prev_state_group {
36+
sql.push_str(&format!(
37+
"INSERT INTO state_group_edges (state_group, prev_state_group) VALUES ({}, {});\n",
38+
sg, prev_sg
39+
));
40+
}
41+
42+
// write entry for each row in delta
43+
if !entry.state_map.is_empty() {
44+
sql.push_str("INSERT INTO state_groups_state (state_group, room_id, type, state_key, event_id) VALUES");
45+
46+
let mut first = true;
47+
for ((t, s), e) in entry.state_map.iter() {
48+
if first {
49+
sql.push_str(" ");
50+
first = false;
51+
} else {
52+
sql.push_str(" ,");
53+
}
54+
sql.push_str(&format!(
55+
"({}, {}, {}, {}, {})",
56+
sg,
57+
PGEscape(room_id),
58+
PGEscape(t),
59+
PGEscape(s),
60+
PGEscape(e)
61+
));
62+
}
63+
sql.push_str(";\n");
64+
}
65+
}
66+
67+
client.batch_execute(&sql).unwrap();
68+
}
69+
70+
// Clears the contents of the testing database
71+
pub fn empty_database() {
72+
// connect to the database
73+
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
74+
builder.set_verify(SslVerifyMode::NONE);
75+
let connector = MakeTlsConnector::new(builder.build());
76+
77+
let mut client = Client::connect(DB_URL, connector).unwrap();
78+
79+
// delete all the contents from all three tables
80+
let sql = r"
81+
DELETE FROM state_groups;
82+
DELETE FROM state_group_edges;
83+
DELETE FROM state_groups_state;
84+
";
85+
86+
client.batch_execute(sql).unwrap();
87+
}
88+
89+
/// Safely escape the strings in sql queries
90+
struct PGEscape<'a>(pub &'a str);
91+
92+
impl<'a> fmt::Display for PGEscape<'a> {
93+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94+
let mut delim = Cow::from("$$");
95+
while self.0.contains(&delim as &str) {
96+
let s: String = thread_rng()
97+
.sample_iter(&Alphanumeric)
98+
.take(10)
99+
.map(char::from)
100+
.collect();
101+
102+
delim = format!("${}$", s).into();
103+
}
104+
105+
write!(f, "{}{}{}", delim, self.0, delim)
106+
}
107+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::collections::BTreeMap;
2+
3+
use state_map::StateMap;
4+
use synapse_compress_state::StateGroupEntry;
5+
6+
/// Generates long chain of state groups each with state deltas
7+
///
8+
/// If called wiht start=0, end=13 this would build the following:
9+
///
10+
/// 0-1-2-3-4-5-6-7-8-9-10-11-12-13
11+
///
12+
/// Where each group i has state:
13+
/// ('node','is', i)
14+
/// ('group', j, 'seen') - for all j less than i
15+
pub fn line_with_state(start: i64, end: i64) -> BTreeMap<i64, StateGroupEntry> {
16+
let mut initial: BTreeMap<i64, StateGroupEntry> = BTreeMap::new();
17+
let mut prev = None;
18+
19+
for i in start..=end {
20+
let mut entry = StateGroupEntry {
21+
in_range: true,
22+
prev_state_group: prev,
23+
state_map: StateMap::new(),
24+
};
25+
entry
26+
.state_map
27+
.insert("group", &i.to_string(), "seen".into());
28+
entry.state_map.insert("node", "is", i.to_string().into());
29+
30+
initial.insert(i, entry);
31+
32+
prev = Some(i)
33+
}
34+
35+
initial
36+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use compressor_integration_tests::{
2+
add_contents_to_database, empty_database, map_builder::line_with_state, DB_URL,
3+
};
4+
use serial_test::serial;
5+
use synapse_compress_state::{run, Config};
6+
7+
// Remember to add #[serial(db)] before any test that access the database.
8+
// Only one test with this annotation can run at once - preventing
9+
// concurrency bugs.
10+
//
11+
// You will probably also want to use common::empty_database() at the start
12+
// of each test as well (since their order of execution is not guaranteed)
13+
14+
#[test]
15+
#[serial(db)]
16+
fn run_succeeds_without_crashing() {
17+
// This starts with the following structure
18+
//
19+
// 0-1-2-3-4-5-6-7-8-9-10-11-12-13
20+
//
21+
// Each group i has state:
22+
// ('node','is', i)
23+
// ('group', j, 'seen') - for all j less than i
24+
let initial = line_with_state(0, 13);
25+
26+
empty_database();
27+
add_contents_to_database("room1", &initial);
28+
29+
let db_url = DB_URL.to_string();
30+
let room_id = "room1".to_string();
31+
let output_file = Some("./tests/tmp/run_succeeds_without_crashing.sql".to_string());
32+
let min_state_group = None;
33+
let groups_to_compress = None;
34+
let min_saved_rows = None;
35+
let max_state_group = None;
36+
let level_sizes = "3,3".to_string();
37+
let transactions = true;
38+
let graphs = false;
39+
40+
let config = Config::new(
41+
db_url.clone(),
42+
room_id.clone(),
43+
output_file,
44+
min_state_group,
45+
groups_to_compress,
46+
min_saved_rows,
47+
max_state_group,
48+
level_sizes,
49+
transactions,
50+
graphs,
51+
)
52+
.unwrap();
53+
54+
run(config);
55+
}

0 commit comments

Comments
 (0)