From 5db19ae117741e1ae5100bb65099cbe2dd9486f3 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 12:05:46 +0200 Subject: [PATCH 01/31] [bfops/consolidate-default-tests]: C++/Rust schema compare tests --- crates/schema/tests/ensure_same_schema.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/schema/tests/ensure_same_schema.rs b/crates/schema/tests/ensure_same_schema.rs index 7e5cb28f626..f08d4aa413e 100644 --- a/crates/schema/tests/ensure_same_schema.rs +++ b/crates/schema/tests/ensure_same_schema.rs @@ -76,6 +76,16 @@ macro_rules! declare_tests { } )* } + mod ensure_same_schema_rust_cpp { + use super::*; + $( + #[test] + #[serial] + fn $name() { + super::assert_identical_modules($path, "C++", "cpp"); + } + )* + } } } From 1b08167dc461d219ca631a90dd2ae47e6a328db3 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 12:32:23 +0200 Subject: [PATCH 02/31] [bfops/consolidate-default-tests]: [revert this] strip down CI --- .github/workflows/ci.yml | 1347 +------------------------------------- 1 file changed, 2 insertions(+), 1345 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 038334613ec..3c7cd10a6a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,244 +22,7 @@ permissions: pull-requests: read jobs: - merge_queue_noop: - name: Detect merge queue no-op - runs-on: ubuntu-latest - outputs: - skip: ${{ steps.compare.outputs.skip }} - steps: - - name: Checkout merge queue commit - uses: actions/checkout@v4 - - - name: Compare merge queue commit to PR head - id: compare - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HEAD_REF: ${{ github.event.merge_group.head_ref }} - shell: bash - run: | - set -euo pipefail - - echo "skip=false" >>"$GITHUB_OUTPUT" - - if [[ "${GITHUB_EVENT_NAME}" != "merge_group" ]]; then - echo "Not a merge_group event; running CI normally." - exit 0 - fi - - if [[ ! "${HEAD_REF}" =~ /pr-([0-9]+)- ]]; then - echo "Could not parse PR number from merge group head ref: ${HEAD_REF}" - exit 0 - fi - - pr_number="${BASH_REMATCH[1]}" - pr_head_sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" --jq '.head.sha')" - pr_head_repo="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" --jq '.head.repo.full_name')" - - if [[ -z "${pr_head_sha}" || "${pr_head_sha}" == "null" || -z "${pr_head_repo}" || "${pr_head_repo}" == "null" ]]; then - echo "Could not resolve PR head SHA for PR #${pr_number}; running CI normally." - exit 0 - fi - - git fetch --no-tags --depth=1 "https://github.com/${pr_head_repo}.git" "${pr_head_sha}" - - if git diff --quiet "${pr_head_sha}" "${GITHUB_SHA}" --; then - echo "Merge queue commit ${GITHUB_SHA} has the same tree as PR #${pr_number} head ${pr_head_sha}; skipping duplicate CI." - echo "skip=true" >>"$GITHUB_OUTPUT" - else - echo "Merge queue commit ${GITHUB_SHA} differs from PR #${pr_number} head ${pr_head_sha}; running CI normally." - fi - - smoketests: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Smoketests (${{ matrix.name }}) - strategy: - matrix: - include: - - name: Linux - runner: spacetimedb-new-runner-2 - - name: Windows - runner: spacetimedb-windows-runner - runs-on: ${{ matrix.runner }} - timeout-minutes: 120 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - SPACETIMEDB_CPP_DIR: ${{ github.workspace }}/crates/bindings-cpp - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Set up sccache - uses: mozilla-actions/sccache-action@v0.0.10 - - - name: Enable sccache - shell: bash - run: | - echo "SCCACHE_GHA_ENABLED=true" >>"$GITHUB_ENV" - echo "RUSTC_WRAPPER=sccache" >>"$GITHUB_ENV" - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - cache-on-failure: false - cache-all-crates: true - cache-workspace-crates: true - prefix-key: v1 - - - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - # nodejs and pnpm are required for the typescript quickstart smoketest - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 18 - - - uses: ./.github/actions/setup-pnpm - with: - run_install: true - - # Install emscripten for C++ module compilation tests. - - name: Install emscripten (Linux) - if: runner.os == 'Linux' - shell: bash - run: | - git clone https://github.com/emscripten-core/emsdk.git ~/emsdk - cd ~/emsdk - ./emsdk install 4.0.21 - ./emsdk activate 4.0.21 - - - name: Install emscripten (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - git clone https://github.com/emscripten-core/emsdk.git $env:USERPROFILE\emsdk - cd $env:USERPROFILE\emsdk - .\emsdk install 4.0.21 - .\emsdk activate 4.0.21 - - - name: Install psql (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - # Fail properly if any individual command fails - $ErrorActionPreference = 'Stop' - $PSNativeCommandUseErrorActionPreference = $true - choco install psql -y --no-progress - # Check for existence, since `choco` doesn't seem to fail the step if it fails to install.. - # See https://github.com/clockworklabs/SpacetimeDB/pull/4399 for more background. - Get-Command psql - - - name: Update dotnet workloads - if: runner.os == 'Windows' - run: | - # Fail properly if any individual command fails - $ErrorActionPreference = 'Stop' - $PSNativeCommandUseErrorActionPreference = $true - - cd modules - # the sdk-manifests on windows-latest are messed up, so we need to update them - dotnet workload config --update-mode manifests - dotnet workload update --from-previous-sdk - # Explicitly install wasi-experimental for .NET 8 SDK (needed for test_build_csharp_module) - # Create temp global.json to target .NET 8 SDK for workload install - $sdk8Json = '{"sdk":{"version":"8.0.400","rollForward":"latestFeature"}}' - $sdk8Json | Out-File -FilePath global.json -Encoding utf8 - dotnet workload install wasi-experimental - Remove-Item global.json - - - name: Override NuGet packages - shell: bash - run: | - dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime - dotnet pack -c Release crates/bindings-csharp/Runtime - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. - - # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # `cargo build --manifest-path` (which apparently build different dependency trees). - # However, we've been unable to fix it so... /shrug - - name: Check v8 outputs - shell: bash - run: | - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean --release -p v8 || true - cargo build --release -p v8 - fi - - - name: Install cargo-nextest - uses: taiki-e/install-action@nextest - - # --test-threads=1 eliminates contention in the C# tests where they fight over bindings - # build artifacts. - # It also seemed to improve performance a fair amount (11m -> 6m) - - name: Run smoketests (Linux) - if: runner.os == 'Linux' - shell: bash - run: | - if [ -f ~/emsdk/emsdk_env.sh ]; then - source ~/emsdk/emsdk_env.sh - fi - cargo ci smoketests -- --test-threads=1 - - # Due to Emscripten PATH issues this was separated to make sure OpenSSL still builds correctly - - name: Run smoketests (Windows) - if: runner.os == 'Windows' - shell: pwsh - run: | - if (Test-Path "$env:USERPROFILE\emsdk\emsdk_env.ps1") { - & "$env:USERPROFILE\emsdk\emsdk_env.ps1" | Out-Null - } - cargo ci smoketests -- --test-threads=1 - - # this is a no-op version of the above check with a trivially-passing body. - # we can't just let the check be entirely skipped because each matrix target is a required check, - # and skipping this check means that the matrix isn't "populated" so the required checks wouldn't be met. - smoketests_noop: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip == 'true' }} - name: Smoketests (${{ matrix.name }}) - strategy: - matrix: - include: - - name: Linux - - name: Windows - runs-on: ubuntu-latest - steps: - - name: Skip duplicate merge queue smoketest - run: echo "Merge queue commit has the same tree as the PR head; smoketest already ran for the PR." - test: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} name: Test Suite runs-on: spacetimedb-new-runner-2 @@ -306,6 +69,7 @@ jobs: cargo build --release -p v8 fi + - name: Test crates/schema - uses: actions/setup-dotnet@v3 with: global-json-file: global.json @@ -357,1111 +121,4 @@ jobs: - name: Run tests run: | source ~/emsdk/emsdk_env.sh - cargo ci test - - keynote_bench: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Keynote Bench - runs-on: spacetimedb-benchmark-runner - concurrency: - group: ci-benchmark-runner - queue: max - timeout-minutes: 60 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - # Node 24 is the current Active LTS line. - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 24 - - - uses: ./.github/actions/setup-pnpm - with: - package_json_file: package.json - working_directory: . - run_install: "true" - - - uses: ./.github/actions/keynote-bench-setup - with: - public_root: . - rust_cache_workspaces: ${{ github.workspace }} - - - name: Run keynote-2 benchmark regression check - run: cargo ci keynote-bench - - index_scan_bench: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Index Scan Bench - runs-on: spacetimedb-benchmark-runner - concurrency: - group: ci-benchmark-runner - queue: max - timeout-minutes: 60 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Set up sccache - uses: mozilla-actions/sccache-action@v0.0.10 - - - name: Enable sccache - run: | - echo "SCCACHE_GHA_ENABLED=true" >>"$GITHUB_ENV" - echo "RUSTC_WRAPPER=sccache" >>"$GITHUB_ENV" - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb-index-scan - cache-on-failure: false - prefix-key: v1 - - - name: Run index scan benchmark regression check - run: cargo run --release -p spacetimedb-index-scan-gate - - lints: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Lints - runs-on: spacetimedb-new-runner-2 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - run: echo ::add-matcher::.github/workflows/rust_matcher.json - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the smoketests job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - - uses: actions/setup-dotnet@v3 - with: - global-json-file: global.json - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - - uses: ./.github/actions/setup-pnpm - with: - run_install: true - - - name: Run ci lint - run: cargo ci lint - - wasm_bindings: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Build and test wasm bindings - runs-on: spacetimedb-new-runner-2 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - uses: actions/checkout@v3 - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - run: echo ::add-matcher::.github/workflows/rust_matcher.json - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the smoketests job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - - name: Run bindgen tests - run: cargo ci wasm-bindings - - publish_checks: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Check that packages are publishable - runs-on: spacetimedb-new-runner-2 - permissions: read-all - env: - RUST_BACKTRACE: full - steps: - - uses: actions/checkout@v3 - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - name: Run checks - run: cargo ci publish-checks - - update: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Test spacetimedb-update flow (${{ matrix.target }}) - permissions: read-all - strategy: - matrix: - include: - - { target: x86_64-unknown-linux-gnu, runner: spacetimedb-new-runner-2 } - - { target: aarch64-apple-darwin, runner: macos-latest } - - { target: x86_64-pc-windows-msvc, runner: windows-latest } - runs-on: ${{ matrix.runner }} - env: - RUST_BACKTRACE: full - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install Rust - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Install rust target - run: rustup target add ${{ matrix.target }} - - - name: Test spacetimedb-update - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: cargo ci update-flow --target=${{ matrix.target }} --github-token-auth - - # this is a no-op version of the above check with a trivially-passing body. - # we can't just let the check be entirely skipped because each matrix target is a required check, - # and skipping this check means that the matrix isn't "populated" so the required checks wouldn't be met. - update_noop: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip == 'true' }} - name: Test spacetimedb-update flow (${{ matrix.target }}) - permissions: read-all - strategy: - matrix: - include: - - { target: x86_64-unknown-linux-gnu } - - { target: aarch64-apple-darwin } - - { target: x86_64-pc-windows-msvc } - runs-on: ubuntu-latest - steps: - - name: Skip duplicate merge queue update test - run: echo "Merge queue commit has the same tree as the PR head; update-flow already ran for the PR." - - unreal_engine_tests: - name: Unreal Engine Tests - # This can't go on e.g. ubuntu-latest because that runner runs out of disk space. ChatGPT suggested that the general solution tends to be to use - # a custom runner. - runs-on: spacetimedb-new-runner-2 - # Disable the tests because they are very flaky at the moment. - # TODO: Remove this line and re-enable the `if` line just below here. - if: false - # Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway. - # if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} - container: - image: ghcr.io/epicgames/unreal-engine:dev-5.6 - credentials: - # Note(bfops): I don't think that `github.actor` needs to match the user that the token is for, because I'm using a token for my account and - # it seems to be totally happy. - # However, the token needs to be for a user that has access to the EpicGames org (see - # https://dev.epicgames.com/documentation/en-us/unreal-engine/downloading-source-code-in-unreal-engine?application_version=5.6) - username: ${{ github.actor }} - password: ${{ secrets.GHCR_TOKEN }} - # Run as root because otherwise we get permission denied for various directories inside the container. I tried doing dances to allow it to run - # without this (reassigning env vars and stuff), but was unable to get it to work and it felt like an uphill battle. - options: --user 0:0 - env: - RUST_BACKTRACE: full - steps: - # Uncomment this before merging so that it will run properly if run manually through the GH actions flow. It was playing weird with rolled back - # commits though. - # - name: Find Git ref - # env: - # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # shell: bash - # run: | - # PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - # if test -n "${PR_NUMBER}"; then - # GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - # else - # GIT_REF="${{ github.ref }}" - # fi - # echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - name: Run Unreal Engine tests - working-directory: sdks/unreal - env: - UE_ROOT_PATH: /home/ue4/UnrealEngine - run: | - - apt-get update - apt-get install -y acl curl ca-certificates - - REPO="$GITHUB_WORKSPACE" - # Let ue4 read/write the workspace & tool caches without changing ownership - for p in "$REPO" "${RUNNER_TEMP:-/__t}" "${RUNNER_TOOL_CACHE:-/__t}"; do - [ -d "$p" ] && setfacl -R -m u:ue4:rwX -m d:u:ue4:rwX "$p" || true - done - - # Rust tool caches live under the runner tool cache so they persist - export CARGO_HOME="${RUNNER_TOOL_CACHE:-/__t}/cargo" - export RUSTUP_HOME="${RUNNER_TOOL_CACHE:-/__t}/rustup" - mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" - chown -R ue4:ue4 "$CARGO_HOME" "$RUSTUP_HOME" - - # Make sure the UE build script is executable (and parents traversable) - UE_DIR="${UE_ROOT_PATH:-/home/ue4/UnrealEngine}" - chmod a+rx "$UE_DIR" "$UE_DIR/Engine" "$UE_DIR/Engine/Build" "$UE_DIR/Engine/Build/BatchFiles/Linux" || true - chmod a+rx "$UE_DIR/Engine/Build/BatchFiles/Linux/Build.sh" || true - - # Run the build & tests as ue4 (who owns the UE tree) - sudo -E -H -u ue4 env \ - HOME=/home/ue4 \ - XDG_CONFIG_HOME=/home/ue4/.config \ - CARGO_HOME="$CARGO_HOME" \ - RUSTUP_HOME="$RUSTUP_HOME" \ - PATH="$CARGO_HOME/bin:$PATH" \ - bash -lc ' - set -euxo pipefail - # Install rustup for ue4 if needed (uses the shared caches) - if ! command -v cargo >/dev/null 2>&1; then - curl -sSf https://sh.rustup.rs | sh -s -- -y - fi - rustup show >/dev/null - git config --global --add safe.directory "$GITHUB_WORKSPACE" || true - - cd "$GITHUB_WORKSPACE/sdks/unreal" - cargo --version - cargo test -- --test-threads=1 - ' - - ci_command_docs: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Check CI command docs - runs-on: spacetimedb-new-runner-2 - env: - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Check for docs change - run: cargo ci self-docs --check - - cli_docs: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Check CLI docs - permissions: read-all - runs-on: spacetimedb-new-runner-2 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - - uses: ./.github/actions/setup-pnpm - with: - run_install: true - - - name: Get pnpm store directory - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the smoketests job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - - name: Check for docs change - run: | - cargo ci cli-docs - - unity-testsuite: - needs: [merge_queue_noop, lints] - # Skip if this is an external contribution. - # The license secrets will be empty, so the step would fail anyway. - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} - permissions: - contents: read - checks: write - runs-on: spacetimedb-unity-runner - timeout-minutes: 30 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Checkout repository - id: checkout-stdb - uses: actions/checkout@v4 - - # Run cheap .NET tests first. If those fail, no need to run expensive Unity tests. - - - name: Setup dotnet - uses: actions/setup-dotnet@v3 - with: - global-json-file: global.json - - - name: Override NuGet packages - run: | - dotnet pack crates/bindings-csharp/BSATN.Runtime - dotnet pack crates/bindings-csharp/Runtime - - # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository - # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if - # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. - # This means that (if version numbers match) we will test the local versions of the C# packages, even - # if they're not pushed to NuGet. - # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. - - - name: Restore .NET solution - working-directory: sdks/csharp - run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln - - # Now, setup the Unity tests. - - name: Patch spacetimedb dependency in Cargo.toml - working-directory: demo/Blackholio/server-rust - run: | - sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml - cat Cargo.toml - - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the main CI job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # `cargo build --manifest-path` (which apparently build different dependency trees). - # However, we've been unable to fix it so... /shrug - - name: Check v8 outputs - run: | - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean --release -p v8 || true - cargo build --release -p v8 - fi - - - name: Install SpacetimeDB CLI from the local checkout - run: | - export CARGO_HOME="$HOME/.cargo" - echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" - cargo install --force --path crates/cli --locked --message-format=short - cargo install --force --path crates/standalone --locked --message-format=short - # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). - ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - - - name: Generate client bindings - working-directory: demo/Blackholio/server-rust - run: bash ./generate.sh -y - - - name: Check for changes - run: | - tools/check-diff.sh demo/Blackholio/client-unity/Assets/Scripts/autogen || { - echo 'Error: Bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' - exit 1 - } - - - name: Hydrate Unity SDK DLLs - run: cargo regen csharp dlls - - - name: Check Unity meta files - uses: DeNA/unity-meta-check@v3 - with: - enable_pr_comment: ${{ github.event_name == 'pull_request' }} - target_path: sdks/csharp - env: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - - - name: Start SpacetimeDB - run: | - spacetime start & - disown - - - name: Publish unity-tests module to SpacetimeDB - working-directory: demo/Blackholio/server-rust - run: | - spacetime logout && spacetime login --server-issued-login local - bash ./publish.sh - - - name: Patch com.clockworklabs.spacetimedbsdk dependency in manifest.json - working-directory: demo/Blackholio/client-unity/Packages - run: | - yq e -i '.dependencies["com.clockworklabs.spacetimedbsdk"] = "file:../../../../sdks/csharp"' manifest.json - cat manifest.json - - - uses: actions/cache@v3 - with: - path: demo/Blackholio/client-unity/Library - key: Unity-${{ github.head_ref }} - restore-keys: Unity- - - - name: Run Unity tests - uses: game-ci/unity-test-runner@v4 - with: - unityVersion: 2022.3.32f1 # Adjust Unity version to a valid tag - projectPath: demo/Blackholio/client-unity # Path to the Unity project subdirectory - githubToken: ${{ secrets.GITHUB_TOKEN }} - testMode: playmode - useHostNetwork: true - artifactsPath: "" - env: - UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} - UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} - UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} - - godot-testsuite: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - permissions: - contents: read - runs-on: spacetimedb-new-runner-2 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - UseLocalBsatnRuntime: true - steps: - - name: Checkout repository - id: checkout-stdb - uses: actions/checkout@v4 - - - name: Setup dotnet - uses: actions/setup-dotnet@v3 - with: - global-json-file: global.json - - - name: Override NuGet packages - run: | - dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime - dotnet pack -c Release crates/bindings-csharp/Runtime - - # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository - # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if - # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. - # This means that (if version numbers match) we will test the local versions of the C# packages, even - # if they're not pushed to NuGet. - # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. - - - name: Restore .NET solution - working-directory: sdks/csharp - run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln - - # Now, setup the Godot tests. - - name: Patch spacetimedb dependency in Cargo.toml - working-directory: demo/Blackholio/server-rust - run: | - sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml - cat Cargo.toml - - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the main CI job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # `cargo build --manifest-path` (which apparently build different dependency trees). - # However, we've been unable to fix it so... /shrug - - name: Check v8 outputs - run: | - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean --release -p v8 || true - cargo build --release -p v8 - fi - - - name: Install SpacetimeDB CLI from the local checkout - run: | - export CARGO_HOME="$HOME/.cargo" - echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" - cargo install --force --path crates/cli --locked --message-format=short - cargo install --force --path crates/standalone --locked --message-format=short - # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). - ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - - - name: Generate client bindings - working-directory: demo/Blackholio/server-rust - run: bash ./generate.sh -y - - - name: Check for changes - run: | - tools/check-diff.sh demo/Blackholio/client-godot/module_bindings || { - echo 'Error: Godot bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' - exit 1 - } - - - name: Patch SpacetimeDB Godot SDK dependency - working-directory: demo/Blackholio/client-godot - run: | - dotnet remove package SpacetimeDB.ClientSDK.Godot - dotnet add reference ../../../sdks/csharp/SpacetimeDB.ClientSDK.Godot.csproj - cat blackholio.csproj - - - name: Setup Godot - uses: chickensoft-games/setup-godot@v2 - with: - version: 4.6.2 - use-dotnet: true - - - name: Restore Godot project - working-directory: demo/Blackholio/client-godot - run: dotnet restore --configfile ../../../NuGet.Config blackholio.csproj - - - name: Build Godot project - working-directory: demo/Blackholio/client-godot - run: godot --headless --verbose --build-solutions --quit - - - name: Start SpacetimeDB - run: | - spacetime start & - disown - - - name: Publish godot-tests module to SpacetimeDB - working-directory: demo/Blackholio/server-rust - run: | - spacetime login --server-issued-login local - bash ./publish.sh - - - name: Run Godot tests - working-directory: demo/Blackholio/client-godot - run: godot --headless --scene res://tests/GodotPlayModeTests.tscn - - csharp-testsuite: - needs: [merge_queue_noop, lints] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - runs-on: spacetimedb-new-runner-2 - timeout-minutes: 30 - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Checkout repository - id: checkout-stdb - uses: actions/checkout@v4 - - - name: Setup dotnet - uses: actions/setup-dotnet@v3 - with: - global-json-file: global.json - - - name: Install .NET workloads - run: | - dotnet workload config --update-mode manifests - dotnet workload update --from-previous-sdk - # Explicitly install wasi-experimental for .NET 8 SDK (needed for test_build_csharp_module) - # Create temp global.json to target .NET 8 SDK for workload install - echo '{"sdk":{"version":"8.0.100","rollForward":"latestFeature"}}' > global.json - dotnet workload install wasi-experimental - rm global.json - - - name: Override NuGet packages - run: | - dotnet pack crates/bindings-csharp/BSATN.Runtime - dotnet pack crates/bindings-csharp/Runtime - - # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository - # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if - # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. - # This means that (if version numbers match) we will test the local versions of the C# packages, even - # if they're not pushed to NuGet. - # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. - - - name: Restore .NET solution - working-directory: sdks/csharp - run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln - - - name: Run .NET tests - working-directory: sdks/csharp - run: dotnet test -warnaserror --no-restore SpacetimeDB.ClientSDK.csproj - - - name: Verify C# formatting - working-directory: sdks/csharp - run: dotnet format --no-restore --verify-no-changes SpacetimeDB.ClientSDK.sln - - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the main CI job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # `cargo build --manifest-path` (which apparently build different dependency trees). - # However, we've been unable to fix it so... /shrug - - name: Check v8 outputs - run: | - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean -p v8 || true - cargo build -p v8 - fi - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean --release -p v8 || true - cargo build --release -p v8 - fi - - - name: Install SpacetimeDB CLI from the local checkout - run: | - export CARGO_HOME="$HOME/.cargo" - echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" - cargo install --force --path crates/cli --locked --message-format=short - cargo install --force --path crates/standalone --features allow_loopback_http_for_tests --locked --message-format=short - # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). - ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - - - name: Check quickstart-chat bindings are up to date - run: | - for dotnet_version in 8 10; do - bash sdks/csharp/tools~/gen-quickstart.sh "$dotnet_version" - done - tools/check-diff.sh templates/chat-console-cs/module_bindings || { - echo 'Error: quickstart-chat bindings have changed. Please run `sdks/csharp/tools~/gen-quickstart.sh 10`.' - exit 1 - } - - # TODO: Re-enable this once csharp is using the v2 ws api. - # - name: Check client-api bindings are up to date - # working-directory: sdks/csharp - # run: | - # bash tools~/gen-client-api.sh - # "${GITHUB_WORKSPACE}"/tools/check-diff.sh src/SpacetimeDB/ClientApi || { - # echo 'Error: Client API bindings are dirty. Please run `sdks/csharp/tools~/gen-client-api.sh`.' - # exit 1 - # } - - - name: Start SpacetimeDB - run: | - spacetime start & - disown - - - name: Run regression tests - run: | - bash sdks/csharp/tools~/run-regression-tests.sh - # Restore global.json symlinks (we replaced them with files to work around .NET 10 SDK bug) - # server is 5 levels deep from root, republishing dirs are 6 levels deep - if [ -f sdks/csharp/examples~/regression-tests/server/global.json ] && [ ! -L sdks/csharp/examples~/regression-tests/server/global.json ]; then - echo "Restoring symlink at server/global.json" - rm -f sdks/csharp/examples~/regression-tests/server/global.json - ln -s ../../../../../global.json sdks/csharp/examples~/regression-tests/server/global.json - fi - for dir in sdks/csharp/examples~/regression-tests/republishing/server-initial \ - sdks/csharp/examples~/regression-tests/republishing/server-republish; do - if [ -f "$dir/global.json" ] && [ ! -L "$dir/global.json" ]; then - echo "Restoring symlink at $dir/global.json" - rm -f "$dir/global.json" - ln -s ../../../../../../global.json "$dir/global.json" - fi - done - tools/check-diff.sh sdks/csharp/examples~/regression-tests || { - echo 'Error: Bindings are dirty. Please run `sdks/csharp/tools~/gen-regression-tests.sh`.' - exit 1 - } - - global_json_policy: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Verify global.json files are symlinks - runs-on: ubuntu-latest - permissions: - contents: read - env: - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - PR_NUMBER: ${{ github.event.inputs.pr_number }} - run: | - if [ -n "$PR_NUMBER" ]; then - GIT_REF="refs/pull/$PR_NUMBER/merge" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - save-if: false - prefix-key: v1 - - - name: Check global.json policy - run: cargo ci global-json-policy - - smoketests_mod_rs_complete: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Check smoketests/mod.rs is complete - runs-on: ubuntu-latest - permissions: - contents: read - env: - CARGO_TARGET_DIR: ${{ github.workspace }}/target - RUST_BACKTRACE: full - steps: - - name: Find Git ref - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - shell: bash - run: | - PR_NUMBER="${{ github.event.inputs.pr_number || null }}" - if test -n "${PR_NUMBER}"; then - GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" - else - GIT_REF="${{ github.ref }}" - fi - echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ env.GIT_REF }} - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - cache-on-failure: false - cache-all-crates: true - cache-workspace-crates: true - prefix-key: v1 - - # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # `cargo build --manifest-path` (which apparently build different dependency trees). - # However, we've been unable to fix it so... /shrug - - name: Check v8 outputs - shell: bash - run: | - find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then - echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - cargo clean -p v8 || true - cargo build -p v8 - fi - - - name: Verify crates/smoketests/tests/smoketests/mod.rs lists all entries - run: | - cargo ci smoketests check-mod-list - - docs-build: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: Docs build - runs-on: spacetimedb-new-runner-2 - env: - RUST_BACKTRACE: full - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '22' - - - uses: ./.github/actions/setup-pnpm - with: - run_install: true - - - name: Get pnpm store directory - working-directory: sdks/typescript - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Docusaurus build - run: cargo ci docs - - typescript-test: - needs: [merge_queue_noop] - if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} - name: TypeScript - Tests - runs-on: spacetimedb-new-runner-2 - env: - RUST_BACKTRACE: full - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - - uses: ./.github/actions/setup-pnpm - with: - run_install: true - - - name: Get pnpm store directory - shell: bash - working-directory: crates/bindings-typescript - run: | - echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - - uses: actions/cache@v4 - name: Setup pnpm cache - with: - path: ${{ env.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - # - name: Extract SpacetimeDB branch name from file - # id: extract-branch - # run: | - # # Define the path to the branch file - # BRANCH_FILE=".github/spacetimedb-branch.txt" - - # # Default to master if file doesn't exist - # if [ ! -f "$BRANCH_FILE" ]; then - # echo "::notice::No SpacetimeDB branch file found, using 'master'" - # echo "branch=master" >> $GITHUB_OUTPUT - # exit 0 - # fi - - # # Read and trim whitespace from the file - # branch=$(cat "$BRANCH_FILE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') - - # # Fallback to master if empty - # if [ -z "$branch" ]; then - # echo "::warning::SpacetimeDB branch file is empty, using 'master'" - # branch="master" - # fi - - # echo "branch=$branch" >> $GITHUB_OUTPUT - # echo "Using SpacetimeDB branch from file: $branch" - - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ github.workspace }} - shared-key: spacetimedb - # Let the main CI job save the cache since it builds the most things - save-if: false - prefix-key: v1 - - # # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. - # # ChatGPT suspects that this could be due to different build invocations using the same target dir, - # # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with - # # `cargo build --manifest-path` (which apparently build different dependency trees). - # # However, we've been unable to fix it so... /shrug - # - name: Check v8 outputs - # run: | - # find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true - # if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then - # echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - # cargo clean -p v8 || true - # cargo build -p v8 - # fi - # if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then - # echo "Could not find v8 output file librusty_v8.a; rebuilding manually." - # cargo clean --release -p v8 || true - # cargo build --release -p v8 - # fi - - # - name: Install SpacetimeDB CLI from the local checkout - # run: | - # export CARGO_HOME="$HOME/.cargo" - # echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" - # cargo install --force --path crates/cli --locked --message-format=short - # cargo install --force --path crates/standalone --locked --message-format=short - # # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). - # ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - # # Clear any existing information - # spacetime server clear -y - - - name: Run TypeScript tests - run: cargo ci typescript-test - - # - name: Run quickstart-chat tests - # working-directory: examples/quickstart-chat - # run: pnpm test - # - # # Run this step always, even if the previous steps fail - # - name: Print rows in the user table - # if: always() - # run: spacetime sql quickstart-chat "SELECT * FROM user" + cargo nextest run --manifest-path crates/schema/Cargo.toml From de11ccf245848724c6a72d8049355cf44fa0c6ab Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 12:55:29 +0200 Subject: [PATCH 03/31] [bfops/consolidate-default-tests]: fix --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c7cd10a6a0..6f0e3d5c16f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,6 @@ jobs: cargo build --release -p v8 fi - - name: Test crates/schema - uses: actions/setup-dotnet@v3 with: global-json-file: global.json From b1e810a606804c6e8408d56d59f73c1d69d9c908 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 13:04:48 +0200 Subject: [PATCH 04/31] [bfops/consolidate-default-tests]: fix --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f0e3d5c16f..215b8f3cc66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,4 +120,4 @@ jobs: - name: Run tests run: | source ~/emsdk/emsdk_env.sh - cargo nextest run --manifest-path crates/schema/Cargo.toml + cargo test --manifest-path crates/schema/Cargo.toml From d292c682cb4e9e605d46279072fe1d191da3a6d1 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:09:14 +0200 Subject: [PATCH 05/31] [bfops/test-suite-fixes]: fix non-forwarded option --- crates/smoketests/src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 998ce935693..170b170881a 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -636,18 +636,21 @@ impl<'a> PublishBuilder<'a> { module_name, &source.module_source, clear, + break_clients, ), ModuleLanguage::CSharp => smoketest.publish_csharp_module_source_internal( &source.project_dir_name, module_name, &source.module_source, clear, + break_clients, ), ModuleLanguage::Cpp => smoketest.publish_cpp_module_source_internal( &source.project_dir_name, module_name, &source.module_source, clear, + break_clients, ), }; } @@ -1202,6 +1205,7 @@ impl Smoketest { module_name: &str, module_source: &str, clear: bool, + break_clients: bool, ) -> Result { let module_root = self.project_dir.path().join(project_dir_name); let module_root_str = module_root.to_str().context("Invalid TypeScript project path")?; @@ -1237,6 +1241,9 @@ impl Smoketest { if clear { publish_args.push("--clear-database"); } + if break_clients { + publish_args.push("--break-clients"); + } publish_args.push(module_name); let publish_output = self.spacetime(&publish_args)?; @@ -1257,6 +1264,7 @@ impl Smoketest { module_name: &str, module_source: &str, clear: bool, + break_clients: bool, ) -> Result { let module_root = self.project_dir.path().join(project_dir_name); let module_root_str = module_root.to_str().context("Invalid C# project path")?; @@ -1290,6 +1298,9 @@ impl Smoketest { if clear { publish_args.push("--clear-database"); } + if break_clients { + publish_args.push("--break-clients"); + } publish_args.push(module_name); let publish_output = self.spacetime(&publish_args)?; csharp::verify_csharp_module_restore(&module_path)?; @@ -1306,6 +1317,7 @@ impl Smoketest { module_name: &str, module_source: &str, clear: bool, + break_clients: bool, ) -> Result { let module_path = self.project_dir.path().join(project_dir_name); let src_dir = module_path.join("src"); @@ -1333,6 +1345,9 @@ impl Smoketest { if clear { publish_args.push("--clear-database"); } + if break_clients { + publish_args.push("--break-clients"); + } publish_args.push(module_name); let publish_output = self.spacetime(&publish_args)?; From d22a17ea3829f41ffe213b48cedde5cd35c4507a Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:22:27 +0200 Subject: [PATCH 06/31] [bfops/test-suite-fixes]: fix --- crates/smoketests/src/lib.rs | 336 +++++++++++++++-------------------- 1 file changed, 143 insertions(+), 193 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 170b170881a..04c037743ba 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -628,34 +628,41 @@ impl<'a> PublishBuilder<'a> { source, } = self; - if let Some(source) = source { + let mut csharp_module_path = None; + let (module_path_arg, module_path, module_build_args): (&str, PathBuf, &[&str]) = if let Some(source) = source { let module_name = name.as_deref().context("No module name provided for source publish")?; - return match source.language { - ModuleLanguage::TypeScript => smoketest.publish_typescript_module_source_internal( + let module_path = match source.language { + ModuleLanguage::TypeScript => smoketest.prepare_typescript_module_source_internal( &source.project_dir_name, module_name, &source.module_source, - clear, - break_clients, - ), - ModuleLanguage::CSharp => smoketest.publish_csharp_module_source_internal( - &source.project_dir_name, - module_name, - &source.module_source, - clear, - break_clients, - ), - ModuleLanguage::Cpp => smoketest.publish_cpp_module_source_internal( - &source.project_dir_name, - module_name, - &source.module_source, - clear, - break_clients, - ), + )?, + ModuleLanguage::CSharp => { + let module_path = smoketest.prepare_csharp_module_source_internal( + &source.project_dir_name, + module_name, + &source.module_source, + )?; + csharp_module_path = Some(module_path.clone()); + module_path + } + ModuleLanguage::Cpp => { + smoketest.prepare_cpp_module_source_internal(&source.project_dir_name, &source.module_source)? + } }; - } + let module_build_args: &[&str] = match source.language { + ModuleLanguage::CSharp => &["--dotnet-version", "10"], + ModuleLanguage::TypeScript | ModuleLanguage::Cpp => &[], + }; + ("--module-path", module_path, module_build_args) + } else { + ("--bin-path", smoketest.prepare_rust_module_internal()?, &[]) + }; - smoketest.publish_module_internal( + let identity = smoketest.publish_prepared_module_internal( + module_path_arg, + &module_path, + module_build_args, name.as_deref(), clear, break_clients, @@ -663,7 +670,13 @@ impl<'a> PublishBuilder<'a> { organization.as_deref(), force, stdin_input.as_deref(), - ) + )?; + + if let Some(module_path) = csharp_module_path { + csharp::verify_csharp_module_restore(&module_path)?; + } + + Ok(identity) } } @@ -1199,14 +1212,12 @@ impl Smoketest { Ok(String::from_utf8_lossy(&output.stdout).to_string()) } - fn publish_typescript_module_source_internal( + fn prepare_typescript_module_source_internal( &mut self, project_dir_name: &str, module_name: &str, module_source: &str, - clear: bool, - break_clients: bool, - ) -> Result { + ) -> Result { let module_root = self.project_dir.path().join(project_dir_name); let module_root_str = module_root.to_str().context("Invalid TypeScript project path")?; self.spacetime(&[ @@ -1229,43 +1240,15 @@ impl Smoketest { let ts_bindings_path = ts_bindings.to_str().context("Invalid TypeScript bindings path")?; pnpm(&["install", ts_bindings_path], &module_path)?; - let module_path_str = module_path.to_str().context("Invalid TypeScript module path")?; - let mut publish_args = vec![ - "publish", - "--server", - &self.server_url, - "--module-path", - module_path_str, - "--yes", - ]; - if clear { - publish_args.push("--clear-database"); - } - if break_clients { - publish_args.push("--break-clients"); - } - publish_args.push(module_name); - let publish_output = self.spacetime(&publish_args)?; - - let re = Regex::new(r"identity: ([0-9a-fA-F]+)").unwrap(); - let identity = re - .captures(&publish_output) - .and_then(|caps| caps.get(1)) - .map(|m| m.as_str().to_string()) - .context("Failed to parse database identity from publish output")?; - self.database_identity = Some(identity.clone()); - - Ok(identity) + Ok(module_path) } - fn publish_csharp_module_source_internal( + fn prepare_csharp_module_source_internal( &mut self, project_dir_name: &str, module_name: &str, module_source: &str, - clear: bool, - break_clients: bool, - ) -> Result { + ) -> Result { let module_root = self.project_dir.path().join(project_dir_name); let module_root_str = module_root.to_str().context("Invalid C# project path")?; self.spacetime(&[ @@ -1284,41 +1267,10 @@ impl Smoketest { fs::write(module_path.join("Lib.cs"), module_source).context("Failed to write C# module code")?; csharp::prepare_csharp_module(&module_path)?; - let module_path_str = module_path.to_str().context("Invalid C# module path")?; - let mut publish_args = vec![ - "publish", - "--server", - &self.server_url, - "--module-path", - module_path_str, - "--dotnet-version", - "10", - "--yes", - ]; - if clear { - publish_args.push("--clear-database"); - } - if break_clients { - publish_args.push("--break-clients"); - } - publish_args.push(module_name); - let publish_output = self.spacetime(&publish_args)?; - csharp::verify_csharp_module_restore(&module_path)?; - - let identity = parse_identity_from_publish_output(&publish_output)?; - self.database_identity = Some(identity.clone()); - - Ok(identity) + Ok(module_path) } - fn publish_cpp_module_source_internal( - &mut self, - project_dir_name: &str, - module_name: &str, - module_source: &str, - clear: bool, - break_clients: bool, - ) -> Result { + fn prepare_cpp_module_source_internal(&mut self, project_dir_name: &str, module_source: &str) -> Result { let module_path = self.project_dir.path().join(project_dir_name); let src_dir = module_path.join("src"); fs::create_dir_all(&src_dir).context("Failed to create C++ source directory")?; @@ -1333,28 +1285,7 @@ impl Smoketest { fs::write(module_path.join("CMakeLists.txt"), cmakelists).context("Failed to write C++ CMakeLists.txt")?; fs::write(src_dir.join("lib.cpp"), module_source).context("Failed to write C++ module code")?; - let module_path_str = module_path.to_str().context("Invalid C++ module path")?; - let mut publish_args = vec![ - "publish", - "--server", - &self.server_url, - "--module-path", - module_path_str, - "--yes", - ]; - if clear { - publish_args.push("--clear-database"); - } - if break_clients { - publish_args.push("--break-clients"); - } - publish_args.push(module_name); - let publish_output = self.spacetime(&publish_args)?; - - let identity = parse_identity_from_publish_output(&publish_output)?; - self.database_identity = Some(identity.clone()); - - Ok(identity) + Ok(module_path) } /// Writes new module code to the project. @@ -1461,18 +1392,44 @@ log = "0.4" PublishBuilder::new(self) } - /// Publishes the module and stores the database identity. - /// - /// If `name` is provided, the database will be published with that name. - /// If `clear` is true, the database will be cleared before publishing. - /// If `force` is false, the publish command will not pass `--yes`, so interactive prompts are not suppressed. - /// If `stdin_input` is provided, it will be passed to the CLI for interactive prompts. - /// - /// When `name` is an existing database identity, this re-publishes to that database, which is useful for testing - /// auto-migrations where you want to update the module without clearing the database. + fn prepare_rust_module_internal(&self) -> Result { + if let Some(precompiled_path) = &self.precompiled_wasm_path { + eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); + return Ok(precompiled_path.clone()); + } + + 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) + ); + } + + let wasm_filename = format!("{}.wasm", self.module_name); + Ok(target_dir.join("wasm32-unknown-unknown/release").join(wasm_filename)) + } + #[allow(clippy::too_many_arguments)] - fn publish_module_internal( + fn publish_prepared_module_internal( &mut self, + module_path_arg: &str, + module_path: &Path, + module_build_args: &[&str], name: Option<&str>, clear: bool, break_clients: bool, @@ -1481,84 +1438,30 @@ log = "0.4" force: Option<&str>, stdin_input: Option<&str>, ) -> Result { - let start = Instant::now(); - - // Determine the WASM path - either precompiled or build it - let wasm_path_str = if let Some(ref precompiled_path) = self.precompiled_wasm_path { - // Use pre-compiled WASM directly (no build needed) - eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); - precompiled_path.to_str().unwrap().to_string() - } else { - // Build the WASM module from source - let project_path = self.project_dir.path().to_str().unwrap().to_string(); - 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); - let wasm_path = target_dir.join("wasm32-unknown-unknown/release").join(&wasm_filename); - wasm_path.to_str().unwrap().to_string() - }; - - // Now publish with --bin-path to skip rebuild let publish_start = Instant::now(); - let mut args = vec!["publish", "--server", &self.server_url, "--bin-path", &wasm_path_str]; - let force_arg; - if let Some(force) = force { - force_arg = format!("--yes={force}"); - args.push(&force_arg); - } - - if clear { - args.push("--clear-database"); - } - - if break_clients { - args.push("--break-clients"); - } - - let num_replicas_owned = num_replicas.map(|n| n.to_string()); - if let Some(n) = num_replicas_owned.as_ref() { - args.push("--num-replicas"); - args.push(n); - } - - if let Some(org) = organization { - args.push("--organization"); - args.push(org); - } - - if let Some(n) = name { - args.push(n); - } + let module_path = module_path.to_str().context("Invalid module path")?; + let args = build_publish_args( + &self.server_url, + module_path_arg, + module_path, + module_build_args, + name, + clear, + break_clients, + num_replicas, + organization, + force, + ); + let arg_refs = args.iter().map(String::as_str).collect::>(); let output = match stdin_input { - Some(stdin_input) => self.spacetime_with_stdin(&args, stdin_input)?, - None => self.spacetime(&args)?, + Some(stdin_input) => self.spacetime_with_stdin(&arg_refs, stdin_input)?, + None => self.spacetime(&arg_refs)?, }; eprintln!( "[TIMING] spacetime publish (after build): {:?}", publish_start.elapsed() ); - eprintln!("[TIMING] publish_module total: {:?}", start.elapsed()); parse_identity_from_publish_output(&output).inspect(|identity| { self.database_identity = Some(identity.clone()); @@ -1956,6 +1859,53 @@ impl Drop for SubscriptionHandle { } } +#[allow(clippy::too_many_arguments)] +#[deny(unused_variables)] +fn build_publish_args( + server_url: &str, + module_path_arg: &str, + module_path: &str, + module_build_args: &[&str], + name: Option<&str>, + clear: bool, + break_clients: bool, + num_replicas: Option, + organization: Option<&str>, + force: Option<&str>, +) -> Vec { + let mut args = vec![ + "publish".to_string(), + "--server".to_string(), + server_url.to_string(), + module_path_arg.to_string(), + module_path.to_string(), + ]; + args.extend(module_build_args.iter().map(|arg| (*arg).to_string())); + + if let Some(force) = force { + args.push(format!("--yes={force}")); + } + if clear { + args.push("--clear-database".to_string()); + } + if break_clients { + args.push("--break-clients".to_string()); + } + if let Some(num_replicas) = num_replicas { + args.push("--num-replicas".to_string()); + args.push(num_replicas.to_string()); + } + if let Some(organization) = organization { + args.push("--organization".to_string()); + args.push(organization.to_string()); + } + if let Some(name) = name { + args.push(name.to_string()); + } + + args +} + fn split_server_url(server_url: &str) -> (&str, &str) { if let Some(host) = server_url.strip_prefix("http://") { ("http", host) From 73c4b6f4ee13ef3d0f8f612fef2244785efa5dde Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:29:11 +0200 Subject: [PATCH 07/31] [bfops/test-suite-fixes]: review --- crates/smoketests/src/lib.rs | 88 ++++++++++++------------------------ 1 file changed, 29 insertions(+), 59 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 04c037743ba..73282224816 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -1440,18 +1440,35 @@ log = "0.4" ) -> Result { let publish_start = Instant::now(); let module_path = module_path.to_str().context("Invalid module path")?; - let args = build_publish_args( - &self.server_url, - module_path_arg, - module_path, - module_build_args, - name, - clear, - break_clients, - num_replicas, - organization, - force, - ); + let mut args = vec![ + "publish".to_string(), + "--server".to_string(), + self.server_url.clone(), + module_path_arg.to_string(), + module_path.to_string(), + ]; + args.extend(module_build_args.iter().map(|arg| (*arg).to_string())); + + if let Some(force) = force { + args.push(format!("--yes={force}")); + } + if clear { + args.push("--clear-database".to_string()); + } + if break_clients { + args.push("--break-clients".to_string()); + } + if let Some(num_replicas) = num_replicas { + args.push("--num-replicas".to_string()); + args.push(num_replicas.to_string()); + } + if let Some(organization) = organization { + args.push("--organization".to_string()); + args.push(organization.to_string()); + } + if let Some(name) = name { + args.push(name.to_string()); + } let arg_refs = args.iter().map(String::as_str).collect::>(); let output = match stdin_input { @@ -1859,53 +1876,6 @@ impl Drop for SubscriptionHandle { } } -#[allow(clippy::too_many_arguments)] -#[deny(unused_variables)] -fn build_publish_args( - server_url: &str, - module_path_arg: &str, - module_path: &str, - module_build_args: &[&str], - name: Option<&str>, - clear: bool, - break_clients: bool, - num_replicas: Option, - organization: Option<&str>, - force: Option<&str>, -) -> Vec { - let mut args = vec![ - "publish".to_string(), - "--server".to_string(), - server_url.to_string(), - module_path_arg.to_string(), - module_path.to_string(), - ]; - args.extend(module_build_args.iter().map(|arg| (*arg).to_string())); - - if let Some(force) = force { - args.push(format!("--yes={force}")); - } - if clear { - args.push("--clear-database".to_string()); - } - if break_clients { - args.push("--break-clients".to_string()); - } - if let Some(num_replicas) = num_replicas { - args.push("--num-replicas".to_string()); - args.push(num_replicas.to_string()); - } - if let Some(organization) = organization { - args.push("--organization".to_string()); - args.push(organization.to_string()); - } - if let Some(name) = name { - args.push(name.to_string()); - } - - args -} - fn split_server_url(server_url: &str) -> (&str, &str) { if let Some(host) = server_url.strip_prefix("http://") { ("http", host) From 18e7aa12f5bb3b3541d1fedc04927b5a587c18dc Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:34:18 +0200 Subject: [PATCH 08/31] [bfops/test-suite-fixes]: review --- crates/smoketests/src/lib.rs | 85 +++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 73282224816..da21deb344b 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -616,6 +616,7 @@ impl<'a> PublishBuilder<'a> { } pub fn run(self) -> Result { + let start = Instant::now(); let PublishBuilder { smoketest, name, @@ -631,12 +632,15 @@ impl<'a> PublishBuilder<'a> { let mut csharp_module_path = None; let (module_path_arg, module_path, module_build_args): (&str, PathBuf, &[&str]) = if let Some(source) = source { let module_name = name.as_deref().context("No module name provided for source publish")?; - let module_path = match source.language { - ModuleLanguage::TypeScript => smoketest.prepare_typescript_module_source_internal( - &source.project_dir_name, - module_name, - &source.module_source, - )?, + let (module_path, module_build_args): (PathBuf, &[&str]) = match source.language { + ModuleLanguage::TypeScript => ( + smoketest.prepare_typescript_module_source_internal( + &source.project_dir_name, + module_name, + &source.module_source, + )?, + &[], + ), ModuleLanguage::CSharp => { let module_path = smoketest.prepare_csharp_module_source_internal( &source.project_dir_name, @@ -644,15 +648,12 @@ impl<'a> PublishBuilder<'a> { &source.module_source, )?; csharp_module_path = Some(module_path.clone()); - module_path - } - ModuleLanguage::Cpp => { - smoketest.prepare_cpp_module_source_internal(&source.project_dir_name, &source.module_source)? + (module_path, &["--dotnet-version", "10"]) } - }; - let module_build_args: &[&str] = match source.language { - ModuleLanguage::CSharp => &["--dotnet-version", "10"], - ModuleLanguage::TypeScript | ModuleLanguage::Cpp => &[], + ModuleLanguage::Cpp => ( + smoketest.prepare_cpp_module_source_internal(&source.project_dir_name, &source.module_source)?, + &[], + ), }; ("--module-path", module_path, module_build_args) } else { @@ -676,6 +677,8 @@ impl<'a> PublishBuilder<'a> { csharp::verify_csharp_module_restore(&module_path)?; } + eprintln!("[TIMING] publish_module total: {:?}", start.elapsed()); + Ok(identity) } } @@ -1424,6 +1427,15 @@ log = "0.4" 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. + /// If `clear` is true, the database will be cleared before publishing. + /// If `force` is false, the publish command will not pass `--yes`, so interactive prompts are not suppressed. + /// If `stdin_input` is provided, it will be passed to the CLI for interactive prompts. + /// + /// When `name` is an existing database identity, this re-publishes to that database, which is useful for testing + /// auto-migrations where you want to update the module without clearing the database. #[allow(clippy::too_many_arguments)] fn publish_prepared_module_internal( &mut self, @@ -1440,40 +1452,41 @@ log = "0.4" ) -> Result { let publish_start = Instant::now(); let module_path = module_path.to_str().context("Invalid module path")?; - let mut args = vec![ - "publish".to_string(), - "--server".to_string(), - self.server_url.clone(), - module_path_arg.to_string(), - module_path.to_string(), - ]; - args.extend(module_build_args.iter().map(|arg| (*arg).to_string())); + let mut args = vec!["publish", "--server", &self.server_url, module_path_arg, module_path]; + args.extend(module_build_args.iter().copied()); + let force_arg; if let Some(force) = force { - args.push(format!("--yes={force}")); + force_arg = format!("--yes={force}"); + args.push(&force_arg); } + if clear { - args.push("--clear-database".to_string()); + args.push("--clear-database"); } + if break_clients { - args.push("--break-clients".to_string()); + args.push("--break-clients"); } - if let Some(num_replicas) = num_replicas { - args.push("--num-replicas".to_string()); - args.push(num_replicas.to_string()); + + let num_replicas_owned = num_replicas.map(|n| n.to_string()); + if let Some(n) = num_replicas_owned.as_ref() { + args.push("--num-replicas"); + args.push(n); } - if let Some(organization) = organization { - args.push("--organization".to_string()); - args.push(organization.to_string()); + + if let Some(org) = organization { + args.push("--organization"); + args.push(org); } - if let Some(name) = name { - args.push(name.to_string()); + + if let Some(n) = name { + args.push(n); } - let arg_refs = args.iter().map(String::as_str).collect::>(); let output = match stdin_input { - Some(stdin_input) => self.spacetime_with_stdin(&arg_refs, stdin_input)?, - None => self.spacetime(&arg_refs)?, + Some(stdin_input) => self.spacetime_with_stdin(&args, stdin_input)?, + None => self.spacetime(&args)?, }; eprintln!( "[TIMING] spacetime publish (after build): {:?}", From 65d2b3e84cf4bf6efaad66fa1ffd8cc93e4bc03e Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:38:34 +0200 Subject: [PATCH 09/31] [bfops/test-suite-fixes]: review --- crates/smoketests/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index da21deb344b..02134787c15 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -629,8 +629,9 @@ impl<'a> PublishBuilder<'a> { source, } = self; - let mut csharp_module_path = None; - let (module_path_arg, module_path, module_build_args): (&str, PathBuf, &[&str]) = if let Some(source) = source { + let (module_path_arg, module_path, module_build_args): (&str, PathBuf, &[&str]) = if let Some(source) = + source.as_ref() + { let module_name = name.as_deref().context("No module name provided for source publish")?; let (module_path, module_build_args): (PathBuf, &[&str]) = match source.language { ModuleLanguage::TypeScript => ( @@ -647,7 +648,6 @@ impl<'a> PublishBuilder<'a> { module_name, &source.module_source, )?; - csharp_module_path = Some(module_path.clone()); (module_path, &["--dotnet-version", "10"]) } ModuleLanguage::Cpp => ( @@ -673,7 +673,10 @@ impl<'a> PublishBuilder<'a> { stdin_input.as_deref(), )?; - if let Some(module_path) = csharp_module_path { + if matches!( + source.as_ref().map(|source| source.language), + Some(ModuleLanguage::CSharp) + ) { csharp::verify_csharp_module_restore(&module_path)?; } From c439c83af319034633a124be1ec68cb5b981af7f Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 17:50:15 +0200 Subject: [PATCH 10/31] [bfops/test-suite-fixes]: restore comments --- crates/smoketests/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 02134787c15..fd750557131 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -1399,11 +1399,14 @@ log = "0.4" } fn prepare_rust_module_internal(&self) -> Result { + // Determine the WASM path - either precompiled or build it if let Some(precompiled_path) = &self.precompiled_wasm_path { + // Use pre-compiled WASM directly (no build needed) eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); return Ok(precompiled_path.clone()); } + // 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(); @@ -1426,6 +1429,7 @@ log = "0.4" ); } + // 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)) } From 563b102000aaacbe82327e976712c91672a8d44e Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 18:57:47 +0200 Subject: [PATCH 11/31] [bfops/test-suite-fixes]: review --- crates/smoketests/src/lib.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index fd750557131..74637ae1500 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -656,11 +656,17 @@ impl<'a> PublishBuilder<'a> { ), }; ("--module-path", module_path, module_build_args) + } else if let Some(module_path) = smoketest.precompiled_wasm_path.clone() { + // Use pre-compiled WASM directly (no build needed) + eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); + ("--bin-path", module_path, &[]) } else { + // 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. ("--bin-path", smoketest.prepare_rust_module_internal()?, &[]) }; - let identity = smoketest.publish_prepared_module_internal( + let identity = smoketest.publish_module_internal( module_path_arg, &module_path, module_build_args, @@ -1398,14 +1404,11 @@ log = "0.4" 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 { - // Determine the WASM path - either precompiled or build it - if let Some(precompiled_path) = &self.precompiled_wasm_path { - // Use pre-compiled WASM directly (no build needed) - eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); - return Ok(precompiled_path.clone()); - } - // Build the WASM module from source let project_path = self.project_dir.path().to_str().unwrap(); let build_start = Instant::now(); @@ -1444,7 +1447,7 @@ log = "0.4" /// When `name` is an existing database identity, this re-publishes to that database, which is useful for testing /// auto-migrations where you want to update the module without clearing the database. #[allow(clippy::too_many_arguments)] - fn publish_prepared_module_internal( + fn publish_module_internal( &mut self, module_path_arg: &str, module_path: &Path, From 5c9a37cb16101c58a93c47eec4bf2954a63fa033 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 19:11:26 +0200 Subject: [PATCH 12/31] [bfops/test-suite-fixes]: review --- crates/smoketests/src/lib.rs | 93 +++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/crates/smoketests/src/lib.rs b/crates/smoketests/src/lib.rs index 74637ae1500..5b90de38a03 100644 --- a/crates/smoketests/src/lib.rs +++ b/crates/smoketests/src/lib.rs @@ -629,47 +629,78 @@ impl<'a> PublishBuilder<'a> { source, } = self; - let (module_path_arg, module_path, module_build_args): (&str, PathBuf, &[&str]) = if let Some(source) = - source.as_ref() - { + let post_publish_step: Option Result<()>>>; + let module_args; + if let Some(source) = source.as_ref() { let module_name = name.as_deref().context("No module name provided for source publish")?; - let (module_path, module_build_args): (PathBuf, &[&str]) = match source.language { - ModuleLanguage::TypeScript => ( - smoketest.prepare_typescript_module_source_internal( + match source.language { + ModuleLanguage::TypeScript => { + post_publish_step = None; + let module_path = smoketest.prepare_typescript_module_source_internal( &source.project_dir_name, module_name, &source.module_source, - )?, - &[], - ), + )?; + module_args = vec![ + "--module-path".to_string(), + module_path + .to_str() + .context("Invalid TypeScript module path")? + .to_string(), + ]; + } ModuleLanguage::CSharp => { let module_path = smoketest.prepare_csharp_module_source_internal( &source.project_dir_name, module_name, &source.module_source, )?; - (module_path, &["--dotnet-version", "10"]) + let module_path_arg = module_path.to_str().context("Invalid C# module path")?.to_string(); + post_publish_step = Some(Box::new(move || csharp::verify_csharp_module_restore(&module_path))); + module_args = vec![ + "--module-path".to_string(), + module_path_arg, + "--dotnet-version".to_string(), + "10".to_string(), + ]; } - ModuleLanguage::Cpp => ( - smoketest.prepare_cpp_module_source_internal(&source.project_dir_name, &source.module_source)?, - &[], - ), - }; - ("--module-path", module_path, module_build_args) - } else if let Some(module_path) = smoketest.precompiled_wasm_path.clone() { + ModuleLanguage::Cpp => { + post_publish_step = None; + let module_path = smoketest + .prepare_cpp_module_source_internal(&source.project_dir_name, &source.module_source)?; + module_args = vec![ + "--module-path".to_string(), + module_path.to_str().context("Invalid C++ module path")?.to_string(), + ]; + } + } + } else if let Some(module_path) = smoketest.precompiled_wasm_path.as_ref() { + post_publish_step = None; // Use pre-compiled WASM directly (no build needed) eprintln!("[TIMING] spacetime build: skipped (using precompiled)"); - ("--bin-path", module_path, &[]) + module_args = vec![ + "--bin-path".to_string(), + module_path + .to_str() + .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. - ("--bin-path", smoketest.prepare_rust_module_internal()?, &[]) - }; + 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( - module_path_arg, - &module_path, - module_build_args, + &module_args, name.as_deref(), clear, break_clients, @@ -679,11 +710,8 @@ impl<'a> PublishBuilder<'a> { stdin_input.as_deref(), )?; - if matches!( - source.as_ref().map(|source| source.language), - Some(ModuleLanguage::CSharp) - ) { - csharp::verify_csharp_module_restore(&module_path)?; + if let Some(post_publish_step) = post_publish_step { + post_publish_step()?; } eprintln!("[TIMING] publish_module total: {:?}", start.elapsed()); @@ -1449,9 +1477,7 @@ log = "0.4" #[allow(clippy::too_many_arguments)] fn publish_module_internal( &mut self, - module_path_arg: &str, - module_path: &Path, - module_build_args: &[&str], + module_args: &[String], name: Option<&str>, clear: bool, break_clients: bool, @@ -1461,9 +1487,8 @@ log = "0.4" stdin_input: Option<&str>, ) -> Result { let publish_start = Instant::now(); - let module_path = module_path.to_str().context("Invalid module path")?; - let mut args = vec!["publish", "--server", &self.server_url, module_path_arg, module_path]; - args.extend(module_build_args.iter().copied()); + let mut args = vec!["publish", "--server", &self.server_url]; + args.extend(module_args.iter().map(String::as_str)); let force_arg; if let Some(force) = force { From 3e7258b0220b3c884f4a350edeb0f1f5ecb5eed0 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 20:09:34 +0200 Subject: [PATCH 13/31] [bfops/consolidate-default-tests]: revert --- .github/workflows/ci.yml | 1346 ++++++++++++++++++++- crates/schema/tests/ensure_same_schema.rs | 10 - 2 files changed, 1345 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 215b8f3cc66..038334613ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,244 @@ permissions: pull-requests: read jobs: + merge_queue_noop: + name: Detect merge queue no-op + runs-on: ubuntu-latest + outputs: + skip: ${{ steps.compare.outputs.skip }} + steps: + - name: Checkout merge queue commit + uses: actions/checkout@v4 + + - name: Compare merge queue commit to PR head + id: compare + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.event.merge_group.head_ref }} + shell: bash + run: | + set -euo pipefail + + echo "skip=false" >>"$GITHUB_OUTPUT" + + if [[ "${GITHUB_EVENT_NAME}" != "merge_group" ]]; then + echo "Not a merge_group event; running CI normally." + exit 0 + fi + + if [[ ! "${HEAD_REF}" =~ /pr-([0-9]+)- ]]; then + echo "Could not parse PR number from merge group head ref: ${HEAD_REF}" + exit 0 + fi + + pr_number="${BASH_REMATCH[1]}" + pr_head_sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" --jq '.head.sha')" + pr_head_repo="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}" --jq '.head.repo.full_name')" + + if [[ -z "${pr_head_sha}" || "${pr_head_sha}" == "null" || -z "${pr_head_repo}" || "${pr_head_repo}" == "null" ]]; then + echo "Could not resolve PR head SHA for PR #${pr_number}; running CI normally." + exit 0 + fi + + git fetch --no-tags --depth=1 "https://github.com/${pr_head_repo}.git" "${pr_head_sha}" + + if git diff --quiet "${pr_head_sha}" "${GITHUB_SHA}" --; then + echo "Merge queue commit ${GITHUB_SHA} has the same tree as PR #${pr_number} head ${pr_head_sha}; skipping duplicate CI." + echo "skip=true" >>"$GITHUB_OUTPUT" + else + echo "Merge queue commit ${GITHUB_SHA} differs from PR #${pr_number} head ${pr_head_sha}; running CI normally." + fi + + smoketests: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Smoketests (${{ matrix.name }}) + strategy: + matrix: + include: + - name: Linux + runner: spacetimedb-new-runner-2 + - name: Windows + runner: spacetimedb-windows-runner + runs-on: ${{ matrix.runner }} + timeout-minutes: 120 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + SPACETIMEDB_CPP_DIR: ${{ github.workspace }}/crates/bindings-cpp + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Set up sccache + uses: mozilla-actions/sccache-action@v0.0.10 + + - name: Enable sccache + shell: bash + run: | + echo "SCCACHE_GHA_ENABLED=true" >>"$GITHUB_ENV" + echo "RUSTC_WRAPPER=sccache" >>"$GITHUB_ENV" + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + cache-on-failure: false + cache-all-crates: true + cache-workspace-crates: true + prefix-key: v1 + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + # nodejs and pnpm are required for the typescript quickstart smoketest + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - uses: ./.github/actions/setup-pnpm + with: + run_install: true + + # Install emscripten for C++ module compilation tests. + - name: Install emscripten (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + git clone https://github.com/emscripten-core/emsdk.git ~/emsdk + cd ~/emsdk + ./emsdk install 4.0.21 + ./emsdk activate 4.0.21 + + - name: Install emscripten (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + git clone https://github.com/emscripten-core/emsdk.git $env:USERPROFILE\emsdk + cd $env:USERPROFILE\emsdk + .\emsdk install 4.0.21 + .\emsdk activate 4.0.21 + + - name: Install psql (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + # Fail properly if any individual command fails + $ErrorActionPreference = 'Stop' + $PSNativeCommandUseErrorActionPreference = $true + choco install psql -y --no-progress + # Check for existence, since `choco` doesn't seem to fail the step if it fails to install.. + # See https://github.com/clockworklabs/SpacetimeDB/pull/4399 for more background. + Get-Command psql + + - name: Update dotnet workloads + if: runner.os == 'Windows' + run: | + # Fail properly if any individual command fails + $ErrorActionPreference = 'Stop' + $PSNativeCommandUseErrorActionPreference = $true + + cd modules + # the sdk-manifests on windows-latest are messed up, so we need to update them + dotnet workload config --update-mode manifests + dotnet workload update --from-previous-sdk + # Explicitly install wasi-experimental for .NET 8 SDK (needed for test_build_csharp_module) + # Create temp global.json to target .NET 8 SDK for workload install + $sdk8Json = '{"sdk":{"version":"8.0.400","rollForward":"latestFeature"}}' + $sdk8Json | Out-File -FilePath global.json -Encoding utf8 + dotnet workload install wasi-experimental + Remove-Item global.json + + - name: Override NuGet packages + shell: bash + run: | + dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime + dotnet pack -c Release crates/bindings-csharp/Runtime + cd sdks/csharp + ./tools~/write-nuget-config.sh ../.. + + # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # `cargo build --manifest-path` (which apparently build different dependency trees). + # However, we've been unable to fix it so... /shrug + - name: Check v8 outputs + shell: bash + run: | + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean --release -p v8 || true + cargo build --release -p v8 + fi + + - name: Install cargo-nextest + uses: taiki-e/install-action@nextest + + # --test-threads=1 eliminates contention in the C# tests where they fight over bindings + # build artifacts. + # It also seemed to improve performance a fair amount (11m -> 6m) + - name: Run smoketests (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + if [ -f ~/emsdk/emsdk_env.sh ]; then + source ~/emsdk/emsdk_env.sh + fi + cargo ci smoketests -- --test-threads=1 + + # Due to Emscripten PATH issues this was separated to make sure OpenSSL still builds correctly + - name: Run smoketests (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + if (Test-Path "$env:USERPROFILE\emsdk\emsdk_env.ps1") { + & "$env:USERPROFILE\emsdk\emsdk_env.ps1" | Out-Null + } + cargo ci smoketests -- --test-threads=1 + + # this is a no-op version of the above check with a trivially-passing body. + # we can't just let the check be entirely skipped because each matrix target is a required check, + # and skipping this check means that the matrix isn't "populated" so the required checks wouldn't be met. + smoketests_noop: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip == 'true' }} + name: Smoketests (${{ matrix.name }}) + strategy: + matrix: + include: + - name: Linux + - name: Windows + runs-on: ubuntu-latest + steps: + - name: Skip duplicate merge queue smoketest + run: echo "Merge queue commit has the same tree as the PR head; smoketest already ran for the PR." + test: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} name: Test Suite runs-on: spacetimedb-new-runner-2 @@ -120,4 +357,1111 @@ jobs: - name: Run tests run: | source ~/emsdk/emsdk_env.sh - cargo test --manifest-path crates/schema/Cargo.toml + cargo ci test + + keynote_bench: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Keynote Bench + runs-on: spacetimedb-benchmark-runner + concurrency: + group: ci-benchmark-runner + queue: max + timeout-minutes: 60 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + # Node 24 is the current Active LTS line. + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - uses: ./.github/actions/setup-pnpm + with: + package_json_file: package.json + working_directory: . + run_install: "true" + + - uses: ./.github/actions/keynote-bench-setup + with: + public_root: . + rust_cache_workspaces: ${{ github.workspace }} + + - name: Run keynote-2 benchmark regression check + run: cargo ci keynote-bench + + index_scan_bench: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Index Scan Bench + runs-on: spacetimedb-benchmark-runner + concurrency: + group: ci-benchmark-runner + queue: max + timeout-minutes: 60 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Set up sccache + uses: mozilla-actions/sccache-action@v0.0.10 + + - name: Enable sccache + run: | + echo "SCCACHE_GHA_ENABLED=true" >>"$GITHUB_ENV" + echo "RUSTC_WRAPPER=sccache" >>"$GITHUB_ENV" + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb-index-scan + cache-on-failure: false + prefix-key: v1 + + - name: Run index scan benchmark regression check + run: cargo run --release -p spacetimedb-index-scan-gate + + lints: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Lints + runs-on: spacetimedb-new-runner-2 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - run: echo ::add-matcher::.github/workflows/rust_matcher.json + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the smoketests job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + - uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - uses: ./.github/actions/setup-pnpm + with: + run_install: true + + - name: Run ci lint + run: cargo ci lint + + wasm_bindings: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Build and test wasm bindings + runs-on: spacetimedb-new-runner-2 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - uses: actions/checkout@v3 + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - run: echo ::add-matcher::.github/workflows/rust_matcher.json + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the smoketests job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + - name: Run bindgen tests + run: cargo ci wasm-bindings + + publish_checks: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Check that packages are publishable + runs-on: spacetimedb-new-runner-2 + permissions: read-all + env: + RUST_BACKTRACE: full + steps: + - uses: actions/checkout@v3 + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - name: Run checks + run: cargo ci publish-checks + + update: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Test spacetimedb-update flow (${{ matrix.target }}) + permissions: read-all + strategy: + matrix: + include: + - { target: x86_64-unknown-linux-gnu, runner: spacetimedb-new-runner-2 } + - { target: aarch64-apple-darwin, runner: macos-latest } + - { target: x86_64-pc-windows-msvc, runner: windows-latest } + runs-on: ${{ matrix.runner }} + env: + RUST_BACKTRACE: full + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Rust + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Install rust target + run: rustup target add ${{ matrix.target }} + + - name: Test spacetimedb-update + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: cargo ci update-flow --target=${{ matrix.target }} --github-token-auth + + # this is a no-op version of the above check with a trivially-passing body. + # we can't just let the check be entirely skipped because each matrix target is a required check, + # and skipping this check means that the matrix isn't "populated" so the required checks wouldn't be met. + update_noop: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip == 'true' }} + name: Test spacetimedb-update flow (${{ matrix.target }}) + permissions: read-all + strategy: + matrix: + include: + - { target: x86_64-unknown-linux-gnu } + - { target: aarch64-apple-darwin } + - { target: x86_64-pc-windows-msvc } + runs-on: ubuntu-latest + steps: + - name: Skip duplicate merge queue update test + run: echo "Merge queue commit has the same tree as the PR head; update-flow already ran for the PR." + + unreal_engine_tests: + name: Unreal Engine Tests + # This can't go on e.g. ubuntu-latest because that runner runs out of disk space. ChatGPT suggested that the general solution tends to be to use + # a custom runner. + runs-on: spacetimedb-new-runner-2 + # Disable the tests because they are very flaky at the moment. + # TODO: Remove this line and re-enable the `if` line just below here. + if: false + # Skip if this is an external contribution. GitHub secrets will be empty, so the step would fail anyway. + # if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} + container: + image: ghcr.io/epicgames/unreal-engine:dev-5.6 + credentials: + # Note(bfops): I don't think that `github.actor` needs to match the user that the token is for, because I'm using a token for my account and + # it seems to be totally happy. + # However, the token needs to be for a user that has access to the EpicGames org (see + # https://dev.epicgames.com/documentation/en-us/unreal-engine/downloading-source-code-in-unreal-engine?application_version=5.6) + username: ${{ github.actor }} + password: ${{ secrets.GHCR_TOKEN }} + # Run as root because otherwise we get permission denied for various directories inside the container. I tried doing dances to allow it to run + # without this (reassigning env vars and stuff), but was unable to get it to work and it felt like an uphill battle. + options: --user 0:0 + env: + RUST_BACKTRACE: full + steps: + # Uncomment this before merging so that it will run properly if run manually through the GH actions flow. It was playing weird with rolled back + # commits though. + # - name: Find Git ref + # env: + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # shell: bash + # run: | + # PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + # if test -n "${PR_NUMBER}"; then + # GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + # else + # GIT_REF="${{ github.ref }}" + # fi + # echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - name: Run Unreal Engine tests + working-directory: sdks/unreal + env: + UE_ROOT_PATH: /home/ue4/UnrealEngine + run: | + + apt-get update + apt-get install -y acl curl ca-certificates + + REPO="$GITHUB_WORKSPACE" + # Let ue4 read/write the workspace & tool caches without changing ownership + for p in "$REPO" "${RUNNER_TEMP:-/__t}" "${RUNNER_TOOL_CACHE:-/__t}"; do + [ -d "$p" ] && setfacl -R -m u:ue4:rwX -m d:u:ue4:rwX "$p" || true + done + + # Rust tool caches live under the runner tool cache so they persist + export CARGO_HOME="${RUNNER_TOOL_CACHE:-/__t}/cargo" + export RUSTUP_HOME="${RUNNER_TOOL_CACHE:-/__t}/rustup" + mkdir -p "$CARGO_HOME" "$RUSTUP_HOME" + chown -R ue4:ue4 "$CARGO_HOME" "$RUSTUP_HOME" + + # Make sure the UE build script is executable (and parents traversable) + UE_DIR="${UE_ROOT_PATH:-/home/ue4/UnrealEngine}" + chmod a+rx "$UE_DIR" "$UE_DIR/Engine" "$UE_DIR/Engine/Build" "$UE_DIR/Engine/Build/BatchFiles/Linux" || true + chmod a+rx "$UE_DIR/Engine/Build/BatchFiles/Linux/Build.sh" || true + + # Run the build & tests as ue4 (who owns the UE tree) + sudo -E -H -u ue4 env \ + HOME=/home/ue4 \ + XDG_CONFIG_HOME=/home/ue4/.config \ + CARGO_HOME="$CARGO_HOME" \ + RUSTUP_HOME="$RUSTUP_HOME" \ + PATH="$CARGO_HOME/bin:$PATH" \ + bash -lc ' + set -euxo pipefail + # Install rustup for ue4 if needed (uses the shared caches) + if ! command -v cargo >/dev/null 2>&1; then + curl -sSf https://sh.rustup.rs | sh -s -- -y + fi + rustup show >/dev/null + git config --global --add safe.directory "$GITHUB_WORKSPACE" || true + + cd "$GITHUB_WORKSPACE/sdks/unreal" + cargo --version + cargo test -- --test-threads=1 + ' + + ci_command_docs: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Check CI command docs + runs-on: spacetimedb-new-runner-2 + env: + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Check for docs change + run: cargo ci self-docs --check + + cli_docs: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Check CLI docs + permissions: read-all + runs-on: spacetimedb-new-runner-2 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - uses: ./.github/actions/setup-pnpm + with: + run_install: true + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the smoketests job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + - name: Check for docs change + run: | + cargo ci cli-docs + + unity-testsuite: + needs: [merge_queue_noop, lints] + # Skip if this is an external contribution. + # The license secrets will be empty, so the step would fail anyway. + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) }} + permissions: + contents: read + checks: write + runs-on: spacetimedb-unity-runner + timeout-minutes: 30 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Checkout repository + id: checkout-stdb + uses: actions/checkout@v4 + + # Run cheap .NET tests first. If those fail, no need to run expensive Unity tests. + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - name: Override NuGet packages + run: | + dotnet pack crates/bindings-csharp/BSATN.Runtime + dotnet pack crates/bindings-csharp/Runtime + + # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository + # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if + # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. + # This means that (if version numbers match) we will test the local versions of the C# packages, even + # if they're not pushed to NuGet. + # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. + cd sdks/csharp + ./tools~/write-nuget-config.sh ../.. + + - name: Restore .NET solution + working-directory: sdks/csharp + run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln + + # Now, setup the Unity tests. + - name: Patch spacetimedb dependency in Cargo.toml + working-directory: demo/Blackholio/server-rust + run: | + sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml + cat Cargo.toml + + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the main CI job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # `cargo build --manifest-path` (which apparently build different dependency trees). + # However, we've been unable to fix it so... /shrug + - name: Check v8 outputs + run: | + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean --release -p v8 || true + cargo build --release -p v8 + fi + + - name: Install SpacetimeDB CLI from the local checkout + run: | + export CARGO_HOME="$HOME/.cargo" + echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" + cargo install --force --path crates/cli --locked --message-format=short + cargo install --force --path crates/standalone --locked --message-format=short + # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). + ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime + + - name: Generate client bindings + working-directory: demo/Blackholio/server-rust + run: bash ./generate.sh -y + + - name: Check for changes + run: | + tools/check-diff.sh demo/Blackholio/client-unity/Assets/Scripts/autogen || { + echo 'Error: Bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' + exit 1 + } + + - name: Hydrate Unity SDK DLLs + run: cargo regen csharp dlls + + - name: Check Unity meta files + uses: DeNA/unity-meta-check@v3 + with: + enable_pr_comment: ${{ github.event_name == 'pull_request' }} + target_path: sdks/csharp + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Start SpacetimeDB + run: | + spacetime start & + disown + + - name: Publish unity-tests module to SpacetimeDB + working-directory: demo/Blackholio/server-rust + run: | + spacetime logout && spacetime login --server-issued-login local + bash ./publish.sh + + - name: Patch com.clockworklabs.spacetimedbsdk dependency in manifest.json + working-directory: demo/Blackholio/client-unity/Packages + run: | + yq e -i '.dependencies["com.clockworklabs.spacetimedbsdk"] = "file:../../../../sdks/csharp"' manifest.json + cat manifest.json + + - uses: actions/cache@v3 + with: + path: demo/Blackholio/client-unity/Library + key: Unity-${{ github.head_ref }} + restore-keys: Unity- + + - name: Run Unity tests + uses: game-ci/unity-test-runner@v4 + with: + unityVersion: 2022.3.32f1 # Adjust Unity version to a valid tag + projectPath: demo/Blackholio/client-unity # Path to the Unity project subdirectory + githubToken: ${{ secrets.GITHUB_TOKEN }} + testMode: playmode + useHostNetwork: true + artifactsPath: "" + env: + UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }} + UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }} + UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }} + + godot-testsuite: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + permissions: + contents: read + runs-on: spacetimedb-new-runner-2 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + UseLocalBsatnRuntime: true + steps: + - name: Checkout repository + id: checkout-stdb + uses: actions/checkout@v4 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - name: Override NuGet packages + run: | + dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime + dotnet pack -c Release crates/bindings-csharp/Runtime + + # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository + # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if + # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. + # This means that (if version numbers match) we will test the local versions of the C# packages, even + # if they're not pushed to NuGet. + # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. + cd sdks/csharp + ./tools~/write-nuget-config.sh ../.. + + - name: Restore .NET solution + working-directory: sdks/csharp + run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln + + # Now, setup the Godot tests. + - name: Patch spacetimedb dependency in Cargo.toml + working-directory: demo/Blackholio/server-rust + run: | + sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml + cat Cargo.toml + + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the main CI job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # `cargo build --manifest-path` (which apparently build different dependency trees). + # However, we've been unable to fix it so... /shrug + - name: Check v8 outputs + run: | + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean --release -p v8 || true + cargo build --release -p v8 + fi + + - name: Install SpacetimeDB CLI from the local checkout + run: | + export CARGO_HOME="$HOME/.cargo" + echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" + cargo install --force --path crates/cli --locked --message-format=short + cargo install --force --path crates/standalone --locked --message-format=short + # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). + ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime + + - name: Generate client bindings + working-directory: demo/Blackholio/server-rust + run: bash ./generate.sh -y + + - name: Check for changes + run: | + tools/check-diff.sh demo/Blackholio/client-godot/module_bindings || { + echo 'Error: Godot bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' + exit 1 + } + + - name: Patch SpacetimeDB Godot SDK dependency + working-directory: demo/Blackholio/client-godot + run: | + dotnet remove package SpacetimeDB.ClientSDK.Godot + dotnet add reference ../../../sdks/csharp/SpacetimeDB.ClientSDK.Godot.csproj + cat blackholio.csproj + + - name: Setup Godot + uses: chickensoft-games/setup-godot@v2 + with: + version: 4.6.2 + use-dotnet: true + + - name: Restore Godot project + working-directory: demo/Blackholio/client-godot + run: dotnet restore --configfile ../../../NuGet.Config blackholio.csproj + + - name: Build Godot project + working-directory: demo/Blackholio/client-godot + run: godot --headless --verbose --build-solutions --quit + + - name: Start SpacetimeDB + run: | + spacetime start & + disown + + - name: Publish godot-tests module to SpacetimeDB + working-directory: demo/Blackholio/server-rust + run: | + spacetime login --server-issued-login local + bash ./publish.sh + + - name: Run Godot tests + working-directory: demo/Blackholio/client-godot + run: godot --headless --scene res://tests/GodotPlayModeTests.tscn + + csharp-testsuite: + needs: [merge_queue_noop, lints] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + runs-on: spacetimedb-new-runner-2 + timeout-minutes: 30 + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Checkout repository + id: checkout-stdb + uses: actions/checkout@v4 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + + - name: Install .NET workloads + run: | + dotnet workload config --update-mode manifests + dotnet workload update --from-previous-sdk + # Explicitly install wasi-experimental for .NET 8 SDK (needed for test_build_csharp_module) + # Create temp global.json to target .NET 8 SDK for workload install + echo '{"sdk":{"version":"8.0.100","rollForward":"latestFeature"}}' > global.json + dotnet workload install wasi-experimental + rm global.json + + - name: Override NuGet packages + run: | + dotnet pack crates/bindings-csharp/BSATN.Runtime + dotnet pack crates/bindings-csharp/Runtime + + # Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository + # to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if + # available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages. + # This means that (if version numbers match) we will test the local versions of the C# packages, even + # if they're not pushed to NuGet. + # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. + cd sdks/csharp + ./tools~/write-nuget-config.sh ../.. + + - name: Restore .NET solution + working-directory: sdks/csharp + run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln + + - name: Run .NET tests + working-directory: sdks/csharp + run: dotnet test -warnaserror --no-restore SpacetimeDB.ClientSDK.csproj + + - name: Verify C# formatting + working-directory: sdks/csharp + run: dotnet format --no-restore --verify-no-changes SpacetimeDB.ClientSDK.sln + + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the main CI job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # `cargo build --manifest-path` (which apparently build different dependency trees). + # However, we've been unable to fix it so... /shrug + - name: Check v8 outputs + run: | + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean -p v8 || true + cargo build -p v8 + fi + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean --release -p v8 || true + cargo build --release -p v8 + fi + + - name: Install SpacetimeDB CLI from the local checkout + run: | + export CARGO_HOME="$HOME/.cargo" + echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" + cargo install --force --path crates/cli --locked --message-format=short + cargo install --force --path crates/standalone --features allow_loopback_http_for_tests --locked --message-format=short + # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). + ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime + + - name: Check quickstart-chat bindings are up to date + run: | + for dotnet_version in 8 10; do + bash sdks/csharp/tools~/gen-quickstart.sh "$dotnet_version" + done + tools/check-diff.sh templates/chat-console-cs/module_bindings || { + echo 'Error: quickstart-chat bindings have changed. Please run `sdks/csharp/tools~/gen-quickstart.sh 10`.' + exit 1 + } + + # TODO: Re-enable this once csharp is using the v2 ws api. + # - name: Check client-api bindings are up to date + # working-directory: sdks/csharp + # run: | + # bash tools~/gen-client-api.sh + # "${GITHUB_WORKSPACE}"/tools/check-diff.sh src/SpacetimeDB/ClientApi || { + # echo 'Error: Client API bindings are dirty. Please run `sdks/csharp/tools~/gen-client-api.sh`.' + # exit 1 + # } + + - name: Start SpacetimeDB + run: | + spacetime start & + disown + + - name: Run regression tests + run: | + bash sdks/csharp/tools~/run-regression-tests.sh + # Restore global.json symlinks (we replaced them with files to work around .NET 10 SDK bug) + # server is 5 levels deep from root, republishing dirs are 6 levels deep + if [ -f sdks/csharp/examples~/regression-tests/server/global.json ] && [ ! -L sdks/csharp/examples~/regression-tests/server/global.json ]; then + echo "Restoring symlink at server/global.json" + rm -f sdks/csharp/examples~/regression-tests/server/global.json + ln -s ../../../../../global.json sdks/csharp/examples~/regression-tests/server/global.json + fi + for dir in sdks/csharp/examples~/regression-tests/republishing/server-initial \ + sdks/csharp/examples~/regression-tests/republishing/server-republish; do + if [ -f "$dir/global.json" ] && [ ! -L "$dir/global.json" ]; then + echo "Restoring symlink at $dir/global.json" + rm -f "$dir/global.json" + ln -s ../../../../../../global.json "$dir/global.json" + fi + done + tools/check-diff.sh sdks/csharp/examples~/regression-tests || { + echo 'Error: Bindings are dirty. Please run `sdks/csharp/tools~/gen-regression-tests.sh`.' + exit 1 + } + + global_json_policy: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Verify global.json files are symlinks + runs-on: ubuntu-latest + permissions: + contents: read + env: + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + PR_NUMBER: ${{ github.event.inputs.pr_number }} + run: | + if [ -n "$PR_NUMBER" ]; then + GIT_REF="refs/pull/$PR_NUMBER/merge" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + save-if: false + prefix-key: v1 + + - name: Check global.json policy + run: cargo ci global-json-policy + + smoketests_mod_rs_complete: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Check smoketests/mod.rs is complete + runs-on: ubuntu-latest + permissions: + contents: read + env: + CARGO_TARGET_DIR: ${{ github.workspace }}/target + RUST_BACKTRACE: full + steps: + - name: Find Git ref + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + PR_NUMBER="${{ github.event.inputs.pr_number || null }}" + if test -n "${PR_NUMBER}"; then + GIT_REF="$( gh pr view --repo clockworklabs/SpacetimeDB $PR_NUMBER --json headRefName --jq .headRefName )" + else + GIT_REF="${{ github.ref }}" + fi + echo "GIT_REF=${GIT_REF}" >>"$GITHUB_ENV" + - name: Checkout sources + uses: actions/checkout@v4 + with: + ref: ${{ env.GIT_REF }} + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + cache-on-failure: false + cache-all-crates: true + cache-workspace-crates: true + prefix-key: v1 + + # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # `cargo build --manifest-path` (which apparently build different dependency trees). + # However, we've been unable to fix it so... /shrug + - name: Check v8 outputs + shell: bash + run: | + find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then + echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + cargo clean -p v8 || true + cargo build -p v8 + fi + + - name: Verify crates/smoketests/tests/smoketests/mod.rs lists all entries + run: | + cargo ci smoketests check-mod-list + + docs-build: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: Docs build + runs-on: spacetimedb-new-runner-2 + env: + RUST_BACKTRACE: full + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + + - uses: ./.github/actions/setup-pnpm + with: + run_install: true + + - name: Get pnpm store directory + working-directory: sdks/typescript + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Docusaurus build + run: cargo ci docs + + typescript-test: + needs: [merge_queue_noop] + if: ${{ needs.merge_queue_noop.outputs.skip != 'true' }} + name: TypeScript - Tests + runs-on: spacetimedb-new-runner-2 + env: + RUST_BACKTRACE: full + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - uses: ./.github/actions/setup-pnpm + with: + run_install: true + + - name: Get pnpm store directory + shell: bash + working-directory: crates/bindings-typescript + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + # - name: Extract SpacetimeDB branch name from file + # id: extract-branch + # run: | + # # Define the path to the branch file + # BRANCH_FILE=".github/spacetimedb-branch.txt" + + # # Default to master if file doesn't exist + # if [ ! -f "$BRANCH_FILE" ]; then + # echo "::notice::No SpacetimeDB branch file found, using 'master'" + # echo "branch=master" >> $GITHUB_OUTPUT + # exit 0 + # fi + + # # Read and trim whitespace from the file + # branch=$(cat "$BRANCH_FILE" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + + # # Fallback to master if empty + # if [ -z "$branch" ]; then + # echo "::warning::SpacetimeDB branch file is empty, using 'master'" + # branch="master" + # fi + + # echo "branch=$branch" >> $GITHUB_OUTPUT + # echo "Using SpacetimeDB branch from file: $branch" + + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@v2 + with: + workspaces: ${{ github.workspace }} + shared-key: spacetimedb + # Let the main CI job save the cache since it builds the most things + save-if: false + prefix-key: v1 + + # # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. + # # ChatGPT suspects that this could be due to different build invocations using the same target dir, + # # and this makes sense to me because we only see it in this job where we mix `cargo build -p` with + # # `cargo build --manifest-path` (which apparently build different dependency trees). + # # However, we've been unable to fix it so... /shrug + # - name: Check v8 outputs + # run: | + # find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true + # if ! [ -f "${CARGO_TARGET_DIR}"/debug/gn_out/obj/librusty_v8.a ]; then + # echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + # cargo clean -p v8 || true + # cargo build -p v8 + # fi + # if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then + # echo "Could not find v8 output file librusty_v8.a; rebuilding manually." + # cargo clean --release -p v8 || true + # cargo build --release -p v8 + # fi + + # - name: Install SpacetimeDB CLI from the local checkout + # run: | + # export CARGO_HOME="$HOME/.cargo" + # echo "$CARGO_HOME/bin" >> "$GITHUB_PATH" + # cargo install --force --path crates/cli --locked --message-format=short + # cargo install --force --path crates/standalone --locked --message-format=short + # # Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules). + # ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime + # # Clear any existing information + # spacetime server clear -y + + - name: Run TypeScript tests + run: cargo ci typescript-test + + # - name: Run quickstart-chat tests + # working-directory: examples/quickstart-chat + # run: pnpm test + # + # # Run this step always, even if the previous steps fail + # - name: Print rows in the user table + # if: always() + # run: spacetime sql quickstart-chat "SELECT * FROM user" diff --git a/crates/schema/tests/ensure_same_schema.rs b/crates/schema/tests/ensure_same_schema.rs index f08d4aa413e..7e5cb28f626 100644 --- a/crates/schema/tests/ensure_same_schema.rs +++ b/crates/schema/tests/ensure_same_schema.rs @@ -76,16 +76,6 @@ macro_rules! declare_tests { } )* } - mod ensure_same_schema_rust_cpp { - use super::*; - $( - #[test] - #[serial] - fn $name() { - super::assert_identical_modules($path, "C++", "cpp"); - } - )* - } } } From a7e863679703a69a8954903d511480e04fcdd968 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 20:34:02 +0200 Subject: [PATCH 14/31] [bfops/consolidate-default-tests]: WIP tests --- .../tests/smoketests/column_defaults.rs | 268 ++++++++++++++++++ crates/smoketests/tests/smoketests/mod.rs | 1 + 2 files changed, 269 insertions(+) create mode 100644 crates/smoketests/tests/smoketests/column_defaults.rs diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs new file mode 100644 index 00000000000..6b3e264150a --- /dev/null +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -0,0 +1,268 @@ +use spacetimedb_smoketests::{ + random_string, require_dotnet, require_emscripten, require_pnpm, ModuleLanguage, Smoketest, +}; + +const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ + ("bool_value", "true"), + ("i8_value", "-8"), + ("u8_value", "8"), + ("i16_value", "-16"), + ("u16_value", "16"), + ("i32_value", "-32"), + ("u32_value", "32"), + ("i64_value", "-64"), + ("u64_value", "64"), + ("f32_value", "32.5"), + ("f64_value", "-64.25"), + ("string_value", r#""default string""#), +]; + +fn test_defaults(test: &mut Smoketest, publish_updated: impl FnOnce(&mut Smoketest)) { + test.sql("INSERT INTO defaults_test_table (id) VALUES (1)").unwrap(); + publish_updated(test); + + for &(column, expected) in EXPECTED_DEFAULTS { + let output = test + .sql(&format!("SELECT {column} FROM defaults_test_table WHERE id = 1")) + .unwrap(); + let actual = output.lines().last().unwrap().trim(); + assert_eq!(actual, expected, "incorrect default for column {column}"); + } +} + +fn test_source_defaults(language: ModuleLanguage, project_name: &str, initial: &str, updated: &str) { + let mut test = Smoketest::builder().autopublish(false).build(); + let database_name = format!("column-defaults-{project_name}-{}", random_string()); + test.publish() + .name(&database_name) + .source(language, project_name, initial) + .run() + .unwrap(); + + test_defaults(&mut test, |test| { + test.publish() + .current_database() + .unwrap() + .break_clients(true) + .source(language, project_name, updated) + .run() + .unwrap(); + }); +} + +#[test] +fn test_rust_column_defaults() { + let mut test = Smoketest::builder().module_code(RUST_INITIAL).build(); + test_defaults(&mut test, |test| { + test.write_module_code(RUST_UPDATED).unwrap(); + test.publish() + .current_database() + .unwrap() + .break_clients(true) + .run() + .unwrap(); + }); +} + +#[test] +fn test_typescript_column_defaults() { + require_pnpm!(); + test_source_defaults( + ModuleLanguage::TypeScript, + "column-defaults-ts", + TYPESCRIPT_INITIAL, + TYPESCRIPT_UPDATED, + ); +} + +#[test] +fn test_csharp_column_defaults() { + require_dotnet!(); + test_source_defaults( + ModuleLanguage::CSharp, + "column-defaults-csharp", + CSHARP_INITIAL, + CSHARP_UPDATED, + ); +} + +#[test] +fn test_cpp_column_defaults() { + require_emscripten!(); + 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_value: f32, + #[default(-64.25)] + pub f64_value: f64, + #[default("default string")] + pub string_value: String, +} +"#; + +const TYPESCRIPT_INITIAL: &str = r#" +import { schema, t, table } from "spacetimedb/server"; + +const defaultsTestTable = table( + { name: "defaults_test_table", public: true }, + { id: t.u32() } +); + +export default schema({ defaultsTestTable }); +"#; + +const TYPESCRIPT_UPDATED: &str = r#" +import { schema, t, table } from "spacetimedb/server"; + +const defaultsTestTable = table( + { name: "defaults_test_table", public: true }, + { + id: t.u32(), + bool_value: t.bool().default(true), + i8_value: t.i8().default(-8), + u8_value: t.u8().default(8), + i16_value: t.i16().default(-16), + u16_value: t.u16().default(16), + i32_value: t.i32().default(-32), + u32_value: t.u32().default(32), + i64_value: t.i64().default(-64n), + u64_value: t.u64().default(64n), + f32_value: t.f32().default(32.5), + f64_value: t.f64().default(-64.25), + string_value: t.string().default("default string"), + } +); + +export default schema({ defaultsTestTable }); +"#; + +const CSHARP_INITIAL: &str = r#" +using SpacetimeDB; + +public static partial class Module +{ + [Table(Accessor = "defaults_test_table", Public = true)] + public partial struct DefaultsTestTable + { + public uint id; + } +} +"#; + +const CSHARP_UPDATED: &str = r#" +using SpacetimeDB; + +public static partial class Module +{ + [Table(Accessor = "defaults_test_table", Public = true)] + public partial struct DefaultsTestTable + { + public uint id; + [Default(true)] public bool bool_value; + [Default((sbyte)-8)] public sbyte i8_value; + [Default((byte)8)] public byte u8_value; + [Default((short)-16)] public short i16_value; + [Default((ushort)16)] public ushort u16_value; + [Default(-32)] public int i32_value; + [Default(32U)] public uint u32_value; + [Default(-64L)] public long i64_value; + [Default(64UL)] public ulong u64_value; + [Default(32.5f)] public float f32_value; + [Default(-64.25)] public double f64_value; + [Default("default string")] public string string_value = ""; + } +} +"#; + +const CPP_INITIAL: &str = r#" +#include "spacetimedb.h" + +using namespace SpacetimeDB; + +struct DefaultsTestTable { + uint32_t id; +}; +SPACETIMEDB_STRUCT(DefaultsTestTable, id) +SPACETIMEDB_TABLE(DefaultsTestTable, defaults_test_table, Public) +"#; + +const CPP_UPDATED: &str = r#" +#include "spacetimedb.h" + +using namespace SpacetimeDB; + +struct DefaultsTestTable { + uint32_t id; + bool bool_value; + int8_t i8_value; + uint8_t u8_value; + int16_t i16_value; + uint16_t u16_value; + int32_t i32_value; + uint32_t u32_value; + int64_t i64_value; + uint64_t u64_value; + float f32_value; + double f64_value; + std::string string_value; +}; +SPACETIMEDB_STRUCT( + DefaultsTestTable, + id, + bool_value, + i8_value, + u8_value, + i16_value, + u16_value, + i32_value, + u32_value, + i64_value, + u64_value, + f32_value, + f64_value, + string_value +) +SPACETIMEDB_TABLE(DefaultsTestTable, defaults_test_table, Public) +FIELD_Default(defaults_test_table, bool_value, true) +FIELD_Default(defaults_test_table, i8_value, int8_t(-8)) +FIELD_Default(defaults_test_table, u8_value, uint8_t(8)) +FIELD_Default(defaults_test_table, i16_value, int16_t(-16)) +FIELD_Default(defaults_test_table, u16_value, uint16_t(16)) +FIELD_Default(defaults_test_table, i32_value, int32_t(-32)) +FIELD_Default(defaults_test_table, u32_value, uint32_t(32)) +FIELD_Default(defaults_test_table, i64_value, int64_t(-64)) +FIELD_Default(defaults_test_table, u64_value, uint64_t(64)) +FIELD_Default(defaults_test_table, f32_value, float(32.5)) +FIELD_Default(defaults_test_table, f64_value, double(-64.25)) +FIELD_Default(defaults_test_table, string_value, std::string("default string")) +"#; diff --git a/crates/smoketests/tests/smoketests/mod.rs b/crates/smoketests/tests/smoketests/mod.rs index f0114e19df5..5831b4813fa 100644 --- a/crates/smoketests/tests/smoketests/mod.rs +++ b/crates/smoketests/tests/smoketests/mod.rs @@ -6,6 +6,7 @@ mod call; mod change_host_type; mod cli; mod client_connection_errors; +mod column_defaults; mod confirmed_reads; mod connect_disconnect_from_cli; mod create_project; From 28b04f57edd3c1cd3bc41c00d306021ea2641fb6 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:00:42 +0200 Subject: [PATCH 15/31] [bfops/consolidate-default-tests]: fixes --- .../tests/smoketests/column_defaults.rs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 6b3e264150a..2333a765599 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -4,17 +4,19 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), - ("i8_value", "-8"), ("u8_value", "8"), - ("i16_value", "-16"), + // TODO: uncomment this and other signed values once they're fixed in Rust + //("i8_value", "-8"), ("u16_value", "16"), - ("i32_value", "-32"), + //("i16_value", "-16"), ("u32_value", "32"), - ("i64_value", "-64"), + //("i32_value", "-32"), ("u64_value", "64"), + //("i64_value", "-64"), ("f32_value", "32.5"), ("f64_value", "-64.25"), - ("string_value", r#""default string""#), + // TODO: uncomment once this is fixed in Rust + //("string_value", r#""default string""#), ]; fn test_defaults(test: &mut Smoketest, publish_updated: impl FnOnce(&mut Smoketest)) { @@ -33,9 +35,11 @@ fn test_defaults(test: &mut Smoketest, publish_updated: impl FnOnce(&mut Smokete fn test_source_defaults(language: ModuleLanguage, project_name: &str, initial: &str, updated: &str) { let mut test = Smoketest::builder().autopublish(false).build(); let database_name = format!("column-defaults-{project_name}-{}", random_string()); + let initial_project_name = format!("{project_name}-initial"); + let updated_project_name = format!("{project_name}-updated"); test.publish() .name(&database_name) - .source(language, project_name, initial) + .source(language, &initial_project_name, initial) .run() .unwrap(); @@ -44,7 +48,7 @@ fn test_source_defaults(language: ModuleLanguage, project_name: &str, initial: & .current_database() .unwrap() .break_clients(true) - .source(language, project_name, updated) + .source(language, &updated_project_name, updated) .run() .unwrap(); }); @@ -105,28 +109,28 @@ pub struct DefaultsTestTable { pub id: u32, #[default(true)] pub bool_value: bool, - #[default(-8)] - pub i8_value: i8, +// #[default(-8)] +// pub i8_value: i8, #[default(8)] pub u8_value: u8, - #[default(-16)] - pub i16_value: i16, +// #[default(-16)] +// pub i16_value: i16, #[default(16)] pub u16_value: u16, - #[default(-32)] - pub i32_value: i32, +// #[default(-32)] +// pub i32_value: i32, #[default(32)] pub u32_value: u32, - #[default(-64)] - pub i64_value: i64, +// #[default(-64)] +// pub i64_value: i64, #[default(64)] pub u64_value: u64, #[default(32.5)] pub f32_value: f32, #[default(-64.25)] pub f64_value: f64, - #[default("default string")] - pub string_value: String, +// #[default("default string")] +// pub string_value: String, } "#; From 2e66dcc5daaff1dd1b95fa07873832e578e1ead0 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:01:55 +0200 Subject: [PATCH 16/31] [bfops/consolidate-default-tests]: comment out --- crates/smoketests/tests/smoketests/column_defaults.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 2333a765599..fe9b8e7e908 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,7 +5,7 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this and other signed values once they're fixed in Rust + // TODO: uncomment this and other negative values once they're fixed in Rust //("i8_value", "-8"), ("u16_value", "16"), //("i16_value", "-16"), @@ -14,7 +14,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("u64_value", "64"), //("i64_value", "-64"), ("f32_value", "32.5"), - ("f64_value", "-64.25"), + //("f64_value", "-64.25"), // TODO: uncomment once this is fixed in Rust //("string_value", r#""default string""#), ]; @@ -127,8 +127,8 @@ pub struct DefaultsTestTable { pub u64_value: u64, #[default(32.5)] pub f32_value: f32, - #[default(-64.25)] - pub f64_value: f64, +// #[default(-64.25)] +// pub f64_value: f64, // #[default("default string")] // pub string_value: String, } From cccd876e3760fd14e88d6ca255af7aa0f5952b56 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:02:26 +0200 Subject: [PATCH 17/31] [bfops/consolidate-default-tests]: empty From fa604aa340f916aff21b94052f397b00e2f50541 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:02:30 +0200 Subject: [PATCH 18/31] [bfops/consolidate-default-tests]: empty From d95227d2fc1cdee681ad2e14189cc34ff2d831e3 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:03:22 +0200 Subject: [PATCH 19/31] [bfops/consolidate-default-tests]: empty From b0f93a40af1a58189ae4d6af0fd5dfe240578835 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:15:52 +0200 Subject: [PATCH 20/31] [bfops/consolidate-default-tests]: moar fix --- crates/smoketests/tests/smoketests/column_defaults.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index fe9b8e7e908..698e5763f52 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,12 +5,15 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this and other negative values once they're fixed in Rust + // TODO: uncomment this and other negative values once they're fixed in Rust. + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i8_value", "-8"), ("u16_value", "16"), //("i16_value", "-16"), ("u32_value", "32"), //("i32_value", "-32"), + // TODO: uncomment once u64s are fixed. + // https://github.com/clockworklabs/SpacetimeDB/issues/5623 ("u64_value", "64"), //("i64_value", "-64"), ("f32_value", "32.5"), @@ -123,8 +126,8 @@ pub struct DefaultsTestTable { pub u32_value: u32, // #[default(-64)] // pub i64_value: i64, - #[default(64)] - pub u64_value: u64, +// #[default(64)] +// pub u64_value: u64, #[default(32.5)] pub f32_value: f32, // #[default(-64.25)] From b3ec5f3b3dca67b86a89f03928a60485a35934c4 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:19:09 +0200 Subject: [PATCH 21/31] Apply suggestion from @bfops Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com> --- crates/smoketests/tests/smoketests/column_defaults.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index fe9b8e7e908..48dc4889a56 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -15,7 +15,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ //("i64_value", "-64"), ("f32_value", "32.5"), //("f64_value", "-64.25"), - // TODO: uncomment once this is fixed in Rust + // TODO: uncomment this once string default values are fixed in Rust //("string_value", r#""default string""#), ]; From d75c708385648fa5b21fed48676298641a54fd7f Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:22:05 +0200 Subject: [PATCH 22/31] [bfops/consolidate-default-tests]: actually comment --- crates/smoketests/tests/smoketests/column_defaults.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 698e5763f52..34d7f6d6a30 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -14,7 +14,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ //("i32_value", "-32"), // TODO: uncomment once u64s are fixed. // https://github.com/clockworklabs/SpacetimeDB/issues/5623 - ("u64_value", "64"), + //("u64_value", "64"), //("i64_value", "-64"), ("f32_value", "32.5"), //("f64_value", "-64.25"), From ba860143df4d80e7a33d512dfa8e66a4a080ab26 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 21:33:50 +0200 Subject: [PATCH 23/31] [bfops/consolidate-default-tests]: more comment out --- crates/smoketests/tests/smoketests/column_defaults.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 9c1e7e56167..cf3c2604b96 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -16,7 +16,9 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ // https://github.com/clockworklabs/SpacetimeDB/issues/5623 //("u64_value", "64"), //("i64_value", "-64"), - ("f32_value", "32.5"), + // TODO: uncomment once f32s are fixed. + // https://github.com/clockworklabs/SpacetimeDB/issues/5624 + //("f32_value", "32.5"), //("f64_value", "-64.25"), // TODO: uncomment this once string default values are fixed in Rust //("string_value", r#""default string""#), From 60caa8f97947ca1995ba2e59c441b9d37c299b31 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 22:10:02 +0200 Subject: [PATCH 24/31] [bfops/consolidate-default-tests]: fix C# --- crates/smoketests/tests/smoketests/column_defaults.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index cf3c2604b96..3ddc2dbcb54 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -208,7 +208,7 @@ public static partial class Module [Default(64UL)] public ulong u64_value; [Default(32.5f)] public float f32_value; [Default(-64.25)] public double f64_value; - [Default("default string")] public string string_value = ""; + [Default("default string")] public string string_value; } } "#; From 89a411095447b1dfc503edeeb833d576953313ad Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 23:01:38 +0200 Subject: [PATCH 25/31] [bfops/consolidate-default-tests]: C# f32s --- crates/smoketests/tests/smoketests/column_defaults.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 3ddc2dbcb54..80d6bbbdb8b 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -206,7 +206,7 @@ public static partial class Module [Default(32U)] public uint u32_value; [Default(-64L)] public long i64_value; [Default(64UL)] public ulong u64_value; - [Default(32.5f)] public float f32_value; + //[Default(32.5f)] public float f32_value; [Default(-64.25)] public double f64_value; [Default("default string")] public string string_value; } From fd633ddb6baf3759917d19a54a3c5cbf501d3816 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 23:01:56 +0200 Subject: [PATCH 26/31] [bfops/consolidate-default-tests]: comment --- crates/smoketests/tests/smoketests/column_defaults.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 80d6bbbdb8b..3f69b8fdf9e 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -18,6 +18,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ //("i64_value", "-64"), // TODO: uncomment once f32s are fixed. // https://github.com/clockworklabs/SpacetimeDB/issues/5624 + // https://github.com/clockworklabs/SpacetimeDB/issues/5627 //("f32_value", "32.5"), //("f64_value", "-64.25"), // TODO: uncomment this once string default values are fixed in Rust From c8cf4f3ff1cbed465d9713b86b4f51d344048bc8 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 23:17:45 +0200 Subject: [PATCH 27/31] [bfops/consolidate-default-tests]: negative and positive floating points --- .../tests/smoketests/column_defaults.rs | 72 ++++++++++++++----- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 3f69b8fdf9e..24e857e307e 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,22 +5,46 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this and other negative values once they're fixed in Rust. + + // TODO: uncomment this once negative values are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i8_value", "-8"), + ("u16_value", "16"), + + // TODO: uncomment this once negative values are fixed in Rust + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i16_value", "-16"), + ("u32_value", "32"), + + // TODO: uncomment this once negative values are fixed in Rust + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i32_value", "-32"), + // TODO: uncomment once u64s are fixed. // https://github.com/clockworklabs/SpacetimeDB/issues/5623 //("u64_value", "64"), + + // TODO: uncomment this once negative values are fixed in Rust + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i64_value", "-64"), - // TODO: uncomment once f32s are fixed. + + // TODO: uncomment once f32s are fixed in C#. // https://github.com/clockworklabs/SpacetimeDB/issues/5624 - // https://github.com/clockworklabs/SpacetimeDB/issues/5627 - //("f32_value", "32.5"), - //("f64_value", "-64.25"), + //("f32_positive_value", "32.5"), + + // TODO: uncomment once f32s are fixed in C# and negative values are fixed in Rust + // https://github.com/clockworklabs/SpacetimeDB/issues/5624 + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 + //("f32_negative_value", "-32.5"), + + ("f64_positive_value", "64.25"), + + // TODO: uncomment once negative defaults are fixed in Rust + // https://github.com/clockworklabs/SpacetimeDB/issues/5622 + //("f64_negative_value", "-64.25"), + // TODO: uncomment this once string default values are fixed in Rust //("string_value", r#""default string""#), ]; @@ -132,9 +156,13 @@ pub struct DefaultsTestTable { // #[default(64)] // pub u64_value: u64, #[default(32.5)] - pub f32_value: f32, + 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_value: f64, +// pub f64_negative_value: f64, // #[default("default string")] // pub string_value: String, } @@ -167,8 +195,10 @@ const defaultsTestTable = table( u32_value: t.u32().default(32), i64_value: t.i64().default(-64n), u64_value: t.u64().default(64n), - f32_value: t.f32().default(32.5), - f64_value: t.f64().default(-64.25), + f32_positive_value: t.f32().default(32.5), + f32_negative_value: t.f32().default(-32.5), + f64_positive_value: t.f64().default(64.25), + f64_negative_value: t.f64().default(-64.25), string_value: t.string().default("default string"), } ); @@ -207,8 +237,10 @@ public static partial class Module [Default(32U)] public uint u32_value; [Default(-64L)] public long i64_value; [Default(64UL)] public ulong u64_value; - //[Default(32.5f)] public float f32_value; - [Default(-64.25)] public double f64_value; + //[Default(32.5f)] public float f32_positive_value; + //[Default(-32.5f)] public float f32_negative_value; + [Default(64.25)] public double f64_positive_value; + [Default(-64.25)] public double f64_negative_value; [Default("default string")] public string string_value; } } @@ -242,8 +274,10 @@ struct DefaultsTestTable { uint32_t u32_value; int64_t i64_value; uint64_t u64_value; - float f32_value; - double f64_value; + float f32_positive_value; + float f32_negative_value; + double f64_positive_value; + double f64_negative_value; std::string string_value; }; SPACETIMEDB_STRUCT( @@ -258,8 +292,10 @@ SPACETIMEDB_STRUCT( u32_value, i64_value, u64_value, - f32_value, - f64_value, + f32_positive_value, + f32_negative_value, + f64_positive_value, + f64_negative_value, string_value ) SPACETIMEDB_TABLE(DefaultsTestTable, defaults_test_table, Public) @@ -272,7 +308,9 @@ FIELD_Default(defaults_test_table, i32_value, int32_t(-32)) FIELD_Default(defaults_test_table, u32_value, uint32_t(32)) FIELD_Default(defaults_test_table, i64_value, int64_t(-64)) FIELD_Default(defaults_test_table, u64_value, uint64_t(64)) -FIELD_Default(defaults_test_table, f32_value, float(32.5)) -FIELD_Default(defaults_test_table, f64_value, double(-64.25)) +FIELD_Default(defaults_test_table, f32_positive_value, float(32.5)) +FIELD_Default(defaults_test_table, f32_negative_value, float(-32.5)) +FIELD_Default(defaults_test_table, f64_positive_value, double(64.25)) +FIELD_Default(defaults_test_table, f64_negative_value, double(-64.25)) FIELD_Default(defaults_test_table, string_value, std::string("default string")) "#; From 42e0b98d8d880b635230414be8c9a684d028d8e2 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 23:18:01 +0200 Subject: [PATCH 28/31] [bfops/consolidate-default-tests]: cargo fmt --- crates/smoketests/tests/smoketests/column_defaults.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 24e857e307e..9f728dcad7a 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,19 +5,14 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this once negative values are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i8_value", "-8"), - ("u16_value", "16"), - // TODO: uncomment this once negative values are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i16_value", "-16"), - ("u32_value", "32"), - // TODO: uncomment this once negative values are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i32_value", "-32"), @@ -38,9 +33,7 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ // https://github.com/clockworklabs/SpacetimeDB/issues/5624 // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("f32_negative_value", "-32.5"), - ("f64_positive_value", "64.25"), - // TODO: uncomment once negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("f64_negative_value", "-64.25"), From 42b607d73b54fb0c87517f8ec4bcdfffe5bbc186 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 29 Jul 2026 23:20:14 +0200 Subject: [PATCH 29/31] [bfops/consolidate-default-tests]: comments --- .../smoketests/tests/smoketests/column_defaults.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 9f728dcad7a..0529e9b1b1c 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,15 +5,15 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this once negative values are fixed in Rust + // TODO: uncomment this once negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i8_value", "-8"), ("u16_value", "16"), - // TODO: uncomment this once negative values are fixed in Rust + // TODO: uncomment this once negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i16_value", "-16"), ("u32_value", "32"), - // TODO: uncomment this once negative values are fixed in Rust + // TODO: uncomment this once negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i32_value", "-32"), @@ -21,15 +21,15 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ // https://github.com/clockworklabs/SpacetimeDB/issues/5623 //("u64_value", "64"), - // TODO: uncomment this once negative values are fixed in Rust + // TODO: uncomment this once negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i64_value", "-64"), - // TODO: uncomment once f32s are fixed in C#. + // TODO: uncomment once floats are fixed in C#. // https://github.com/clockworklabs/SpacetimeDB/issues/5624 //("f32_positive_value", "32.5"), - // TODO: uncomment once f32s are fixed in C# and negative values are fixed in Rust + // TODO: uncomment once floats are fixed in C# and negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5624 // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("f32_negative_value", "-32.5"), From 5c5f6bb8a0455935316eb2c060ddcefbc50363e4 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Thu, 30 Jul 2026 15:06:38 +0200 Subject: [PATCH 30/31] [bfops/consolidate-default-tests]: update comments --- crates/smoketests/tests/smoketests/column_defaults.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index 0529e9b1b1c..fb4bcb6d2e2 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -25,12 +25,14 @@ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("i64_value", "-64"), - // TODO: uncomment once floats are fixed in C#. + // TODO: uncomment once floats are fixed in C# and f32s are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5624 + // https://github.com/clockworklabs/SpacetimeDB/issues/5627 //("f32_positive_value", "32.5"), - // TODO: uncomment once floats are fixed in C# and negative defaults are fixed in Rust + // TODO: uncomment once floats are fixed in C#, f32s are fixed in Rust, and negative defaults are fixed in Rust // https://github.com/clockworklabs/SpacetimeDB/issues/5624 + // https://github.com/clockworklabs/SpacetimeDB/issues/5627 // https://github.com/clockworklabs/SpacetimeDB/issues/5622 //("f32_negative_value", "-32.5"), ("f64_positive_value", "64.25"), From 4c982b091c1df45678e85f47dc078e0abaab7ce5 Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Fri, 31 Jul 2026 09:59:37 +0200 Subject: [PATCH 31/31] Fix some default column value bugs (#5630) # Description of Changes Fixes https://github.com/clockworklabs/SpacetimeDB/issues/5623 Fixes https://github.com/clockworklabs/SpacetimeDB/issues/5624 Fixes https://github.com/clockworklabs/SpacetimeDB/issues/5627 Fixes https://github.com/clockworklabs/SpacetimeDB/issues/5622 # API and ABI breaking changes No. Fixes bugs with things that won't working (two build failures, one publish failure, and one incorrect default value) # Expected complexity level and risk 2 # Testing - [x] Further smoketest cases have been enabled - [x] modules in those smoketests are building and publishing successfully --------- Co-authored-by: Zeke Foppa Co-authored-by: clockwork-labs-bot --- .../diag/snapshots/Module#FFI.verified.cs | 2 +- crates/bindings-csharp/Runtime/Attrs.cs | 4 ++ crates/bindings-macro/src/table.rs | 6 +- .../tests/smoketests/column_defaults.rs | 72 +++++++------------ 4 files changed, 34 insertions(+), 50 deletions(-) diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index e6df8c9e52a..8ae73ee287e 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -3584,7 +3584,7 @@ public static void Main() var value = new SpacetimeDB.BSATN.F32(); __memoryStream.Position = 0; __memoryStream.SetLength(0); - value.Write(__writer, 2); + value.Write(__writer, 2F); var array = __memoryStream.ToArray(); SpacetimeDB.Internal.Module.RegisterTableDefaultValue( "TestDefaultFieldValues", diff --git a/crates/bindings-csharp/Runtime/Attrs.cs b/crates/bindings-csharp/Runtime/Attrs.cs index 38b3c45edd9..3865f925a2a 100644 --- a/crates/bindings-csharp/Runtime/Attrs.cs +++ b/crates/bindings-csharp/Runtime/Attrs.cs @@ -172,6 +172,10 @@ public string Value { return value.ToString()?.ToLower()!; } + if (value is float f) + { + return $"{f.ToString("R", System.Globalization.CultureInfo.InvariantCulture)}F"; + } var str = value.ToString(); if (value is string) { diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index 1cbba4c5ca1..f25e044358c 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -1026,10 +1026,14 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R let col_defaults: Vec = columns.iter().filter_map(|col| { if let Some(val) = &col.default_value { let col_id = col.index; + let ty = &col.ty; Some(quote! { spacetimedb::table::ColumnDefault { col_id: #col_id, - value: #val.serialize(spacetimedb::sats::algebraic_value::ser::ValueSerializer).expect("default value serialization failed"), + value: { + let value: #ty = #val; + value.serialize(spacetimedb::sats::algebraic_value::ser::ValueSerializer).expect("default value serialization failed") + }, }, }) } else { diff --git a/crates/smoketests/tests/smoketests/column_defaults.rs b/crates/smoketests/tests/smoketests/column_defaults.rs index fb4bcb6d2e2..83af4e28afa 100644 --- a/crates/smoketests/tests/smoketests/column_defaults.rs +++ b/crates/smoketests/tests/smoketests/column_defaults.rs @@ -5,41 +5,17 @@ use spacetimedb_smoketests::{ const EXPECTED_DEFAULTS: &[(&str, &str)] = &[ ("bool_value", "true"), ("u8_value", "8"), - // TODO: uncomment this once negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("i8_value", "-8"), + ("i8_value", "-8"), ("u16_value", "16"), - // TODO: uncomment this once negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("i16_value", "-16"), + ("i16_value", "-16"), ("u32_value", "32"), - // TODO: uncomment this once negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("i32_value", "-32"), - - // TODO: uncomment once u64s are fixed. - // https://github.com/clockworklabs/SpacetimeDB/issues/5623 - //("u64_value", "64"), - - // TODO: uncomment this once negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("i64_value", "-64"), - - // TODO: uncomment once floats are fixed in C# and f32s are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5624 - // https://github.com/clockworklabs/SpacetimeDB/issues/5627 - //("f32_positive_value", "32.5"), - - // TODO: uncomment once floats are fixed in C#, f32s are fixed in Rust, and negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5624 - // https://github.com/clockworklabs/SpacetimeDB/issues/5627 - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("f32_negative_value", "-32.5"), + ("i32_value", "-32"), + ("u64_value", "64"), + ("i64_value", "-64"), + ("f32_positive_value", "32.5"), + ("f32_negative_value", "-32.5"), ("f64_positive_value", "64.25"), - // TODO: uncomment once negative defaults are fixed in Rust - // https://github.com/clockworklabs/SpacetimeDB/issues/5622 - //("f64_negative_value", "-64.25"), - + ("f64_negative_value", "-64.25"), // TODO: uncomment this once string default values are fixed in Rust //("string_value", r#""default string""#), ]; @@ -134,30 +110,30 @@ pub struct DefaultsTestTable { pub id: u32, #[default(true)] pub bool_value: bool, -// #[default(-8)] -// pub i8_value: i8, + #[default(-8)] + pub i8_value: i8, #[default(8)] pub u8_value: u8, -// #[default(-16)] -// pub i16_value: i16, + #[default(-16)] + pub i16_value: i16, #[default(16)] pub u16_value: u16, -// #[default(-32)] -// pub i32_value: i32, + #[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(-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(-32.5)] + pub f32_negative_value: f32, #[default(64.25)] pub f64_positive_value: f64, -// #[default(-64.25)] -// pub f64_negative_value: f64, + #[default(-64.25)] + pub f64_negative_value: f64, // #[default("default string")] // pub string_value: String, } @@ -232,8 +208,8 @@ public static partial class Module [Default(32U)] public uint u32_value; [Default(-64L)] public long i64_value; [Default(64UL)] public ulong u64_value; - //[Default(32.5f)] public float f32_positive_value; - //[Default(-32.5f)] public float f32_negative_value; + [Default(32.5f)] public float f32_positive_value; + [Default(-32.5f)] public float f32_negative_value; [Default(64.25)] public double f64_positive_value; [Default(-64.25)] public double f64_negative_value; [Default("default string")] public string string_value;