mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-05 08:09:22 +00:00
290 lines
11 KiB
JavaScript
290 lines
11 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFileSync } from "node:child_process";
|
|
import {
|
|
mkdtempSync,
|
|
copyFileSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
readdirSync,
|
|
realpathSync,
|
|
rmSync,
|
|
renameSync,
|
|
statSync,
|
|
symlinkSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { test } from "node:test";
|
|
import { configurationDigest, environmentDigest, timings } from "./runtime.mjs";
|
|
import { validateReports } from "./validate.mjs";
|
|
import { reportedTest } from "./reporter.ts";
|
|
|
|
const webRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
|
|
test("reporting preserves genuinely pending runnable tests and terminal failures", () => {
|
|
const pending = {
|
|
id: "required",
|
|
fullName: "required test",
|
|
options: { mode: "run" },
|
|
parent: { type: "module" },
|
|
result: () => ({ state: "pending" }),
|
|
};
|
|
assert.deepEqual(reportedTest(pending), {
|
|
id: "required",
|
|
name: "required test",
|
|
mode: "run",
|
|
state: "pending",
|
|
});
|
|
const parent = { type: "suite", options: { mode: "skip" }, parent: pending.parent };
|
|
for (const state of ["failed", "passed"]) {
|
|
assert.equal(reportedTest({ ...pending, parent, result: () => ({ state }) }).state, state);
|
|
}
|
|
});
|
|
|
|
test(
|
|
"real Vitest blobs preserve nested skipped/todo cases and merge covered shards",
|
|
{ timeout: 60000 },
|
|
() => {
|
|
const root = realpathSync(mkdtempSync(join(tmpdir(), "ui-shard-report-")));
|
|
const env = {
|
|
...process.env,
|
|
GITHUB_SHA: execFileSync("git", ["rev-parse", "HEAD"], {
|
|
cwd: webRoot,
|
|
encoding: "utf8",
|
|
}).trim(),
|
|
GITHUB_RUN_ID: "fixture-run",
|
|
GITHUB_RUN_ATTEMPT: "2",
|
|
UI_TEST_REPORTS_DIRECTORY: join(root, ".vitest-reports"),
|
|
NODE_ENV: "test",
|
|
};
|
|
try {
|
|
const gitDir = execFileSync("git", ["rev-parse", "--absolute-git-dir"], {
|
|
cwd: webRoot,
|
|
encoding: "utf8",
|
|
}).trim();
|
|
writeFileSync(join(root, ".git"), `gitdir: ${gitDir}\n`);
|
|
mkdirSync(join(root, "src"));
|
|
mkdirSync(join(root, "scripts"));
|
|
symlinkSync(join(webRoot, "node_modules"), join(root, "node_modules"), "dir");
|
|
mkdirSync(join(root, "scripts/ui-tests"));
|
|
for (const name of ["run.mjs", "runtime.mjs", "partition.mjs", "validate.mjs"])
|
|
copyFileSync(join(webRoot, "scripts/ui-tests", name), join(root, "scripts/ui-tests", name));
|
|
copyFileSync(join(webRoot, "coverage.sh"), join(root, "coverage.sh"));
|
|
writeFileSync(join(root, "scripts/ui-tests/config.json"), '{"shards":4}');
|
|
writeFileSync(join(root, "package.json"), '{"type":"module"}');
|
|
writeFileSync(
|
|
join(root, "src/value.ts"),
|
|
"export const value = (n: number) => n > 2 ? n + 1 : 1 + n;",
|
|
);
|
|
for (const [name, body] of Object.entries({
|
|
a: 'it("passes", () => expect(value(1)).toBe(2));',
|
|
b: `it.skip("skips", () => expect(value(1)).toBe(0));
|
|
describe.skip("outer skip", () => {
|
|
describe("middle", () => {
|
|
describe.skip("inner skip", () => {
|
|
it("inherits skip", () => { throw new Error("must stay skipped"); });
|
|
});
|
|
});
|
|
});`,
|
|
c: `it.todo("todo");
|
|
describe.todo("outer todo", () => {
|
|
describe("middle", () => {
|
|
describe.skip("inner", () => {
|
|
it("inherits todo", () => { throw new Error("must stay todo"); });
|
|
});
|
|
});
|
|
});
|
|
describe("ordinary todo", () => {
|
|
it.todo("ordinary todo case");
|
|
});
|
|
describe("mixed nonrunning", () => {
|
|
it.skip("mixed skipped case", () => { throw new Error("must stay skipped"); });
|
|
it.todo("mixed todo case");
|
|
});`,
|
|
d: `it("also passes", () => expect(value(3)).toBe(4));
|
|
it("dynamic skip", ({ skip }) => skip());`,
|
|
})) {
|
|
writeFileSync(
|
|
join(root, `src/${name}.spec.ts`),
|
|
`import { describe, it, expect } from "vitest"; import { value } from "./value"; ${body}`,
|
|
);
|
|
}
|
|
const config = {
|
|
test: {
|
|
root,
|
|
include: ["src/*.spec.ts"],
|
|
environment: "node",
|
|
isolate: true,
|
|
maxWorkers: 1,
|
|
reporters: ["default", join(webRoot, "scripts/ui-tests/reporter.ts")],
|
|
sequence: { sequencer: join(webRoot, "scripts/ui-tests/sequencer.ts") },
|
|
coverage: {
|
|
provider: "v8",
|
|
include: ["src/value.ts"],
|
|
reporter: [],
|
|
reportsDirectory: join(root, "coverage"),
|
|
},
|
|
},
|
|
};
|
|
const renderConfig = (value = config) =>
|
|
`import Sequencer from ${JSON.stringify(join(webRoot, "scripts/ui-tests/sequencer.ts"))}; export default ${JSON.stringify(value).replace(JSON.stringify(join(webRoot, "scripts/ui-tests/sequencer.ts")), "Sequencer")}`;
|
|
writeFileSync(join(root, "vitest.config.ts"), renderConfig());
|
|
const mergeConfig = structuredClone(config);
|
|
mergeConfig.test.reporters = ["default"];
|
|
mergeConfig.test.coverage.reporter = ["json-summary"];
|
|
mergeConfig.test.coverage.thresholds = {
|
|
lines: 100,
|
|
branches: 100,
|
|
statements: 100,
|
|
functions: 100,
|
|
};
|
|
writeFileSync(join(root, "vitest.ci.config.ts"), renderConfig(mergeConfig));
|
|
const run = (args) =>
|
|
execFileSync(
|
|
process.execPath,
|
|
[join(webRoot, "node_modules/vitest/vitest.mjs"), "run", ...args],
|
|
{ cwd: root, env, stdio: "pipe", timeout: 12000, encoding: "utf8" },
|
|
);
|
|
for (let index = 1; index <= 4; index++) run(["--coverage", "--shard", `${index}/4`]);
|
|
const directory = join(root, ".vitest-reports");
|
|
const reports = readdirSync(directory).map((name) => ({
|
|
metadata: JSON.parse(readFileSync(join(directory, name, "metadata.json"), "utf8")),
|
|
blob: readFileSync(join(directory, name, "blob.json")),
|
|
}));
|
|
const expected = {
|
|
configDigest: configurationDigest(root),
|
|
environmentDigest: environmentDigest(env),
|
|
node: process.version,
|
|
vitest: JSON.parse(readFileSync(join(root, "node_modules/vitest/package.json"), "utf8"))
|
|
.version,
|
|
sha: env.GITHUB_SHA,
|
|
runId: env.GITHUB_RUN_ID,
|
|
attempt: 2,
|
|
count: 4,
|
|
timings: timings(root),
|
|
files: ["a", "b", "c", "d"].map((name) => ({
|
|
file: `src/${name}.spec.ts`,
|
|
project: "",
|
|
pool: "forks",
|
|
})),
|
|
};
|
|
assert.deepEqual(validateReports(reports, expected), {
|
|
files: 4,
|
|
tests: 10,
|
|
dispositions: { passed: 2, skipped: 4, todo: 4 },
|
|
});
|
|
const tests = reports.flatMap(({ metadata }) => metadata.files.flatMap((file) => file.tests));
|
|
for (const mode of ["skip", "todo"]) {
|
|
const inherited = tests.find((entry) => entry.name.endsWith(`inherits ${mode}`));
|
|
assert.equal(inherited.mode, mode);
|
|
assert.equal(inherited.state, "skipped");
|
|
}
|
|
for (const [name, mode] of [
|
|
["ordinary todo > ordinary todo case", "todo"],
|
|
["mixed nonrunning > mixed skipped case", "skip"],
|
|
["mixed nonrunning > mixed todo case", "todo"],
|
|
]) {
|
|
const entry = tests.find((entry) => entry.name === name);
|
|
assert.equal(entry.mode, mode);
|
|
assert.equal(entry.state, "skipped");
|
|
}
|
|
const unfinished = reports.map(({ metadata, blob }) => ({
|
|
metadata: structuredClone(metadata),
|
|
blob,
|
|
}));
|
|
const required = unfinished
|
|
.flatMap(({ metadata }) => metadata.files.flatMap((file) => file.tests))
|
|
.find((entry) => entry.state === "passed");
|
|
required.state = "pending";
|
|
assert.throws(() => validateReports(unfinished, expected), /Incomplete test/);
|
|
mkdirSync(join(root, "blobs"));
|
|
for (const report of reports)
|
|
writeFileSync(join(root, "blobs", `${report.metadata.index}.json`), report.blob);
|
|
run(["--config", "vitest.ci.config.ts", "--coverage", "--merge-reports", "blobs"]);
|
|
const summary = JSON.parse(
|
|
readFileSync(join(root, "coverage/coverage-summary.json"), "utf8"),
|
|
);
|
|
assert.ok(summary.total.lines.total > 0);
|
|
assert.equal(summary.total.lines.pct, 100);
|
|
const merge = () =>
|
|
execFileSync(process.execPath, [join(root, "scripts/ui-tests/run.mjs"), "merge"], {
|
|
cwd: root,
|
|
env,
|
|
stdio: "pipe",
|
|
timeout: 12000,
|
|
encoding: "utf8",
|
|
});
|
|
merge();
|
|
assert.deepEqual(
|
|
JSON.parse(readFileSync(join(root, "coverage/coverage-summary.json"), "utf8")),
|
|
summary,
|
|
);
|
|
assert.deepEqual(
|
|
JSON.parse(readFileSync(join(directory, "completeness.json"), "utf8")),
|
|
validateReports(reports, expected),
|
|
);
|
|
const artifact = join(directory, "ui-unit-shard-1-4");
|
|
assert.equal(
|
|
statSync(join(artifact, "blob.json")).ino,
|
|
statSync(join(directory, "merged/blob-1.json")).ino,
|
|
);
|
|
merge();
|
|
renameSync(artifact, join(root, "missing-report"));
|
|
assert.throws(merge, /Missing shards/);
|
|
renameSync(join(root, "missing-report"), artifact);
|
|
const original = readFileSync(join(artifact, "metadata.json"), "utf8");
|
|
writeFileSync(
|
|
join(artifact, "metadata.json"),
|
|
JSON.stringify({ ...JSON.parse(original), attempt: 1 }),
|
|
);
|
|
merge();
|
|
for (const reason of ["failed", "interrupted"]) {
|
|
writeFileSync(
|
|
join(artifact, "metadata.json"),
|
|
JSON.stringify({ ...JSON.parse(original), reason }),
|
|
);
|
|
assert.throws(merge, /did not complete with coverage/);
|
|
}
|
|
writeFileSync(join(artifact, "metadata.json"), original);
|
|
const blob = readFileSync(join(artifact, "blob.json"));
|
|
writeFileSync(join(artifact, "blob.json"), "corrupt");
|
|
assert.throws(merge, /Corrupt blob/);
|
|
writeFileSync(join(artifact, "blob.json"), blob);
|
|
merge();
|
|
execFileSync("bash", [join(root, "coverage.sh"), "merge"], {
|
|
cwd: root,
|
|
env,
|
|
stdio: "pipe",
|
|
timeout: 12000,
|
|
});
|
|
assert.throws(
|
|
() =>
|
|
execFileSync("bash", [join(root, "coverage.sh"), "merge"], {
|
|
cwd: root,
|
|
env: { ...env, FRONTEND_COVERAGE_LINES: "101" },
|
|
stdio: "pipe",
|
|
timeout: 12000,
|
|
encoding: "utf8",
|
|
}),
|
|
/lines.*below.*threshold/i,
|
|
);
|
|
mergeConfig.test.coverage.thresholds.lines = 101;
|
|
writeFileSync(join(root, "vitest.ci.config.ts"), renderConfig(mergeConfig));
|
|
for (const report of reports) {
|
|
writeFileSync(
|
|
join(directory, `ui-unit-shard-${report.metadata.index}-4/metadata.json`),
|
|
JSON.stringify({ ...report.metadata, configDigest: configurationDigest(root) }),
|
|
);
|
|
}
|
|
assert.throws(merge, /Coverage for lines.*does not meet global.*threshold/);
|
|
assert.throws(() => validateReports(reports.slice(1), expected), /Missing shards/);
|
|
reports[0].blob = Buffer.from("invalid");
|
|
assert.throws(() => validateReports(reports, expected), /Corrupt blob/);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
},
|
|
);
|