Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@
[test-groups.cluster-global]
max-threads = 1

# binary(), NOT test(). `test()` matches TEST NAMES; every name below is a test
# BINARY name, so the old `test(/apalis_adapter/)` form grouped only the tests whose
# own name happened to contain the binary name. Measured 2026-08-18 with
# `cargo nextest show-config test-groups`: apalis_adapter 1 of 4 tests grouped,
# apalis_schema_contract 1 of 1, and leave_migration_expand_contract 0 of 9 -- the
# serial control covered 2 of 14 tests across those binaries while reading as if it
# covered all of them. With binary() the same three report 4/4, 1/1 and 9/9.
[[profile.default.overrides]]
filter = '''
test(/leave_migration_expand_contract/)
+ test(/key_revision_migration_upgrade/)
+ test(/attendance_console_migration_contract/)
+ test(/apalis_adapter/)
+ test(/apalis_schema_contract/)
binary(leave_migration_expand_contract)
+ binary(key_revision_migration_upgrade)
+ binary(attendance_console_migration_contract)
+ binary(apalis_adapter)
+ binary(apalis_schema_contract)
'''
test-group = 'cluster-global'

Expand Down
44 changes: 41 additions & 3 deletions tools/ci/check-nextest-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ const REQUIRED_FILTERS = [
"apalis_schema_contract",
];

/**
* The cluster-global override's filter value, or null.
* Scoped deliberately: comments elsewhere in the file may legitimately quote the
* broken test(...) form while explaining why it is broken.
*
* @param {string} tomlText
* @returns {string|null}
*/
export function extractOverrideFilter(tomlText) {
const text = String(tomlText ?? "");
// TOML permits multi-line literal, single-quoted, and basic strings. Accept
// all three: a filter is no less wrong for being written on one line.
const multi = /filter\s*=\s*'''([\s\S]*?)'''/.exec(text);
if (multi) return multi[1];
const single = /filter\s*=\s*'([^'\n]*)'/.exec(text);
if (single) return single[1];
const basic = /filter\s*=\s*"([^"\n]*)"/.exec(text);
return basic ? basic[1] : null;
}

export function checkNextestConfig(tomlText) {
const failures = [];
if (!tomlText.includes("[test-groups.cluster-global]")) {
Expand All @@ -29,9 +49,27 @@ export function checkNextestConfig(tomlText) {
if (!tomlText.includes("test-group = 'cluster-global'") && !tomlText.includes('test-group = "cluster-global"')) {
failures.push("override must assign test-group = cluster-global");
}
for (const name of REQUIRED_FILTERS) {
if (!tomlText.includes(name)) {
failures.push(`filter missing serial suite marker: ${name}`);
// The names below are test BINARY names, so they must be matched with
// `binary(...)`. `test(...)` matches TEST names, and the difference is not
// cosmetic: measured 2026-08-18 with `cargo nextest show-config test-groups`,
// the old test-name form put 1 of 4 tests of apalis_adapter in the serial
// group and 0 of 9 for leave_migration_expand_contract, while the control read
// green throughout because every name was present as a substring. Assert the
// form that actually groups, not the spelling.
const filterBlock = extractOverrideFilter(tomlText);
if (filterBlock === null) {
failures.push("cluster-global override must declare a filter block");
} else {
for (const name of REQUIRED_FILTERS) {
if (!filterBlock.includes(`binary(${name})`)) {
failures.push(`filter must group serial suite by binary(${name})`);
}
if (new RegExp(String.raw`test\(/?${name}`).test(filterBlock)) {
failures.push(
`filter uses test(${name}); that matches test names, not the binary, `
+ `and silently under-groups it -- use binary(${name})`,
);
}
}
}
if (!tomlText.includes("0.9.138")) {
Expand Down
38 changes: 37 additions & 1 deletion tools/ci/check-nextest-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import { checkNextestConfig } from "./check-nextest-config.mjs";
import { checkNextestConfig, extractOverrideFilter } from "./check-nextest-config.mjs";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");

Expand All @@ -28,3 +28,39 @@ test-group = 'cluster-global'
const fails = checkNextestConfig(text);
assert.ok(fails.some((f) => f.includes("key_revision")));
});

test("the pre-fix test() form is rejected, not merely tolerated", () => {
// This is the exact shape that shipped: every required name present as a
// substring, so the old gate was green, while cargo-nextest grouped 1 of 4
// apalis_adapter tests and 0 of 9 leave_migration_expand_contract tests.
const good = readFileSync(resolve(root, ".config/nextest.toml"), "utf8");
const broken = good.replace(/binary\((\w+)\)/g, "test(/$1/)");
const fails = checkNextestConfig(broken);
assert.ok(fails.length > 0, "the under-grouping form must fail");
assert.ok(
fails.some((f) => /use binary\(apalis_adapter\)/.test(f)),
`expected a binary() remedy, got ${JSON.stringify(fails)}`,
);
});

test("a serial suite dropped from the filter fails", () => {
const good = readFileSync(resolve(root, ".config/nextest.toml"), "utf8");
const dropped = good.replace("binary(apalis_adapter)\n + ", "");
assert.ok(
checkNextestConfig(dropped).some((f) => f.includes("binary(apalis_adapter)")),
);
});

test("the filter scope excludes prose, so comments may quote the broken form", () => {
// The committed file explains the defect using a literal test(...) example.
// Scanning the whole file instead of the filter block would fail on its own
// documentation.
const good = readFileSync(resolve(root, ".config/nextest.toml"), "utf8");
assert.match(good, /test\(\/apalis_adapter\/\)/, "fixture assumes the comment exists");
assert.deepEqual(checkNextestConfig(good), []);
});

test("extractOverrideFilter returns null when no filter block exists", () => {
assert.equal(extractOverrideFilter("[profile.ci]\n"), null);
assert.equal(extractOverrideFilter(null), null);
});