Skip to content

Commit b2697df

Browse files
Use SpacetimeDBGuard for SDK test suite
1 parent 77ffdbb commit b2697df

37 files changed

Lines changed: 214 additions & 181 deletions

File tree

Cargo.lock

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

crates/bindings-typescript/case-conversion-test-client/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919

2020
import { DbConnection, tables } from './module_bindings/index.js';
2121

22-
const LOCALHOST = 'http://localhost:3000';
22+
const SERVER_URL = process.env.SPACETIME_SDK_TEST_SERVER_URL;
23+
if (!SERVER_URL) {
24+
throw new Error('Missing SPACETIME_SDK_TEST_SERVER_URL');
25+
}
2326

2427
function dbNameOrPanic(): string {
2528
const name = process.env.SPACETIME_SDK_TEST_DB_NAME;
@@ -53,7 +56,7 @@ function connectThen(callback: (db: DbConnection) => void): Promise<void> {
5356
return new Promise<void>((resolve, reject) => {
5457
const conn = DbConnection.builder()
5558
.withDatabaseName(name)
56-
.withUri(LOCALHOST)
59+
.withUri(SERVER_URL)
5760
.onConnect((ctx, _identity, _token) => {
5861
try {
5962
callback(ctx);

crates/testing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ spacetimedb-core.workspace = true
1616
spacetimedb-standalone.workspace = true
1717
spacetimedb-client-api.workspace = true
1818
spacetimedb-client-api-messages.workspace = true
19+
spacetimedb-guard.workspace = true
1920
spacetimedb-paths.workspace = true
2021
spacetimedb-schema.workspace = true
2122

crates/testing/src/sdk.rs

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,26 @@ use duct::cmd;
22
use rand::seq::IteratorRandom;
33
use spacetimedb::messages::control_db::HostType;
44
use spacetimedb_data_structures::map::HashMap;
5+
use spacetimedb_guard::SpacetimeDbGuard;
56
use spacetimedb_paths::{RootDir, SpacetimePaths};
67
use std::fs::create_dir_all;
7-
use std::sync::{Mutex, OnceLock};
8-
use std::thread::JoinHandle;
8+
use std::sync::Mutex;
99

1010
use crate::invoke_cli;
11-
use crate::modules::{start_runtime, CompilationMode, CompiledModule};
11+
use crate::modules::{CompilationMode, CompiledModule};
1212
use tempfile::TempDir;
1313

14-
/// Ensure that the server thread we're testing against is still running, starting
15-
/// it if it hasn't been started yet.
16-
pub fn ensure_standalone_process() -> &'static SpacetimePaths {
17-
static PATHS: OnceLock<SpacetimePaths> = OnceLock::new();
18-
static JOIN_HANDLE: OnceLock<Mutex<Option<JoinHandle<anyhow::Result<()>>>>> = OnceLock::new();
19-
20-
let paths = PATHS.get_or_init(|| {
21-
let dir = TempDir::with_prefix("stdb-sdk-test")
22-
.expect("Failed to create tempdir")
23-
// TODO: This leaks the tempdir.
24-
// We need the tempdir to live for the duration of the process,
25-
// and all the options for post-`main` cleanup seem sketchy.
26-
.keep();
27-
SpacetimePaths::from_root_dir(&RootDir(dir))
28-
});
29-
30-
let join_handle = JOIN_HANDLE.get_or_init(|| {
31-
Mutex::new(Some(std::thread::spawn(move || {
32-
start_runtime().block_on(spacetimedb_standalone::start_server(
33-
&paths.data_dir,
34-
Some(&paths.cli_config_dir.0),
35-
))
36-
})))
37-
});
38-
39-
let mut join_handle = join_handle.lock().unwrap_or_else(|e| e.into_inner());
40-
41-
if join_handle
42-
.as_ref()
43-
.expect("Standalone process already finished")
44-
.is_finished()
45-
{
46-
match join_handle.take().unwrap().join() {
47-
Ok(Ok(())) => {}
48-
Ok(Err(e)) => panic!("standalone process failed: {e:?}"),
49-
Err(e) => {
50-
let msg = if let Some(s) = e.downcast_ref::<String>() {
51-
s
52-
} else if let Some(s) = e.downcast_ref::<&str>() {
53-
s
54-
} else {
55-
"dyn Any"
56-
};
57-
panic!("standalone process failed by panic: {msg}")
58-
}
59-
}
60-
}
14+
struct SdkTestPaths {
15+
paths: SpacetimePaths,
16+
_root: TempDir,
17+
}
6118

62-
paths
19+
impl SdkTestPaths {
20+
fn new() -> Self {
21+
let root = TempDir::with_prefix("stdb-sdk-test").expect("Failed to create tempdir");
22+
let paths = SpacetimePaths::from_root_dir(&RootDir(root.path().to_path_buf()));
23+
Self { paths, _root: root }
24+
}
6325
}
6426

6527
pub struct Test {
@@ -105,11 +67,13 @@ pub struct Test {
10567
/// Will run with access to the env vars:
10668
/// - `SPACETIME_SDK_TEST_CLIENT_PROJECT` bound to the `client_project` path.
10769
/// - `SPACETIME_SDK_TEST_DB_NAME` bound to the database identity or name.
70+
/// - `SPACETIME_SDK_TEST_SERVER_URL` bound to the server URL for this test.
10871
run_command: String,
10972
}
11073

11174
pub const TEST_MODULE_PROJECT_ENV_VAR: &str = "SPACETIME_SDK_TEST_MODULE_PROJECT";
11275
pub const TEST_DB_NAME_ENV_VAR: &str = "SPACETIME_SDK_TEST_DB_NAME";
76+
pub const TEST_SERVER_URL_ENV_VAR: &str = "SPACETIME_SDK_TEST_SERVER_URL";
11377
pub const TEST_CLIENT_PROJECT_ENV_VAR: &str = "SPACETIME_SDK_TEST_CLIENT_PROJECT";
11478

11579
fn language_is_unreal(language: &str) -> bool {
@@ -121,7 +85,8 @@ impl Test {
12185
TestBuilder::default()
12286
}
12387
pub fn run(self) {
124-
let paths = ensure_standalone_process();
88+
let sdk_paths = SdkTestPaths::new();
89+
let paths = &sdk_paths.paths;
12590

12691
let (file, host_type) = compile_module(&self.module_name);
12792

@@ -137,9 +102,11 @@ impl Test {
137102

138103
compile_client(&self.compile_command, &self.client_project);
139104

140-
let db_name = publish_module(paths, &file, host_type);
105+
let guard = SpacetimeDbGuard::spawn_in_temp_data_dir();
106+
let server_url = guard.host_url.as_str();
107+
let db_name = publish_module(paths, server_url, &file, host_type);
141108

142-
run_client(&self.run_command, &self.client_project, &db_name);
109+
run_client(&self.run_command, &self.client_project, server_url, &db_name);
143110
}
144111
}
145112

@@ -213,15 +180,15 @@ fn compile_module(module: &str) -> (String, HostType) {
213180

214181
// Note: this function does not memoize because we want each test to publish the same
215182
// module as a separate clean database instance for isolation purposes.
216-
fn publish_module(paths: &SpacetimePaths, wasm_file: &str, host_type: HostType) -> String {
183+
fn publish_module(paths: &SpacetimePaths, server_url: &str, wasm_file: &str, host_type: HostType) -> String {
217184
let name = random_module_name();
218185
invoke_cli(
219186
paths,
220187
&[
221188
"publish",
222189
"--anonymous",
223190
"--server",
224-
"local",
191+
server_url,
225192
match host_type {
226193
HostType::Wasm => "--bin-path",
227194
HostType::Js => "--js-path",
@@ -268,10 +235,7 @@ fn publish_module(paths: &SpacetimePaths, wasm_file: &str, host_type: HostType)
268235
/// If you need bindings for multiple different modules, put them in different subdirs.
269236
/// - If multiple distinct test harness processes run concurrently,
270237
/// they will encounter the race condition described above,
271-
/// because the `BINDINGS_GENERATED` lock is not shared between harness processes.
272-
/// Running multiple test harness processes concurrently will break anyways
273-
/// because each will try to run `spacetime start` as a subprocess and will therefore
274-
/// contend over port 3000.
238+
/// because the binding-generation lock is not shared between harness processes.
275239
/// Prefer constructing multiple `Test`s and `Test::run`ing them
276240
/// from within the same harness process.
277241
//
@@ -384,12 +348,13 @@ fn compile_client(compile_command: &str, client_project: &str) {
384348
})
385349
}
386350

387-
fn run_client(run_command: &str, client_project: &str, db_name: &str) {
351+
fn run_client(run_command: &str, client_project: &str, server_url: &str, db_name: &str) {
388352
let (exe, args) = split_command_string(run_command);
389353

390354
let output = cmd(exe, args)
391355
.dir(client_project)
392356
.env(TEST_CLIENT_PROJECT_ENV_VAR, client_project)
357+
.env(TEST_SERVER_URL_ENV_VAR, server_url)
393358
.env(TEST_DB_NAME_ENV_VAR, db_name)
394359
.env(
395360
"RUST_LOG",

modules/sdk-test-procedure-cpp/src/lib.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,18 @@ SPACETIMEDB_PROCEDURE(Unit, insert_with_tx_rollback, ProcedureContext ctx) {
140140
#ifdef SPACETIMEDB_UNSTABLE_FEATURES
141141

142142
// Test HTTP GET request to the module's own schema endpoint
143-
SPACETIMEDB_PROCEDURE(std::string, read_my_schema, ProcedureContext ctx) {
143+
SPACETIMEDB_PROCEDURE(std::string, read_my_schema, ProcedureContext ctx, std::string server_url) {
144144
// Get the module identity (database address)
145145
Identity module_identity = ctx.database_identity();
146146
std::string identity_hex = module_identity.to_hex_string();
147+
while (!server_url.empty() && server_url.back() == '/') {
148+
server_url.pop_back();
149+
}
147150

148151
LOG_INFO("read_my_schema using identity: " + identity_hex);
149152

150153
// Make HTTP GET request to the schema endpoint (matches Rust)
151-
std::string url = "http://localhost:3000/v1/database/" + identity_hex + "/schema?version=9";
154+
std::string url = server_url + "/v1/database/" + identity_hex + "/schema?version=9";
152155
auto result = ctx.http.get(url);
153156

154157
if (!result.is_ok()) {

modules/sdk-test-procedure-cs/Lib.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,11 @@ public static void WillPanic(ProcedureContext ctx)
6666
/// Test HTTP GET request to the module's own schema endpoint
6767
/// </summary>
6868
[SpacetimeDB.Procedure]
69-
public static string ReadMySchema(ProcedureContext ctx)
69+
public static string ReadMySchema(ProcedureContext ctx, string serverUrl)
7070
{
7171
var moduleIdentity = ProcedureContextBase.Identity;
72-
var result = ctx.Http.Get($"http://localhost:3000/v1/database/{moduleIdentity}/schema?version=9");
72+
serverUrl = serverUrl.TrimEnd('/');
73+
var result = ctx.Http.Get($"{serverUrl}/v1/database/{moduleIdentity}/schema?version=9");
7374
return result.Match(
7475
response => response.Body.ToStringUtf8Lossy(),
7576
error => throw new Exception($"HTTP request failed: {error}")
@@ -243,4 +244,4 @@ public static void SortedUuidsInsert(ProcedureContext ctx)
243244
return 0;
244245
});
245246
}
246-
}
247+
}

modules/sdk-test-procedure-ts/src/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,18 @@ export const will_panic = spacetimedb.procedure(t.unit(), _ctx => {
9090
throw new Error('This procedure is expected to panic');
9191
});
9292

93-
export const read_my_schema = spacetimedb.procedure(t.string(), ctx => {
94-
const module_identity = ctx.databaseIdentity;
95-
const response = ctx.http.fetch(
96-
`http://localhost:3000/v1/database/${module_identity}/schema?version=9`
97-
);
98-
return response.text();
99-
});
93+
export const read_my_schema = spacetimedb.procedure(
94+
{ server_url: t.string() },
95+
t.string(),
96+
(ctx, { server_url }) => {
97+
const module_identity = ctx.databaseIdentity;
98+
const base_url = server_url.replace(/\/+$/, '');
99+
const response = ctx.http.fetch(
100+
`${base_url}/v1/database/${module_identity}/schema?version=9`
101+
);
102+
return response.text();
103+
}
104+
);
100105

101106
export const invalid_request = spacetimedb.procedure(t.string(), ctx => {
102107
try {

modules/sdk-test-procedure/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ fn will_panic(_ctx: &mut ProcedureContext) {
4141
}
4242

4343
#[procedure]
44-
fn read_my_schema(ctx: &mut ProcedureContext) -> String {
44+
fn read_my_schema(ctx: &mut ProcedureContext, server_url: String) -> String {
4545
let module_identity = ctx.identity();
46-
match ctx.http.get(format!(
47-
"http://localhost:3000/v1/database/{module_identity}/schema?version=9"
48-
)) {
46+
let server_url = server_url.trim_end_matches('/');
47+
match ctx
48+
.http
49+
.get(format!("{server_url}/v1/database/{module_identity}/schema?version=9"))
50+
{
4951
Ok(result) => result.into_body().into_string_lossy(),
5052
Err(e) => panic!("{e}"),
5153
}

sdks/csharp/examples~/regression-tests/procedure-client/module_bindings/Procedures/ReadMySchema.g.cs

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

sdks/rust/tests/case-conversion-client/src/main.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ use module_bindings::*;
88
use spacetimedb_sdk::error::InternalError;
99
use spacetimedb_sdk::{DbContext, Table, TableWithPrimaryKey};
1010
use std::sync::Arc;
11-
use test_counter::TestCounter;
12-
13-
const LOCALHOST: &str = "http://localhost:3000";
11+
use test_counter::{server_url, TestCounter};
1412

1513
fn db_name_or_panic() -> String {
1614
std::env::var("SPACETIME_SDK_TEST_DB_NAME").expect("Failed to read db name from env")
@@ -83,7 +81,7 @@ fn connect_then(
8381
let name = db_name_or_panic();
8482
let conn = DbConnection::builder()
8583
.with_database_name(name)
86-
.with_uri(LOCALHOST)
84+
.with_uri(server_url())
8785
.on_connect(move |ctx, _, _| {
8886
callback(ctx);
8987
connected_result(Ok(()));

0 commit comments

Comments
 (0)