Files
Tyler Cloutier 42f679e612 Add remote server support to Rust smoketests
Enable running Rust smoketests against a remote server instead of
spawning local servers, similar to Python smoketests --remote-server.

- Add SPACETIME_REMOTE_SERVER env var support to skip local server spawn
- Add --server CLI option to cargo smoketest
- Add skip_if_remote!() macro for tests requiring local server control
- Mark restart tests with skip_if_remote!() since they need local server
2026-01-26 23:27:18 -05:00

198 lines
5.9 KiB
Rust

#![allow(clippy::disallowed_macros)]
use anyhow::{ensure, Result};
use clap::{Parser, Subcommand};
use std::env;
use std::process::{Command, Stdio};
/// SpacetimeDB development tasks
#[derive(Parser)]
#[command(name = "cargo xtask")]
struct Cli {
#[command(subcommand)]
cmd: XtaskCmd,
}
#[derive(Subcommand)]
enum XtaskCmd {
/// Run smoketests with pre-built binaries
///
/// This command first builds the spacetimedb-cli and spacetimedb-standalone binaries,
/// then runs the smoketests. This prevents race conditions when running tests in parallel
/// with nextest, where multiple test processes might try to build the same binaries
/// simultaneously.
Smoketest {
#[command(subcommand)]
cmd: Option<SmoketestCmd>,
/// Run tests against a remote server instead of spawning local servers.
///
/// When specified, tests will connect to the given URL instead of starting
/// local server instances. Tests that require local server control (like
/// restart tests) will be skipped.
#[arg(long)]
server: Option<String>,
/// Additional arguments to pass to the test runner
#[arg(trailing_var_arg = true)]
args: Vec<String>,
},
}
#[derive(Subcommand)]
enum SmoketestCmd {
/// Only build binaries without running tests
///
/// Use this before running `cargo test --all` to ensure binaries are built.
Prepare,
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.cmd {
XtaskCmd::Smoketest {
cmd: Some(SmoketestCmd::Prepare),
..
} => {
build_binaries()?;
eprintln!("Binaries ready. You can now run `cargo test --all`.");
Ok(())
}
XtaskCmd::Smoketest {
cmd: None,
server,
args,
} => run_smoketest(server, args),
}
}
fn build_binaries() -> Result<()> {
eprintln!("Building spacetimedb-cli and spacetimedb-standalone (release)...");
let mut cmd = Command::new("cargo");
cmd.args([
"build",
"--release",
"-p",
"spacetimedb-cli",
"-p",
"spacetimedb-standalone",
]);
// Remove cargo/rust env vars that could cause fingerprint mismatches
// when the test later runs cargo build from a different environment
for (key, _) in env::vars() {
let should_remove = (key.starts_with("CARGO") && key != "CARGO_HOME" && key != "CARGO_TARGET_DIR")
|| key.starts_with("RUST")
|| key == "__CARGO_FIX_YOLO";
if should_remove {
cmd.env_remove(&key);
}
}
let status = cmd.status()?;
ensure!(status.success(), "Failed to build binaries");
eprintln!("Binaries built successfully.\n");
Ok(())
}
fn build_precompiled_modules() -> Result<()> {
let workspace_root = env::current_dir()?;
let modules_dir = workspace_root.join("crates/smoketests/modules");
// Check if the modules workspace exists
if !modules_dir.join("Cargo.toml").exists() {
eprintln!("Skipping pre-compiled modules (workspace not found).\n");
return Ok(());
}
eprintln!("Building pre-compiled smoketest modules...");
let status = Command::new("cargo")
.args([
"build",
"--workspace",
"--release",
"--target",
"wasm32-unknown-unknown",
])
.current_dir(&modules_dir)
.status()?;
ensure!(status.success(), "Failed to build pre-compiled modules");
eprintln!("Pre-compiled modules built.\n");
Ok(())
}
/// Default parallelism for smoketests.
/// 16 was found to be optimal - higher values cause OS scheduler overhead.
const DEFAULT_PARALLELISM: &str = "16";
fn run_smoketest(server: Option<String>, args: Vec<String>) -> Result<()> {
// 1. Build binaries first (single process, no race)
build_binaries()?;
// 2. Build pre-compiled modules (this also warms the WASM dependency cache)
build_precompiled_modules()?;
// 4. Detect whether to use nextest or cargo test
let use_nextest = Command::new("cargo")
.args(["nextest", "--version"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
// 5. Run tests with appropriate runner (release mode for faster execution)
let status = if use_nextest {
if server.is_some() {
eprintln!("Running smoketests against remote server with cargo nextest (release)...\n");
} else {
eprintln!("Running smoketests with cargo nextest (release)...\n");
}
let mut cmd = Command::new("cargo");
cmd.args([
"nextest",
"run",
"--release",
"-p",
"spacetimedb-smoketests",
"--no-fail-fast",
]);
// Set default parallelism if user didn't specify -j
if !args
.iter()
.any(|a| a == "-j" || a.starts_with("-j") || a.starts_with("--jobs"))
{
cmd.args(["-j", DEFAULT_PARALLELISM]);
}
// Set remote server environment variable if specified
if let Some(ref server_url) = server {
cmd.env("SPACETIME_REMOTE_SERVER", server_url);
}
cmd.args(&args).status()?
} else {
if server.is_some() {
eprintln!("Running smoketests against remote server with cargo test (release)...\n");
} else {
eprintln!("Running smoketests with cargo test (release)...\n");
}
let mut cmd = Command::new("cargo");
cmd.args(["test", "--release", "-p", "spacetimedb-smoketests"]);
// Set remote server environment variable if specified
if let Some(ref server_url) = server {
cmd.env("SPACETIME_REMOTE_SERVER", server_url);
}
cmd.args(&args).status()?
};
ensure!(status.success(), "Tests failed");
Ok(())
}