diff --git a/crates/smoketests/DEVELOP.md b/crates/smoketests/DEVELOP.md index 6acba49aea2..43698b527ae 100644 --- a/crates/smoketests/DEVELOP.md +++ b/crates/smoketests/DEVELOP.md @@ -10,7 +10,8 @@ cargo smoketest This command: 1. Builds `spacetimedb-cli` and `spacetimedb-standalone` binaries -2. Runs all smoketests in parallel using nextest (or cargo test if nextest isn't installed) +2. Builds the Rust fixture workspace in `crates/smoketests/modules/` to WASM +3. Runs all smoketests in parallel using nextest (or cargo test if nextest isn't installed) To run specific tests: ```bash @@ -51,7 +52,8 @@ cargo smoketest # Option 2: Manually rebuild, then run tests directly cargo build -p spacetimedb-cli -p spacetimedb-standalone --features spacetimedb-standalone/allow_loopback_http_for_tests -cargo nextest run -p spacetimedb-smoketests +cargo build --manifest-path crates/smoketests/modules/Cargo.toml --workspace --release --target wasm32-unknown-unknown +CARGO_BUILD_PROFILE=debug cargo nextest run -p spacetimedb-smoketests ``` **If you run `cargo nextest run` or `cargo test` directly without rebuilding, @@ -75,44 +77,46 @@ Standard `cargo test` also works, but you must rebuild first: ```bash cargo build -p spacetimedb-cli -p spacetimedb-standalone --features spacetimedb-standalone/allow_loopback_http_for_tests -cargo test -p spacetimedb-smoketests +cargo build --manifest-path crates/smoketests/modules/Cargo.toml --workspace --release --target wasm32-unknown-unknown +CARGO_BUILD_PROFILE=debug cargo test -p spacetimedb-smoketests ``` ## Test Performance -Each test takes ~15-20s due to: -- **WASM compilation** (~12s): Each test compiles a fresh Rust module to WASM -- **Server spawn** (~2s): Each test starts its own SpacetimeDB server -- **Module publish** (~2s): Server processes and initializes the WASM module +Rust fixtures are compiled once during warmup and reused across tests. Ordinary +tests then start a server and publish the selected WASM without invoking Cargo. +Tests of build diagnostics explicitly compile temporary modules. When running tests in parallel, resource contention increases individual test times but reduces overall runtime. ## Writing Tests -See existing tests for patterns. Key points: +Add a fixture crate under `crates/smoketests/modules/`, following an existing +crate's `Cargo.toml` and `src/lib.rs`, and list it in that workspace's members. +The package name `smoketest-module-example` makes it available as `example`: ```rust use spacetimedb_smoketests::Smoketest; -const MODULE_CODE: &str = r#" -use spacetimedb::{ReducerContext, Table}; - -#[spacetimedb::table(accessor = example, public)] -pub struct Example { value: u64 } - -#[spacetimedb::reducer] -pub fn add(ctx: &ReducerContext, value: u64) { - ctx.db.example().insert(Example { value }); -} -"#; - #[test] fn test_example() { let test = Smoketest::builder() - .module_code(MODULE_CODE) + .precompiled_module("example") .build(); test.call("add", &["42"]).unwrap(); test.assert_sql("SELECT * FROM example", "value\n-----\n42"); } ``` + +Place the table and `add` reducer in the fixture's `src/lib.rs`. Use +`test.use_precompiled_module("example-updated")` to switch fixtures for migration +tests. If no module is selected, publishing uses the precompiled `noop` fixture. +`autopublish(false)` leaves the database unpublished and does not need that fixture +until a publish is requested. + +For tests that expect Rust build failures, use `build_rust_module(source, extra_deps)` +and assert the specific diagnostic in its raw output. This helper runs +`spacetime build` without starting a server. Keep ordinary test modules in the +fixture workspace. The `http-handlers-tutorial` fixture shows how a build script +can compile examples directly from current documentation during warmup. diff --git a/crates/smoketests/modules/Cargo.toml b/crates/smoketests/modules/Cargo.toml index 63dc67687eb..8d507a6367f 100644 --- a/crates/smoketests/modules/Cargo.toml +++ b/crates/smoketests/modules/Cargo.toml @@ -14,7 +14,7 @@ members = [ # Views tests "views-basic", - # "views-broken-namespace" - intentionally broken, uses runtime compilation + # "views-broken-namespace" is exercised by an explicit build-failure test "views-broken-return-type", "views-sql", "views-auto-migrate", @@ -47,6 +47,10 @@ members = [ "call-empty", "call-many", + # Column defaults + "column-defaults-initial", + "column-defaults-updated", + # Auto-migration tests "auto-migration-simple", "auto-migration-incompatible", @@ -63,6 +67,7 @@ members = [ "auto-migration-drop-event-table-after", # HTTP tests + "http-handlers-tutorial", "http-egress", "http-routes", "http-routes-example", diff --git a/crates/smoketests/modules/column-defaults-initial/Cargo.toml b/crates/smoketests/modules/column-defaults-initial/Cargo.toml new file mode 100644 index 00000000000..7adbdcb2553 --- /dev/null +++ b/crates/smoketests/modules/column-defaults-initial/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smoketest-module-column-defaults-initial" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true diff --git a/crates/smoketests/modules/column-defaults-initial/src/lib.rs b/crates/smoketests/modules/column-defaults-initial/src/lib.rs new file mode 100644 index 00000000000..b1daede0071 --- /dev/null +++ b/crates/smoketests/modules/column-defaults-initial/src/lib.rs @@ -0,0 +1,4 @@ +#[spacetimedb::table(accessor = defaults_test_table, public)] +pub struct DefaultsTestTable { + pub id: u32, +} diff --git a/crates/smoketests/modules/column-defaults-updated/Cargo.toml b/crates/smoketests/modules/column-defaults-updated/Cargo.toml new file mode 100644 index 00000000000..cb009247597 --- /dev/null +++ b/crates/smoketests/modules/column-defaults-updated/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smoketest-module-column-defaults-updated" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true diff --git a/crates/smoketests/modules/column-defaults-updated/src/lib.rs b/crates/smoketests/modules/column-defaults-updated/src/lib.rs new file mode 100644 index 00000000000..a404e9b5289 --- /dev/null +++ b/crates/smoketests/modules/column-defaults-updated/src/lib.rs @@ -0,0 +1,32 @@ +#[spacetimedb::table(accessor = defaults_test_table, public)] +pub struct DefaultsTestTable { + pub id: u32, + #[default(true)] + pub bool_value: bool, + #[default(-8)] + pub i8_value: i8, + #[default(8)] + pub u8_value: u8, + #[default(-16)] + pub i16_value: i16, + #[default(16)] + pub u16_value: u16, + #[default(-32)] + pub i32_value: i32, + #[default(32)] + pub u32_value: u32, + #[default(-64)] + pub i64_value: i64, + #[default(64)] + pub u64_value: u64, + #[default(32.5)] + pub f32_positive_value: f32, + #[default(-32.5)] + pub f32_negative_value: f32, + #[default(64.25)] + pub f64_positive_value: f64, + #[default(-64.25)] + pub f64_negative_value: f64, + #[default("default string")] + pub string_value: String, +} diff --git a/crates/smoketests/modules/http-handlers-tutorial/Cargo.toml b/crates/smoketests/modules/http-handlers-tutorial/Cargo.toml new file mode 100644 index 00000000000..de8bc6958cf --- /dev/null +++ b/crates/smoketests/modules/http-handlers-tutorial/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smoketest-module-http-handlers-tutorial" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb.workspace = true diff --git a/crates/smoketests/modules/http-handlers-tutorial/build.rs b/crates/smoketests/modules/http-handlers-tutorial/build.rs new file mode 100644 index 00000000000..20925f91771 --- /dev/null +++ b/crates/smoketests/modules/http-handlers-tutorial/build.rs @@ -0,0 +1,25 @@ +use std::{env, fs, path::PathBuf}; + +fn main() { + let doc_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../../../docs/docs/00200-core-concepts/00200-functions/00600-HTTP-handlers.md"); + println!("cargo:rerun-if-changed={}", doc_path.display()); + let doc = fs::read_to_string(&doc_path).expect("Failed to read HTTP handlers tutorial"); + let doc = doc.replace("\r\n", "\n"); + let blocks: Vec<_> = doc + .split("```rust\n") + .skip(1) + .map(|block| { + block + .split_once("\n```") + .expect("Unterminated Rust code block in HTTP handlers tutorial") + .0 + }) + .collect(); + assert!( + !blocks.is_empty(), + "No Rust code blocks found in HTTP handlers tutorial" + ); + let out_path = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("module.rs"); + fs::write(out_path, blocks.join("\n\n")).expect("Failed to write HTTP handlers tutorial module"); +} diff --git a/crates/smoketests/modules/http-handlers-tutorial/src/lib.rs b/crates/smoketests/modules/http-handlers-tutorial/src/lib.rs new file mode 100644 index 00000000000..8252a5f76ba --- /dev/null +++ b/crates/smoketests/modules/http-handlers-tutorial/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/module.rs")); diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 3ec8f5b7141..ab6e8ce187d 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -6,9 +6,9 @@ //! //! # Pre-compiled Modules //! -//! For better performance, modules can be pre-compiled during the warmup phase. -//! Use `Smoketest::builder().precompiled_module("name")` to use a pre-compiled module -//! instead of `module_code()` which compiles at runtime. +//! Rust modules are pre-compiled during the warmup phase. Use +//! `Smoketest::builder().precompiled_module("name")` to select a module from +//! `crates/smoketests/modules/`. The default module is `noop`. //! //! # Running Smoketests //! @@ -25,28 +25,13 @@ //! ```ignore //! use spacetimedb_smoketests::Smoketest; //! -//! const MODULE_CODE: &str = r#" -//! use spacetimedb::{table, reducer}; -//! -//! #[spacetimedb::table(accessor = person, public)] -//! pub struct Person { -//! name: String, -//! } -//! -//! #[spacetimedb::reducer] -//! pub fn add(ctx: &ReducerContext, name: String) { -//! ctx.db.person().insert(Person { name }); -//! } -//! "#; -//! //! #[test] //! fn test_example() { -//! let mut test = Smoketest::builder() -//! .module_code(MODULE_CODE) +//! let test = Smoketest::builder() +//! .precompiled_module("noop") //! .build(); //! -//! test.call("add", &["Alice"]).unwrap(); -//! test.assert_sql("SELECT * FROM person", "name\n-----\nAlice"); +//! test.call("noop", &[]).unwrap(); //! } //! ``` @@ -218,10 +203,8 @@ pub fn patch_module_cargo_to_local_bindings(module_dir: &Path) -> Result<()> { /// Returns the shared target directory for smoketest module builds. /// -/// All tests share this directory to cache compiled dependencies. The warmup step -/// pre-compiles dependencies, then each test only needs to compile its unique module. -/// Cargo serializes builds due to directory locking, but this is still faster than -/// each test compiling all dependencies from scratch. +/// Explicit build-diagnostic tests share this directory to cache dependencies. +/// Cargo serializes their builds through directory locking. fn shared_target_dir() -> PathBuf { static TARGET_DIR: OnceLock = OnceLock::new(); TARGET_DIR @@ -233,6 +216,55 @@ fn shared_target_dir() -> PathBuf { .clone() } +/// Runs `spacetime build` on a temporary Rust project and returns its raw output. +/// +/// Use this for tests of build diagnostics, such as rejected wasm-bindgen imports. +/// Ordinary smoketests should add a precompiled fixture to `crates/smoketests/modules/`. +/// This does not start a server or publish a module, and removes the source project +/// after the command finishes. `extra_deps` is appended to `[dependencies]`. +pub fn build_rust_module(source: &str, extra_deps: &str) -> Output { + let cli_path = ensure_binaries_built(); + let project_dir = tempfile::tempdir().expect("Failed to create temporary Rust project"); + let workspace_root = workspace_root(); + let bindings_path = workspace_root + .join("crates/bindings") + .display() + .to_string() + .replace('\\', "/"); + let module_name = format!("smoketest_module_{}", random_string()); + let cargo_toml = format!( + r#"[package] +name = "{module_name}" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +spacetimedb = {{ path = "{bindings_path}", features = ["unstable"] }} +log = "0.4" +{extra_deps} +"# + ); + fs::create_dir(project_dir.path().join("src")).expect("Failed to create Rust source directory"); + fs::write(project_dir.path().join("Cargo.toml"), cargo_toml).expect("Failed to write Cargo.toml"); + fs::write(project_dir.path().join("src/lib.rs"), source).expect("Failed to write Rust module source"); + fs::copy( + workspace_root.join("rust-toolchain.toml"), + project_dir.path().join("rust-toolchain.toml"), + ) + .expect("Failed to copy rust-toolchain.toml"); + + Command::new(cli_path) + .args(["build", "--module-path"]) + .arg(project_dir.path()) + .current_dir(project_dir.path()) + .env("CARGO_TARGET_DIR", shared_target_dir()) + .output() + .expect("Failed to execute spacetime build") +} + /// Generates a random lowercase alphabetic string suitable for database names. pub fn random_string() -> String { use std::time::{SystemTime, UNIX_EPOCH}; @@ -470,19 +502,12 @@ pub struct Smoketest { _data_dir_fixture: Option, /// Temporary directory containing the module project. pub project_dir: tempfile::TempDir, - /// Additional features for the spacetimedb bindings dependency. - pub bindings_features: Vec, - /// Additional dependencies to add to the module's Cargo.toml. - pub extra_deps: String, /// Database identity after publishing (if any). pub database_identity: Option, /// The server URL (e.g., "http://127.0.0.1:3000"). pub server_url: String, /// Path to the test-specific CLI config file (isolates tests from user config). pub config_path: std::path::PathBuf, - /// Unique module name for this test instance. - /// Used to avoid wasm output conflicts when tests run in parallel. - module_name: String, /// Path to pre-compiled WASM file (if using precompiled_module). precompiled_wasm_path: Option, /// Optional path to a specific CLI binary to run for this test. @@ -674,8 +699,12 @@ impl<'a> PublishBuilder<'a> { ]; } } - } else if let Some(module_path) = smoketest.precompiled_wasm_path.as_ref() { + } else { post_publish_step = None; + if smoketest.precompiled_wasm_path.is_none() { + smoketest.use_precompiled_module("noop"); + } + let module_path = smoketest.precompiled_wasm_path.as_ref().unwrap(); // Use pre-compiled WASM directly (no build needed) eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); module_args = vec![ @@ -685,18 +714,6 @@ impl<'a> PublishBuilder<'a> { .context("Invalid precompiled module path")? .to_string(), ]; - } else { - post_publish_step = None; - // Rust is built separately to use the shared Cargo target cache; publishing the resulting WASM - // by path avoids rebuilding it. This is a harness optimization, not a Rust requirement. - module_args = vec![ - "--bin-path".to_string(), - smoketest - .prepare_rust_module_internal()? - .to_str() - .context("Invalid Rust module path")? - .to_string(), - ]; } let identity = smoketest.publish_module_internal( @@ -794,11 +811,8 @@ impl<'a> SubscribeBuilder<'a> { /// Builder for creating `Smoketest` instances. pub struct SmoketestBuilder { - module_code: Option, precompiled_module: Option, data_dir_fixture: Option, - bindings_features: Vec, - extra_deps: String, autopublish: bool, pg_port: Option, server_url_override: Option, @@ -820,11 +834,8 @@ impl SmoketestBuilder { /// Creates a new builder with default settings. pub fn new() -> Self { Self { - module_code: None, precompiled_module: None, data_dir_fixture: None, - bindings_features: vec!["unstable".to_string()], - extra_deps: String::new(), autopublish: true, pg_port: None, server_url_override: None, @@ -861,13 +872,7 @@ impl SmoketestBuilder { self } - /// Sets the module code to compile and publish. - pub fn module_code(mut self, code: &str) -> Self { - self.module_code = Some(code.to_string()); - self - } - - /// Uses a pre-compiled module instead of runtime compilation. + /// Selects a pre-compiled module instead of the default `noop` module. /// /// Pre-compiled modules are built during the warmup phase and stored in /// `crates/smoketests/modules/target/`. This eliminates per-test compilation @@ -889,18 +894,6 @@ impl SmoketestBuilder { self } - /// Sets additional features for the spacetimedb bindings dependency. - pub fn bindings_features(mut self, features: &[&str]) -> Self { - self.bindings_features = features.iter().map(|s| s.to_string()).collect(); - self - } - - /// Adds extra dependencies to the module's Cargo.toml. - pub fn extra_deps(mut self, deps: &str) -> Self { - self.extra_deps = deps.to_string(); - self - } - /// Sets whether to automatically publish the module on build. /// Default is true. pub fn autopublish(mut self, yes: bool) -> Self { @@ -911,8 +904,9 @@ impl SmoketestBuilder { /// Builds the `Smoketest` instance. /// /// This spawns a SpacetimeDB server (unless `SPACETIME_REMOTE_SERVER` is set), - /// creates a temporary project directory, writes the module code, and optionally - /// publishes the module. + /// creates a temporary project directory, and optionally publishes a precompiled + /// module. The default `noop` module is only resolved when publishing, so tests + /// using `autopublish(false)` need no module artifacts unless they publish one. /// /// When `SPACETIME_REMOTE_SERVER` is set, tests run against the remote server /// instead of spawning a local server. Tests that require local server control @@ -991,12 +985,6 @@ impl SmoketestBuilder { path }); - let project_setup_start = Instant::now(); - - // Generate a unique module name to avoid wasm output conflicts in parallel tests. - // The format is smoketest_module_{random} which produces smoketest_module_{random}.wasm - let module_name = format!("smoketest_module_{}", random_string()); - let config_path = project_dir.path().join("config.toml"); if let Ok(base_config_path) = std::env::var("SPACETIME_SMOKETEST_BASE_CONFIG_PATH") { fs::copy(&base_config_path, &config_path) @@ -1009,28 +997,10 @@ impl SmoketestBuilder { database_identity: fixture_identity, server_url, config_path, - module_name, precompiled_wasm_path: precompiled_wasm_path.clone(), cli_path: self.cli_path.clone(), - bindings_features: self.bindings_features.clone(), - extra_deps: self.extra_deps.clone(), }; - // Only set up project structure if not using precompiled module - if precompiled_wasm_path.is_none() { - let module_code = self.module_code.unwrap_or_else(|| { - r#"use spacetimedb::ReducerContext; - -#[spacetimedb::reducer] -pub fn noop(_ctx: &ReducerContext) {} -"# - .to_string() - }); - smoketest.write_module_code(&module_code).unwrap(); - - eprintln!("[TIMING] project setup: {:?}", project_setup_start.elapsed()); - } - if self.autopublish { smoketest.publish().run().expect("Failed to publish module"); } @@ -1328,57 +1298,6 @@ impl Smoketest { Ok(module_path) } - /// Writes new module code to the project. - /// - /// This switches from precompiled mode to runtime compilation mode. - /// If the project structure doesn't exist (e.g., started with `precompiled_module()`), - /// it will be created on demand. - pub fn write_module_code(&mut self, code: &str) -> Result<()> { - // Clear precompiled module path so we use the source code instead - self.precompiled_wasm_path = None; - - // Create project structure on demand if it doesn't exist - // (happens when test started with precompiled_module) - let src_dir = self.project_dir.path().join("src"); - if !src_dir.exists() { - fs::create_dir_all(&src_dir).context("Failed to create src directory")?; - - // Write Cargo.toml with default settings - let workspace_root = workspace_root(); - let bindings_path = workspace_root.join("crates/bindings"); - let bindings_path_str = bindings_path.display().to_string().replace('\\', "/"); - let features_str = format!("{:?}", self.bindings_features); - - let cargo_toml = format!( - r#"[package] -name = "{}" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -spacetimedb = {{ path = "{}", features = {} }} -log = "0.4" -{} -"#, - self.module_name, bindings_path_str, features_str, self.extra_deps - ); - fs::write(self.project_dir.path().join("Cargo.toml"), cargo_toml).context("Failed to write Cargo.toml")?; - - // Copy rust-toolchain.toml - let toolchain_src = workspace_root.join("rust-toolchain.toml"); - if toolchain_src.exists() { - fs::copy(&toolchain_src, self.project_dir.path().join("rust-toolchain.toml")) - .context("Failed to copy rust-toolchain.toml")?; - } - } - - fs::write(self.project_dir.path().join("src/lib.rs"), code).context("Failed to write module code")?; - Ok(()) - } - /// Switches to using a precompiled module. /// /// After calling this, subsequent `publish_module*` calls will use the @@ -1410,61 +1329,10 @@ log = "0.4" Ok(()) } - /// Runs `spacetime build` and returns the raw output. - /// - /// Use this when you need to check for build failures (e.g., wasm_bindgen detection). - pub fn spacetime_build(&self) -> Output { - let start = Instant::now(); - let project_path = self.project_dir.path().to_str().unwrap(); - let cli_path = self.cli_path(); - - let mut cmd = Command::new(&cli_path); - cmd.args(["build", "--module-path", project_path]) - .current_dir(self.project_dir.path()) - .env("CARGO_TARGET_DIR", shared_target_dir()); - - let output = cmd.output().expect("Failed to execute spacetime build"); - eprintln!("[TIMING] spacetime build: {:?}", start.elapsed()); - output - } - pub fn publish(&mut self) -> PublishBuilder<'_> { PublishBuilder::new(self) } - /// Builds the Rust module using the target directory shared by smoketests. - /// - /// The caller publishes the resulting WASM with `--bin-path` to avoid a second build. Rust modules - /// could instead use `--module-path` if the publish command inherited this shared target directory. - fn prepare_rust_module_internal(&self) -> Result { - // Build the WASM module from source - let project_path = self.project_dir.path().to_str().unwrap(); - let build_start = Instant::now(); - let cli_path = self.cli_path(); - let target_dir = shared_target_dir(); - - let mut build_cmd = Command::new(&cli_path); - build_cmd - .args(["build", "--module-path", project_path]) - .current_dir(self.project_dir.path()) - .env("CARGO_TARGET_DIR", &target_dir); - - let build_output = build_cmd.output().expect("Failed to execute spacetime build"); - eprintln!("[TIMING] spacetime build: {:?}", build_start.elapsed()); - - if !build_output.status.success() { - bail!( - "spacetime build failed:\nstdout: {}\nstderr: {}", - String::from_utf8_lossy(&build_output.stdout), - String::from_utf8_lossy(&build_output.stderr) - ); - } - - // Construct the wasm path using the unique module name - let wasm_filename = format!("{}.wasm", self.module_name); - Ok(target_dir.join("wasm32-unknown-unknown/release").join(wasm_filename)) - } - /// Publishes the prepared module and stores the database identity. /// /// If `name` is provided, the database will be published with that name. @@ -1943,6 +1811,19 @@ fn normalize_whitespace(s: &str) -> String { mod tests { use super::*; + #[test] + fn test_unpublished_builder_needs_no_module() { + let test = Smoketest::builder() + .server_url("http://127.0.0.1:1") + .cli_path("unused-cli") + .autopublish(false) + .build(); + assert!(test.database_identity.is_none()); + assert!(test.precompiled_wasm_path.is_none()); + assert!(!test.project_dir.path().join("Cargo.toml").exists()); + assert!(!test.project_dir.path().join("src").exists()); + } + #[test] fn test_normalize_whitespace() { let input = "hello \nworld \n foo "; diff --git a/crates/smoketests/tests/cluster/column_defaults.rs b/crates/smoketests/tests/cluster/column_defaults.rs index 258cd6507cf..feef4e2c5ae 100644 --- a/crates/smoketests/tests/cluster/column_defaults.rs +++ b/crates/smoketests/tests/cluster/column_defaults.rs @@ -56,9 +56,11 @@ fn test_source_defaults(language: ModuleLanguage, project_name: &str, initial: & #[test] fn test_rust_column_defaults() { - let mut test = Smoketest::builder().module_code(RUST_INITIAL).build(); + let mut test = Smoketest::builder() + .precompiled_module("column-defaults-initial") + .build(); test_defaults(&mut test, |test| { - test.write_module_code(RUST_UPDATED).unwrap(); + test.use_precompiled_module("column-defaults-updated"); test.publish() .current_database() .unwrap() @@ -96,48 +98,6 @@ fn test_cpp_column_defaults() { test_source_defaults(ModuleLanguage::Cpp, "column-defaults-cpp", CPP_INITIAL, CPP_UPDATED); } -const RUST_INITIAL: &str = r#" -#[spacetimedb::table(accessor = defaults_test_table, public)] -pub struct DefaultsTestTable { - pub id: u32, -} -"#; - -const RUST_UPDATED: &str = r#" -#[spacetimedb::table(accessor = defaults_test_table, public)] -pub struct DefaultsTestTable { - pub id: u32, - #[default(true)] - pub bool_value: bool, - #[default(-8)] - pub i8_value: i8, - #[default(8)] - pub u8_value: u8, - #[default(-16)] - pub i16_value: i16, - #[default(16)] - pub u16_value: u16, - #[default(-32)] - pub i32_value: i32, - #[default(32)] - pub u32_value: u32, - #[default(-64)] - pub i64_value: i64, - #[default(64)] - pub u64_value: u64, - #[default(32.5)] - pub f32_positive_value: f32, - #[default(-32.5)] - pub f32_negative_value: f32, - #[default(64.25)] - pub f64_positive_value: f64, - #[default(-64.25)] - pub f64_negative_value: f64, - #[default("default string")] - pub string_value: String, -} -"#; - const TYPESCRIPT_INITIAL: &str = r#" import { schema, t, table } from "spacetimedb/server"; diff --git a/crates/smoketests/tests/cluster/http_routes.rs b/crates/smoketests/tests/cluster/http_routes.rs index d5ebf77bce2..bf0b46414c0 100644 --- a/crates/smoketests/tests/cluster/http_routes.rs +++ b/crates/smoketests/tests/cluster/http_routes.rs @@ -1290,12 +1290,9 @@ fn csharp_handle_request_body() { /// Validates the Rust example from `docs/docs/00200-core-concepts/00200-functions/00600-HTTP-handlers.md`. #[test] fn http_handlers_tutorial_say_hello_route_works() { - let module_code = extract_code_blocks( - &workspace_root().join("docs/docs/00200-core-concepts/00200-functions/00600-HTTP-handlers.md"), - r"```rust\n([\s\S]*?)\n```", - "rust", - ); - let test = Smoketest::builder().module_code(&module_code).build(); + let test = Smoketest::builder() + .precompiled_module("http-handlers-tutorial") + .build(); let identity = test.database_identity.as_ref().expect("database identity missing"); let url = format!("{}/v1/database/{}/route/say-hello", test.server_url, identity); diff --git a/crates/smoketests/tests/cluster/modules.rs b/crates/smoketests/tests/cluster/modules.rs index f086249085b..928919a5c28 100644 --- a/crates/smoketests/tests/cluster/modules.rs +++ b/crates/smoketests/tests/cluster/modules.rs @@ -1,5 +1,21 @@ use spacetimedb_smoketests::Smoketest; +#[test] +fn test_default_noop_is_precompiled() { + let test = Smoketest::builder().build(); + assert!(!test.project_dir.path().join("Cargo.toml").exists()); + test.call("noop", &[]).unwrap(); +} + +#[test] +fn test_default_noop_can_be_published_later() { + let mut test = Smoketest::builder().autopublish(false).build(); + assert!(test.database_identity.is_none()); + test.publish().run().unwrap(); + assert!(!test.project_dir.path().join("Cargo.toml").exists()); + test.call("noop", &[]).unwrap(); +} + /// Test publishing a module without the --delete-data option #[test] fn test_module_update() { diff --git a/crates/smoketests/tests/cluster/views.rs b/crates/smoketests/tests/cluster/views.rs index c592cce3070..ae05a6e4d73 100644 --- a/crates/smoketests/tests/cluster/views.rs +++ b/crates/smoketests/tests/cluster/views.rs @@ -263,22 +263,6 @@ fn test_st_view_tables() { ); } -/// Publishing a module should fail if a table and view have the same name -#[test] -fn test_fail_publish_namespace_collision() { - let mut test = Smoketest::builder() - // Can't be precompiled because the code is intentionally broken - .module_code(include_str!("../../modules/views-broken-namespace/src/lib.rs")) - .autopublish(false) - .build(); - - let result = test.publish().run(); - assert!( - result.is_err(), - "Expected publish to fail when table and view have same name" - ); -} - /// Publishing a module should fail if the inner return type is not a product type #[test] fn test_fail_publish_wrong_return_type() { diff --git a/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs b/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs index baf95009eb4..e87d7a00cd1 100644 --- a/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs +++ b/crates/smoketests/tests/standalone/detect_wasm_bindgen.rs @@ -1,4 +1,4 @@ -use spacetimedb_smoketests::Smoketest; +use spacetimedb_smoketests::build_rust_module; /// Module code that uses wasm_bindgen (should be rejected) const MODULE_CODE_WASM_BINDGEN: &str = r#" @@ -29,13 +29,7 @@ pub fn test(_ctx: &ReducerContext) { /// Standalone-only: this validates local CLI build diagnostics without publishing a module. #[test] fn test_detect_wasm_bindgen() { - let test = Smoketest::builder() - .module_code(MODULE_CODE_WASM_BINDGEN) - .extra_deps(r#"wasm-bindgen = "0.2""#) - .autopublish(false) - .build(); - - let output = test.spacetime_build(); + let output = build_rust_module(MODULE_CODE_WASM_BINDGEN, r#"wasm-bindgen = "0.2""#); assert!(!output.status.success(), "Expected build to fail with wasm_bindgen"); let stderr = String::from_utf8_lossy(&output.stderr); @@ -50,13 +44,7 @@ fn test_detect_wasm_bindgen() { /// Standalone-only: this validates local CLI build diagnostics without publishing a module. #[test] fn test_detect_getrandom() { - let test = Smoketest::builder() - .module_code(MODULE_CODE_GETRANDOM) - .extra_deps(r#"rand = "0.8""#) - .autopublish(false) - .build(); - - let output = test.spacetime_build(); + let output = build_rust_module(MODULE_CODE_GETRANDOM, r#"rand = "0.8""#); assert!(!output.status.success(), "Expected build to fail with getrandom"); let stderr = String::from_utf8_lossy(&output.stderr); diff --git a/crates/smoketests/tests/standalone/views.rs b/crates/smoketests/tests/standalone/views.rs index b65f2ecb058..30c7f6d8d52 100644 --- a/crates/smoketests/tests/standalone/views.rs +++ b/crates/smoketests/tests/standalone/views.rs @@ -1,7 +1,22 @@ use std::path::PathBuf; use serde_json::{json, Value}; -use spacetimedb_smoketests::{require_local_server, workspace_root, Smoketest}; +use spacetimedb_smoketests::{build_rust_module, require_local_server, workspace_root, Smoketest}; + +/// A table and view with the same accessor collide during Rust compilation. +#[test] +fn test_fail_build_namespace_collision() { + let output = build_rust_module(include_str!("../../modules/views-broken-namespace/src/lib.rs"), ""); + assert!( + !output.status.success(), + "Expected a namespace collision to fail compilation" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("the name `person` is defined multiple times"), + "Expected the duplicate person diagnostic, got: {stderr}" + ); +} const STALE_VIEW_BACKING_TABLE_FIXTURE_IDENTITY: &str = "c200f6ec405075e508c2ed6474019332d6a2a46c69614306cc4bd980e0b8b767";