diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2282b830..5a3ab48e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,11 +48,41 @@ jobs: os: ubuntu-22.04 - suite: plugin os: ubuntu-22.04 + - suite: nextjs + os: ubuntu-22.04 + shard: 1/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 2/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 3/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 4/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 5/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 6/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 7/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 7/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 7/7 + - suite: nextjs + os: ubuntu-22.04 + shard: 7/7 - suite: lynx-stack os: ubuntu-22.04 fail-fast: false runs-on: ${{ matrix.os }} - name: ${{ matrix.suite }} + name: ${{ matrix.suite }} ${{ matrix.shard || '' }} steps: - uses: actions/checkout@v4 - uses: ./.github/actions/build-rspack @@ -77,4 +107,5 @@ jobs: - run: >- pnpm tsx ecosystem-ci.ts run-suites + ${{ matrix.shard && '--shard ' || '' }}${{ matrix.shard || '' }} ${{ matrix.suite }} diff --git a/.github/workflows/ecosystem-ci-from-commit.yml b/.github/workflows/ecosystem-ci-from-commit.yml index d02a6693..87797535 100644 --- a/.github/workflows/ecosystem-ci-from-commit.yml +++ b/.github/workflows/ecosystem-ci-from-commit.yml @@ -32,6 +32,7 @@ on: - examples - devserver - plugin + - nextjs suiteRefType: description: "type of suite ref to use" required: true diff --git a/.github/workflows/ecosystem-ci-from-pr.yml b/.github/workflows/ecosystem-ci-from-pr.yml index a47d1fe4..fbc418e1 100644 --- a/.github/workflows/ecosystem-ci-from-pr.yml +++ b/.github/workflows/ecosystem-ci-from-pr.yml @@ -37,6 +37,7 @@ on: - examples - devserver - plugin + - nextjs suiteRefType: description: "type of suite ref to use" required: true diff --git a/.github/workflows/ecosystem-ci-selected.yml b/.github/workflows/ecosystem-ci-selected.yml index 8121cb25..870ec69d 100644 --- a/.github/workflows/ecosystem-ci-selected.yml +++ b/.github/workflows/ecosystem-ci-selected.yml @@ -44,6 +44,7 @@ on: - examples - devserver - plugin + - nextjs suiteRefType: description: "type of suite ref to use" required: true diff --git a/ecosystem-ci.ts b/ecosystem-ci.ts index 0f68a1e3..3c6a7982 100644 --- a/ecosystem-ci.ts +++ b/ecosystem-ci.ts @@ -12,7 +12,7 @@ import { parseMajorVersion, ignorePrecoded, } from './utils' -import { CommandOptions, RunOptions } from './types' +import { CommandOptions, RunOptions, ShardPair } from './types' const cli = cac() cli @@ -89,9 +89,14 @@ cli .option('--suite-branch ', 'suite branch to use') .option('--suite-tag ', 'suite tag to use') .option('--suite-commit ', 'suite commit sha to use') + .option( + '--shard ', + 'Shard tests and execute only the selected shard, specify in the form "current/all". 1-based, for example "3/5"', + ) .action(async (suites, options: CommandOptions) => { const { root, rspackPath, workspace } = await setupEnvironment() const suitesToRun = getSuitesToRun(suites, root) + const shardPair = options.shard ? parseShardPair(options.shard) : undefined const runOptions: RunOptions = { ...options, root, @@ -101,6 +106,7 @@ cli suiteBranch: ignorePrecoded(options.suiteBranch), suiteTag: ignorePrecoded(options.suiteTag), suiteCommit: ignorePrecoded(options.suiteCommit), + shardPair, } for (const suite of suitesToRun) { await run(suite, runOptions) @@ -197,3 +203,36 @@ function getSuitesToRun(suites: string[], root: string) { } return suitesToRun } + +const parseShardPair = (pair: string): ShardPair => { + const shardPair = pair + .split('/') + .filter((d) => /^\d+$/.test(d)) + .map((d) => Number.parseInt(d, 10)) + .filter((shard) => !Number.isNaN(shard)) + + const [shardIndex, shardCount] = shardPair + + if (shardPair.length !== 2) { + throw new Error( + 'The shard option requires a string in the format of /.', + ) + } + + if (shardIndex === 0 || shardCount === 0) { + throw new Error( + 'The shard option requires 1-based values, received 0 or lower in the pair.', + ) + } + + if (shardIndex > shardCount) { + throw new Error( + 'The shard option / requires to be lower or equal than .', + ) + } + + return { + shardCount, + shardIndex, + } +} diff --git a/tests/nextjs.ts b/tests/nextjs.ts new file mode 100644 index 00000000..b6fcd006 --- /dev/null +++ b/tests/nextjs.ts @@ -0,0 +1,78 @@ +import path from 'path' +import fs from 'fs' +import { runInRepo, execa, $, cd } from '../utils' +import { RunOptions } from '../types' + +export async function test(options: RunOptions) { + const { workspace, shardPair, rspackPath } = options + + await runInRepo({ + ...options, + repo: 'vercel/next.js', + branch: 'canary', + build: async () => { + const rspackCorePath = path.join(rspackPath, 'packages/rspack') + const nextWorkspaceDir = path.join(workspace, 'next.js') + + const getRspackPath = path.join( + nextWorkspaceDir, + 'packages/next/src/shared/lib/get-rspack.ts', + ) + const getRspackContent = fs.readFileSync(getRspackPath, 'utf-8') + const replacedGetRspackContent = getRspackContent.replace( + "require.resolve('@rspack/core'", + `require.resolve('${rspackCorePath}'`, + ) + fs.writeFileSync(getRspackPath, replacedGetRspackContent) + + const compiledWebpackPath = path.join( + nextWorkspaceDir, + 'packages/next/src/compiled/webpack/webpack.js', + ) + const compiledWebpackContent = fs.readFileSync( + compiledWebpackPath, + 'utf-8', + ) + const replacedCompiledWebpackContent = compiledWebpackContent.replace( + "require('@rspack/core')", + `require('${rspackCorePath}')`, + ) + fs.writeFileSync(compiledWebpackPath, replacedCompiledWebpackContent) + + const nodeBindingPath = path.join(rspackPath, 'crates/node_binding') + cd(nodeBindingPath) + await $`pnpm run move-binding` + + cd(nextWorkspaceDir) + await $`pnpm run build` + await $`pnpm install` + }, + beforeTest: async () => { + await $`pnpm playwright install --with-deps` + }, + test: async () => { + const env = { + ...process.env, + NEXT_EXTERNAL_TESTS_FILTERS: `${workspace}/next.js/test/rspack-build-tests-manifest.json`, + NEXT_RSPACK: '1', + NEXT_TEST_USE_RSPACK: '1', + TEST_CONCURRENCY: '8', + NEXT_TEST_MODE: 'start', + } + if (shardPair) { + await execa( + `node run-tests.js -g ${shardPair.shardIndex}/${shardPair.shardCount} --type production`, + { + env, + shell: true, + }, + ) + } else { + await execa('node run-tests.js --type production', { + env, + shell: true, + }) + } + }, + }) +} diff --git a/types.d.ts b/types.d.ts index 75697846..890dba18 100644 --- a/types.d.ts +++ b/types.d.ts @@ -8,6 +8,11 @@ export interface EnvironmentData { env: NodeJS.ProcessEnv } +interface ShardPair { + shardCount: number + shardIndex: number +} + export interface RunOptions { workspace: string root: string @@ -26,6 +31,7 @@ export interface RunOptions { suiteBranch?: string suiteTag?: string suiteCommit?: string + shardPair?: ShardPair } type Task = string | (() => Promise) @@ -41,6 +47,7 @@ export interface CommandOptions { suiteBranch?: string suiteTag?: string suiteCommit?: string + shard?: string } export interface RepoOptions {