Skip to content

Commit f3705f8

Browse files
committed
Better updateFailing checker
1 parent cac2644 commit f3705f8

File tree

1 file changed

+115
-15
lines changed

1 file changed

+115
-15
lines changed
Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,136 @@
11
import * as cp from "child_process";
22
import * as fs from "fs";
33
import path from "path";
4+
import * as readline from "readline";
45
import which from "which";
56

67
const failingTestsPath = path.join(import.meta.dirname, "failingTests.txt");
78

8-
function main() {
9+
interface TestEvent {
10+
Time?: string;
11+
Action: string;
12+
Package?: string;
13+
Test?: string;
14+
Output?: string;
15+
Elapsed?: number;
16+
}
17+
18+
async function main() {
19+
const oldFailingTests = fs.readFileSync(failingTestsPath, "utf-8");
920
const go = which.sync("go");
10-
let testOutput: string;
21+
22+
let testProcess: cp.ChildProcess;
1123
try {
1224
// Run tests with TSGO_FOURSLASH_IGNORE_FAILING=1 to run all tests including those in failingTests.txt
13-
testOutput = cp.execFileSync(go, ["test", "-v", "./internal/fourslash/tests/gen"], {
14-
encoding: "utf-8",
25+
testProcess = cp.spawn(go, ["test", "-json", "./internal/fourslash/tests/gen"], {
26+
stdio: ["ignore", "pipe", "pipe"],
1527
env: { ...process.env, TSGO_FOURSLASH_IGNORE_FAILING: "1" },
1628
});
1729
}
1830
catch (error) {
19-
testOutput = (error as { stdout: string; }).stdout as string;
31+
fs.writeFileSync(failingTestsPath, oldFailingTests, "utf-8");
32+
throw new Error("Failed to spawn test process: " + error);
2033
}
21-
const panicRegex = /^panic/m;
22-
if (panicRegex.test(testOutput)) {
23-
throw new Error("Unrecovered panic detected in tests\n" + testOutput);
34+
35+
if (!testProcess.stdout || !testProcess.stderr) {
36+
throw new Error("Test process stdout or stderr is null");
2437
}
25-
const failRegex = /--- FAIL: ([\S]+)/gm;
38+
2639
const failingTests: string[] = [];
27-
let match;
40+
const testOutputs = new Map<string, string[]>();
41+
const allOutputs: string[] = [];
42+
let hadPanic = false;
2843

29-
while ((match = failRegex.exec(testOutput)) !== null) {
30-
failingTests.push(match[1]);
31-
}
44+
const rl = readline.createInterface({
45+
input: testProcess.stdout,
46+
crlfDelay: Infinity,
47+
});
48+
49+
rl.on("line", line => {
50+
try {
51+
const event: TestEvent = JSON.parse(line);
52+
53+
// Collect output for each test
54+
if (event.Action === "output" && event.Output) {
55+
allOutputs.push(event.Output);
56+
if (event.Test) {
57+
if (!testOutputs.has(event.Test)) {
58+
testOutputs.set(event.Test, []);
59+
}
60+
testOutputs.get(event.Test)!.push(event.Output);
61+
}
62+
63+
// Check for panics
64+
if (/^panic/m.test(event.Output)) {
65+
hadPanic = true;
66+
}
67+
}
68+
69+
// Process failed tests
70+
if (event.Action === "fail" && event.Test) {
71+
const outputs = testOutputs.get(event.Test) || [];
72+
const fullOutput = outputs.join("");
3273

33-
fs.writeFileSync(failingTestsPath, failingTests.sort((a, b) => a.localeCompare(b, "en-US")).join("\n") + "\n", "utf-8");
74+
// Check if this is a baseline-only failure
75+
// Baseline failures contain specific messages from baseline.go
76+
const hasBaselineMessage = /new baseline created at/.test(fullOutput) ||
77+
/the baseline file .* has changed/.test(fullOutput);
78+
79+
// Check for non-baseline errors
80+
// Look for patterns that indicate real test failures
81+
// We need to filter out baseline messages when checking for errors
82+
const outputWithoutBaseline = fullOutput
83+
.replace(/the baseline file .* has changed\. \(Run `hereby baseline-accept` if the new baseline is correct\.\)/g, "")
84+
.replace(/new baseline created at .*\./g, "")
85+
.replace(/the baseline file .* does not exist in the TypeScript submodule/g, "")
86+
.replace(/the baseline file .* does not match the reference in the TypeScript submodule/g, "");
87+
88+
const hasNonBaselineError = /^panic/m.test(outputWithoutBaseline) ||
89+
/Error|error/i.test(outputWithoutBaseline) ||
90+
/fatal|Fatal/.test(outputWithoutBaseline) ||
91+
/Unexpected/.test(outputWithoutBaseline);
92+
93+
// Only mark as failing if it's not a baseline-only failure
94+
// (i.e., if there's no baseline message, or if there are other errors besides baseline)
95+
if (!hasBaselineMessage || hasNonBaselineError) {
96+
failingTests.push(event.Test);
97+
}
98+
}
99+
}
100+
catch (e) {
101+
// Not JSON, possibly stderr or other output - ignore
102+
}
103+
});
104+
105+
testProcess.stderr.on("data", data => {
106+
// Check stderr for panics too
107+
const output = data.toString();
108+
allOutputs.push(output);
109+
if (/^panic/m.test(output)) {
110+
hadPanic = true;
111+
}
112+
});
113+
114+
await new Promise<void>((resolve, reject) => {
115+
testProcess.on("close", code => {
116+
if (hadPanic) {
117+
fs.writeFileSync(failingTestsPath, oldFailingTests, "utf-8");
118+
reject(new Error("Unrecovered panic detected in tests\n" + allOutputs.join("")));
119+
return;
120+
}
121+
122+
fs.writeFileSync(failingTestsPath, failingTests.sort((a, b) => a.localeCompare(b, "en-US")).join("\n") + "\n", "utf-8");
123+
resolve();
124+
});
125+
126+
testProcess.on("error", error => {
127+
fs.writeFileSync(failingTestsPath, oldFailingTests, "utf-8");
128+
reject(error);
129+
});
130+
});
34131
}
35132

36-
main();
133+
main().catch(error => {
134+
console.error("Error:", error);
135+
process.exit(1);
136+
});

0 commit comments

Comments
 (0)