mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
feat(ci): migrate Rust test runners to nextest
This commit is contained in:
@@ -110,7 +110,7 @@ EOF
|
||||
TDENGINE_INTEGRATION=1 \
|
||||
TDENGINE_TEST_HOST=127.0.0.1 \
|
||||
TDENGINE_TEST_PORT=6041 \
|
||||
cargo test --manifest-path drivers/tdengine/Cargo.toml --locked --test live -- --nocapture
|
||||
cargo nextest run --manifest-path drivers/tdengine/Cargo.toml --locked --test live --no-capture --no-fail-fast
|
||||
;;
|
||||
cassandra)
|
||||
cd "$root/agents/drivers/cassandra-go"
|
||||
|
||||
@@ -85,7 +85,7 @@ for (const entry of integrationCases) {
|
||||
assert.equal(tests[0].env.DBX_ZOOKEEPER_TEST_USERNAME, "dbx");
|
||||
assert.ok(start.args.some((arg) => arg.includes("sessionRequireClientSASLAuth=true")));
|
||||
} else if (entry.scenario === "tdengine") {
|
||||
assert.deepEqual(tests[0].args, ["test", "--manifest-path", "drivers/tdengine/Cargo.toml", "--locked", "--test", "live", "--", "--nocapture"]);
|
||||
assert.deepEqual(tests[0].args, ["nextest", "run", "--manifest-path", "drivers/tdengine/Cargo.toml", "--locked", "--test", "live", "--no-capture", "--no-fail-fast"]);
|
||||
assert.equal(tests[0].env.TDENGINE_INTEGRATION, "1");
|
||||
assert.equal(tests[0].env.TDENGINE_TEST_PORT, "6041");
|
||||
} else if (entry.scenario === "cassandra") assert.ok(tests[0].args.includes("^TestCassandraIntegration$"));
|
||||
@@ -104,6 +104,13 @@ test("failed live tests stay failed and still clean their container", () => with
|
||||
assert.deepEqual(commands().at(-1).args.slice(0, 2), ["rm", "-fv"]);
|
||||
}, { CI_STUB_FAIL: "go" }));
|
||||
|
||||
test("failed nextest live tests propagate failure and still clean their container", () => withToolStubs((env, commands) => {
|
||||
const result = spawnSync("/bin/bash", [".github/scripts/ci-agent-integration.sh", "tdengine", "3.4.2.2"], { cwd: root, env, encoding: "utf8" });
|
||||
assert.equal(result.status, 23);
|
||||
assert.deepEqual(commands().find((command) => command.tool === "cargo").args.slice(0, 2), ["nextest", "run"]);
|
||||
assert.deepEqual(commands().at(-1).args.slice(0, 2), ["rm", "-fv"]);
|
||||
}, { CI_STUB_FAIL: "cargo" }));
|
||||
|
||||
test("lock preflight resolves full graphs, keeps --locked and propagates failures", () => {
|
||||
const plan = { rust: true, agent_rust: { include: [{ driver: "duckdb" }, { driver: "tdengine" }] } };
|
||||
assert.deepEqual(lockfileManifests(plan), ["Cargo.toml", "agents/drivers/duckdb/Cargo.toml", "agents/drivers/tdengine/Cargo.toml"]);
|
||||
|
||||
@@ -139,6 +139,7 @@ test("test groups partition every current workspace package exactly once", () =>
|
||||
test("workspace Cargo flags preserve full versus fast coverage and strict clippy", () => {
|
||||
for (const mode of ["fast", "full"]) {
|
||||
const command = rustCommand("test", "workspace", mode);
|
||||
assert.deepEqual(command.slice(0, 3), ["nextest", "run", "--no-fail-fast"]);
|
||||
const features = command.at(-1).split(",");
|
||||
assert.ok(command.includes("--workspace"));
|
||||
assert.ok(command.includes("--locked"));
|
||||
@@ -147,12 +148,30 @@ test("workspace Cargo flags preserve full versus fast coverage and strict clippy
|
||||
assert.equal(features.includes(`${pkg}/system-fonts`), mode === "full");
|
||||
}
|
||||
assert.deepEqual(rustCommand("clippy", "workspace", mode).slice(-3), ["--", "-D", "warnings"]);
|
||||
assert.equal(rustCommand("clippy", "workspace", mode)[0], "clippy");
|
||||
}
|
||||
for (const group of Object.keys(rustGroups)) assert.ok(rustCommand("test", group, "fast").includes("--package"));
|
||||
assert.throws(() => rustCommand("test", "unknown", "full"));
|
||||
assert.throws(() => rustCommand("clippy", "foundation", "full"));
|
||||
});
|
||||
|
||||
test("nextest, doctests and coverage select identical packages and features in every Rust lane", () => {
|
||||
for (const group of ["workspace", ...Object.keys(rustGroups)]) {
|
||||
for (const mode of ["fast", "full"]) {
|
||||
const nextest = rustCommand("test", group, mode);
|
||||
const doctest = rustCommand("doctest", group, mode);
|
||||
assert.deepEqual(nextest.slice(0, 3), ["nextest", "run", "--no-fail-fast"]);
|
||||
assert.deepEqual(doctest, ["test", "--doc", ...nextest.slice(3)]);
|
||||
assert.deepEqual(rustCommand("tree", group, mode), ["tree", ...nextest.slice(3)]);
|
||||
assert.ok(nextest.includes("--no-default-features"));
|
||||
assert.ok(nextest.includes("--locked"));
|
||||
}
|
||||
}
|
||||
assert.throws(() => rustCommand("doctest", "unknown", "full"));
|
||||
assert.throws(() => rustCommand("doctest", "workspace", "unknown"));
|
||||
assert.throws(() => rustCommand("unknown", "workspace", "full"));
|
||||
});
|
||||
|
||||
function results(files) {
|
||||
const selected = plan(files);
|
||||
return { changes: { result: "success", outputs: { plan: JSON.stringify(selected) } },
|
||||
|
||||
@@ -29,8 +29,7 @@ export function assertCoverage(coverage) {
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
||||
const coverage = {};
|
||||
for (const group of ["workspace", ...Object.keys(rustGroups)]) {
|
||||
const args = rustCommand("test", group, "fast");
|
||||
args[0] = "tree";
|
||||
const args = rustCommand("tree", group, "fast");
|
||||
args.push("--depth", "0", "--prefix", "none", "--format", "{p}|{f}");
|
||||
coverage[group] = parseCoverage(execFileSync("cargo", args, { encoding: "utf8", maxBuffer: 8 * 1024 * 1024, stdio: ["ignore", "pipe", "inherit"] }));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
import { rustGroups } from "./ci-config.mjs";
|
||||
|
||||
export function rustCommand(action, group, mode) {
|
||||
if (!["test", "clippy"].includes(action) || !["full", "fast"].includes(mode)
|
||||
if (!["test", "doctest", "clippy", "tree"].includes(action) || !["full", "fast"].includes(mode)
|
||||
|| (group !== "workspace" && !Object.hasOwn(rustGroups, group)) || (action === "clippy" && group !== "workspace")) {
|
||||
throw new Error(`Unknown Rust CI configuration: ${action}/${group}/${mode}`);
|
||||
}
|
||||
@@ -18,7 +18,8 @@ export function rustCommand(action, group, mode) {
|
||||
"dbx-sqlite-worker/runtime", "dbx-types/openapi", "dbx-sql/openapi",
|
||||
] : appFeatures;
|
||||
const packages = group === "workspace" ? ["--workspace"] : rustGroups[group].flatMap((name) => ["--package", name]);
|
||||
return [action, ...packages, "--locked", ...(action === "clippy" ? ["--all-targets"] : []),
|
||||
const command = action === "test" ? ["nextest", "run", "--no-fail-fast"] : action === "doctest" ? ["test", "--doc"] : [action];
|
||||
return [...command, ...packages, "--locked", ...(action === "clippy" ? ["--all-targets"] : []),
|
||||
"--no-default-features", "--features", features.join(","), ...(action === "clippy" ? ["--", "-D", "warnings"] : [])];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
const workflow = readFileSync(new URL("../workflows/ci.yml", import.meta.url), "utf8");
|
||||
function job(name) {
|
||||
const jobs = workflow.slice(workflow.indexOf("\njobs:\n") + 7);
|
||||
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);
|
||||
@@ -62,7 +62,8 @@ test("every old Agent stage has an independent owner and Java packaging remains
|
||||
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 test --manifest-path "drivers/$DRIVER/Cargo.toml" --locked'));
|
||||
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"));
|
||||
@@ -78,6 +79,38 @@ test("native Rust driver caches exclude failed build artifacts", () => {
|
||||
assert.ok(content.includes("cache-on-failure: false"));
|
||||
});
|
||||
|
||||
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("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@9114bf4d891761788c546334fd37538eae1bf8b3"));
|
||||
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("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");
|
||||
assert.ok(content.includes('SCCACHE_GHA_ENABLED: "true"'));
|
||||
|
||||
@@ -600,12 +600,21 @@ jobs:
|
||||
# PR caches are large and branch-scoped; restore them from main without saving per-PR copies.
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@9114bf4d891761788c546334fd37538eae1bf8b3
|
||||
with:
|
||||
tool: cargo-nextest@0.9.137
|
||||
|
||||
# mold -run wraps the whole process tree (see the packages job note):
|
||||
# test-binary linking dominates this job and sccache cannot cache links.
|
||||
- name: Cargo test
|
||||
- name: Nextest
|
||||
run: |-
|
||||
mold -run node .github/scripts/ci-rust.mjs test "$RUST_TEST_GROUP" "$RUST_FEATURE_MODE"
|
||||
|
||||
- name: Rust doc tests
|
||||
run: |-
|
||||
mold -run node .github/scripts/ci-rust.mjs doctest "$RUST_TEST_GROUP" "$RUST_FEATURE_MODE"
|
||||
|
||||
- name: Show sccache stats
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
@@ -1047,12 +1056,18 @@ jobs:
|
||||
shared-key: ci-agent-rust-v2-${{ matrix.driver }}
|
||||
cache-on-failure: false
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@9114bf4d891761788c546334fd37538eae1bf8b3
|
||||
with:
|
||||
tool: cargo-nextest@0.9.137
|
||||
# mold -run wraps the whole process tree (see the packages job note);
|
||||
# the duckdb sidecar links a large bundled C++ static library.
|
||||
- name: Native Rust driver tests
|
||||
env:
|
||||
DRIVER: ${{ matrix.driver }}
|
||||
run: mold -run cargo test --manifest-path "drivers/$DRIVER/Cargo.toml" --locked
|
||||
run: |
|
||||
mold -run cargo nextest run --manifest-path "drivers/$DRIVER/Cargo.toml" --locked --no-fail-fast
|
||||
mold -run cargo test --doc --manifest-path "drivers/$DRIVER/Cargo.toml" --locked
|
||||
- name: TDengine native agent build
|
||||
if: matrix.driver == 'tdengine'
|
||||
run: mold -run cargo build --manifest-path drivers/tdengine/Cargo.toml --locked --release --bin dbx-tdengine-driver
|
||||
@@ -1113,6 +1128,11 @@ jobs:
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@1.97.1
|
||||
if: matrix.driver == 'tdengine'
|
||||
- name: Install nextest
|
||||
if: matrix.driver == 'tdengine'
|
||||
uses: taiki-e/install-action@9114bf4d891761788c546334fd37538eae1bf8b3
|
||||
with:
|
||||
tool: cargo-nextest@0.9.137
|
||||
- name: TDengine Rust cache
|
||||
if: matrix.driver == 'tdengine'
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
@@ -129,8 +129,16 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@9114bf4d891761788c546334fd37538eae1bf8b3
|
||||
with:
|
||||
tool: cargo-nextest@0.9.137
|
||||
|
||||
- name: Test Rust CLI
|
||||
run: cargo test --locked --manifest-path plugins/sdk/cli/Cargo.toml
|
||||
run: cargo nextest run --locked --manifest-path plugins/sdk/cli/Cargo.toml --no-fail-fast
|
||||
|
||||
- name: Test Rust CLI docs
|
||||
run: cargo test --doc --locked --manifest-path plugins/sdk/cli/Cargo.toml
|
||||
|
||||
- name: Prepare and test plugin development runtime
|
||||
run: |
|
||||
|
||||
@@ -33,7 +33,11 @@ jobs:
|
||||
- run: npm run fmt:check --prefix plugins/sdk/dev-host
|
||||
- run: npm test --prefix plugins/sdk/dev-host
|
||||
timeout-minutes: 5
|
||||
- run: cargo test --locked --manifest-path plugins/sdk/cli/Cargo.toml
|
||||
- uses: taiki-e/install-action@9114bf4d891761788c546334fd37538eae1bf8b3
|
||||
with:
|
||||
tool: cargo-nextest@0.9.137
|
||||
- run: cargo nextest run --locked --manifest-path plugins/sdk/cli/Cargo.toml --no-fail-fast
|
||||
- run: cargo test --doc --locked --manifest-path plugins/sdk/cli/Cargo.toml
|
||||
- run: npm test --prefix packages/plugin-cli
|
||||
- run: node scripts/verify-plugin-cli-package.mjs
|
||||
env:
|
||||
|
||||
@@ -113,7 +113,8 @@ cargo-check-fast:
|
||||
cargo check --no-default-features --features sqlite-bundled
|
||||
|
||||
cargo-test-fast:
|
||||
RUST_MIN_STACK=8388608 cargo test --no-default-features --features sqlite-bundled
|
||||
RUST_MIN_STACK=8388608 cargo nextest run --no-default-features --features sqlite-bundled --no-fail-fast
|
||||
RUST_MIN_STACK=8388608 cargo test --doc --no-default-features --features sqlite-bundled
|
||||
|
||||
db-list:
|
||||
@$(PNPM) db:env -- list
|
||||
|
||||
@@ -100,6 +100,11 @@ core 保留原来的 default feature 集合,并向实际实现 crate 转发
|
||||
Linux 的文件权限回归测试应由非 root 用户执行;root 可绕过目录写权限,会让测试
|
||||
前提失效。测试进程的代理应排除 localhost,避免本地 HTTP 模拟器请求被转发。
|
||||
|
||||
Rust 单元与集成测试使用 `cargo nextest run`,CI 固定 nextest `0.9.137`。
|
||||
首次使用执行 `cargo install cargo-nextest --locked --version 0.9.137`。
|
||||
文档测试仍单独使用 `cargo test --doc`;`make cargo-test-fast` 会依次运行 nextest
|
||||
和文档测试,并保留跳过 DuckDB、启用 bundled SQLite 的本地验证配置。
|
||||
|
||||
```sh
|
||||
pnpm test:architecture
|
||||
cargo check -p dbx-core --no-default-features --all-targets
|
||||
@@ -107,14 +112,14 @@ cargo check -p dbx-core --no-default-features --all-targets
|
||||
env -u DOCKER_CONTEXT \
|
||||
DOCKER_HOST=unix:///tmp/dbx-disabled-docker.sock \
|
||||
RUST_MIN_STACK=33554432 \
|
||||
cargo test -p dbx-core -p dbx-drivers -p dbx-sql \
|
||||
cargo nextest run -p dbx-core -p dbx-drivers -p dbx-sql \
|
||||
-p dbx-types -p dbx-formats -p dbx-platform \
|
||||
-p dbx-ai-provider -p dbx-plugin-runtime \
|
||||
--no-default-features --features dbx-core/sqlite-bundled --lib
|
||||
--no-default-features --features dbx-core/sqlite-bundled --lib --no-fail-fast
|
||||
|
||||
cargo test -p dbx-core --no-default-features --features sqlite-bundled \
|
||||
cargo nextest run -p dbx-core --no-default-features --features sqlite-bundled \
|
||||
--test public_api_compatibility --test connection_url_compatibility \
|
||||
--test agent_recovery_contract
|
||||
--test agent_recovery_contract --no-fail-fast
|
||||
```
|
||||
|
||||
交付验证还应覆盖桌面/Web/CLI/MCP 消费者、合法 feature 组合、前端类型与源码契约、
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "cargo run -p dbx-cli --no-default-features --",
|
||||
"test": "cargo test -p dbx-cli --no-default-features",
|
||||
"test": "cargo nextest run -p dbx-cli --no-default-features --no-fail-fast",
|
||||
"build": "cargo build -p dbx-cli --release --no-default-features",
|
||||
"prepublishOnly": "node --check bin/dbx.js"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user