Skip to content

Commit 170369c

Browse files
miraoclaude
andcommitted
fix: run-workers --by suite parallelization broken by test file sorting (#5412)
The sorting of test files in loadTests() (added in #5386) broke the --by suite parallelization. When files were sorted before worker distribution, all workers could receive the same tests instead of different suites being distributed to different workers. Fix: Move testFiles.sort() from loadTests() to run(). This ensures: - Worker distribution uses original (unsorted) file order for consistent distribution across workers - Test execution still uses alphabetical order (sorted in run()) Added unit test to verify files are not sorted after loadTests(). Fixes #5412 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent bb6410e commit 170369c

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

lib/codecept.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,9 @@ class Codecept {
284284
// Ignore if gherkin module not available
285285
}
286286

287+
// Sort test files alphabetically for consistent execution order
288+
this.testFiles.sort()
289+
287290
return new Promise((resolve, reject) => {
288291
const mocha = container.mocha()
289292
mocha.files = this.testFiles
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { expect } from 'chai'
2+
import Codecept from '../../lib/codecept.js'
3+
import Container from '../../lib/container.js'
4+
import path from 'path'
5+
import fs from 'fs'
6+
import { fileURLToPath } from 'url'
7+
import { dirname } from 'path'
8+
9+
const __filename = fileURLToPath(import.meta.url)
10+
const __dirname = dirname(__filename)
11+
12+
describe('Test Files Alphabetical Order', () => {
13+
let codecept
14+
const tempDir = path.join(__dirname, '../data/sandbox/configs/alphabetical_order')
15+
const config = {
16+
tests: `${tempDir}/*_test.js`,
17+
gherkin: { features: null },
18+
output: './output',
19+
hooks: [],
20+
}
21+
22+
before(() => {
23+
if (!fs.existsSync(tempDir)) {
24+
fs.mkdirSync(tempDir, { recursive: true })
25+
}
26+
27+
fs.writeFileSync(
28+
path.join(tempDir, 'zzz_test.js'),
29+
`
30+
Feature('Test');
31+
Scenario('zzz test', () => {});
32+
`,
33+
)
34+
fs.writeFileSync(
35+
path.join(tempDir, 'aaa_test.js'),
36+
`
37+
Feature('Test');
38+
Scenario('aaa test', () => {});
39+
`,
40+
)
41+
fs.writeFileSync(
42+
path.join(tempDir, 'mmm_test.js'),
43+
`
44+
Feature('Test');
45+
Scenario('mmm test', () => {});
46+
`,
47+
)
48+
})
49+
50+
after(() => {
51+
const files = ['zzz_test.js', 'aaa_test.js', 'mmm_test.js']
52+
files.forEach(file => {
53+
const filePath = path.join(tempDir, file)
54+
if (fs.existsSync(filePath)) {
55+
fs.unlinkSync(filePath)
56+
}
57+
})
58+
})
59+
60+
beforeEach(async () => {
61+
codecept = new Codecept(config, {})
62+
await codecept.init(path.join(__dirname, '../data/sandbox'))
63+
})
64+
65+
afterEach(() => {
66+
Container.clear()
67+
})
68+
69+
it('should sort test files alphabetically when run() is called', async () => {
70+
codecept.loadTests()
71+
72+
if (codecept.testFiles.length === 0) {
73+
console.log('No test files found, skipping test')
74+
return
75+
}
76+
77+
// Capture the files that would be passed to mocha during run()
78+
let filesPassedToMocha = null
79+
const mocha = Container.mocha()
80+
mocha.run = callback => {
81+
filesPassedToMocha = [...mocha.files]
82+
// Call callback immediately to complete the run
83+
if (callback) callback()
84+
}
85+
86+
// Call run() which should sort files before passing to mocha
87+
await codecept.run()
88+
89+
// Verify files passed to mocha are sorted alphabetically
90+
expect(filesPassedToMocha).to.not.be.null
91+
const filenames = filesPassedToMocha.map(filePath => path.basename(filePath))
92+
const sortedFilenames = [...filenames].sort()
93+
94+
expect(filenames).to.deep.equal(sortedFilenames, 'Files should be sorted alphabetically for execution')
95+
96+
const aaaIndex = filenames.findIndex(f => f.includes('aaa_test.js'))
97+
const mmmIndex = filenames.findIndex(f => f.includes('mmm_test.js'))
98+
const zzzIndex = filenames.findIndex(f => f.includes('zzz_test.js'))
99+
100+
expect(aaaIndex).to.be.greaterThan(-1)
101+
expect(mmmIndex).to.be.greaterThan(-1)
102+
expect(zzzIndex).to.be.greaterThan(-1)
103+
104+
expect(aaaIndex).to.be.lessThan(mmmIndex, 'aaa_test.js should come before mmm_test.js')
105+
expect(mmmIndex).to.be.lessThan(zzzIndex, 'mmm_test.js should come before zzz_test.js')
106+
})
107+
})

test/unit/worker_test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,4 +382,34 @@ describe('Workers', function () {
382382

383383
workers.run()
384384
})
385+
386+
it('should distribute suites across workers for --by suite (issue #5412)', async () => {
387+
// This test verifies the fix for issue #5412:
388+
// Test files should NOT be sorted in loadTests() because that affects worker distribution.
389+
// Sorting should only happen in run() for execution order.
390+
//
391+
// The bug was: sorting in loadTests() changed the order of suites during distribution,
392+
// causing all workers to receive the same tests instead of different suites.
393+
394+
// Ensure clean state
395+
Container.clear()
396+
Container.createMocha()
397+
398+
const workerConfig = {
399+
by: 'suite',
400+
testConfig: './test/data/sandbox/codecept.customworker.js',
401+
}
402+
403+
const workers = new Workers(3, workerConfig)
404+
await workers._ensureInitialized()
405+
406+
// Verify that test files were loaded
407+
const testFiles = workers.codecept.testFiles
408+
expect(testFiles.length).to.be.greaterThan(0, 'Test files should be loaded')
409+
410+
// Verify that suites are distributed across multiple worker groups
411+
const groups = workers.testGroups
412+
const nonEmptyGroups = groups.filter(g => g.length > 0)
413+
expect(nonEmptyGroups.length).to.be.greaterThan(1, 'Suites should be distributed across multiple worker groups')
414+
})
385415
})

0 commit comments

Comments
 (0)