diff --git a/package.json b/package.json index 2b02ced87f1..a5fc052cc65 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "start": "node ./bin/run.js", "test": "run-s test:unit test:integration test:e2e", "test:e2e": "vitest run --config vitest.e2e.config.ts", - "test:init": "run-s test:init:*", + "test:init": "run-p test:init:*", "test:init:cli-help": "npm run start -- --help", "test:init:cli-version": "npm run start -- --version", "test:init:hugo-deps": "npm ci --prefix tests/integration/__fixtures__/hugo-site --no-audit", diff --git a/tests/integration/commands/clone/clone.test.ts b/tests/integration/commands/clone/clone.test.ts index a4a0a45f0b5..f7e0a207669 100644 --- a/tests/integration/commands/clone/clone.test.ts +++ b/tests/integration/commands/clone/clone.test.ts @@ -1,6 +1,6 @@ import { sep } from 'node:path' -import { describe, expect, it, beforeEach } from 'vitest' +import { describe, expect, it } from 'vitest' import js from 'dedent' import stripAnsi from 'strip-ansi' @@ -11,33 +11,29 @@ import { createMock } from '../../utils/mock-execa.js' import execa from 'execa' import { cliPath } from '../../utils/cli-path.js' -describe.concurrent('clone command', () => { - let execaMock: Awaited>[0] - - beforeEach(async () => { - ;[execaMock] = await createMock(js` - module.exports = function execa(command, args) { - // Mock git clone command - if (command === 'git' && args[0] === 'clone') { - const targetDir = args[2] - return require('fs/promises').mkdir(targetDir, { recursive: true }) - .then(() => { - const stdout = 'Mocked git clone received: ' + command + ' ' + args.join(' ') - console.log(stdout) - return { stdout, stderr: '' } - }) - } - // For any other command, use the real execa - // Normalize paths for Windows - const realExeca = require('${require.resolve('execa').split(sep).join('/')}') - // execa's APi is... really weird - const result = Promise.resolve(realExeca(command, args)) - result.unref = () => {} - return result - } - `) - }) +const EXECA_MOCK_SOURCE = js` + module.exports = function execa(command, args) { + // Mock git clone command + if (command === 'git' && args[0] === 'clone') { + const targetDir = args[2] + return require('fs/promises').mkdir(targetDir, { recursive: true }) + .then(() => { + const stdout = 'Mocked git clone received: ' + command + ' ' + args.join(' ') + console.log(stdout) + return { stdout, stderr: '' } + }) + } + // For any other command, use the real execa + // Normalize paths for Windows + const realExeca = require('${require.resolve('execa').split(sep).join('/')}') + // execa's APi is... really weird + const result = Promise.resolve(realExeca(command, args)) + result.unref = () => {} + return result + } +` +describe.concurrent('clone command', () => { const SITE_INFO_FIXTURE = { id: 'site_id', name: 'test-site', @@ -68,6 +64,7 @@ describe.concurrent('clone command', () => { it.todo('prints an error and exits if not authenticated') it("clones a repo and links it to the one project connected to that repo's HTTPS URL", async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const routes = [...API_ROUTES_FIXTURE] const questions = [ { @@ -132,6 +129,7 @@ To unlink this project, run: netlify unlink`) }) it('clones into the provided `[targetDir]` if arg is provided', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const routes = [...API_ROUTES_FIXTURE] const questions: never[] = [] // no interactive prompts at all @@ -185,6 +183,7 @@ To unlink this project, run: netlify unlink`) }) it('clones into the entered target dir if user enters one when prompted', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const routes = [...API_ROUTES_FIXTURE] const questions = [ { @@ -249,6 +248,7 @@ To unlink this project, run: netlify unlink`) }) it('links to project with given `--id` when provided', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const otherSiteInfo = { id: 'other-site-id', name: 'other-site', @@ -306,6 +306,7 @@ To unlink this project, run: netlify unlink`) }) it('links to project with given `--name` when provided', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const otherSiteInfo = { id: 'other-site-id', name: 'other-site', @@ -367,6 +368,7 @@ To unlink this project, run: netlify unlink`) }) it('prompts user when multiple projects match git repo HTTPS URL', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const otherSiteInfo = { id: 'other-site-id', name: 'other-site', @@ -425,6 +427,7 @@ To unlink this project, run: netlify unlink`) }) it('prints an error and exits when no project is connected to the git repo HTTPS URL', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const otherSiteInfo = { id: 'other-site-id', name: 'other-site', @@ -485,6 +488,7 @@ Run git remote -v to see a list of your git remotes.`) }) it('prints an error and exits given an invalid git repo specifier', async (t) => { + const [execaMock] = await createMock(EXECA_MOCK_SOURCE) const routes = [ { path: 'sites', diff --git a/tests/integration/sequencer.ts b/tests/integration/sequencer.ts new file mode 100644 index 00000000000..0aae6523554 --- /dev/null +++ b/tests/integration/sequencer.ts @@ -0,0 +1,16 @@ +import { BaseSequencer, type TestSpecification } from 'vitest/node' + +/** + * Round-robin sharding so slow files (e.g. all `dev/*` tests) don't cluster in + * the same shard like they do under vitest's default sha1-hash distribution. + */ +export class BalancedShardSequencer extends BaseSequencer { + shard(files: TestSpecification[]): Promise { + const shardCfg = this.ctx.config.shard + if (!shardCfg) return Promise.resolve(files) + + const sorted = [...files].sort((a, b) => a.moduleId.localeCompare(b.moduleId)) + const mine = sorted.filter((_, i) => i % shardCfg.count === shardCfg.index - 1) + return Promise.resolve(mine) + } +} diff --git a/vitest.config.ts b/vitest.config.ts index e2dc214919f..dcabcac24be 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,7 @@ import { defineConfig } from 'vitest/config' +import { BalancedShardSequencer } from './tests/integration/sequencer.js' + export default defineConfig({ test: { include: ['tests/**/*.test.js', 'tests/**/*.test.ts'], @@ -25,6 +27,9 @@ export default defineConfig({ singleThread: true, }, }, + sequence: { + sequencer: BalancedShardSequencer, + }, coverage: { provider: 'v8', reporter: ['text', 'lcov'],