From 797772e1f3290b3b33363d13b549a8041b52c6dc Mon Sep 17 00:00:00 2001 From: Ian Calvert Date: Tue, 13 Jan 2026 09:43:50 +0000 Subject: [PATCH 01/20] Run chunks of realm-server tests on ci --- .github/workflows/ci.yaml | 102 +++++++++--------- packages/realm-server/package.json | 3 +- .../realm-server/scripts/lint-test-shards.ts | 43 ++++++-- .../realm-server/scripts/run-test-modules.js | 57 ++++++++++ 4 files changed, 150 insertions(+), 55 deletions(-) create mode 100644 packages/realm-server/scripts/run-test-modules.js diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c8b43656be8..8fed1d8ddcc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -419,60 +419,66 @@ jobs: -d '{"context":"Matrix Playwright tests report","description":"'"$description"'","target_url":"'"$PLAYWRIGHT_REPORT_URL"'","state":"'"$state"'"}' realm-server-test: - name: Realm Server Tests + name: Realm Server Tests (shard ${{ matrix.shard }}) needs: [change-check, test-web-assets] if: needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true' runs-on: ubuntu-latest concurrency: - group: realm-server-test-${{ matrix.testModule }}-${{ github.head_ref || github.run_id }} + group: realm-server-test-${{ matrix.shard }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true strategy: fail-fast: false matrix: - testModule: - [ - "auth-client-test.ts", - "billing-test.ts", - "card-dependencies-endpoint-test.ts", - "card-endpoints-test.ts", - "card-source-endpoints-test.ts", - "definition-lookup-test.ts", - "file-watcher-events-test.ts", - "indexing-test.ts", - "transpile-test.ts", - "module-syntax-test.ts", - "permissions/permission-checker-test.ts", - "prerendering-test.ts", - "prerender-server-test.ts", - "prerender-manager-test.ts", - "prerender-proxy-test.ts", - "remote-prerenderer-test.ts", - "queue-test.ts", - "realm-endpoints/dependencies-test.ts", - "realm-endpoints/directory-test.ts", - "realm-endpoints/info-test.ts", - "realm-endpoints/lint-test.ts", - "realm-endpoints/mtimes-test.ts", - "realm-endpoints/permissions-test.ts", - "realm-endpoints/publishability-test.ts", - "realm-endpoints/search-test.ts", - "realm-endpoints/user-test.ts", - "realm-endpoints-test.ts", - "search-prerendered-test.ts", - "types-endpoint-test.ts", - "server-endpoints-test.ts", - "server-endpoints/search-test.ts", - "virtual-network-test.ts", - "atomic-endpoints-test.ts", - "request-forward-test.ts", - "publish-unpublish-realm-test.ts", - "boxel-domain-availability-test.ts", - "claim-boxel-domain-test.ts", - "delete-boxel-claimed-domain-test.ts", - "get-boxel-claimed-domain-test.ts", - "realm-auth-test.ts", - "queries-test.ts", - ] + include: + - shard: 1 + testModules: + - server-endpoints-test.ts + - atomic-endpoints-test.ts + - request-forward-test.ts + - realm-endpoints/info-test.ts + - realm-endpoints/user-test.ts + - file-watcher-events-test.ts + - types-endpoint-test.ts + - prerender-manager-test.ts + - prerender-proxy-test.ts + - transpile-test.ts + - shard: 2 + testModules: + - card-endpoints-test.ts + - publish-unpublish-realm-test.ts + - realm-endpoints/lint-test.ts + - claim-boxel-domain-test.ts + - realm-endpoints/publishability-test.ts + - boxel-domain-availability-test.ts + - realm-endpoints/dependencies-test.ts + - queue-test.ts + - remote-prerenderer-test.ts + - permissions/permission-checker-test.ts + - shard: 3 + testModules: + - realm-endpoints-test.ts + - prerendering-test.ts + - search-prerendered-test.ts + - card-dependencies-endpoint-test.ts + - delete-boxel-claimed-domain-test.ts + - get-boxel-claimed-domain-test.ts + - realm-endpoints/mtimes-test.ts + - billing-test.ts + - virtual-network-test.ts + - module-syntax-test.ts + - shard: 4 + testModules: + - indexing-test.ts + - card-source-endpoints-test.ts + - realm-endpoints/search-test.ts + - server-endpoints/search-test.ts + - realm-endpoints/permissions-test.ts + - realm-endpoints/directory-test.ts + - definition-lookup-test.ts + - prerender-server-test.ts + - realm-auth-test.ts + - auth-client-test.ts + - queries-test.ts steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - uses: ./.github/actions/init @@ -506,7 +512,7 @@ jobs: run: pnpm test:wait-for-servers working-directory: packages/realm-server env: - TEST_MODULE: ${{matrix.testModule}} + TEST_MODULES: ${{ join(matrix.testModules, '|') }} - name: Print realm server logs if: always() run: cat /tmp/server.log @@ -514,7 +520,7 @@ jobs: id: artifact_name if: always() run: | - export SAFE_ARTIFACT_NAME=$(echo ${{ matrix.testModule }} | sed 's/[/]/_/g') + export SAFE_ARTIFACT_NAME=shard-${{ matrix.shard }} echo "artifact_name=$SAFE_ARTIFACT_NAME" >> "$GITHUB_OUTPUT" - name: Upload realm server log uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1 diff --git a/packages/realm-server/package.json b/packages/realm-server/package.json index efaaab3aff8..bf0b1b0ef15 100644 --- a/packages/realm-server/package.json +++ b/packages/realm-server/package.json @@ -81,11 +81,12 @@ "scripts": { "test": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key qunit --require ts-node/register/transpile-only tests/index.ts", "test-module": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key qunit --require ts-node/register/transpile-only --module ${TEST_MODULE} tests/index.ts", + "test-modules": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key node ./scripts/run-test-modules.js", "start:matrix": "cd ../matrix && pnpm assert-synapse-running", "start:smtp": "cd ../matrix && pnpm assert-smtp-running", "start:pg": "./scripts/start-pg.sh", "stop:pg": "./scripts/stop-pg.sh", - "test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test-module'", + "test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test-modules'", "setup:base-in-deployment": "mkdir -p /persistent/base && rsync --dry-run --itemize-changes --checksum --recursive --delete ../base/. /persistent/base/ && rsync --checksum --recursive --delete ../base/. /persistent/base/", "setup:experiments-in-deployment": "mkdir -p /persistent/experiments && rsync --dry-run --itemize-changes --checksum --recursive ../experiments-realm/. /persistent/experiments/ && rsync --checksum --recursive ../experiments-realm/. /persistent/experiments/", "setup:catalog-in-deployment": "mkdir -p /persistent/catalog && rsync --dry-run --itemize-changes --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/ && rsync --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/", diff --git a/packages/realm-server/scripts/lint-test-shards.ts b/packages/realm-server/scripts/lint-test-shards.ts index f0df182e839..4576ce5225f 100755 --- a/packages/realm-server/scripts/lint-test-shards.ts +++ b/packages/realm-server/scripts/lint-test-shards.ts @@ -19,18 +19,49 @@ function getCiTestModules(yamlFilePath: string) { const yamlContent = readFileSync(yamlFilePath, 'utf8'); const yamlData = yaml.load(yamlContent) as Record; - const shardIndexes: string[] = - yamlData?.jobs?.['realm-server-test']?.strategy?.matrix?.testModule; + const matrix = yamlData?.jobs?.['realm-server-test']?.strategy?.matrix; + const testModules = matrix?.testModule; - if (!Array.isArray(shardIndexes)) { + if (Array.isArray(testModules)) { + return testModules; + } + + const include = matrix?.include; + if (!Array.isArray(include)) { throw new Error( - `Invalid 'jobs.realm-server-test.strategy.matrix.testModule' format in the YAML file.`, + `Invalid 'jobs.realm-server-test.strategy.matrix' format in the YAML file.`, ); } - return shardIndexes; + const modules = new Set(); + const invalidEntries: number[] = []; + include.forEach((entry: Record, index: number) => { + const entryModules = entry?.testModules; + if (Array.isArray(entryModules)) { + entryModules.forEach((moduleName: string) => modules.add(moduleName)); + return; + } + if (typeof entryModules === 'string') { + entryModules + .split(/[,\s]+/) + .filter(Boolean) + .forEach((moduleName) => modules.add(moduleName)); + return; + } + invalidEntries.push(index); + }); + + if (invalidEntries.length > 0) { + throw new Error( + `Invalid 'jobs.realm-server-test.strategy.matrix.include[*].testModules' entries at indexes: ${invalidEntries.join(', ')}`, + ); + } + + return Array.from(modules); } catch (error: any) { - console.error(`Error reading shardIndex from YAML file: ${error.message}`); + console.error( + `Error reading test modules from YAML file: ${error.message}`, + ); process.exit(1); } } diff --git a/packages/realm-server/scripts/run-test-modules.js b/packages/realm-server/scripts/run-test-modules.js new file mode 100644 index 00000000000..cf024713813 --- /dev/null +++ b/packages/realm-server/scripts/run-test-modules.js @@ -0,0 +1,57 @@ +#!/usr/bin/env node +'use strict'; + +const { spawnSync } = require('node:child_process'); + +function buildModuleFilter(modulesToMatch) { + const escaped = modulesToMatch + .map((moduleName) => escapeRegex(moduleName)) + .join('|'); + const pattern = `^(?:${escaped})(?:\\s>\\s|:)`; + return `/${pattern}/`; +} + +function escapeRegex(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\//g, '\\/'); +} + +const rawModules = process.env.TEST_MODULES ?? ''; +const cleanedRaw = rawModules.trim(); + +if (!cleanedRaw) { + console.error('TEST_MODULES must be set.'); + process.exit(1); +} + +const modules = cleanedRaw + .split(/[|,]/) + .map((value) => value.trim()) + .filter(Boolean) + .map((value) => value.replace(/^['"]+|['"]+$/g, '')); + +if (modules.length === 0) { + console.error('No module names found in TEST_MODULES.'); + process.exit(1); +} + +const args = ['--require', 'ts-node/register/transpile-only']; + +args.push('--filter', buildModuleFilter(modules)); + +args.push('tests/index.ts'); + +const qunitBin = require.resolve('qunit/bin/qunit.js'); +const result = spawnSync(process.execPath, [qunitBin, ...args], { + stdio: 'inherit', + env: process.env, +}); + +if (typeof result.status === 'number') { + process.exit(result.status); +} + +if (result.error) { + console.error(result.error); +} + +process.exit(1); From f1139a3ca05544f0e27847cedcdf394b7e1df750 Mon Sep 17 00:00:00 2001 From: Ian Calvert Date: Tue, 13 Jan 2026 12:02:45 +0000 Subject: [PATCH 02/20] Run multiple modules as a group in CI --- README.md | 13 ++++++----- packages/realm-server/package.json | 1 - packages/realm-server/tests/index.ts | 33 ++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9a8d18a99ca..f94bfbda4c1 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ You can also use `start:development` if you want the functionality of `start:all Optional environment variables for `start:development`: -- `USE_EXTERNAL_CATALOG=1` to load `/catalog` from `packages/catalog/contents` (cloned from the `boxel-catalog` repo). +- `USE_EXTERNAL_CATALOG=1` to load `/catalog` from `packages/catalog/contents` (cloned from the `boxel-catalog` repo). ### Card Pre-rendering @@ -394,12 +394,15 @@ The tests are available at `http://localhost:4200/tests` ### Realm Server Node tests -First make sure to generate the host app's `dist/` output in order to support card pre-rendering by first starting the host app (instructions above). If you want to make the host app's `dist/` output without starting the host app, you can run `pnpm build` in the host app's workspace. - To run the `packages/realm-server/` workspace tests start: -1. `pnpm start:all` in the `packages/realm-server/` to serve _both_ the base realm and the realm that serves the test cards for node. -2. Run `pnpm test` in the `packages/realm-server/` workspace to run the realm node tests. `TEST_MODULE=realm-endpoints-test.ts pnpm test-module` is an example of how to run a single test module. +1. The host application on port 4200. You can do this by running `pnpm start` in the `packages/host/` workspace, or if you have a built folder you can serve it with a static server with `pnpm serve:dist`. +2. The base realm and associated workers, postgres and synapse. You can do this by running `pnpm start:all` in the `packages/realm-server/` workspace, or `pnpm:start-services-for-host-tests` for a more lightweight setup. +3. Run the realm server tests: + +- `pnpm test` in the `packages/realm-server/` workspace to run the realm node tests in full (~1hr). +- `TEST_MODULES="types-endpoint-test.ts|another-test-module.ts" pnpm test` in the `packages/realm-server/` workspace to run tests for a subset of modules. +- `TEST_MODULE="types-endpoint-test.ts" pnpm test-module` in the `packages/realm-server/` workspace to run tests for a specific module. ### Boxel UI diff --git a/packages/realm-server/package.json b/packages/realm-server/package.json index bf0b1b0ef15..bbbf3facbab 100644 --- a/packages/realm-server/package.json +++ b/packages/realm-server/package.json @@ -81,7 +81,6 @@ "scripts": { "test": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key qunit --require ts-node/register/transpile-only tests/index.ts", "test-module": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key qunit --require ts-node/register/transpile-only --module ${TEST_MODULE} tests/index.ts", - "test-modules": "./scripts/remove-test-dbs.sh; LOG_LEVELS=\"*=error,prerenderer-chrome=silent,pg-adapter=warn,realm:requests=warn${LOG_LEVELS:+,}${LOG_LEVELS}\" NODE_NO_WARNINGS=1 PGPORT=5435 STRIPE_WEBHOOK_SECRET=stripe-webhook-secret STRIPE_API_KEY=stripe-api-key node ./scripts/run-test-modules.js", "start:matrix": "cd ../matrix && pnpm assert-synapse-running", "start:smtp": "cd ../matrix && pnpm assert-smtp-running", "start:pg": "./scripts/start-pg.sh", diff --git a/packages/realm-server/tests/index.ts b/packages/realm-server/tests/index.ts index a96d6da1d54..21dc42b7207 100644 --- a/packages/realm-server/tests/index.ts +++ b/packages/realm-server/tests/index.ts @@ -30,6 +30,21 @@ import * as ContentTagGlobal from 'content-tag'; import QUnit from 'qunit'; QUnit.config.testTimeout = 60000; +const testModules = process.env.TEST_MODULES?.trim(); + +if (testModules) { + const modules = parseModules(testModules); + if (modules.length > 0) { + QUnit.config.filter = buildModuleFilter(modules); + console.log( + `Filtering tests to modules from TEST_MODULES: ${modules.join(', ')}`, + ); + } else { + console.warn( + 'TEST_MODULES was provided but no module names were parsed. Running full suite.', + ); + } +} // Cleanup here ensures lingering servers/prerenderers/queues don't keep the // Node event loop alive after tests finish. @@ -155,3 +170,21 @@ import './delete-boxel-claimed-domain-test'; import './realm-auth-test'; import './queries-test'; import './remote-prerenderer-test'; + +function parseModules(value: string): string[] { + return value + .split(/[|,]/) + .map((entry) => entry.trim()) + .filter(Boolean) + .map((entry) => entry.replace(/^['"]+|['"]+$/g, '')); +} + +function buildModuleFilter(modulesToMatch: string[]): string { + const escaped = modulesToMatch.map((moduleName) => escapeRegex(moduleName)); + const pattern = `^(?:${escaped.join('|')})(?:\\s>\\s|:)`; + return `/${pattern}/`; +} + +function escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\//g, '\\/'); +} From 3cc9065ab09be89f63e4391ed9acca8367840832 Mon Sep 17 00:00:00 2001 From: Ian Calvert Date: Tue, 13 Jan 2026 12:21:36 +0000 Subject: [PATCH 03/20] Call original test command --- packages/realm-server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm-server/package.json b/packages/realm-server/package.json index bbbf3facbab..479a5a4c3e5 100644 --- a/packages/realm-server/package.json +++ b/packages/realm-server/package.json @@ -85,7 +85,7 @@ "start:smtp": "cd ../matrix && pnpm assert-smtp-running", "start:pg": "./scripts/start-pg.sh", "stop:pg": "./scripts/stop-pg.sh", - "test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test-modules'", + "test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test'", "setup:base-in-deployment": "mkdir -p /persistent/base && rsync --dry-run --itemize-changes --checksum --recursive --delete ../base/. /persistent/base/ && rsync --checksum --recursive --delete ../base/. /persistent/base/", "setup:experiments-in-deployment": "mkdir -p /persistent/experiments && rsync --dry-run --itemize-changes --checksum --recursive ../experiments-realm/. /persistent/experiments/ && rsync --checksum --recursive ../experiments-realm/. /persistent/experiments/", "setup:catalog-in-deployment": "mkdir -p /persistent/catalog && rsync --dry-run --itemize-changes --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/ && rsync --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/", From 7c01d8d1317b32cd46281a43c9c5bc3894fd559e Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:33:15 -0700 Subject: [PATCH 04/20] Fix test --- packages/realm-server/tests/indexing-test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/realm-server/tests/indexing-test.ts b/packages/realm-server/tests/indexing-test.ts index ae92c35c415..5d620c595ce 100644 --- a/packages/realm-server/tests/indexing-test.ts +++ b/packages/realm-server/tests/indexing-test.ts @@ -1469,6 +1469,11 @@ module(basename(__filename), function () { { clientRequestId: 'burst-2' }, ); + let expectedUrls = [ + `${testRealm}mango`, + `${testRealm}post-1`, + `${testRealm}vangogh`, + ]; let row = (await waitUntil( async () => { let rows = (await testDbAdapter.execute( @@ -1485,13 +1490,19 @@ module(basename(__filename), function () { changes: { url: string; operation: 'update' | 'delete' }[]; }; }[]; - return rows.length === 1 ? rows[0] : undefined; + if (rows.length !== 1) { + return undefined; + } + let urls = rows[0].args.changes + .map((change) => change.url) + .sort(); + return urls.length === expectedUrls.length ? rows[0] : undefined; }, { timeout: 3000, interval: 50, timeoutMessage: - 'expected exactly one pending incremental canonical job', + 'expected exactly one pending incremental canonical job with all coalesced URLs', }, )) as { id: number; @@ -1504,7 +1515,7 @@ module(basename(__filename), function () { let urls = row.args.changes.map((change) => change.url).sort(); assert.deepEqual( urls, - [`${testRealm}mango`, `${testRealm}post-1`, `${testRealm}vangogh`], + expectedUrls, 'pending canonical incremental args include union of burst invalidations', ); assert.strictEqual( From 2e79398408941915fe7e34a5665f0e89e06bc4df Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:34:46 -0700 Subject: [PATCH 05/20] Fix lint errors --- packages/realm-server/scripts/run-test-modules.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/realm-server/scripts/run-test-modules.js b/packages/realm-server/scripts/run-test-modules.js index cf024713813..9d3d2246865 100644 --- a/packages/realm-server/scripts/run-test-modules.js +++ b/packages/realm-server/scripts/run-test-modules.js @@ -1,7 +1,8 @@ #!/usr/bin/env node +/* eslint-env node */ 'use strict'; -const { spawnSync } = require('node:child_process'); +const { spawnSync } = require('node:child_process'); // eslint-disable-line @typescript-eslint/no-var-requires function buildModuleFilter(modulesToMatch) { const escaped = modulesToMatch From 5527bb718e7c1efcfc1a8aa85cd37eeaf1458f96 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:38:01 -0700 Subject: [PATCH 06/20] Replace manual sharding with script --- .github/workflows/ci.yaml | 87 +++------------------------------------ 1 file changed, 6 insertions(+), 81 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9fba5510953..1b993290224 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -499,86 +499,7 @@ jobs: strategy: fail-fast: false matrix: - include: - - shard: 1 - testModules: - - atomic-endpoints-test.ts - - request-forward-test.ts - - realm-endpoints/info-test.ts - - realm-endpoints/user-test.ts - - file-watcher-events-test.ts - - types-endpoint-test.ts - - prerender-manager-test.ts - - prerender-proxy-test.ts - - transpile-test.ts - - server-endpoints/authentication-test.ts - - server-endpoints/bot-commands-test.ts - - server-endpoints/bot-registration-test.ts - - server-endpoints/delete-realm-test.ts - - server-endpoints/download-realm-test.ts - - server-endpoints/info-test.ts - - server-endpoints/index-responses-test.ts - - server-endpoints/realm-lifecycle-test.ts - - shard: 2 - testModules: - - card-endpoints-test.ts - - publish-unpublish-realm-test.ts - - realm-endpoints/lint-test.ts - - claim-boxel-domain-test.ts - - realm-endpoints/publishability-test.ts - - boxel-domain-availability-test.ts - - realm-endpoints/dependencies-test.ts - - queue-test.ts - - remote-prerenderer-test.ts - - permissions/permission-checker-test.ts - - server-endpoints/maintenance-endpoints-test.ts - - server-endpoints/queue-status-test.ts - - server-endpoints/stripe-session-test.ts - - server-endpoints/stripe-webhook-test.ts - - server-endpoints/user-and-catalog-test.ts - - command-parsing-utils-test.ts - - run-command-task-test.ts - - shard: 3 - testModules: - - realm-endpoints-test.ts - - prerendering-test.ts - - search-prerendered-test.ts - - card-dependencies-endpoint-test.ts - - delete-boxel-claimed-domain-test.ts - - get-boxel-claimed-domain-test.ts - - realm-endpoints/mtimes-test.ts - - billing-test.ts - - virtual-network-test.ts - - module-syntax-test.ts - - card-reference-resolver-test.ts - - sanitize-head-html-test.ts - - server-config-test.ts - - server-endpoints/incoming-webhook-test.ts - - server-endpoints/webhook-commands-test.ts - - server-endpoints/webhook-receiver-test.ts - - server-endpoints/federated-types-test.ts - - session-room-queries-test.ts - - shard: 4 - testModules: - - indexing-test.ts - - card-source-endpoints-test.ts - - realm-endpoints/search-test.ts - - server-endpoints/search-test.ts - - realm-endpoints/permissions-test.ts - - realm-endpoints/directory-test.ts - - definition-lookup-test.ts - - prerender-server-test.ts - - realm-auth-test.ts - - auth-client-test.ts - - queries-test.ts - - realm-endpoints/cancel-indexing-job-test.ts - - realm-endpoints/invalidate-urls-test.ts - - realm-endpoints/reindex-test.ts - - indexing-event-sink-test.ts - - runtime-dependency-tracker-test.ts - - node-realm-test.ts - - server-endpoints/search-prerendered-test.ts - - full-reindex-test.ts + shard: [1, 2, 3, 4] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/init @@ -592,6 +513,10 @@ jobs: run: | shopt -s dotglob cp -a .test-web-assets-artifact/. ./ + - name: Compute shard test modules + id: shard_modules + run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shard }} 4)" >> "$GITHUB_OUTPUT" + working-directory: packages/realm-server - name: Start test services (icons + host dist + realm servers) run: mise run test-services:realm-server | tee -a /tmp/server.log & - name: create realm users @@ -601,7 +526,7 @@ jobs: run: pnpm test:wait-for-servers working-directory: packages/realm-server env: - TEST_MODULES: ${{ join(matrix.testModules, '|') }} + TEST_MODULES: ${{ steps.shard_modules.outputs.modules }} - name: Print realm server logs if: ${{ !cancelled() }} run: cat /tmp/server.log From 3def21c93d9d4904352dddbad85a6e973cc08078 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:46:00 -0700 Subject: [PATCH 07/20] Add reporting --- .github/workflows/ci.yaml | 41 +++++++ .../realm-server/scripts/junit-reporter.js | 113 ++++++++++++++++++ .../tests/scripts/run-qunit-with-test-pg.sh | 7 +- 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 packages/realm-server/scripts/junit-reporter.js diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b993290224..9b4a92ee11d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -527,6 +527,14 @@ jobs: working-directory: packages/realm-server env: TEST_MODULES: ${{ steps.shard_modules.outputs.modules }} + JUNIT_OUTPUT_FILE: ${{ github.workspace }}/junit/realm-server-${{ matrix.shard }}.xml + - name: Upload junit report + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1 + if: ${{ !cancelled() }} + with: + name: realm-server-test-report-${{ matrix.shard }} + path: junit/realm-server-${{ matrix.shard }}.xml + retention-days: 30 - name: Print realm server logs if: ${{ !cancelled() }} run: cat /tmp/server.log @@ -603,6 +611,39 @@ jobs: path: /tmp/host-dist.log retention-days: 30 + realm-server-merge-reports: + name: Merge Realm Server reports and publish + if: ${{ !cancelled() }} + concurrency: + group: realm-server-merge-reports-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + needs: realm-server-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: ./.github/actions/init + - name: Download JUnit reports + uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # 4.2.0 + with: + path: all-realm-server-reports + pattern: realm-server-test-report-* + merge-multiple: true + - name: Merge reports + run: npx junit-report-merger realm-server.xml "./all-realm-server-reports/*.xml" + - name: Upload merged report + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1 + if: ${{ !cancelled() }} + with: + name: realm-server-test-report-merged + path: realm-server.xml + retention-days: 30 + - name: Publish test results + uses: EnricoMi/publish-unit-test-result-action@170bf24d20d201b842d7a52403b73ed297e6645b # 2.18.0 + if: ${{ !cancelled() }} + with: + junit_files: realm-server.xml + check_name: Realm Server Test Results + software-factory-test: name: Software Factory Tests needs: [change-check, test-web-assets] diff --git a/packages/realm-server/scripts/junit-reporter.js b/packages/realm-server/scripts/junit-reporter.js new file mode 100644 index 00000000000..cae34991d49 --- /dev/null +++ b/packages/realm-server/scripts/junit-reporter.js @@ -0,0 +1,113 @@ +#!/usr/bin/env node +/* eslint-env node */ +'use strict'; + +// QUnit JUnit XML reporter for CI. +// +// Usage: qunit --reporter junit-reporter.js ... +// Or: qunit --require ./scripts/junit-reporter.js --reporter console ... +// +// Set JUNIT_OUTPUT_FILE to control the output path (default: junit/realm-server.xml). +// When used as a --require module, this attaches alongside the default console +// reporter so you get both terminal output and a JUnit file. + +const fs = require('node:fs'); // eslint-disable-line @typescript-eslint/no-var-requires +const path = require('node:path'); // eslint-disable-line @typescript-eslint/no-var-requires +const QUnit = require('qunit'); // eslint-disable-line @typescript-eslint/no-var-requires + +const outputFile = + process.env.JUNIT_OUTPUT_FILE || + path.join(process.cwd(), '..', '..', 'junit', 'realm-server.xml'); + +const suites = new Map(); // moduleName -> { tests, failures, errors, time, testCases } + +function escapeXml(str) { + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +QUnit.on('testEnd', (data) => { + const moduleName = data.fullName + ? data.fullName.slice(0, data.fullName.lastIndexOf(' > ')) + : data.module || 'default'; + const testName = data.name || 'unknown'; + const runtime = (data.runtime || 0) / 1000; // ms → seconds + const status = data.status; // passed, failed, skipped, todo + + if (!suites.has(moduleName)) { + suites.set(moduleName, { + tests: 0, + failures: 0, + errors: 0, + skipped: 0, + time: 0, + testCases: [], + }); + } + + const suite = suites.get(moduleName); + suite.tests++; + suite.time += runtime; + + let caseXml = ` { + let msg = e.message || ''; + if (e.actual !== undefined && e.expected !== undefined) { + msg += `\nExpected: ${JSON.stringify(e.expected)}\nActual: ${JSON.stringify(e.actual)}`; + } + if (e.stack) { + msg += `\n${e.stack}`; + } + return msg; + }) + .join('\n---\n'); + caseXml += `>\n ${escapeXml(messages)}\n `; + } else if (status === 'skipped' || status === 'todo') { + suite.skipped++; + caseXml += `>\n \n `; + } else { + caseXml += ` />`; + } + + suite.testCases.push(caseXml); +}); + +QUnit.on('runEnd', () => { + const suitesXml = []; + let totalTests = 0; + let totalFailures = 0; + let totalErrors = 0; + let totalSkipped = 0; + let totalTime = 0; + + for (const [name, suite] of suites) { + totalTests += suite.tests; + totalFailures += suite.failures; + totalErrors += suite.errors; + totalSkipped += suite.skipped; + totalTime += suite.time; + + suitesXml.push( + ` \n${suite.testCases.join('\n')}\n `, + ); + } + + const xml = [ + ``, + ``, + ...suitesXml, + ``, + ].join('\n'); + + const dir = path.dirname(outputFile); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(outputFile, xml, 'utf8'); +}); diff --git a/packages/realm-server/tests/scripts/run-qunit-with-test-pg.sh b/packages/realm-server/tests/scripts/run-qunit-with-test-pg.sh index 4ef285c291e..1c41f9c8d8b 100755 --- a/packages/realm-server/tests/scripts/run-qunit-with-test-pg.sh +++ b/packages/realm-server/tests/scripts/run-qunit-with-test-pg.sh @@ -14,9 +14,14 @@ else EFFECTIVE_LOG_LEVELS="$BASE_LOG_LEVELS" fi +JUNIT_REPORTER_ARGS="" +if [ -n "${JUNIT_OUTPUT_FILE-}" ]; then + JUNIT_REPORTER_ARGS="--require ${SCRIPT_DIR}/../../scripts/junit-reporter.js" +fi + LOG_LEVELS="$EFFECTIVE_LOG_LEVELS" \ NODE_NO_WARNINGS=1 \ PGPORT=55436 \ STRIPE_WEBHOOK_SECRET=stripe-webhook-secret \ STRIPE_API_KEY=stripe-api-key \ -qunit --require ts-node/register/transpile-only "$@" tests/index.ts +qunit --require ts-node/register/transpile-only $JUNIT_REPORTER_ARGS "$@" tests/index.ts From 633390ce5fe9d58550c16d6a098896a76a879d62 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:46:05 -0700 Subject: [PATCH 08/20] Add missed script --- .../scripts/shard-test-modules.js | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/realm-server/scripts/shard-test-modules.js diff --git a/packages/realm-server/scripts/shard-test-modules.js b/packages/realm-server/scripts/shard-test-modules.js new file mode 100644 index 00000000000..04e1b4ff03a --- /dev/null +++ b/packages/realm-server/scripts/shard-test-modules.js @@ -0,0 +1,51 @@ +#!/usr/bin/env node +/* eslint-env node */ +'use strict'; + +// Discovers all *-test.ts files under tests/ and outputs the subset assigned +// to the requested shard (1-based). Files are sorted alphabetically and +// distributed round-robin so every shard gets a roughly equal share. +// +// Usage: node shard-test-modules.js +// Output: module names joined by "|", suitable for TEST_MODULES. + +const fs = require('node:fs'); // eslint-disable-line @typescript-eslint/no-var-requires +const path = require('node:path'); // eslint-disable-line @typescript-eslint/no-var-requires + +const shard = parseInt(process.argv[2], 10); +const totalShards = parseInt(process.argv[3], 10); + +if (!shard || !totalShards || shard < 1 || shard > totalShards) { + console.error( + `Usage: shard-test-modules.js (got shard=${process.argv[2]}, totalShards=${process.argv[3]})`, + ); + process.exit(1); +} + +const testsDir = path.resolve(__dirname, '..', 'tests'); + +function collectTestModules(dir, prefix) { + let modules = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const relative = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + modules = modules.concat(collectTestModules(path.join(dir, entry.name), relative)); + } else if (entry.isFile() && entry.name.endsWith('-test.ts')) { + modules.push(relative); + } + } + return modules; +} + +const allModules = collectTestModules(testsDir, '').sort(); + +const shardModules = allModules.filter( + (_, index) => (index % totalShards) + 1 === shard, +); + +if (shardModules.length === 0) { + console.error(`Shard ${shard}/${totalShards} has no test modules.`); + process.exit(1); +} + +process.stdout.write(shardModules.join('|')); From 08b469d2312c8db590bbd6f3d2b9d1d82d6c987b Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Fri, 27 Mar 2026 16:53:09 -0700 Subject: [PATCH 09/20] Fix lint error --- packages/realm-server/scripts/shard-test-modules.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/realm-server/scripts/shard-test-modules.js b/packages/realm-server/scripts/shard-test-modules.js index 04e1b4ff03a..7eaeb1124c5 100644 --- a/packages/realm-server/scripts/shard-test-modules.js +++ b/packages/realm-server/scripts/shard-test-modules.js @@ -29,7 +29,9 @@ function collectTestModules(dir, prefix) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const relative = prefix ? `${prefix}/${entry.name}` : entry.name; if (entry.isDirectory()) { - modules = modules.concat(collectTestModules(path.join(dir, entry.name), relative)); + modules = modules.concat( + collectTestModules(path.join(dir, entry.name), relative), + ); } else if (entry.isFile() && entry.name.endsWith('-test.ts')) { modules.push(relative); } From 63a147218c0624d41a1a3e0d591933572ab6eca6 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 07:48:56 -0700 Subject: [PATCH 10/20] Restore deleted paragraph --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 16ae4a56e2b..4ecf8b37017 100644 --- a/README.md +++ b/README.md @@ -459,6 +459,8 @@ The tests are available at `http://localhost:4200/tests` To run the `packages/realm-server/` workspace tests start: +First make sure to generate the host app's `dist/` output in order to support card pre-rendering by first starting the host app (instructions above). If you want to make the host app's `dist/` output without starting the host app, you can run `pnpm build` in the host app's workspace. + 1. `mise run dev` from the repo root to serve _both_ the base realm and the realm that serves the test cards for node. 2. Run the realm server tests: From a9d868cc83b9364c6cd0a288fc5c982132d84da5 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 07:50:38 -0700 Subject: [PATCH 11/20] Remove test shards lintinng --- packages/realm-server/package.json | 1 - .../realm-server/scripts/lint-test-shards.ts | 113 ------------------ 2 files changed, 114 deletions(-) delete mode 100755 packages/realm-server/scripts/lint-test-shards.ts diff --git a/packages/realm-server/package.json b/packages/realm-server/package.json index 1aaf704294e..88e39a633f8 100644 --- a/packages/realm-server/package.json +++ b/packages/realm-server/package.json @@ -131,7 +131,6 @@ "lint:js": "eslint . --report-unused-disable-directives --cache", "lint:js:fix": "eslint . --report-unused-disable-directives --fix", "lint:glint": "glint", - "lint:test-shards": "ts-node --transpileOnly scripts/lint-test-shards.ts", "full-reset": "./scripts/full-reset.sh", "full-reindex": "./scripts/full-reindex.sh", "clear-modules-cache": "NODE_NO_WARNINGS=1 PGDATABASE=${PGDATABASE:-boxel} PGPORT=${PGPORT:-5435} ts-node --transpileOnly scripts/clear-modules-cache.ts", diff --git a/packages/realm-server/scripts/lint-test-shards.ts b/packages/realm-server/scripts/lint-test-shards.ts deleted file mode 100755 index 4576ce5225f..00000000000 --- a/packages/realm-server/scripts/lint-test-shards.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { readFileSync } from 'fs-extra'; -import { glob } from 'glob'; -import yaml from 'js-yaml'; -import { join } from 'path'; - -const YAML_FILE = join( - __dirname, - '..', - '..', - '..', - '.github', - 'workflows', - 'ci.yaml', -); -const TEST_DIR = join(__dirname, '..', 'tests'); - -function getCiTestModules(yamlFilePath: string) { - try { - const yamlContent = readFileSync(yamlFilePath, 'utf8'); - const yamlData = yaml.load(yamlContent) as Record; - - const matrix = yamlData?.jobs?.['realm-server-test']?.strategy?.matrix; - const testModules = matrix?.testModule; - - if (Array.isArray(testModules)) { - return testModules; - } - - const include = matrix?.include; - if (!Array.isArray(include)) { - throw new Error( - `Invalid 'jobs.realm-server-test.strategy.matrix' format in the YAML file.`, - ); - } - - const modules = new Set(); - const invalidEntries: number[] = []; - include.forEach((entry: Record, index: number) => { - const entryModules = entry?.testModules; - if (Array.isArray(entryModules)) { - entryModules.forEach((moduleName: string) => modules.add(moduleName)); - return; - } - if (typeof entryModules === 'string') { - entryModules - .split(/[,\s]+/) - .filter(Boolean) - .forEach((moduleName) => modules.add(moduleName)); - return; - } - invalidEntries.push(index); - }); - - if (invalidEntries.length > 0) { - throw new Error( - `Invalid 'jobs.realm-server-test.strategy.matrix.include[*].testModules' entries at indexes: ${invalidEntries.join(', ')}`, - ); - } - - return Array.from(modules); - } catch (error: any) { - console.error( - `Error reading test modules from YAML file: ${error.message}`, - ); - process.exit(1); - } -} - -function getFilesystemTestModules(testDir: string) { - try { - const files = glob.sync(`${testDir}/**/*-test.ts`, { nodir: true }); - return files.map((file: string) => file.replace(`${testDir}/`, '')); - } catch (error: any) { - console.error( - `Error reading test files from dir ${testDir}: ${error.message}`, - ); - process.exit(1); - } -} - -function validateTestFiles(yamlFilePath: string, testDir: string) { - const ciTestModules = getCiTestModules(yamlFilePath); - const filesystemTestModules = getFilesystemTestModules(testDir); - - let errorFound = false; - - for (let filename of filesystemTestModules) { - if (!ciTestModules.includes(filename)) { - console.error( - `Error: Test file '${filename}' exists in the filesystem but not in the ${yamlFilePath} file.`, - ); - errorFound = true; - } - } - for (let filename of ciTestModules) { - if (!filesystemTestModules.includes(filename)) { - console.error( - `Error: Test file '${filename}' exists in the YAML file but not in the filesystem.`, - ); - errorFound = true; - } - } - - if (errorFound) { - process.exit(1); - } else { - console.log( - `All test files are accounted for in the ${yamlFilePath} file for the realm-server matrix strategy.`, - ); - } -} - -validateTestFiles(YAML_FILE, TEST_DIR); From 0e6e75342e12dcd46bfd59a7f374c306101a8312 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 07:59:29 -0700 Subject: [PATCH 12/20] Change to six shards --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9b4a92ee11d..bc914f9bdd5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -499,7 +499,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2, 3, 4] + shard: [1, 2, 3, 4, 5, 6] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/init @@ -515,7 +515,7 @@ jobs: cp -a .test-web-assets-artifact/. ./ - name: Compute shard test modules id: shard_modules - run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shard }} 4)" >> "$GITHUB_OUTPUT" + run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shard }} 6)" >> "$GITHUB_OUTPUT" working-directory: packages/realm-server - name: Start test services (icons + host dist + realm servers) run: mise run test-services:realm-server | tee -a /tmp/server.log & From 191a8cd6a50e9c3c1895ec8e41fce509f2d3a4f4 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 08:40:59 -0700 Subject: [PATCH 13/20] Add deliberate test failure --- .../realm-server/tests/server-endpoints/bot-commands-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm-server/tests/server-endpoints/bot-commands-test.ts b/packages/realm-server/tests/server-endpoints/bot-commands-test.ts index 09e0c2795c8..585ad482b28 100644 --- a/packages/realm-server/tests/server-endpoints/bot-commands-test.ts +++ b/packages/realm-server/tests/server-endpoints/bot-commands-test.ts @@ -65,7 +65,7 @@ module(`server-endpoints/${basename(__filename)}`, function () { }, }); - assert.strictEqual(response.status, 201, 'HTTP 201 status'); + assert.strictEqual(response.status, 205, 'HTTP 201 status'); assert.strictEqual( response.body.data.attributes.botId, botRegistrationId, From 6af24f264f69d132ce8d274476a13934ec2d6e00 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 09:33:38 -0700 Subject: [PATCH 14/20] Remove custom shard name --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bc914f9bdd5..b8c86714ff6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -489,7 +489,7 @@ jobs: -d '{"context":"Matrix Playwright tests report","description":"'"$description"'","target_url":"'"$PLAYWRIGHT_REPORT_URL"'","state":"'"$state"'"}' realm-server-test: - name: Realm Server Tests (shard ${{ matrix.shard }}) + name: Realm Server Tests needs: [change-check, test-web-assets] if: needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true' runs-on: ubuntu-latest From 5ad24c35fb01037d2aebde0c6b577893a67e5f30 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 09:33:53 -0700 Subject: [PATCH 15/20] Remove deliberate test failure --- .../realm-server/tests/server-endpoints/bot-commands-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/realm-server/tests/server-endpoints/bot-commands-test.ts b/packages/realm-server/tests/server-endpoints/bot-commands-test.ts index 585ad482b28..09e0c2795c8 100644 --- a/packages/realm-server/tests/server-endpoints/bot-commands-test.ts +++ b/packages/realm-server/tests/server-endpoints/bot-commands-test.ts @@ -65,7 +65,7 @@ module(`server-endpoints/${basename(__filename)}`, function () { }, }); - assert.strictEqual(response.status, 205, 'HTTP 201 status'); + assert.strictEqual(response.status, 201, 'HTTP 201 status'); assert.strictEqual( response.body.data.attributes.botId, botRegistrationId, From 8520c5d20a1a3c276768a6349c09e0137af1811b Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 09:51:33 -0700 Subject: [PATCH 16/20] Change matrix variables to match --- .github/workflows/ci.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b8c86714ff6..4d15aaa74eb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -494,12 +494,13 @@ jobs: if: needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true' runs-on: ubuntu-latest concurrency: - group: realm-server-test-${{ matrix.shard }}-${{ github.head_ref || github.run_id }} + group: realm-server-test-${{ matrix.shardIndex }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true strategy: fail-fast: false matrix: - shard: [1, 2, 3, 4, 5, 6] + shardIndex: [1, 2, 3, 4, 5, 6] + shardTotal: [6] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/init @@ -515,7 +516,7 @@ jobs: cp -a .test-web-assets-artifact/. ./ - name: Compute shard test modules id: shard_modules - run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shard }} 6)" >> "$GITHUB_OUTPUT" + run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shardIndex }} 6)" >> "$GITHUB_OUTPUT" working-directory: packages/realm-server - name: Start test services (icons + host dist + realm servers) run: mise run test-services:realm-server | tee -a /tmp/server.log & @@ -527,13 +528,13 @@ jobs: working-directory: packages/realm-server env: TEST_MODULES: ${{ steps.shard_modules.outputs.modules }} - JUNIT_OUTPUT_FILE: ${{ github.workspace }}/junit/realm-server-${{ matrix.shard }}.xml + JUNIT_OUTPUT_FILE: ${{ github.workspace }}/junit/realm-server-${{ matrix.shardIndex }}.xml - name: Upload junit report uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1 if: ${{ !cancelled() }} with: - name: realm-server-test-report-${{ matrix.shard }} - path: junit/realm-server-${{ matrix.shard }}.xml + name: realm-server-test-report-${{ matrix.shardIndex }} + path: junit/realm-server-${{ matrix.shardIndex }}.xml retention-days: 30 - name: Print realm server logs if: ${{ !cancelled() }} @@ -542,7 +543,7 @@ jobs: id: artifact_name if: ${{ !cancelled() }} run: | - export SAFE_ARTIFACT_NAME=shard-${{ matrix.shard }} + export SAFE_ARTIFACT_NAME=shard-${{ matrix.shardIndex }} echo "artifact_name=$SAFE_ARTIFACT_NAME" >> "$GITHUB_OUTPUT" - name: Extract worker and prerender logs if: ${{ !cancelled() }} From 48365eaac5663452d831382a40aea3dd16a433c6 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 10:09:32 -0700 Subject: [PATCH 17/20] Fix ordering --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ecf8b37017..2557ea8194a 100644 --- a/README.md +++ b/README.md @@ -457,10 +457,10 @@ The tests are available at `http://localhost:4200/tests` ### Realm Server Node tests -To run the `packages/realm-server/` workspace tests start: - First make sure to generate the host app's `dist/` output in order to support card pre-rendering by first starting the host app (instructions above). If you want to make the host app's `dist/` output without starting the host app, you can run `pnpm build` in the host app's workspace. +To run the `packages/realm-server/` workspace tests start: + 1. `mise run dev` from the repo root to serve _both_ the base realm and the realm that serves the test cards for node. 2. Run the realm server tests: From 521e7e61b1bc9c4f913bafd501e8a2201b6cc99f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 11:05:50 -0700 Subject: [PATCH 18/20] Add multi-file test explanation to AGENTS.md --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 06974549b91..1a0056340b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,6 +116,8 @@ `pnpm test` - Run a single module: `TEST_MODULE=card-endpoints-test.ts pnpm test-module` +- Run a list of modules: + `TEST_MODULES=card-endpoints-test.ts|another-module-test.ts pnpm test` - Focusing on single test or module: Add `.only` to module/test declaration (`test.only('returns a 201 response', ...)`) Then run `pnpm test` From 49e2c988c46d995da4478e440de1735fb9f81057 Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 11:08:17 -0700 Subject: [PATCH 19/20] Add filter on merge realm server reports job --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4d15aaa74eb..eee80bdf886 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -614,11 +614,11 @@ jobs: realm-server-merge-reports: name: Merge Realm Server reports and publish - if: ${{ !cancelled() }} + if: ${{ !cancelled() && (needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true') }} concurrency: group: realm-server-merge-reports-${{ github.head_ref || github.run_id }} cancel-in-progress: true - needs: realm-server-test + needs: [change-check, realm-server-test] runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From eace2d983fb5a436925876f3305f19a1beed0a2f Mon Sep 17 00:00:00 2001 From: Buck Doyle Date: Mon, 30 Mar 2026 11:10:09 -0700 Subject: [PATCH 20/20] Fix grouping by name --- packages/realm-server/scripts/junit-reporter.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/realm-server/scripts/junit-reporter.js b/packages/realm-server/scripts/junit-reporter.js index cae34991d49..3081188c711 100644 --- a/packages/realm-server/scripts/junit-reporter.js +++ b/packages/realm-server/scripts/junit-reporter.js @@ -31,9 +31,7 @@ function escapeXml(str) { } QUnit.on('testEnd', (data) => { - const moduleName = data.fullName - ? data.fullName.slice(0, data.fullName.lastIndexOf(' > ')) - : data.module || 'default'; + const moduleName = data.module || 'default'; const testName = data.name || 'unknown'; const runtime = (data.runtime || 0) / 1000; // ms → seconds const status = data.status; // passed, failed, skipped, todo