Skip to content

Commit 54836c7

Browse files
committed
feat(polluter): add polluter bisect script
1 parent 8b3e4ec commit 54836c7

File tree

3 files changed

+143
-1
lines changed

3 files changed

+143
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ yarn-error.log
3939
testem.log
4040
/typings
4141
TESTS-**.xml
42+
polluter-runner.spec.ts
4243

4344
# System Files
4445
.DS_Store

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"postinstall": "gulp copyGitHooks",
5656
"cypress:open": "cypress open --config-file=cypress.config.ts",
5757
"cypress:run": "cypress run --config-file=cypress.config.ts",
58-
"serve:ssr:ssr-test": "node dist/ssr-test/server/server.mjs"
58+
"serve:ssr:ssr-test": "node dist/ssr-test/server/server.mjs",
59+
"polluter:bisect": "node scripts/test-polluter-bisect.js"
5960
},
6061
"private": true,
6162
"dependencies": {

scripts/test-polluter-bisect.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { spawn } from 'child_process';
2+
import fs from "fs";
3+
import path from "path";
4+
5+
main().catch((err) => {
6+
console.error(err);
7+
process.exit(1);
8+
})
9+
10+
async function main() {
11+
const allFiles = getAllSpecFiles("projects/igniteui-angular/src/lib");
12+
13+
const sentinelArg = process.argv[2];
14+
const mode = process.argv[3] || "before";
15+
const skipInitial = process.argv.includes("--skip-initial");
16+
17+
if (!sentinelArg) {
18+
console.error("Usage: node test-polluter-bisect.js <sentinel-file.spec.ts> [before|all]");
19+
process.exit(1);
20+
}
21+
22+
const sentinelFile = allFiles.find(f => f.includes(sentinelArg));
23+
24+
if (!sentinelFile) {
25+
console.error(`Sentinel file '${sentinelArg}' not found in the test set.`);
26+
process.exit(1);
27+
}
28+
29+
console.log(`Running polluter search with sentinel: ${sentinelArg}, mode: ${mode}`);
30+
const culprit = await findPolluter(allFiles, sentinelFile, mode, !skipInitial);
31+
32+
if (culprit) {
33+
console.log(`Polluter file is: ${culprit}`);
34+
} else {
35+
console.log("No polluter found in the set.");
36+
}
37+
}
38+
39+
async function findPolluter(allFiles, sentinelFile, mode = "before", doInitialScan = true) {
40+
let suspects;
41+
42+
if (mode === "before") {
43+
suspects = allFiles.slice(0, allFiles.indexOf(sentinelFile));
44+
} else if (mode === "all") {
45+
suspects = allFiles.filter(f => f !== sentinelFile);
46+
} else {
47+
throw new Error(`Unknown mode: ${mode}`);
48+
}
49+
50+
if (doInitialScan) {
51+
console.log("Initial run with full set...");
52+
const initialPass = await runTests([...suspects, sentinelFile], sentinelFile);
53+
54+
if (initialPass) {
55+
console.log("Sentinel passed even after full set — no polluter detected.");
56+
return null;
57+
}
58+
} else {
59+
console.log("Skipping initial full-set scan.");
60+
}
61+
62+
while (suspects.length > 1) {
63+
const mid = Math.floor(suspects.length / 2);
64+
const left = suspects.slice(0, mid);
65+
const right = suspects.slice(mid);
66+
67+
if (await runTests([...left, sentinelFile], sentinelFile)) {
68+
suspects = right;
69+
} else {
70+
suspects = left;
71+
}
72+
}
73+
return suspects[0];
74+
}
75+
76+
function runTests(files, sentinelFile) {
77+
return new Promise((resolve) => {
78+
const sentinelNorm = normalizeForNg(sentinelFile);
79+
const runnerFile = createPolluterRunner(files);
80+
81+
const args = [
82+
"test",
83+
"igniteui-angular",
84+
"--watch=false",
85+
"--browsers=ChromeHeadlessNoSandbox",
86+
"--include",
87+
runnerFile
88+
];
89+
90+
let output = "";
91+
const proc = spawn("npx", ["ng", ...args], { shell: true });
92+
93+
proc.stdout.on("data", (data) => {
94+
const text = data.toString();
95+
output += text;
96+
process.stdout.write(text);
97+
});
98+
proc.stderr.on("data", (data) => {
99+
const text = data.toString();
100+
output += text;
101+
process.stdout.write(text);
102+
});
103+
104+
proc.on("exit", () => {
105+
const sentinelFailed = path.basename(sentinelNorm);
106+
const failed = output.includes("FAILED") && output.includes(sentinelFailed);
107+
console.log(`Sentinel ${sentinelFailed} ${failed ? "FAILED" : "PASSED"}`);
108+
resolve(!failed);
109+
});
110+
})
111+
}
112+
113+
function getAllSpecFiles(dir) {
114+
let files = [];
115+
fs.readdirSync(dir).forEach((file) => {
116+
const full = path.join(dir, file);
117+
if (fs.statSync(full).isDirectory()) {
118+
files = files.concat(getAllSpecFiles(full));
119+
} else if (file.endsWith(".spec.ts")) {
120+
files.push(full);
121+
}
122+
});
123+
return files.sort();
124+
}
125+
126+
function normalizeForNg(file) {
127+
const rel = path.relative(process.cwd(), file);
128+
return rel.split(path.sep).join("/");
129+
}
130+
131+
function createPolluterRunner(files) {
132+
const imports = files.map(f =>
133+
`require('${normalizeForNg(f).replace(/\.ts$/, "")}');`
134+
).join("\n");
135+
136+
const runnerPath = path.join(process.cwd(), "projects/igniteui-angular/src/polluter-runner.spec.ts");
137+
fs.mkdirSync(path.dirname(runnerPath), { recursive: true});
138+
fs.writeFileSync(runnerPath, imports, "utf8");
139+
return runnerPath;
140+
}

0 commit comments

Comments
 (0)