Skip to content

Conversation

@cloutiertyler
Copy link
Contributor

@cloutiertyler cloutiertyler commented Jan 23, 2026

Description of Changes

This PR translates all of our Python smoketests into Rust tests which can be run from cargo run

Motivation

The purpose of this fivefold:

  1. All developers on the team are familiar with Rust
  2. It simplifies our devops because we can drop Python as a dependency to run the tests
  3. You can now run all tests in the repo through the single cargo test interface
  4. Because we use the SpacetimeDbGuard and cargo test/cargo nextest we can easily parallelize the smoke tests
  5. The smoketests can now use machinery imported from SpacetimeDB crates (e.g. bsatn etc.)

API and ABI breaking changes

None

Expected complexity level and risk

3, this is partially AI translated. We need to carefully review to ensure the semantics have not regressed.

Testing

Use OnceLock to build spacetimedb-cli and spacetimedb-standalone once
per test process, then run the pre-built binary directly instead of
using `cargo run`. This avoids repeated cargo overhead and ensures
consistent binary reuse across parallel tests.
Create `crates/smoketests/` to translate Python smoketests to Rust:

- Add `Smoketest` struct with builder pattern for test setup
- Implement CLI helpers: `spacetime_cmd()`, `call()`, `sql()`, `logs()`, etc.
- Translate `smoketests/tests/sql.py` → `tests/sql.rs`
- Translate `smoketests/tests/call.py` → `tests/call.rs`
- Reuse `ensure_binaries_built()` from guard crate (now public)

Also fix Windows process cleanup in `SpacetimeDbGuard`:
- Use `taskkill /F /T /PID` to kill entire process tree
- Prevents orphaned `spacetimedb-standalone.exe` processes
- Translate 4 more Python smoketests to Rust: auto_inc, describe,
  module_nested_op, panic
- Simplify the call API by removing the generic call<T: Serialize>
  method and renaming call_raw to call, since CLI args are strings
- Remove unused serde dependency
Translate additional Python smoketests to Rust:
- dml.rs: DML subscription tests
- filtering.rs: Unique/non-unique index filtering tests
- namespaces.rs: C# code generation namespace tests
- add_remove_index.rs: Index add/remove with subscription tests
- schedule_reducer.rs: Scheduled reducer tests

Infrastructure improvements:
- Add subscribe_background() and SubscriptionHandle for proper
  background subscription semantics matching Python tests
- Add spacetime_local() for commands that don't need --server flag
- Add timing instrumentation for debugging test performance
- Separate build and publish timing in lib.rs to identify bottlenecks
- Use --bin-path to skip redundant rebuild during publish
- Add DEVELOP.md explaining cargo-nextest for faster test runs

Timing breakdown per test:
- WASM build: ~12s (75%)
- Server publish: ~2s (12%)
- Server spawn: ~2s (12%)

cargo-nextest runs all test binaries in parallel, reducing total
runtime from ~265s to ~160s (40% faster).
@cloutiertyler cloutiertyler changed the title Tyler/translate smoketests Translate smoketests from Python to Rust Jan 23, 2026
Translate from Python smoketests:
- detect_wasm_bindgen.rs: Tests build rejects wasm_bindgen and getrandom (2 tests)
- default_module_clippy.rs: Tests default module passes clippy
- delete_database.rs: Tests deleting database stops scheduled reducers
- fail_initial_publish.rs: Tests failed publish doesn't corrupt control DB
- modules.rs: Tests module update lifecycle and breaking changes (2 tests)

Also adds spacetime_build() method to Smoketest for testing build failures.

Total: 16 test files translated, 32 tests
Clear CARGO* environment variables (except CARGO_HOME) when spawning
child cargo build processes. When running under `cargo test`, cargo
sets env vars like CARGO_ENCODED_RUSTFLAGS that differ from a normal
build, causing child cargo processes to think they need to recompile.

This reduces single-test runtime from ~45s to ~18s by avoiding
redundant rebuilds of spacetimedb-standalone and spacetimedb-cli.
Add test translations for:
- connect_disconnect_from_cli.rs - client connection callbacks
- domains.rs - database rename functionality
- client_connection_errors.rs - client_connected error handling
- confirmed_reads.rs - --confirmed flag for subscriptions/SQL
- create_project.rs - spacetime init command

Also fix subscription race condition by waiting for initial update
before returning from subscribe_background_*, matching Python behavior.
Translate tests for:
- views.rs: st_view_* system tables, namespace collisions, SQL views
- auto_migration.rs: schema changes, add table migration
@cloutiertyler cloutiertyler force-pushed the tyler/translate-smoketests branch from bda7652 to bea9069 Compare January 23, 2026 07:06
Add new_identity() method to support multi-identity tests.

Translate tests for:
- rls.rs: Row-level security filter tests
- energy.rs: Energy balance endpoint test
- permissions.rs: Private tables, lifecycle reducers, delete protection
Translate tests for:
- new_user_flow.rs: Basic publish/call/SQL workflow
- servers.rs: Server add/list/edit commands
docker_smoketests:
needs: [lints]
# TODO: before merging re-enable this
# needs: [lints]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Address this before merging

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdetter they're not actually running via cargo ci smoketests right now.. also it uses the version of this workflow from the base PR so this didn't make a difference 😅

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay wired up this command to actually run the new smoketests!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Copy link
Collaborator

