Skip to content

Commit 2cd479d

Browse files
Copilotkobenguyent
andcommitted
Changes before error encountered
Co-authored-by: kobenguyent <[email protected]>
1 parent 348f3a8 commit 2cd479d

File tree

2 files changed

+173
-8
lines changed

2 files changed

+173
-8
lines changed

lib/command/workers/runTests.js

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const stderr = ''
2020
// Requiring of Codecept need to be after tty.getWindowSize is available.
2121
const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept')
2222

23-
const { options, tests, testRoot, workerIndex } = workerData
23+
const { options, tests, testRoot, workerIndex, poolMode } = workerData
2424

2525
// hide worker output
2626
if (!options.debug && !options.verbose)
@@ -39,11 +39,23 @@ const codecept = new Codecept(config, options)
3939
codecept.init(testRoot)
4040
codecept.loadTests()
4141
const mocha = container.mocha()
42-
filterTests()
42+
43+
if (poolMode) {
44+
// In pool mode, don't filter tests upfront - wait for assignments
45+
// Set up the mocha files but don't filter yet
46+
const files = codecept.testFiles
47+
mocha.files = files
48+
mocha.loadFiles()
49+
} else {
50+
// Legacy mode - filter tests upfront
51+
filterTests()
52+
}
4353

4454
// run tests
4555
;(async function () {
46-
if (mocha.suite.total()) {
56+
if (poolMode) {
57+
await runPoolTests()
58+
} else if (mocha.suite.total()) {
4759
await runTests()
4860
}
4961
})()
@@ -64,7 +76,93 @@ async function runTests() {
6476
}
6577
}
6678

67-
function filterTests() {
79+
async function runPoolTests() {
80+
try {
81+
await codecept.bootstrap()
82+
} catch (err) {
83+
throw new Error(`Error while running bootstrap file :${err}`)
84+
}
85+
86+
initializeListeners()
87+
listenToParentThread()
88+
disablePause()
89+
90+
// Request first test
91+
sendToParentThread({ type: 'REQUEST_TEST', workerIndex })
92+
93+
// Wait for tests to be completed
94+
await new Promise((resolve, reject) => {
95+
let completedTests = 0
96+
let hasError = false
97+
98+
const originalListen = listenToParentThread
99+
100+
// Enhanced listener for pool mode
101+
const poolListener = () => {
102+
parentPort?.on('message', async eventData => {
103+
if (eventData.type === 'TEST_ASSIGNED') {
104+
const testUid = eventData.test
105+
106+
try {
107+
// Filter mocha suite to include only the assigned test
108+
filterTestById(testUid)
109+
110+
if (mocha.suite.total() > 0) {
111+
// Run the single test
112+
await codecept.run()
113+
completedTests++
114+
}
115+
116+
// Notify completion and request next test
117+
sendToParentThread({ type: 'TEST_COMPLETED', workerIndex })
118+
sendToParentThread({ type: 'REQUEST_TEST', workerIndex })
119+
120+
} catch (err) {
121+
hasError = true
122+
reject(err)
123+
}
124+
} else if (eventData.type === 'NO_MORE_TESTS') {
125+
// All tests completed
126+
resolve()
127+
} else {
128+
// Handle other message types (support messages, etc.)
129+
container.append({ support: eventData.data })
130+
}
131+
})
132+
}
133+
134+
poolListener()
135+
})
136+
137+
try {
138+
// Final cleanup
139+
await codecept.teardown()
140+
} catch (err) {
141+
// Log teardown errors but don't throw
142+
console.error('Teardown error:', err)
143+
}
144+
}
145+
146+
function filterTestById(testUid) {
147+
// Clear previous tests
148+
for (const suite of mocha.suite.suites) {
149+
suite.tests = []
150+
}
151+
152+
// Add only the specific test
153+
for (const suite of mocha.suite.suites) {
154+
const originalTests = suite.tests
155+
suite.tests = suite.tests.filter(test => test.uid === testUid)
156+
157+
// If we found the test, break out
158+
if (suite.tests.length > 0) {
159+
break
160+
}
161+
162+
// Restore original tests for next iteration
163+
suite.tests = originalTests
164+
}
165+
}
68166
const files = codecept.testFiles
69167
mocha.files = files
70168
mocha.loadFiles()

lib/workers.js

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,14 @@ const populateGroups = numberOfWorkers => {
4949
return groups
5050
}
5151

52-
const createWorker = workerObject => {
52+
const createWorker = (workerObject, isPoolMode = false) => {
5353
const worker = new Worker(pathToWorker, {
5454
workerData: {
5555
options: simplifyObject(workerObject.options),
5656
tests: workerObject.tests,
5757
testRoot: workerObject.testRoot,
5858
workerIndex: workerObject.workerIndex + 1,
59+
poolMode: isPoolMode,
5960
},
6061
})
6162
worker.on('error', err => output.error(`Worker Error: ${err.stack}`))
@@ -236,6 +237,9 @@ class Workers extends EventEmitter {
236237
this.closedWorkers = 0
237238
this.workers = []
238239
this.testGroups = []
240+
this.testPool = []
241+
this.isPoolMode = config.by === 'pool'
242+
this.activeWorkers = new Map()
239243

240244
createOutputDir(config.testConfig)
241245
if (numberOfWorkers) this._initWorkers(numberOfWorkers, config)
@@ -255,6 +259,7 @@ class Workers extends EventEmitter {
255259
*
256260
* - `suite`
257261
* - `test`
262+
* - `pool`
258263
* - function(numberOfWorkers)
259264
*
260265
* This method can be overridden for a better split.
@@ -270,7 +275,11 @@ class Workers extends EventEmitter {
270275
this.testGroups.push(convertToMochaTests(testGroup))
271276
}
272277
} else if (typeof numberOfWorkers === 'number' && numberOfWorkers > 0) {
273-
this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
278+
if (config.by === 'pool') {
279+
this.createTestPool(numberOfWorkers)
280+
} else {
281+
this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
282+
}
274283
}
275284
}
276285

@@ -311,7 +320,29 @@ class Workers extends EventEmitter {
311320
/**
312321
* @param {Number} numberOfWorkers
313322
*/
314-
createGroupsOfSuites(numberOfWorkers) {
323+
createTestPool(numberOfWorkers) {
324+
const files = this.codecept.testFiles
325+
const mocha = Container.mocha()
326+
mocha.files = files
327+
mocha.loadFiles()
328+
329+
mocha.suite.eachTest(test => {
330+
if (test) {
331+
this.testPool.push(test.uid)
332+
}
333+
})
334+
335+
// For pool mode, create empty groups for each worker
336+
this.testGroups = populateGroups(numberOfWorkers)
337+
}
338+
339+
/**
340+
* Gets the next test from the pool
341+
* @returns {String|null} test uid or null if no tests available
342+
*/
343+
getNextTest() {
344+
return this.testPool.shift() || null
345+
}
315346
const files = this.codecept.testFiles
316347
const groups = populateGroups(numberOfWorkers)
317348

@@ -352,7 +383,7 @@ class Workers extends EventEmitter {
352383
process.env.RUNS_WITH_WORKERS = 'true'
353384
recorder.add('starting workers', () => {
354385
for (const worker of this.workers) {
355-
const workerThread = createWorker(worker)
386+
const workerThread = createWorker(worker, this.isPoolMode)
356387
this._listenWorkerEvents(workerThread)
357388
}
358389
})
@@ -376,9 +407,45 @@ class Workers extends EventEmitter {
376407
}
377408

378409
_listenWorkerEvents(worker) {
410+
// Track worker thread for pool mode
411+
if (this.isPoolMode) {
412+
this.activeWorkers.set(worker, { available: true, workerIndex: null })
413+
}
414+
379415
worker.on('message', message => {
380416
output.process(message.workerIndex)
381417

418+
// Handle test requests for pool mode
419+
if (message.type === 'REQUEST_TEST') {
420+
if (this.isPoolMode) {
421+
const nextTest = this.getNextTest()
422+
if (nextTest) {
423+
worker.postMessage({ type: 'TEST_ASSIGNED', test: nextTest })
424+
const workerInfo = this.activeWorkers.get(worker)
425+
if (workerInfo) {
426+
workerInfo.available = false
427+
workerInfo.workerIndex = message.workerIndex
428+
}
429+
} else {
430+
worker.postMessage({ type: 'NO_MORE_TESTS' })
431+
const workerInfo = this.activeWorkers.get(worker)
432+
if (workerInfo) {
433+
workerInfo.available = true
434+
}
435+
}
436+
}
437+
return
438+
}
439+
440+
// Handle test completion for pool mode
441+
if (message.type === 'TEST_COMPLETED' && this.isPoolMode) {
442+
const workerInfo = this.activeWorkers.get(worker)
443+
if (workerInfo) {
444+
workerInfo.available = true
445+
}
446+
return
447+
}
448+
382449
// deal with events that are not test cycle related
383450
if (!message.event) {
384451
return this.emit('message', message)

0 commit comments

Comments
 (0)