fix: 补齐候选诊断环境与证据保护(09-06-release-gate-integrity)
这个提交包含在:
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env node
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const TEST_FILE_PATTERN = /\.(?:spec|test)\.[cm]?[jt]sx?$/u;
|
||||
|
||||
export function fullLocalSpecFiles(root) {
|
||||
const files = [];
|
||||
function visit(directory) {
|
||||
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name !== "manual") visit(path.join(directory, entry.name));
|
||||
} else if (entry.isFile() && TEST_FILE_PATTERN.test(entry.name)) {
|
||||
files.push(path.relative(root, path.join(directory, entry.name)).split(path.sep).join("/"));
|
||||
} else if (entry.isSymbolicLink()) {
|
||||
throw new Error("full_local_symlink_requires_explicit_ownership");
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(path.join(root, "client/tests/e2e"));
|
||||
return files.sort();
|
||||
}
|
||||
|
||||
function sourceFile(root, file) {
|
||||
if (typeof file !== "string" || !file) return "invalid_source";
|
||||
let relative = path.isAbsolute(file) ? path.relative(root, file) : file;
|
||||
relative = relative.replaceAll("\\", "/");
|
||||
if (relative.startsWith("tests/e2e/")) relative = `client/${relative}`;
|
||||
if (!relative.startsWith("client/tests/e2e/")) relative = `client/tests/e2e/${relative}`;
|
||||
relative = path.posix.normalize(relative);
|
||||
return relative.startsWith("client/tests/e2e/") ? relative : "invalid_source";
|
||||
}
|
||||
|
||||
export function verifyFullLocalDiscovery(root, report) {
|
||||
const expectedSpecs = fullLocalSpecFiles(root);
|
||||
const cases = [];
|
||||
const observed = new Set();
|
||||
const issues = [];
|
||||
function visit(suites, inheritedFile = "") {
|
||||
for (const suite of Array.isArray(suites) ? suites : []) {
|
||||
const suiteFile = suite.file ?? inheritedFile;
|
||||
for (const spec of Array.isArray(suite.specs) ? suite.specs : []) {
|
||||
const file = sourceFile(root, spec.file ?? suiteFile);
|
||||
observed.add(file);
|
||||
if (!Array.isArray(spec.tests) || !spec.tests.length) issues.push("spec_without_cases");
|
||||
for (const test of Array.isArray(spec.tests) ? spec.tests : []) {
|
||||
cases.push({ file, id: spec.id, title: spec.title, line: spec.line, projectName: test.projectName ?? "", repeatEachIndex: test.repeatEachIndex ?? 0 });
|
||||
}
|
||||
}
|
||||
visit(suite.suites, suiteFile);
|
||||
}
|
||||
}
|
||||
visit(report?.suites);
|
||||
if (!Array.isArray(report?.suites) || !Array.isArray(report?.errors)) issues.push("invalid_discovery_report");
|
||||
if (report?.errors?.length) issues.push("discovery_errors");
|
||||
if (!expectedSpecs.length || !cases.length) issues.push("empty_discovery");
|
||||
const missingSpecs = expectedSpecs.filter((file) => !observed.has(file));
|
||||
const unexpectedSpecs = [...observed].filter((file) => !expectedSpecs.includes(file)).sort();
|
||||
if (missingSpecs.length) issues.push("missing_specs");
|
||||
if (unexpectedSpecs.length) issues.push("unexpected_specs");
|
||||
return {
|
||||
status: issues.length ? "FAIL" : "PASS",
|
||||
kind: "FULL_LOCAL_DISCOVERY_ONLY",
|
||||
expectedSpecs,
|
||||
observedSpecs: [...observed].sort(),
|
||||
missingSpecs,
|
||||
unexpectedSpecs,
|
||||
cases,
|
||||
issues: [...new Set(issues)],
|
||||
};
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
||||
try {
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const summary = verifyFullLocalDiscovery(root, JSON.parse(readFileSync(process.argv[2], "utf8")));
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
process.exitCode = summary.status === "PASS" ? 0 : 1;
|
||||
} catch {
|
||||
console.error("[full-local-discovery] report_or_source_invalid");
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户