Skip to content

Commit a2d1e2a

Browse files
committed
Core: Add QUnit.config.testFilter to help filter tests
1 parent a8e1436 commit a2d1e2a

File tree

6 files changed

+516
-7
lines changed

6 files changed

+516
-7
lines changed

demos/testFilter.js

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
/**
2+
* Demo: QUnit.config.testFilter
3+
*
4+
* The testFilter callback allows you to programmatically filter which tests run
5+
* at runtime. This is useful for CI workflows, flaky test quarantine, dynamic
6+
* test selection, and other advanced scenarios.
7+
*/
8+
9+
// ============================================================================
10+
// Example: Combined testFilter implementation
11+
// ============================================================================
12+
13+
const quarantineList = ['flaky network test', 'timing-dependent test'];
14+
15+
QUnit.config.testFilter = function (testInfo) {
16+
// testInfo contains: { testId, testName, module, skip }
17+
18+
// 1. Skip quarantined tests in CI
19+
if (process.env.CI === 'true') {
20+
const isQuarantined = quarantineList.some(function (pattern) {
21+
return testInfo.testName.indexOf(pattern) !== -1;
22+
});
23+
if (isQuarantined) {
24+
console.log('[QUARANTINE] Skipping: ' + testInfo.module + ' > ' + testInfo.testName);
25+
return false;
26+
}
27+
}
28+
29+
// 2. Skip slow tests in quick mode
30+
if (process.env.QUICK_RUN === 'true') {
31+
if (testInfo.testName.toLowerCase().indexOf('slow') !== -1) {
32+
console.log('[QUICK_RUN] Skipping slow test: ' + testInfo.testName);
33+
return false;
34+
}
35+
}
36+
37+
// 3. Filter by module name if specified
38+
if (process.env.TEST_MODULE) {
39+
if (testInfo.module.indexOf(process.env.TEST_MODULE) === -1) {
40+
return false;
41+
}
42+
}
43+
44+
// 4. Parallel sharding - distribute tests across workers
45+
if (process.env.WORKER_ID !== undefined) {
46+
const WORKER_ID = parseInt(process.env.WORKER_ID, 10);
47+
const TOTAL_WORKERS = parseInt(process.env.TOTAL_WORKERS || '1', 10);
48+
49+
let hash = 0;
50+
for (let i = 0; i < testInfo.testId.length; i++) {
51+
const char = testInfo.testId.charCodeAt(i);
52+
hash = ((hash << 5) - hash) + char;
53+
hash = hash & hash;
54+
}
55+
hash = Math.abs(hash);
56+
57+
const assignedWorker = hash % TOTAL_WORKERS;
58+
if (assignedWorker !== WORKER_ID) {
59+
return false;
60+
}
61+
console.log(' [Worker ' + WORKER_ID + '] Running: ' + testInfo.module + ' > ' + testInfo.testName);
62+
}
63+
64+
// 5. Feature detection - skip tests for unavailable features
65+
// Tests tagged with [feature] in name are checked against runtime capabilities
66+
const features = {
67+
webgl: typeof WebGLRenderingContext !== 'undefined',
68+
webrtc: typeof RTCPeerConnection !== 'undefined',
69+
serviceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
70+
indexedDB: typeof indexedDB !== 'undefined'
71+
};
72+
73+
for (const feature in features) {
74+
const tag = '[' + feature + ']';
75+
if (testInfo.testName.indexOf(tag) !== -1 && !features[feature]) {
76+
console.log('[FEATURE] Skipping ' + feature + ' test: ' + testInfo.testName);
77+
return false;
78+
}
79+
}
80+
81+
return true;
82+
};
83+
84+
// ============================================================================
85+
// Example 2: Quarantine flaky tests in CI
86+
// ============================================================================
87+
88+
if (process.env.CI === 'true') {
89+
const quarantineList = [
90+
'flaky network test',
91+
'timing-dependent test',
92+
'unreliable integration test'
93+
];
94+
95+
QUnit.config.testFilter = function (testInfo) {
96+
const isQuarantined = quarantineList.some(pattern =>
97+
testInfo.testName.includes(pattern)
98+
);
99+
100+
if (isQuarantined) {
101+
console.log(`[QUARANTINE] Skipping: ${testInfo.module} > ${testInfo.testName}`);
102+
return false;
103+
}
104+
105+
return true;
106+
};
107+
}
108+
109+
// ============================================================================
110+
// Example 3: Filter by module name
111+
// ============================================================================
112+
113+
if (process.env.TEST_MODULE) {
114+
const targetModule = process.env.TEST_MODULE;
115+
116+
QUnit.config.testFilter = function (testInfo) {
117+
const moduleMatches = testInfo.module.includes(targetModule);
118+
119+
if (!moduleMatches) {
120+
console.log(`[MODULE_FILTER] Skipping: ${testInfo.module}`);
121+
}
122+
123+
return moduleMatches;
124+
};
125+
}
126+
127+
// ============================================================================
128+
// Example 4: Feature flags - Run tests based on runtime capabilities
129+
// ============================================================================
130+
131+
const features = {
132+
webgl: typeof WebGLRenderingContext !== 'undefined',
133+
webrtc: typeof RTCPeerConnection !== 'undefined',
134+
serviceWorker: typeof navigator !== 'undefined' && 'serviceWorker' in navigator,
135+
indexedDB: typeof indexedDB !== 'undefined'
136+
};
137+
138+
QUnit.config.testFilter = function (testInfo) {
139+
for (const [feature, available] of Object.entries(features)) {
140+
if (testInfo.testName.includes(`[${feature}]`) && !available) {
141+
console.log(`[FEATURE_FLAG] Skipping ${feature} test: ${testInfo.testName}`);
142+
return false;
143+
}
144+
}
145+
146+
return true;
147+
};
148+
149+
// ============================================================================
150+
// Example 5: Parallel test sharding across workers
151+
// ============================================================================
152+
153+
if (process.env.WORKER_ID !== undefined) {
154+
const WORKER_ID = parseInt(process.env.WORKER_ID, 10);
155+
const TOTAL_WORKERS = parseInt(process.env.TOTAL_WORKERS || '1', 10);
156+
157+
console.log(`Worker ${WORKER_ID + 1} of ${TOTAL_WORKERS}`);
158+
159+
function hashTestId (testId) {
160+
let hash = 0;
161+
for (let i = 0; i < testId.length; i++) {
162+
const char = testId.charCodeAt(i);
163+
hash = ((hash << 5) - hash) + char;
164+
hash = hash & hash;
165+
}
166+
return Math.abs(hash);
167+
}
168+
169+
QUnit.config.testFilter = function (testInfo) {
170+
const hash = hashTestId(testInfo.testId);
171+
const assignedWorker = hash % TOTAL_WORKERS;
172+
const shouldRun = assignedWorker === WORKER_ID;
173+
174+
if (shouldRun) {
175+
console.log(` [Worker ${WORKER_ID}] Running: ${testInfo.module} > ${testInfo.testName}`);
176+
}
177+
178+
return shouldRun;
179+
};
180+
}
181+
182+
// ============================================================================
183+
// Example 6: Combine multiple filter conditions
184+
// ============================================================================
185+
186+
QUnit.config.testFilter = function (testInfo) {
187+
const quarantined = ['flaky test'];
188+
if (quarantined.some(pattern => testInfo.testName.includes(pattern))) {
189+
return false;
190+
}
191+
192+
if (process.env.QUICK_RUN && testInfo.testName.includes('slow')) {
193+
return false;
194+
}
195+
196+
if (process.env.TEST_MODULE && !testInfo.module.includes(process.env.TEST_MODULE)) {
197+
return false;
198+
}
199+
200+
return true;
201+
};
202+
203+
// ============================================================================
204+
// Example tests
205+
// ============================================================================
206+
207+
QUnit.module('Fast tests', function () {
208+
QUnit.test('quick calculation', function (assert) {
209+
assert.equal(2 + 2, 4);
210+
});
211+
212+
QUnit.test('string operation', function (assert) {
213+
assert.equal('test'.toUpperCase(), 'TEST');
214+
});
215+
});
216+
217+
QUnit.module('Slow tests', function () {
218+
QUnit.test('slow database query', function (assert) {
219+
assert.ok(true);
220+
});
221+
222+
QUnit.test('slow network request', function (assert) {
223+
assert.ok(true);
224+
});
225+
});
226+
227+
QUnit.module('Integration tests', function () {
228+
QUnit.test('API integration', function (assert) {
229+
assert.ok(true);
230+
});
231+
232+
QUnit.test('flaky network test', function (assert) {
233+
assert.ok(true);
234+
});
235+
236+
QUnit.test('timing-dependent test', function (assert) {
237+
assert.ok(true);
238+
});
239+
});
240+
241+
QUnit.module('Feature-specific tests', function () {
242+
QUnit.test('[webgl] 3D rendering', function (assert) {
243+
assert.ok(true, 'WebGL test');
244+
});
245+
246+
QUnit.test('[webrtc] peer connection', function (assert) {
247+
assert.ok(true, 'WebRTC test');
248+
});
249+
250+
QUnit.test('[serviceWorker] caching', function (assert) {
251+
assert.ok(true, 'Service Worker test');
252+
});
253+
254+
QUnit.test('[indexedDB] storage', function (assert) {
255+
assert.ok(true, 'IndexedDB test');
256+
});
257+
});
258+
259+
// ============================================================================
260+
// Usage Examples:
261+
// ============================================================================
262+
//
263+
// Skip slow tests:
264+
// QUICK_RUN=true node bin/qunit.js demos/testFilter.js
265+
//
266+
// Run only "Fast tests" module:
267+
// TEST_MODULE="Fast tests" node bin/qunit.js demos/testFilter.js
268+
//
269+
// Enable quarantine in CI:
270+
// CI=true node bin/qunit.js demos/testFilter.js
271+
//
272+
// Parallel execution with 3 workers:
273+
// WORKER_ID=0 TOTAL_WORKERS=3 node bin/qunit.js demos/testFilter.js
274+
// WORKER_ID=1 TOTAL_WORKERS=3 node bin/qunit.js demos/testFilter.js
275+
// WORKER_ID=2 TOTAL_WORKERS=3 node bin/qunit.js demos/testFilter.js
276+
//
277+
// ============================================================================
278+
// Notes:
279+
// ============================================================================
280+
//
281+
// - testFilter runs AFTER test.only/test.skip/test.if checks
282+
// - testFilter runs BEFORE CLI --filter and --module parameters
283+
// - Return true to run the test, false to skip it
284+
// - Thrown errors are logged but don't stop the test run
285+
// - testInfo.skip is true if test was already marked to skip
286+
//

0 commit comments

Comments
 (0)