Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

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

const LOCALHOST = 'http://localhost:3000';
const SERVER_URL = process.env.SPACETIME_SDK_TEST_SERVER_URL;
if (!SERVER_URL) {
throw new Error('Missing SPACETIME_SDK_TEST_SERVER_URL');
}

function dbNameOrPanic(): string {
const name = process.env.SPACETIME_SDK_TEST_DB_NAME;
Expand Down Expand Up @@ -53,7 +56,7 @@ function connectThen(callback: (db: DbConnection) => void): Promise<void> {
return new Promise<void>((resolve, reject) => {
const conn = DbConnection.builder()
.withDatabaseName(name)
.withUri(LOCALHOST)
.withUri(SERVER_URL)
.onConnect((ctx, _identity, _token) => {
try {
callback(ctx);
Expand Down
1 change: 1 addition & 0 deletions crates/testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ spacetimedb-core.workspace = true
spacetimedb-standalone.workspace = true
spacetimedb-client-api.workspace = true
spacetimedb-client-api-messages.workspace = true
spacetimedb-guard.workspace = true
spacetimedb-paths.workspace = true
spacetimedb-schema.workspace = true

Expand Down
87 changes: 26 additions & 61 deletions crates/testing/src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,26 @@ use duct::cmd;
use rand::seq::IteratorRandom;
use spacetimedb::messages::control_db::HostType;
use spacetimedb_data_structures::map::HashMap;
use spacetimedb_guard::SpacetimeDbGuard;
use spacetimedb_paths::{RootDir, SpacetimePaths};
use std::fs::create_dir_all;
use std::sync::{Mutex, OnceLock};
use std::thread::JoinHandle;
use std::sync::Mutex;

use crate::invoke_cli;
use crate::modules::{start_runtime, CompilationMode, CompiledModule};
use crate::modules::{CompilationMode, CompiledModule};
use tempfile::TempDir;

/// Ensure that the server thread we're testing against is still running, starting
/// it if it hasn't been started yet.
pub fn ensure_standalone_process() -> &'static SpacetimePaths {
static PATHS: OnceLock<SpacetimePaths> = OnceLock::new();
static JOIN_HANDLE: OnceLock<Mutex<Option<JoinHandle<anyhow::Result<()>>>>> = OnceLock::new();

let paths = PATHS.get_or_init(|| {
let dir = TempDir::with_prefix("stdb-sdk-test")
.expect("Failed to create tempdir")
// TODO: This leaks the tempdir.
// We need the tempdir to live for the duration of the process,
// and all the options for post-`main` cleanup seem sketchy.
.keep();
SpacetimePaths::from_root_dir(&RootDir(dir))
});

let join_handle = JOIN_HANDLE.get_or_init(|| {
Mutex::new(Some(std::thread::spawn(move || {
start_runtime().block_on(spacetimedb_standalone::start_server(
&paths.data_dir,
Some(&paths.cli_config_dir.0),
))
})))
});

let mut join_handle = join_handle.lock().unwrap_or_else(|e| e.into_inner());

if join_handle
.as_ref()
.expect("Standalone process already finished")
.is_finished()
{
match join_handle.take().unwrap().join() {
Ok(Ok(())) => {}
Ok(Err(e)) => panic!("standalone process failed: {e:?}"),
Err(e) => {
let msg = if let Some(s) = e.downcast_ref::<String>() {
s
} else if let Some(s) = e.downcast_ref::<&str>() {
s
} else {
"dyn Any"
};
panic!("standalone process failed by panic: {msg}")
}
}
}
struct SdkTestPaths {
paths: SpacetimePaths,
_root: TempDir,
}

paths
impl SdkTestPaths {
fn new() -> Self {
let root = TempDir::with_prefix("stdb-sdk-test").expect("Failed to create tempdir");
let paths = SpacetimePaths::from_root_dir(&RootDir(root.path().to_path_buf()));
Self { paths, _root: root }
}
}

pub struct Test {
Expand Down Expand Up @@ -105,11 +67,13 @@ pub struct Test {
/// Will run with access to the env vars:
/// - `SPACETIME_SDK_TEST_CLIENT_PROJECT` bound to the `client_project` path.
/// - `SPACETIME_SDK_TEST_DB_NAME` bound to the database identity or name.
/// - `SPACETIME_SDK_TEST_SERVER_URL` bound to the server URL for this test.
run_command: String,
}

pub const TEST_MODULE_PROJECT_ENV_VAR: &str = "SPACETIME_SDK_TEST_MODULE_PROJECT";
pub const TEST_DB_NAME_ENV_VAR: &str = "SPACETIME_SDK_TEST_DB_NAME";
pub const TEST_SERVER_URL_ENV_VAR: &str = "SPACETIME_SDK_TEST_SERVER_URL";
pub const TEST_CLIENT_PROJECT_ENV_VAR: &str = "SPACETIME_SDK_TEST_CLIENT_PROJECT";

fn language_is_unreal(language: &str) -> bool {
Expand All @@ -121,7 +85,8 @@ impl Test {
TestBuilder::default()
}
pub fn run(self) {
let paths = ensure_standalone_process();
let sdk_paths = SdkTestPaths::new();
let paths = &sdk_paths.paths;

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

Expand All @@ -137,9 +102,11 @@ impl Test {

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

let db_name = publish_module(paths, &file, host_type);
let guard = SpacetimeDbGuard::spawn_in_temp_data_dir();
let server_url = guard.host_url.as_str();
let db_name = publish_module(paths, server_url, &file, host_type);

run_client(&self.run_command, &self.client_project, &db_name);
run_client(&self.run_command, &self.client_project, server_url, &db_name);
}
}

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

// Note: this function does not memoize because we want each test to publish the same
// module as a separate clean database instance for isolation purposes.
fn publish_module(paths: &SpacetimePaths, wasm_file: &str, host_type: HostType) -> String {
fn publish_module(paths: &SpacetimePaths, server_url: &str, wasm_file: &str, host_type: HostType) -> String {
let name = random_module_name();
invoke_cli(
paths,
&[
"publish",
"--anonymous",
"--server",
"local",
server_url,
match host_type {
HostType::Wasm => "--bin-path",
HostType::Js => "--js-path",
Expand Down Expand Up @@ -268,10 +235,7 @@ fn publish_module(paths: &SpacetimePaths, wasm_file: &str, host_type: HostType)
/// If you need bindings for multiple different modules, put them in different subdirs.
/// - If multiple distinct test harness processes run concurrently,
/// they will encounter the race condition described above,
/// because the `BINDINGS_GENERATED` lock is not shared between harness processes.
/// Running multiple test harness processes concurrently will break anyways
/// because each will try to run `spacetime start` as a subprocess and will therefore
/// contend over port 3000.
/// because the binding-generation lock is not shared between harness processes.
/// Prefer constructing multiple `Test`s and `Test::run`ing them
/// from within the same harness process.
//
Expand Down Expand Up @@ -384,12 +348,13 @@ fn compile_client(compile_command: &str, client_project: &str) {
})
}

fn run_client(run_command: &str, client_project: &str, db_name: &str) {
fn run_client(run_command: &str, client_project: &str, server_url: &str, db_name: &str) {
let (exe, args) = split_command_string(run_command);

let output = cmd(exe, args)
.dir(client_project)
.env(TEST_CLIENT_PROJECT_ENV_VAR, client_project)
.env(TEST_SERVER_URL_ENV_VAR, server_url)
.env(TEST_DB_NAME_ENV_VAR, db_name)
.env(
"RUST_LOG",
Expand Down
7 changes: 5 additions & 2 deletions modules/sdk-test-procedure-cpp/src/lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,18 @@ SPACETIMEDB_PROCEDURE(Unit, insert_with_tx_rollback, ProcedureContext ctx) {
#ifdef SPACETIMEDB_UNSTABLE_FEATURES

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

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

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

if (!result.is_ok()) {
Expand Down
7 changes: 4 additions & 3 deletions modules/sdk-test-procedure-cs/Lib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ public static void WillPanic(ProcedureContext ctx)
/// Test HTTP GET request to the module's own schema endpoint
/// </summary>
[SpacetimeDB.Procedure]
public static string ReadMySchema(ProcedureContext ctx)
public static string ReadMySchema(ProcedureContext ctx, string serverUrl)
{
var moduleIdentity = ProcedureContextBase.Identity;
var result = ctx.Http.Get($"http://localhost:3000/v1/database/{moduleIdentity}/schema?version=9");
serverUrl = serverUrl.TrimEnd('/');
var result = ctx.Http.Get($"{serverUrl}/v1/database/{moduleIdentity}/schema?version=9");
return result.Match(
response => response.Body.ToStringUtf8Lossy(),
error => throw new Exception($"HTTP request failed: {error}")
Expand Down Expand Up @@ -243,4 +244,4 @@ public static void SortedUuidsInsert(ProcedureContext ctx)
return 0;
});
}
}
}
19 changes: 12 additions & 7 deletions modules/sdk-test-procedure-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,18 @@ export const will_panic = spacetimedb.procedure(t.unit(), _ctx => {
throw new Error('This procedure is expected to panic');
});

export const read_my_schema = spacetimedb.procedure(t.string(), ctx => {
const module_identity = ctx.databaseIdentity;
const response = ctx.http.fetch(
`http://localhost:3000/v1/database/${module_identity}/schema?version=9`
);
return response.text();
});
export const read_my_schema = spacetimedb.procedure(
{ server_url: t.string() },
t.string(),
(ctx, { server_url }) => {
const module_identity = ctx.databaseIdentity;
const base_url = server_url.replace(/\/+$/, '');
const response = ctx.http.fetch(
`${base_url}/v1/database/${module_identity}/schema?version=9`
);
return response.text();
}
);

export const invalid_request = spacetimedb.procedure(t.string(), ctx => {
try {
Expand Down
10 changes: 6 additions & 4 deletions modules/sdk-test-procedure/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ fn will_panic(_ctx: &mut ProcedureContext) {
}

#[procedure]
fn read_my_schema(ctx: &mut ProcedureContext) -> String {
fn read_my_schema(ctx: &mut ProcedureContext, server_url: String) -> String {
let module_identity = ctx.identity();
match ctx.http.get(format!(
"http://localhost:3000/v1/database/{module_identity}/schema?version=9"
)) {
let server_url = server_url.trim_end_matches('/');
match ctx
.http
.get(format!("{server_url}/v1/database/{module_identity}/schema?version=9"))
{
Ok(result) => result.into_body().into_string_lossy(),
Err(e) => panic!("{e}"),
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions sdks/rust/tests/case-conversion-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ use module_bindings::*;
use spacetimedb_sdk::error::InternalError;
use spacetimedb_sdk::{DbContext, Table, TableWithPrimaryKey};
use std::sync::Arc;
use test_counter::TestCounter;

const LOCALHOST: &str = "http://localhost:3000";
use test_counter::{server_url, TestCounter};

fn db_name_or_panic() -> String {
std::env::var("SPACETIME_SDK_TEST_DB_NAME").expect("Failed to read db name from env")
Expand Down Expand Up @@ -83,7 +81,7 @@ fn connect_then(
let name = db_name_or_panic();
let conn = DbConnection::builder()
.with_database_name(name)
.with_uri(LOCALHOST)
.with_uri(server_url())
.on_connect(move |ctx, _, _| {
callback(ctx);
connected_result(Ok(()));
Expand Down
3 changes: 2 additions & 1 deletion sdks/rust/tests/connect_disconnect_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use wasm_bindgen::prelude::wasm_bindgen;

#[cfg(all(target_arch = "wasm32", feature = "browser"))]
#[wasm_bindgen]
pub async fn run(_test_name: String, db_name: String) {
pub async fn run(_test_name: String, db_name: String, server_url: String) {
console_error_panic_hook::set_once();
// The shared wasm test harness always passes `(test_name, db_name)`, even for
// fixed-flow clients like this one that ignore the selector.
test_counter::set_server_url(server_url);
test_handlers::dispatch(&db_name).await;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ use crate::module_bindings::*;

use spacetimedb_sdk::{DbConnectionBuilder, DbContext, Table};

use test_counter::TestCounter;

const LOCALHOST: &str = "http://localhost:3000";
use test_counter::{server_url, TestCounter};

pub async fn dispatch(db_name: &str) {
let disconnect_test_counter = TestCounter::new();
Expand All @@ -16,7 +14,7 @@ pub async fn dispatch(db_name: &str) {

let connection = DbConnection::builder()
.with_database_name(db_name)
.with_uri(LOCALHOST)
.with_uri(server_url())
.on_connect_error(|_ctx, error| panic!("on_connect_error: {error:?}"))
.on_connect(move |ctx, _, _| {
connected_result(Ok(()));
Expand Down Expand Up @@ -80,7 +78,7 @@ pub async fn dispatch(db_name: &str) {
reconnected_result(Ok(()));
})
.with_database_name(db_name)
.with_uri(LOCALHOST);
.with_uri(server_url());
let new_connection = build_connection(new_connection).await;

new_connection
Expand Down
Loading
Loading