@jdetter jdetter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like these changes so far, I'm excited to talk about this more later today 👍

fn test_add_then_remove_index() {
let mut test = Smoketest::builder().module_code(MODULE_CODE).autopublish(false).build();

let name = format!("test-db-{}", std::process::id());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would have been an issue if we were to parallelize the smoketests but now we're running 1 spacetime server per test right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually kind of weird now that we're appending the process ID to the name - why not just use a static name like "test-db"?

#[test]
fn test_autoinc_i64() {
do_test_autoinc_basic("i64");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of just testing these 4 here I would have 1 test which iterates over the array of types we used to test before:

"u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128"

#[test]
fn test_autoinc_unique_i64() {
do_test_autoinc_unique("i64");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, we're only test 2 of the 10 we used to test before.

let identity = test.database_identity.as_ref().unwrap();

// Describe the whole module
test.spacetime(&["describe", "--json", identity]).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we weren't doing it before - but we're not doing any output validation here. Possibly worth a TODO.

@@ -0,0 +1,63 @@
//! Tests translated from smoketests/tests/new_user_flow.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test originally was testing to make sure that our tutorial isn't broken. Since our onboarding has changed we should probably update this test in the future. Probably worth dropping a TODO here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 80/20 here might just be replacing this with testing the templates to be honest

// First, run spacetime build to compile the WASM module (separate from publish)
let build_start = Instant::now();
let cli_path = ensure_binaries_built();
let build_output = Command::new(&cli_path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This takes 8-10s on my machine for every test case.

Could we not, instead of a fresh tempdir for each test on every run, put all the module cargo projects under CARGO_TARGET_TMPDIR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm investigating this, because indeed building the modules takes the vast majority of the time of the smoke tests.

Not for nothing but also rewriting the smoke tests to run in TypeScript would publish them much faster. Although probably we should have both languages so that we're smoke testing v8.

@@ -0,0 +1,89 @@
//! Add/remove index tests translated from smoketests/tests/add_remove_index.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that each test is linked into its own executable.
It's also possible to structure it like this:

tests/
  smoketests.rs
  smoketests/
    mod.rs
    add_remove_index.rs
    auto_inc.rs
    ...

Which will create a single executable and save a few seconds on link time.
The namespacing will also make it nicer to filter subsets of tests to run, which has been a frequent source of frustration with the python smoketests.

/// returning the path to the CLI binary.
///
/// This is useful for tests that need to run CLI commands directly.
pub fn ensure_binaries_built() -> PathBuf {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so great about this approach is that there is no feedback as to what it is going on, all while the test runner tells the user that the tests are running for over 60s (in yellow even, when using nextest).

I'd also expect recursive cargo to create problems on certain systems, despite the env var hack.

Why not just create an xtask for running smoketests? Arguably, typing cargo smoketests isn't worse than cargo test -p spacetimedb-smoketests.

Invoking cargo from an xtask is fine, plus we can customize the output however we please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can fix up the feedback with cargo nextest, so at least that I can resolve.

I'm not opposed to creating an xtask per se, I just wanted it to be so that any schmo could run all of our tests without needing to know about our special special tools (i.e. cargo test runs all the tests), but I could be convinced.

In any case this PR is not a regression since I'm largely reusing existing machinery. I would request that we change that up in a separate PR.

cloutiertyler and others added 12 commits January 23, 2026 11:42
- Add --config-path to spacetime_local() for test isolation
- Fix new_identity() to not pass server arg to logout (matches Python)
- Insert --server flag before -- separator in spacetime_cmd()
- Update servers.rs to use spacetime_local() for local-only commands
- Simplify test files by removing redundant publish_module() calls

All 56 smoketests now pass.
Translate smoketests/tests/quickstart.py to Rust. This test validates
that the quickstart documentation is correct by extracting code from
markdown docs and running it.

- Add parse_quickstart() to parse code blocks from markdown with CRLF handling
- Add have_pnpm() to check for pnpm availability
- Implement QuickstartTest with support for Rust, C#, and TypeScript servers
- Rust test passes; C#/TypeScript skip gracefully if dependencies unavailable
bfops and others added 7 commits January 23, 2026 11:08
…s' of github.com:clockworklabs/SpacetimeDB into tyler/translate-smoketests
Translate server restart tests from smoketests/tests/zz_docker.py to Rust.
These tests verify SpacetimeDB behavior across server restarts:
- Data persistence (test_restart_module)
- SQL queries after restart (test_restart_sql)
- Client auto-disconnection (test_restart_auto_disconnect)
- Autoinc sequence integrity (test_add_remove_index_after_restart)

Infrastructure changes:
- Add data_dir and restart() to SpacetimeDbGuard
- Add restart_server() to Smoketest
- Consolidate duplicated kill/spawn logic into helpers
@cloutiertyler cloutiertyler marked this pull request as ready for review January 23, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants