Skip to content

Commit 5a075f4

Browse files
committed
fix tests
1 parent 5b2c5f9 commit 5a075f4

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

lib/command/workers/runTests.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,15 @@ const config = deepMerge(getConfig(options.config || testRoot), overrideConfigs)
6969

7070
filterTests()
7171

72-
// run tests
72+
// run tests, or gracefully finish if none left after filtering
7373
if (mocha.suite.total()) {
7474
await runTests()
75+
} else {
76+
// still need to notify parent about result and close port
77+
initializeListeners()
78+
disablePause()
79+
event.dispatcher.emit(event.all.result, container.result())
80+
parentPort?.close()
7581
}
7682
})()
7783

lib/mocha/asyncWrapper.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,24 +61,26 @@ function test(test) {
6161
recorder.startUnlessRunning()
6262
// Fire started event immediately so listeners are notified regardless of recorder queue
6363
event.emit(event.test.started, test)
64+
let failed = false
6465
recorder.errHandler(err => {
66+
failed = true
6567
recorder.session.start('teardown')
6668
recorder.cleanAsyncErr()
6769
if (test.throws) {
6870
// check that test should actually fail
6971
try {
7072
assertThrown(err, test.throws)
7173
event.emit(event.test.passed, test)
72-
event.emit(event.test.finished, test)
74+
// finished is emitted in teardown after hooks complete
7375
recorder.add(doneFn)
7476
return
7577
} catch (newErr) {
7678
err = newErr
7779
}
7880
}
81+
// mark error on test, emit failed now; finished will be emitted later in teardown
7982
test.err = err
8083
event.emit(event.test.failed, test, err)
81-
event.emit(event.test.finished, test)
8284
recorder.add(() => doneFn(err))
8385
})
8486

@@ -90,9 +92,11 @@ function test(test) {
9092
if (isAsyncFunction(testFn)) await res
9193
})
9294
recorder.add('restore test session', () => recorder.session.restore('test'))
95+
// Only after session is restored (i.e., all inner tasks finished) and no failure happened, mark test as passed
9396
recorder.add('fire test.passed', () => {
97+
if (failed) return
9498
event.emit(event.test.passed, test)
95-
event.emit(event.test.finished, test)
99+
// finished is emitted in teardown after hooks complete
96100
})
97101
recorder.add('finish test', doneFn)
98102
recorder.catch()
@@ -191,7 +195,10 @@ function teardown(suite) {
191195
recorder.startUnlessRunning()
192196
const testModule = await import('./test.js')
193197
const { enhanceMochaTest } = testModule.default || testModule
194-
event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
198+
const currentTest = enhanceMochaTest(suite?.ctx?.currentTest)
199+
event.emit(event.test.after, currentTest)
200+
// emit finished after after-hook so parent can count pass/fail reliably
201+
event.emit(event.test.finished, currentTest, currentTest.err)
195202
}, suite)
196203
}
197204

lib/workers.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ class Workers extends EventEmitter {
244244
this.testGroups = []
245245
this.config = config
246246
this.numberOfWorkersRequested = numberOfWorkers
247+
// Track emitted pass events to avoid double-counting duplicates from retries/race conditions
248+
this._passedUids = new Set()
249+
this._pendingPass = new Set()
247250

248251
createOutputDir(config.testConfig)
249252
// Defer worker initialization until codecept is ready
@@ -252,7 +255,7 @@ class Workers extends EventEmitter {
252255
async _ensureInitialized() {
253256
if (!this.codecept) {
254257
this.codecept = await this.codeceptPromise
255-
if (this.numberOfWorkersRequested) {
258+
if (typeof this.numberOfWorkersRequested === 'number' && this.numberOfWorkersRequested > 0) {
256259
this._initWorkers(this.numberOfWorkersRequested, this.config)
257260
}
258261
}
@@ -298,6 +301,10 @@ class Workers extends EventEmitter {
298301
*/
299302
spawn() {
300303
const worker = new WorkerObject(this.numberOfWorkers)
304+
// Default testRoot to the configured testConfig location for manual spawns
305+
if (this.config?.testConfig) {
306+
worker.setTestRoot(this.config.testConfig)
307+
}
301308
this.workers.push(worker)
302309
this.numberOfWorkers += 1
303310
return worker
@@ -307,7 +314,9 @@ class Workers extends EventEmitter {
307314
* @param {Number} numberOfWorkers
308315
*/
309316
createGroupsOfTests(numberOfWorkers) {
310-
const files = this.codecept.testFiles
317+
// If Codecept isn't initialized yet, return empty groups as a safe fallback
318+
if (!this.codecept) return populateGroups(numberOfWorkers)
319+
const files = this.codecept.testFiles
311320
const mocha = Container.mocha()
312321
mocha.files = files
313322
mocha.loadFiles()
@@ -329,7 +338,9 @@ class Workers extends EventEmitter {
329338
* @param {Number} numberOfWorkers
330339
*/
331340
createGroupsOfSuites(numberOfWorkers) {
332-
const files = this.codecept.testFiles
341+
// If Codecept isn't initialized yet, return empty groups as a safe fallback
342+
if (!this.codecept) return populateGroups(numberOfWorkers)
343+
const files = this.codecept.testFiles
333344
const groups = populateGroups(numberOfWorkers)
334345

335346
const mocha = Container.mocha()
@@ -423,15 +434,25 @@ class Workers extends EventEmitter {
423434
this.emit(event.test.started, deserializeTest(message.data))
424435
break
425436
case event.test.failed:
437+
if (message?.data?.uid) this._pendingPass.delete(message.data.uid)
426438
this.emit(event.test.failed, deserializeTest(message.data))
427439
break
428440
case event.test.passed:
429-
this.emit(event.test.passed, deserializeTest(message.data))
441+
// Buffer pass until finished to avoid counting tests that will fail later
442+
if (message?.data?.uid) this._pendingPass.add(message.data.uid)
430443
break
431444
case event.test.skipped:
432445
this.emit(event.test.skipped, deserializeTest(message.data))
433446
break
434447
case event.test.finished:
448+
// Emit a deduped 'passed' only if it was pending and no error provided
449+
if (message?.data?.uid && !message?.data?.err) {
450+
if (!this._passedUids.has(message.data.uid) && this._pendingPass.has(message.data.uid)) {
451+
this._passedUids.add(message.data.uid)
452+
this.emit(event.test.passed, deserializeTest(message.data))
453+
}
454+
}
455+
if (message?.data?.uid) this._pendingPass.delete(message.data.uid)
435456
this.emit(event.test.finished, deserializeTest(message.data))
436457
break
437458
case event.test.after:

test/unit/worker_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ describe('Workers', function () {
226226
testConfig: './test/data/sandbox/codecept.non-test-events-worker.js',
227227
}
228228

229-
workers = new Workers(2, workerConfig)
229+
let workers = new Workers(2, workerConfig)
230230

231231
workers.run()
232232

0 commit comments

Comments
 (0)