Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/runner/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,8 @@ export async function runSuite(suite: Suite, runner: VitestRunner): Promise<void
else {
for (let tasksGroup of partitionSuiteChildren(suite)) {
if (tasksGroup[0].concurrent === true) {
await Promise.all(tasksGroup.map(c => runSuiteChild(c, runner)))
const groupLimiter = limitConcurrency(runner.config.maxConcurrency)
await Promise.all(tasksGroup.map(c => groupLimiter(() => runSuiteChild(c, runner))))
}
else {
const { sequence } = runner.config
Expand Down
293 changes: 293 additions & 0 deletions test/cli/test/concurrent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1074,3 +1074,296 @@ test('aroundAll enforces teardown timeout when inner error is caught', async ()
}
`)
})

function extractLogs(log: string) {
const result = log.split('\n').filter(line => line.match(/^![<>]/)).join('\n')
return `\n${result.trim()}\n`
}

test('sibling task sequential lifecycle guarantee', async () => {
const result = await runInlineTests({
'basic.test.ts': `
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

beforeEach(async ({ task }) => {
console.log("!> beforeEach", task.name)
await sleep(10)
console.log("!< beforeEach", task.name)
})

afterEach(async ({ task }) => {
console.log("!> afterEach", task.name)
await sleep(10)
console.log("!< afterEach", task.name)
})

test.concurrent.for(["a", "b"])("%s", async (_, { task }) => {
console.log("!> test", task.name)
await sleep(10)
console.log("!< test", task.name)
})
`,
}, {
maxConcurrency: 1,
globals: true,
})

expect(extractLogs(result.stdout)).toMatchInlineSnapshot(`
"
!> beforeEach a
!< beforeEach a
!> test a
!< test a
!> afterEach a
!< afterEach a
!> beforeEach b
!< beforeEach b
!> test b
!< test b
!> afterEach b
!< afterEach b
"
`)
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"a": "passed",
"b": "passed",
},
}
`)
})

test('sibling suite sequential lifecycle guarantee', async () => {
const result = await runInlineTests({
'basic.test.ts': `
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

describe.for(["a", "b"])("%s", { concurrent: true }, () => {
beforeAll(async ({}, suite) => {
console.log("!> beforeAll", suite.name)
await sleep(10)
console.log("!< beforeAll", suite.name)
})

afterAll(async ({}, suite) => {
console.log("!> afterAll", suite.name)
await sleep(10)
console.log("!< afterAll", suite.name)
})

test.concurrent("test", async ({ task }) => {
console.log("!> test", task.suite.name)
await sleep(10)
console.log("!< test", task.suite.name)
})
})
`,
}, {
maxConcurrency: 1,
globals: true,
})

expect(extractLogs(result.stdout)).toMatchInlineSnapshot(`
"
!> beforeAll a
!< beforeAll a
!> test a
!< test a
!> afterAll a
!< afterAll a
!> beforeAll b
!< beforeAll b
!> test b
!< test b
!> afterAll b
!< afterAll b
"
`)
expect(result.errorTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"a": {
"test": "passed",
},
"b": {
"test": "passed",
},
},
}
`)
})

// we could enforce this by adding yet another limit globally at `runTest`
// (like we originally had before https://github.com/vitest-dev/vitest/pull/9653)
// but there's no way to achieve the same for deep suite-level hooks anyways,
// so we don't do that (yet).
test('non-sibling test sequential lifecycle non-guarantee', async () => {
const result = await runInlineTests({
'basic.test.ts': `
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

describe.for(["a0", "a1"])("%s", { concurrent: true }, () => {
describe.for(["b0", "b1"])("%s", { concurrent: true }, () => {
beforeEach(async ({ task }) => {
console.log("!> beforeEach", task.suite.suite.name, task.suite.name, task.name)
await sleep(10)
console.log("!< beforeEach", task.suite.suite.name, task.suite.name, task.name)
})

afterEach(async ({ task }) => {
console.log("!> afterEach", task.suite.suite.name, task.suite.name, task.name)
await sleep(10)
console.log("!< afterEach", task.suite.suite.name, task.suite.name, task.name)
})

test("test", async ({ task }) => {
console.log("!> test", task.suite.suite.name,task.suite.name, task.name)
await sleep(10)
console.log("!< test", task.suite.suite.name,task.suite.name, task.name)
})
})
})
`,
}, {
maxConcurrency: 2,
globals: true,
})

expect(extractLogs(result.stdout)).toMatchInlineSnapshot(`
"
!> beforeEach a0 b0 test
!> beforeEach a0 b1 test
!< beforeEach a0 b0 test
!> beforeEach a1 b0 test
!< beforeEach a0 b1 test
!> beforeEach a1 b1 test
!< beforeEach a1 b0 test
!> test a0 b0 test
!< beforeEach a1 b1 test
!> test a0 b1 test
!< test a0 b0 test
!> test a1 b0 test
!< test a0 b1 test
!> test a1 b1 test
!< test a1 b0 test
!> afterEach a0 b0 test
!< test a1 b1 test
!> afterEach a0 b1 test
!< afterEach a0 b0 test
!> afterEach a1 b0 test
!< afterEach a0 b1 test
!> afterEach a1 b1 test
!< afterEach a1 b0 test
!< afterEach a1 b1 test
"
`)

expect(result.errorTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"a0": {
"b0": {
"test": "passed",
},
"b1": {
"test": "passed",
},
},
"a1": {
"b0": {
"test": "passed",
},
"b1": {
"test": "passed",
},
},
},
}
`)
})

test('non-sibling suite sequential lifecycle non-guarantee', async () => {
const result = await runInlineTests({
'basic.test.ts': `
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

describe.for(["a0", "a1"])("%s", { concurrent: true }, () => {
describe.for(["b0", "b1"])("%s", { concurrent: true }, () => {
beforeAll(async ({}, suite) => {
console.log("!> beforeAll", suite.suite.name, suite.name)
await sleep(10)
console.log("!< beforeAll", suite.suite.name, suite.name)
})

afterAll(async ({}, suite) => {
console.log("!> afterAll", suite.suite.name, suite.name)
await sleep(10)
console.log("!< afterAll", suite.suite.name, suite.name)
})

test("test", async ({ task }) => {
console.log("!> test", task.suite.suite.name, task.suite.name, task.name)
await sleep(10)
console.log("!< test", task.suite.suite.name, task.suite.name, task.name)
})
})
})
`,
}, {
maxConcurrency: 2,
globals: true,
})

expect(extractLogs(result.stdout)).toMatchInlineSnapshot(`
"
!> beforeAll a0 b0
!> beforeAll a0 b1
!< beforeAll a0 b0
!> beforeAll a1 b0
!< beforeAll a0 b1
!> beforeAll a1 b1
!< beforeAll a1 b0
!> test a0 b0 test
!< beforeAll a1 b1
!> test a0 b1 test
!< test a0 b0 test
!> test a1 b0 test
!< test a0 b1 test
!> test a1 b1 test
!< test a1 b0 test
!> afterAll a0 b0
!< test a1 b1 test
!> afterAll a0 b1
!< afterAll a0 b0
!> afterAll a1 b0
!< afterAll a0 b1
!> afterAll a1 b1
!< afterAll a1 b0
!< afterAll a1 b1
"
`)

expect(result.errorTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
"a0": {
"b0": {
"test": "passed",
},
"b1": {
"test": "passed",
},
},
"a1": {
"b0": {
"test": "passed",
},
"b1": {
"test": "passed",
},
},
},
}
`)
})
Loading