Files
dbx/.github/scripts/ci-workflow.test.mjs
T
zipgandDBX Agent fbb588eecb fix(windows): keep the 8 MiB main-thread stack on legacy builds (0xC00000FD) (#13672)
* fix(windows): keep the 8 MiB main-thread stack on legacy builds

Windows 7 / Server 2012 R2 users on 0.6.58 report the WER dialog "DBX 已停止
工作" with APPCRASH signature dbx.exe / faulting module dbx.exe / exception
c00000fd (STATUS_STACK_OVERFLOW) as soon as they click a database connection
(MySQL, Oracle 11 and Dameng 8 all reproduce), while 0.6.55 is fine.

Root cause: .cargo/config.toml raises the main-thread stack to 8 MiB with
-C link-arg=/STACK:8388608, but Cargo ignores target rustflags whenever a
RUSTFLAGS env var is set. The Win7/Server 2012 R2 jobs set RUSTFLAGS for the
static CRT, so the legacy PE kept the 1 MiB linker default while the standard
Windows build kept 8 MiB. The 0.6.56 connect-path rewrite made the DB query
stack deeper, and the legacy 1 MiB stack overflows on connect.

- repeat -C link-arg=/STACK:8388608 in every workflow RUSTFLAGS that links a
  Windows PE (release Win7 app + dbx-web, CI Win7/standard/duckdb jobs, SQL
  Server 2000 Win7 test build)
- add the same flag to the legacy target table in .cargo/config.toml for local
  builds
- fail the Win7 PE audit when SizeOfStackReserve drops below 8 MiB, so this
  cannot silently regress again
- assert the flag stays in the workflows from ci-workflow.test.mjs

* chore(clippy): indent the MariaDB KB doc continuation

main's 7958f7ad9 added an unindented continuation line after a doc list
item, so rust-fmt-clippy now fails on main and on every PR with
clippy::doc_lazy_continuation under -D warnings. Indent the line the way
the lint documents so the build can go green.

---------

Co-authored-by: DBX Agent <agent@dbx.local>
2026-10-27 20:19:24 +08:00

522 lines
31 KiB
JavaScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
const workflow = readFileSync(new URL("../workflows/ci.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
function job(name, content = workflow) {
const jobs = content.slice(content.indexOf("\njobs:\n") + 7);
const pattern = /^ ([\w-]+):\s*$/gm;
const definitions = [...jobs.matchAll(pattern)];
const index = definitions.findIndex((match) => match[1] === name);
assert.ok(index >= 0, `missing job ${name}`);
return jobs.slice(definitions[index].index, definitions[index + 1]?.index ?? jobs.length);
}
function concurrencySettings(github) {
const block = workflow.match(/^concurrency:\n((?: [^\n]+\n)+)/m)?.[1];
assert.ok(block, "missing workflow concurrency");
const format = (template, ...values) => template.replace(/\{(\d+)\}/g, (_, index) => String(values[Number(index)]));
const endsWith = (search, value) => String(search).toLowerCase().endsWith(String(value).toLowerCase());
return Object.fromEntries(
block
.trim()
.split("\n")
.map((line) => {
const [, setting, value] = line.match(/^\s*([\w-]+):\s*(.+)$/);
const evaluate = (expression) => new Function("github", "format", "endsWith", `return (${expression});`)(github, format, endsWith);
const expressions = [...value.matchAll(/\$\{\{\s*(.*?)\s*\}\}/g)];
return [setting, expressions.length === 1 && expressions[0][0] === value ? evaluate(expressions[0][1]) : value.replace(/\$\{\{\s*(.*?)\s*\}\}/g, (_, expression) => evaluate(expression))];
}),
);
}
function concurrencyContext({ authorId = 101, author = "contributor", actor = author, number = 100, ref = `refs/pull/${number}/merge`, eventName = "pull_request" } = {}) {
return { workflow: "CI", repository_owner: "t8y2", actor, ref, event_name: eventName, event: eventName === "pull_request" ? { pull_request: { number, user: { id: authorId, login: author } } } : {} };
}
test("a contributor's PRs share two main CI queues split by PR number parity, regardless of trigger actor", () => {
const even = concurrencySettings(concurrencyContext());
assert.deepEqual(even, { group: "CI-pr-author-101-even", "cancel-in-progress": false, queue: "max" });
for (const context of [concurrencyContext({ number: 202 }), concurrencyContext({ actor: "t8y2" }), concurrencyContext({ actor: "github-actions[bot]" }), concurrencyContext({ author: "renamed-contributor" })]) {
assert.deepEqual(concurrencySettings(context), even);
}
for (const number of [101, 11849, 11853, 7]) {
assert.deepEqual(concurrencySettings(concurrencyContext({ number })), { ...even, group: "CI-pr-author-101-odd" }, String(number));
}
for (const number of [2, 4, 6, 8, 11850]) assert.equal(concurrencySettings(concurrencyContext({ number })).group, even.group);
assert.notEqual(concurrencySettings(concurrencyContext({ authorId: 102, author: "another-contributor" })).group, even.group);
});
test("repository-owner PRs remain independent and superseded runs are cancelled only within a PR", () => {
const first = concurrencySettings(concurrencyContext({ authorId: 1, author: "t8y2" }));
const second = concurrencySettings(concurrencyContext({ authorId: 1, author: "t8y2", ref: "refs/pull/200/merge" }));
assert.deepEqual(first, { group: "CI-refs/pull/100/merge", "cancel-in-progress": true, queue: "single" });
assert.notEqual(first.group, second.group);
assert.deepEqual(concurrencySettings(concurrencyContext({ authorId: 1, author: "t8y2", actor: "another-contributor" })), first);
});
test("main pushes retain their independent cancellation group without PR context", () => {
const main = concurrencySettings(concurrencyContext({ eventName: "push", ref: "refs/heads/main", actor: "github-actions[bot]" }));
assert.deepEqual(main, { group: "CI-refs/heads/main", "cancel-in-progress": true, queue: "single" });
assert.notEqual(main.group, concurrencySettings(concurrencyContext()).group);
});
test("expanded author queues never combine max queuing with in-progress cancellation", () => {
for (const context of [concurrencyContext(), concurrencyContext({ author: "t8y2" }), concurrencyContext({ eventName: "push", ref: "refs/heads/main" })]) {
const settings = concurrencySettings(context);
assert.ok(settings.queue === "single" || settings.queue === "max");
assert.equal(settings.queue === "max" && settings["cancel-in-progress"], false);
}
const concurrency = workflow.match(/^concurrency:\n((?: [^\n]+\n)+)/m)[1];
assert.doesNotMatch(concurrency, /github\.actor|author_association/);
});
test("fast checks run format and contracts before graph resolution or compilation", () => {
const fast = job("fast-checks");
for (const command of ["cargo fmt --check", "node --test scripts/core-architecture.test.mjs", "node scripts/sync-connection-types.mjs --check", "node .github/scripts/ci-lockfiles.mjs", "node .github/scripts/ci-rust-coverage.mjs"]) assert.ok(fast.includes(command));
assert.ok(fast.indexOf("cargo fmt --check") < fast.indexOf("ci-lockfiles.mjs"));
assert.doesNotMatch(fast, /cargo (?:test|build|clippy)/);
assert.ok(fast.includes("needs.changes.outputs.rust_groups_known == 'true'"));
});
test("fast checks retain ownership of the planner subset before expensive jobs", () => {
assert.match(job("github-scripts"), /FAST_CHECKS: \$\{\{ needs\.changes\.outputs\.fast \}\}/);
assert.match(job("github-scripts"), /for file in \.github\/scripts\/\*\.test\.mjs/);
assert.match(job("fast-checks"), /name: CI planner and gate tests\s+run: node --test \.github\/scripts\/ci-\*\.test\.mjs/);
});
test("contributor snapshot routing preserves docs deployment and mixed frontend validation", () => {
assert.ok(job("changes").includes("frontend: ${{ steps.filter.outputs.frontend == 'true' && steps.plan.outputs.contributor_snapshot_only != 'true' }}"));
assert.match(job("changes"), /frontend:\s+- 'apps\/desktop\/\*\*'\s+- 'docs\/\*\*'/);
const docsWorkflow = readFileSync(new URL("../workflows/docs.yml", import.meta.url), "utf8");
assert.match(docsWorkflow, /paths: \["docs\/\*\*"\]/);
for (const file of ["contributors", "issue-claim", "notify", "plugin-dev-host", "spam-comment-guard"]) {
assert.ok(job("changes").includes(`- '.github/workflows/${file}.yml'`));
}
for (const file of ["contributor-snapshot", "update-contributors"]) {
assert.ok(job("changes").includes(`- 'docs/scripts/${file}.mjs'`));
}
});
test("Agent and Rust matrices are bounded and do not cancel sibling failures", () => {
for (const [name, output, parallel] of [
["rust-test", "rust_matrix", 3],
["agent-rust", "agent_rust", 2],
["agent-go", "agent_go", 4],
["agent-integration", "agent_integration", 4],
]) {
const content = job(name);
assert.match(content, /fail-fast: false/);
assert.ok(content.includes(`max-parallel: ${parallel}`));
assert.ok(content.includes(`fromJSON(needs.changes.outputs.${output})`));
assert.match(content, /if: needs\.changes\.outputs\.[\w_]+ == 'true'/);
if (name.startsWith("agent-")) assert.ok(content.includes("needs: [changes, fast-checks, agent-checks]"));
}
});
test("stable Rust, Agent and overall gates always inspect selected upstream results", () => {
for (const [name, mode, dependencies] of [
["rust", "rust", ["fast-checks", "rust-fmt-clippy", "rust-test"]],
["agents", "agents", ["fast-checks", "agent-checks", "agent-rust", "agent-go", "agent-integration", "agent-java"]],
["frontend", "frontend", ["frontend-checks", "frontend-typecheck", "frontend-test"]],
["ci", "all", ["rust", "agents", "frontend", "packages", "windows-standard-check", "windows-win7-bundle", "duckdb-windows-driver", "nix-packaging"]],
]) {
const content = job(name);
// A superseded run must not leave a failing gate behind: when the workflow is
// cancelled the selected upstream jobs are cancelled too, and `always()` alone
// would still run the gate and report those cancellations as failures.
assert.match(content, /if: always\(\) && !cancelled\(\)/);
assert.ok(content.includes(`node .github/scripts/ci-gate.mjs ${mode}`));
assert.ok(content.includes("${{ toJSON(needs) }}"));
assert.ok(content.includes("uses: actions/checkout@v7"));
assert.doesNotMatch(content, /actions\/setup-node|name: Setup Node\.js/);
for (const dependency of dependencies) assert.match(content, new RegExp(`^ - ${dependency}$`, "m"));
}
});
test("frontend tests use two shards on separate runners", () => {
const content = job("frontend-test");
assert.match(content, /fail-fast: false/);
assert.match(content, /shard: \[1, 2\]/);
assert.ok(content.includes("--shard=${{ matrix.shard }}/2"));
assert.ok(content.includes("--reporter=github-actions"));
assert.ok(content.includes("ci-vitest-file-timing-reporter.mjs"));
assert.doesNotMatch(job("frontend-typecheck"), /vitest|oxfmt|oxlint/);
});
test("frontend type checking retains its configured heap budget", () => {
const content = job("frontend-typecheck");
assert.match(content, /NODE_OPTIONS: --max-old-space-size=8192/);
assert.match(content, /name: Type-check frontend\s+run: pnpm exec vue-tsc --noEmit --project apps\/desktop\/tsconfig\.json/);
});
test("Windows 7 bundling uses the tested retry script and still audits the installer", () => {
const content = job("windows-win7-bundle");
assert.ok(content.includes("run: ./.github/scripts/bundle-win7-installer.ps1"));
assert.doesNotMatch(content, /bundle-win7-installer\.test\.ps1/);
assert.ok(content.includes("assert-win7-installer-content.ps1"));
assert.ok(content.indexOf("bundle-win7-installer.ps1") < content.indexOf("assert-win7-installer-content.ps1"));
assert.ok(job("github-scripts").includes("run: ./.github/scripts/bundle-win7-installer.test.ps1"));
});
test("every old Agent stage has an independent owner and Java packaging remains strict", () => {
assert.ok(job("agent-checks").includes("python3 -m unittest discover"));
assert.ok(job("agent-checks").includes("python3 scripts/validate_agents.py"));
assert.ok(job("agent-checks").includes("bump-agent-versions.test.mjs"));
assert.ok(job("agent-java").includes("./gradlew test shadowJar --continue"));
assert.ok(job("agent-java").includes("python3 scripts/validate_agent_jars.py"));
assert.ok(job("agent-rust").includes('cargo nextest run --manifest-path "drivers/$DRIVER/Cargo.toml" --locked --no-fail-fast'));
assert.ok(job("agent-rust").includes('cargo test --doc --manifest-path "drivers/$DRIVER/Cargo.toml" --locked'));
assert.ok(job("agent-rust").includes("cargo build --manifest-path drivers/tdengine/Cargo.toml --locked --release --bin dbx-tdengine-driver"));
assert.ok(job("agent-go").includes("ci-agent-go.sh"));
assert.ok(job("agent-integration").includes("ci-agent-integration.sh"));
assert.ok(job("agent-integration").includes("Setup RocketMQ server Java"));
assert.ok(job("agent-integration").includes("if: matrix.scenario == 'rocketmq'"));
assert.ok(job("agent-integration").includes('java-version: "21"'));
for (const name of ["agent-rust", "agent-go", "agent-integration", "agent-java"]) assert.doesNotMatch(job(name), /continue-on-error: true/);
});
test("shared Rust caches publish only complete builds outside the old cache namespace", () => {
for (const name of ["packages", "rust-fmt-clippy", "rust-test", "agent-rust", "agent-integration"]) {
const content = job(name);
assert.ok(content.includes("prefix-key: v1-rust-complete"), name);
assert.ok(content.includes("cache-on-failure: false"), name);
assert.doesNotMatch(content, /cache-on-failure: true/);
assert.ok(content.includes("save-if: ${{ github.ref == 'refs/heads/main'"), name);
}
});
test("main and fork Rust jobs use the same rust-cache fingerprint", () => {
const stableCacheEnvironment = /- name: Rust cache\s+env:\s+RUST_FEATURE_MODE: ""\s+RUST_TEST_GROUP: ""\s+uses: swatinem\/rust-cache@v2/;
for (const name of ["packages", "rust-fmt-clippy", "rust-test", "agent-rust"]) {
const content = job(name);
assert.match(content, stableCacheEnvironment);
assert.match(content, /^ RUSTC_WRAPPER: sccache$/m);
assert.ok(content.includes('version: "v0.16.0"'));
assert.doesNotMatch(content, /RUSTC_WRAPPER: ""|CC: ""|CXX: ""/);
}
});
test("S3 and secretless Rust jobs export identical compiler wrappers", (context) => {
const directory = mkdtempSync(path.join(tmpdir(), "dbx-ci-sccache-"));
context.after(() => rmSync(directory, { recursive: true, force: true }));
const compiler = "/opt/hostedtoolcache/sccache/0.16.0/x64/sccache";
const backend = {
SCCACHE_BUCKET: "test-bucket",
SCCACHE_ENDPOINT: "https://cache.example.test",
SCCACHE_REGION: "test-region",
SCCACHE_S3_KEY_PREFIX: "test-prefix",
SCCACHE_S3_USE_SSL: "true",
AWS_ACCESS_KEY_ID: "test-access-key",
AWS_SECRET_ACCESS_KEY: "test-secret-key",
};
for (const name of ["packages", "rust-fmt-clippy", "rust-test", "agent-rust"]) {
const setup = job(name).match(/- name: Configure sccache\n([\s\S]*?)(?=\n - name: Rust cache)/)?.[1];
assert.ok(setup, name);
assert.doesNotMatch(setup, /^\s+if:/m);
const script = setup
.match(/ run: \|\n([\s\S]*)/)[1]
.split("\n")
.map((line) => line.slice(10))
.join("\n");
for (const remote of [true, false]) {
const output = path.join(directory, `${name}-${remote}.env`);
const result = spawnSync("bash", ["-e", "-o", "pipefail", "-c", script], {
encoding: "utf8",
env: {
PATH: process.env.PATH,
GITHUB_ENV: output,
SCCACHE_PATH: compiler,
CACHE_BUCKET: remote ? backend.SCCACHE_BUCKET : "",
CACHE_ENDPOINT: remote ? backend.SCCACHE_ENDPOINT : "",
CACHE_REGION: remote ? backend.SCCACHE_REGION : "",
CACHE_KEY_PREFIX: remote ? backend.SCCACHE_S3_KEY_PREFIX : "",
CACHE_ACCESS_KEY_ID: remote ? backend.AWS_ACCESS_KEY_ID : "",
CACHE_SECRET_ACCESS_KEY: remote ? backend.AWS_SECRET_ACCESS_KEY : "",
},
});
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout, "");
const exported = Object.fromEntries(
readFileSync(output, "utf8")
.trim()
.split("\n")
.map((line) => {
const separator = line.indexOf("=");
return [line.slice(0, separator), line.slice(separator + 1)];
}),
);
assert.deepEqual(
exported,
{
...(remote ? backend : {}),
SCCACHE_IDLE_TIMEOUT: "0",
CC: `${compiler} cc`,
CXX: `${compiler} c++`,
},
`${name}: ${remote ? "S3" : "local"}`,
);
}
}
});
test("Rust build timings remain available after failures without waiting for cancelled runs", () => {
for (const [name, artifact, location] of [
["rust-fmt-clippy", "DBX-clippy-cargo-timings", "target/cargo-timings/"],
["rust-test", "DBX-rust-${{ matrix.group }}-cargo-timings", "target/cargo-timings/"],
["agent-rust", "DBX-agent-${{ matrix.driver }}-cargo-timings", "agents/drivers/${{ matrix.driver }}/target/cargo-timings/"],
]) {
const content = job(name);
assert.match(content, /name: Upload Rust Cargo timings\s+if: always\(\) && !cancelled\(\)\s+uses: actions\/upload-artifact@v6/);
assert.ok(content.includes(`name: ${artifact}`));
assert.ok(content.includes(`path: ${location}`));
assert.ok(content.includes("retention-days: 3"));
assert.ok(content.includes("if-no-files-found: ignore"));
}
assert.ok(job("agent-rust").includes('--manifest-path "drivers/$DRIVER/Cargo.toml" --locked --no-fail-fast --timings'));
});
test("Linux dependency installs share bounded acquisition before offline installation", () => {
for (const name of ["packages", "rust-fmt-clippy", "rust-test"]) {
assert.ok(job(name).includes("sudo python3 .github/scripts/ci-install-system-deps.py"));
assert.ok(!job(name).includes("apt-get"));
}
for (const name of ["agent-checks", "agent-java"]) {
assert.ok(job(name).includes("sudo python3 ../.github/scripts/ci-install-system-deps.py --group packaging"));
}
assert.ok(job("agent-rust").includes("sudo python3 ../.github/scripts/ci-install-system-deps.py --group duckdb"));
assert.ok(job("github-scripts").includes("python3 .github/scripts/ci-install-system-deps.test.py"));
});
test("Java CI acquires before original commands without retrying packaging or tests", () => {
assert.ok(job("agent-java").includes("python3 ../.github/scripts/ci-gradle.py agents -- ./gradlew test shadowJar --continue"));
for (const name of ["jdbc", "offline-jdbc-release"]) {
assert.ok(job(name).includes("python3 .github/scripts/ci-gradle.py jdbc -- ./package.sh"));
}
assert.ok(job("github-scripts").includes("python3 .github/scripts/ci-gradle.test.py"));
});
test("mold downloads are isolated from extraction in CI and keepalive", () => {
for (const name of ["packages", "rust-test"]) {
assert.ok(job(name).includes("run: bash .github/scripts/ci-install-mold.sh"));
}
assert.ok(job("agent-rust").includes("run: bash ../.github/scripts/ci-install-mold.sh"));
const keepalive = readFileSync(new URL("../workflows/cache-keepalive.yml", import.meta.url), "utf8");
assert.ok(keepalive.includes("run: bash .github/scripts/ci-install-mold.sh"));
assert.ok(keepalive.includes("sudo python3 .github/scripts/ci-install-system-deps.py"));
assert.ok(job("github-scripts").includes("python3 .github/scripts/ci-install-mold.test.py"));
assert.ok(job("github-scripts").includes("./.github/scripts/prepare-webview2-win7-runtime.test.ps1"));
});
for (const name of ["rust-test", "agent-integration"]) {
for (const scenario of ["ready", "delayed", "unavailable", "missing-mirror"]) {
test(`${name} Docker readiness: ${scenario}`, () => {
const step = job(name).match(/ - name: Configure public Docker Hub cache\n([\s\S]*?)(?=\n - )/)[1];
const block = step.slice(step.indexOf(" timeout 30s")).replace(/^ /gm, "");
const directory = mkdtempSync(path.join(tmpdir(), "dbx-docker-ready-"));
try {
const result = spawnSync(
"bash",
[
"-e",
"-o",
"pipefail",
"-c",
`
sudo() { "$@"; }
systemctl() { printf 'restart\\n' >> "$STATE/restarts"; }
timeout() { shift; "$@"; }
sleep() { :; }
${process.platform === "win32" ? 'python3() { python "$@"; }' : ""}
docker() {
local count=0
[[ ! -f "$STATE/count" ]] || read -r count < "$STATE/count"
count=$((count + 1)); printf '%s\\n' "$count" > "$STATE/count"
if [[ "$SCENARIO" == unavailable ]] || { [[ "$SCENARIO" == delayed ]] && ((count < 3)); }; then
printf 'daemon not ready\\n' >&2; return 124
fi
if [[ "$SCENARIO" == missing-mirror ]]; then printf '[]\\n'; else printf '["https://mirror.gcr.io/"]\\n'; fi
}
${block}
printf 'verified\\n'
`,
],
{ env: { ...process.env, STATE: directory.replaceAll("\\", "/"), SCENARIO: scenario }, encoding: "utf8", timeout: 15000 },
);
assert.ifError(result.error);
assert.equal(readFileSync(path.join(directory, "restarts"), "utf8"), "restart\n");
const count = Number(readFileSync(path.join(directory, "count"), "utf8"));
if (scenario === "ready" || scenario === "delayed") {
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /verified/);
assert.equal(count, scenario === "ready" ? 1 : 3);
} else {
assert.notEqual(result.status, 0);
assert.doesNotMatch(result.stdout, /verified/);
assert.equal(count, scenario === "unavailable" ? 12 : 1);
}
} finally {
rmSync(directory, { recursive: true, force: true });
}
});
}
}
test("Rust test jobs install pinned nextest and retain separate doctests", () => {
const pluginDevHost = readFileSync(new URL("../workflows/plugin-dev-host.yml", import.meta.url), "utf8");
const pluginRelease = readFileSync(new URL("../workflows/plugin-cli-release.yml", import.meta.url), "utf8");
const consumers = [job("packages"), job("rust-test"), job("agent-rust"), job("agent-integration"), job("test", pluginDevHost), job("prepare", pluginRelease)];
for (const content of consumers) {
assert.ok(content.includes("uses: taiki-e/install-action@1e29709c0ec63babbe8f266397a465f29a825f91"));
assert.ok(content.includes("tool: cargo-nextest@0.9.137"));
assert.doesNotMatch(content, /cargo test (?!.*--doc)/);
const testCommand = content.indexOf("cargo nextest run") >= 0 ? "cargo nextest run" : content.includes("pnpm test:packages") ? "pnpm test:packages" : content.includes("ci-rust.mjs test") ? "ci-rust.mjs test" : "ci-agent-integration.sh";
assert.ok(content.indexOf("tool: cargo-nextest@0.9.137") < content.indexOf(testCommand));
}
assert.ok(job("rust-test").includes('ci-rust.mjs doctest "$RUST_TEST_GROUP" "$RUST_FEATURE_MODE"'));
for (const content of [job("test", pluginDevHost), job("prepare", pluginRelease)]) {
assert.ok(content.includes("cargo nextest run --locked --manifest-path plugins/sdk/cli/Cargo.toml --no-fail-fast"));
assert.ok(content.includes("cargo test --doc --locked --manifest-path plugins/sdk/cli/Cargo.toml"));
}
assert.match(job("agent-integration"), /name: Install nextest\s+if: matrix\.driver == 'tdengine'/);
});
test("local Rust entrypoints and live TDengine tests use nextest", () => {
const makefile = readFileSync(new URL("../../Makefile", import.meta.url), "utf8");
assert.ok(makefile.includes("RUST_MIN_STACK=8388608 cargo nextest run --no-default-features --features sqlite-bundled --no-fail-fast"));
assert.ok(makefile.includes("RUST_MIN_STACK=8388608 cargo test --doc --no-default-features --features sqlite-bundled"));
const cli = JSON.parse(readFileSync(new URL("../../packages/cli/package.json", import.meta.url), "utf8"));
assert.equal(cli.scripts.test, "cargo nextest run -p dbx-cli --no-default-features --no-fail-fast");
const integration = readFileSync(new URL("./ci-agent-integration.sh", import.meta.url), "utf8");
assert.ok(integration.includes("cargo nextest run --manifest-path drivers/tdengine/Cargo.toml --locked --test live --no-capture --no-fail-fast"));
assert.doesNotMatch(integration, /cargo test/);
});
test("DuckDB Windows builds persist Rust and C++ compiler results", () => {
const content = job("duckdb-windows-driver");
// S3 is preferred over the quota-limited GHA backend; GHA remains the fallback.
assert.ok(content.includes("SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}"));
assert.ok(content.includes('CC: "sccache cl.exe"'));
assert.ok(content.includes('CXX: "sccache cl.exe"'));
assert.ok(content.includes("0b201ec74fa43914dc39ae48a89fd1d8cb592756"));
assert.ok(content.includes("fc920bf0ec8de6ee65d409111f7ec508035751ba"));
assert.ok(content.includes('version: "v0.16.0"'));
});
test("Windows compatibility jobs cache Rust compilation without wrapping C or C++", () => {
const standard = job("windows-standard-check");
assert.ok(standard.includes("needs.changes.outputs.windows_standard == 'true'"));
assert.ok(standard.includes("RUSTC_WRAPPER: sccache"));
assert.ok(standard.includes("SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}"));
assert.ok(standard.includes("SCCACHE_GHA_VERSION: windows-standard-v1"));
assert.ok(standard.includes("fc920bf0ec8de6ee65d409111f7ec508035751ba"));
assert.ok(standard.includes('version: "v0.16.0"'));
assert.ok(standard.includes("cargo check --locked --package dbx --no-default-features --target x86_64-pc-windows-msvc"));
assert.ok(standard.includes("sccache --show-stats"));
const win7 = job("windows-win7-bundle");
assert.ok(win7.includes("needs.changes.outputs.windows_win7_bundle == 'true'"));
assert.doesNotMatch(win7, /x86_64-pc-windows-msvc|Setup Rust for standard Windows/);
for (const setting of ["RUSTC_WRAPPER: sccache", "SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }}", "SCCACHE_GHA_VERSION: win7-webview2-1.0.902.49-v1", 'SCCACHE_IDLE_TIMEOUT: "0"', 'CARGO_PROFILE_RELEASE_LTO: "off"', 'CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "8"'])
assert.ok(win7.includes(setting));
assert.ok(win7.includes("fc920bf0ec8de6ee65d409111f7ec508035751ba"));
assert.ok(win7.includes('version: "v0.16.0"'));
assert.ok(win7.includes("sccache --show-stats"));
for (const flag of ["-Z host-config", "-Z target-applies-to-host", "-Z build-std=std,panic_abort"]) assert.ok(win7.includes(flag));
assert.ok(win7.includes("--config .github/fixtures/win7-host-repro-config.toml"));
const hostConfig = readFileSync(new URL("../fixtures/win7-host-repro-config.toml", import.meta.url), "utf8");
assert.ok(hostConfig.includes("target-applies-to-host = false"));
assert.ok(hostConfig.includes("[host.x86_64-pc-windows-msvc]"));
assert.ok(hostConfig.includes('rustflags = ["-Clink-arg=/Brepro"]'));
assert.ok(win7.includes("--timings"));
assert.ok(win7.includes("name: DBX-win7-cargo-timings"));
assert.ok(win7.includes("path: target/cargo-timings/"));
assert.ok(win7.includes("name: Measure dbx library command registry expansion"));
assert.ok(win7.includes("if: vars.WIN7_REGISTRY_AB == 'true'"));
assert.ok(win7.includes("--package dbx"));
assert.ok(win7.includes("--lib"));
assert.ok(win7.includes('$env:RUSTC_WRAPPER = ""'));
assert.ok(win7.includes('Measure-Registry "full" $fullCommandCount'));
assert.ok(win7.includes('Measure-Registry "half" $keptCommandCount'));
assert.ok(win7.includes('"-Zdump-mono-stats=$outputDir"'));
assert.ok(win7.includes('"-Zdump-mono-stats-format=json"'));
assert.ok(win7.includes("git restore --source=HEAD -- $sourcePath"));
assert.ok(win7.includes("name: DBX-win7-dbx-lib-command-registry-ab"));
assert.ok(win7.includes("if: always() && vars.WIN7_REGISTRY_AB == 'true'"));
assert.ok(win7.includes("path: ${{ runner.temp }}/dbx-lib-command-registry-ab/"));
assert.ok(win7.includes("retention-days: 3"));
assert.ok(win7.indexOf("name: Build DBX for Windows 7") < win7.indexOf("name: Measure dbx library command registry expansion"));
assert.ok(win7.indexOf("name: Measure dbx library command registry expansion") < win7.indexOf("name: Upload dbx library command registry A/B"));
assert.doesNotMatch(win7, /^\s+(?:CC|CXX):/m);
});
test("Win7 TLS artifact names ignore the workspace lockfile", () => {
const win7 = job("windows-win7-bundle");
assert.ok(win7.includes("hashFiles('.github/fixtures/win7-aws-lc-cache/Cargo.toml', '.github/fixtures/win7-aws-lc-cache/Cargo.lock')"));
assert.ok(win7.includes("hashFiles('.github/fixtures/win7-openssl-cache/Cargo.toml', '.github/fixtures/win7-openssl-cache/Cargo.lock')"));
assert.doesNotMatch(win7, /WIN7_PREBUILT_ARTIFACT_NAME: win7-(?:aws-lc|openssl).*hashFiles\('Cargo\.lock'/);
});
test("Win7 TLS artifacts come from main and retain a build fallback", () => {
const win7 = job("windows-win7-bundle");
assert.ok(win7.includes("actions: read"));
assert.doesNotMatch(win7, /uses: actions\/cache(?:\/restore|\/save)?@v4/);
for (const [name, id] of [
["AWS-LC", "aws-lc-artifact"],
["OpenSSL", "openssl-artifact"],
]) {
assert.ok(win7.includes(`if: steps.${id}.outputs.run_id != ''`));
assert.ok(win7.includes(`if: steps.${id}.outputs.run_id == ''`));
assert.ok(win7.includes(`if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.${id}.outputs.run_id == ''`));
assert.ok(win7.includes(`name: \${{ steps.${id}.outputs.name }}`));
assert.ok(win7.indexOf(`name: Find prebuilt Win7 ${name} artifact`) < win7.indexOf(`name: Download prebuilt Win7 ${name} artifact`));
assert.ok(win7.indexOf(`name: Download prebuilt Win7 ${name} artifact`) < win7.indexOf(`name: Build prebuilt Win7 ${name}`));
assert.ok(win7.indexOf(`name: Build prebuilt Win7 ${name}`) < win7.indexOf(`name: Upload prebuilt Win7 ${name} on main`));
assert.ok(win7.indexOf(`name: Configure prebuilt Win7 ${name}`) < win7.indexOf(`name: Upload prebuilt Win7 ${name} on main`));
}
});
test("path filtering and the planner use the exact test merge first parent", () => {
const changes = job("changes");
assert.ok(changes.includes("BEFORE_SHA: ${{ github.event.before }}"));
assert.ok(changes.includes('BASE_SHA="$(node .github/scripts/ci-change-base.mjs)"'));
assert.ok(changes.includes("fetch-depth: 2"));
assert.ok(changes.includes('git fetch --no-tags --depth=1 origin "$BASE_SHA"'));
assert.ok(changes.includes("base: ${{ steps.change-base.outputs.sha }}"));
assert.match(changes, /with:\s+token: ''\s+base: \$\{\{ steps\.change-base\.outputs\.sha \}\}\s+ref: \$\{\{ github\.sha \}\}/);
assert.ok(changes.includes("BASE_SHA: ${{ steps.change-base.outputs.sha }}"));
assert.ok(changes.includes("node .github/scripts/ci-plan.mjs"));
assert.doesNotMatch(changes, /fetch-depth:\s*0/);
assert.doesNotMatch(changes, /dtolnay\/rust-toolchain/);
for (const flag of ["rust", "rust_full", "rust_matrix", "agents", "agent_go", "agent_rust", "agent_integration", "windows_standard", "windows_win7_bundle", "windows_win7_candidate", "windows_win7_affected_packages", "windows_win7_reasons", "plan"]) {
assert.ok(changes.includes(`steps.plan.outputs.${flag}`));
}
assert.doesNotMatch(changes, /steps\.filter\.outputs\.windows_win7_bundle|github\.event\.pull_request\.base\.sha|WIN7_CURRENT/);
});
test("legacy Windows builds keep the 8 MiB main-thread stack", () => {
const release = readFileSync(new URL("../workflows/release.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
const sqlServerUserTest = readFileSync(new URL("../workflows/sqlserver2000-windows-user-test.yml", import.meta.url), "utf8").replaceAll("\r\n", "\n");
const stackFlag = /RUSTFLAGS:.*link-arg=\/STACK:8388608/;
// An env RUSTFLAGS replaces .cargo/config.toml target rustflags, so every job
// that links a Windows PE has to repeat the /STACK link argument; otherwise
// the 1 MiB linker default returns and deep DB call stacks abort with
// 0xC00000FD ("DBX 已停止工作").
for (const name of ["windows-standard-check", "windows-win7-bundle", "duckdb-windows-driver"]) {
assert.match(job(name), stackFlag, `${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
}
for (const name of ["build-windows-7-offline", "build-windows-7-static-browser"]) {
assert.match(job(name, release), stackFlag, `release job ${name} must keep the 8 MiB main-thread stack in RUSTFLAGS`);
}
assert.match(job("build-windows-7-x64", sqlServerUserTest), stackFlag, "the SQL Server 2000 Win7 test build must keep the 8 MiB main-thread stack");
});
test("the Win7 PE audit fails when the stack reserve regresses", () => {
const audit = readFileSync(new URL("./assert-win7-pe-compat.ps1", import.meta.url), "utf8");
assert.ok(audit.includes("minimumStackReserve"));
assert.ok(audit.includes("link-arg=/STACK:8388608"));
});