name: Test (reusable) on: workflow_call: inputs: node: required: true type: string node_major: required: true type: string platform: required: true type: string garnet: required: false type: boolean default: false test_chunk: required: false type: string default: '1' test_chunk_total: required: false type: string default: '1' secrets: GARNET_API_TOKEN: required: false permissions: contents: read jobs: test: name: Node ${{ inputs.node_major }} / chunk ${{ inputs.test_chunk }}/${{ inputs.test_chunk_total }} concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.platform }}-${{ inputs.node }}-${{ inputs.test_chunk }}-${{ inputs.test_chunk_total }} cancel-in-progress: true runs-on: ${{ inputs.platform }} steps: # A near-full "affected packages" sweep can exhaust the hosted runner's # free disk mid-test (the runner worker itself dies with "No space left # on device"). Drop the preinstalled toolchains this job never uses # (~25 GB) first. - name: Free up runner disk space if: ${{ runner.os == 'Linux' }} run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL df -h / - name: Configure Git run: | git config --global core.autocrlf false git config --global user.name "xyz" git config --global user.email "x@y.z" - name: Checkout Commit uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - if: ${{ inputs.garnet }} uses: garnet-org/action@3d47f4a9004f7356c980a0e8d420ef5984750e3c # v2.2.0 with: api_token: ${{ secrets.GARNET_API_TOKEN }} - name: Install pnpm and Node uses: pnpm/setup@6523ce966bcf7a1061ce6401e5c6e0b60466bd31 with: runtime: node@${{ inputs.node }} - name: Verify Node version shell: bash env: NODE_VERSION: ${{ inputs.node }} # `pn node -v` falls back through `run`/`exec`, which would # otherwise trigger a verifyDepsBeforeRun install just to print # the version. Disable it so this step measures the runtime that # pnpm/setup provisioned, not one a stray install pulled in. pnpm_config_verify_deps_before_run: false run: | actual=$(pn node -v) expected="v${NODE_VERSION}" if [ "$actual" != "$expected" ]; then echo "Expected Node version $expected but got $actual" exit 1 fi # npm is needed for preparing git-hosted dependencies (e.g. in dlx tests). # `pnpm runtime set node` does not extract npm; the runner image's # pre-installed Node toolchain provides it on PATH. - name: Verify npm run: npm --version - name: Download compiled artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: compiled-packages - name: Extract compiled artifacts run: tar -xzf compiled.tar.gz # The test harness serves package fixtures through the in-repo `pnpr` # server; `pnpr-prepare` turns the raw fixtures under # `pnpr/.fixtures/packages` into the storage the server reads. Both are # built from source (so the tests exercise the current server, not a # published `@pnpm/pnpr` that may predate it) once per OS by the # `build-pnpr` reusable workflow, then shared with every test job/shard # here as an artifact instead of rebuilt per job. - name: Download prebuilt pnpr binaries uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: pnpr-bins-${{ runner.os }} path: .pnpr-bin - name: Export pnpr binary paths shell: bash run: | ext="" [ -f "$PWD/.pnpr-bin/pnpr.exe" ] && ext=".exe" # Artifacts don't preserve the executable bit; restore it on POSIX. [ -z "$ext" ] && chmod +x "$PWD/.pnpr-bin/pnpr" "$PWD/.pnpr-bin/pnpr-prepare" echo "PNPR_BIN=$PWD/.pnpr-bin/pnpr$ext" >> "$GITHUB_ENV" echo "PNPR_PREPARE_BIN=$PWD/.pnpr-bin/pnpr-prepare$ext" >> "$GITHUB_ENV" - name: Determine test scope id: test-scope shell: bash env: REF_NAME: ${{ github.ref_name }} run: | if [[ "$REF_NAME" == "main" || "$REF_NAME" == "chore/update-lockfile" || "$REF_NAME" == release/* ]]; then { echo "script=ci:test-all" echo "scope=all" echo "full_tests=true" echo "benchmark=tests.all" } >> "$GITHUB_OUTPUT" else git remote set-branches --add origin main && git fetch origin main --depth=1 if [ -n "$(git diff --name-only origin/main HEAD -- pnpm-workspace.yaml)" ]; then { echo "script=ci:test-all" echo "scope=all — pnpm-workspace.yaml modified" echo "full_tests=true" echo "benchmark=tests.all" } >> "$GITHUB_OUTPUT" else { echo "script=ci:test-branch" echo "scope=affected packages" echo "full_tests=false" echo "benchmark=tests.affected" } >> "$GITHUB_OUTPUT" fi fi - name: Validate test chunk inputs shell: bash env: TEST_CHUNK: ${{ inputs.test_chunk }} TEST_CHUNK_TOTAL: ${{ inputs.test_chunk_total }} run: | case "$TEST_CHUNK" in ''|*[!0-9]*) echo "::error::test_chunk must be a positive integer" exit 1 ;; esac case "$TEST_CHUNK_TOTAL" in ''|*[!0-9]*) echo "::error::test_chunk_total must be a positive integer" exit 1 ;; esac if (( TEST_CHUNK < 1 || TEST_CHUNK_TOTAL < 1 || TEST_CHUNK > TEST_CHUNK_TOTAL )); then echo "::error::test_chunk must be between 1 and test_chunk_total" exit 1 fi - name: Run tests (${{ steps.test-scope.outputs.scope }}) timeout-minutes: 70 shell: bash env: BENCHMARK: ${{ steps.test-scope.outputs.benchmark }} PNPM_WORKERS: 3 TEST_SCRIPT: ${{ steps.test-scope.outputs.script }} TEST_CHUNK: ${{ inputs.test_chunk }} TEST_CHUNK_TOTAL: ${{ inputs.test_chunk_total }} run: | benchmark="$BENCHMARK" command=(pn run "$TEST_SCRIPT") if [ "$TEST_CHUNK_TOTAL" != "1" ]; then benchmark="${BENCHMARK}.chunk${TEST_CHUNK}" command=( node .github/scripts/run-ts-tests-chunk.mjs --script "$TEST_SCRIPT" --chunk "$TEST_CHUNK" --chunks "$TEST_CHUNK_TOTAL" ) fi node .github/scripts/measure-command.mjs \ --name "$benchmark" \ --output ".bench/${benchmark}.json" \ -- "${command[@]}" - name: Extract pnpm CLI e2e test duration if: steps.test-scope.outputs.full_tests == 'true' shell: bash env: TEST_CHUNK: ${{ inputs.test_chunk }} TEST_CHUNK_TOTAL: ${{ inputs.test_chunk_total }} run: | benchmark=tests.cli args=( --name "$benchmark" --output ".bench/${benchmark}.json" --package-dir pnpm11/pnpm ) if [ "$TEST_CHUNK_TOTAL" != "1" ]; then benchmark="${benchmark}.chunk${TEST_CHUNK}" args=( --name "$benchmark" --output ".bench/${benchmark}.json" --package-dir pnpm11/pnpm --allow-missing ) fi node .github/scripts/bencher-result-from-pnpm-summary.mjs "${args[@]}" - name: Stage Bencher test durations if: steps.test-scope.outputs.full_tests == 'true' env: BENCHMARK: ${{ steps.test-scope.outputs.benchmark }} NODE_VERSION: ${{ inputs.node }} PLATFORM: ${{ inputs.platform }} TEST_CHUNK: ${{ inputs.test_chunk }} TEST_CHUNK_TOTAL: ${{ inputs.test_chunk_total }} shell: bash run: | benchmark="$BENCHMARK" cli_benchmark=tests.cli if [ "$TEST_CHUNK_TOTAL" != "1" ]; then benchmark="${BENCHMARK}.chunk${TEST_CHUNK}" cli_benchmark="${cli_benchmark}.chunk${TEST_CHUNK}" fi # Map runner labels to the bare OS slug the Bencher testbed name requires. case "$PLATFORM" in *ubuntu*) platform_slug=ubuntu ;; *windows*) platform_slug=windows ;; *) echo "::error::Unsupported platform '$PLATFORM' for pnpm benchmarks; expected an ubuntu or windows runner" exit 1 ;; esac node_major=${NODE_VERSION%%.*} case "$node_major" in 22|24|26) ;; *) echo "::error::Unsupported Node major '$node_major' for pnpm benchmarks; expected 22, 24, or 26" exit 1 ;; esac artifact_dir=.bench/artifact/pnpm mkdir -p "$artifact_dir" files=(".bench/${benchmark}.json") if [ -f ".bench/${cli_benchmark}.json" ]; then files+=(".bench/${cli_benchmark}.json") fi node .github/scripts/merge-bencher-results.mjs \ --output "$artifact_dir/results.json" \ -- "${files[@]}" cat > "$artifact_dir/metadata.json" <