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 ( / \. t s $ / , "" ) } ');`
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