name: Windows Free Tests # The Windows-safe subset of the free suite on GitHub's free `windows-latest` # runners. scripts/test-free-shards.ts --windows-only is the single source of # truth for which files run: POSIX-bound tests (bin/ shebang spawns, raw /tmp # paths, /bin/sh, mode bitmasks, the Bun-driven browse server) are excluded by # pattern there, with explicit force-include and known-incompatible lists. # Every run prints the curated count and top exclusion reasons in the job # summary so a shrinking subset is visible. # # Same plan/run/verify model as free-tests.yml: one duration-balanced plan of # the curated files, one shard per Windows job, and an aggregate that requires # every strict result and exact coverage. The plan and the verify step are # static (file contents and the git revision), so they run on Linux. Packing # reads Windows-measured durations (scripts/free-test-durations-windows.json): # Windows-only files such as the native cookie job cost minutes there and # nothing on Linux. Refresh it with a `record_durations` dispatch and commit # the artifact. # # Native browser qualification campaigns (Windows cookie extraction, launch # diagnostics, Dia on macOS) are dispatch-only in native-qualification.yml. on: pull_request: branches: [main] workflow_dispatch: inputs: record_durations: description: Record per-file Windows durations (artifact) instead of running the lane type: boolean default: false concurrency: group: windows-free-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: true # Test-only lane — no token writes. permissions: contents: read jobs: windows-plan: if: ${{ !inputs.record_durations }} runs-on: ubuntu-24.04 timeout-minutes: 5 outputs: matrix: ${{ steps.plan.outputs.matrix }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.4.2 - id: plan name: Curate and balance the Windows-safe free suite # Six shards: ~85 s of Linux-measured work each, ~2.5 min on a # 4-vCPU Windows runner, so the lane lands near 4 min with setup. env: GSTACK_FREE_TEST_DURATIONS: scripts/free-test-durations-windows.json run: | matrix=$(bun run scripts/test-free-shards.ts --windows-only --ci-plan "$RUNNER_TEMP/windows-plan.json" --shards 6) echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-plan path: ${{ runner.temp }}/windows-plan.json if-no-files-found: error windows-free-shard: needs: windows-plan runs-on: windows-latest timeout-minutes: 15 strategy: fail-fast: false matrix: ${{ fromJSON(needs.windows-plan.outputs.matrix) }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.4.2 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with: node-version: 24.18.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: windows-plan path: ${{ runner.temp }} # bun's install cache lives under ~/.bun/install/cache on every platform. - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ~/.bun/install/cache key: windows-bun-${{ hashFiles('bun.lock') }} # A lockfile bump starts from the previous cache instead of cold. restore-keys: | windows-bun- - name: Configure git identity (required by tests that init temp repos) run: | git config --global user.email "windows-ci@gstack.test" git config --global user.name "Windows CI" git config --global init.defaultBranch main shell: bash - name: Install dependencies run: bun install --frozen-lockfile - name: Build server-node.mjs (required by Windows browse path) # browse/src/cli.ts throws at module load on Windows if server-node.mjs # is missing — Bun can't drive Playwright's Chromium on Windows # (oven-sh/bun#4253), so any test that transitively loads cli.ts needs # the Node-compatible server bundle. The full build is unnecessary. run: bash browse/scripts/build-node-server.sh shell: bash - name: Generate host SKILL.md outputs (.agents, .factory) # Golden-file tests read gitignored generated host outputs. run: bun run gen:skill-docs --host all shell: bash - name: Install Chromium for the Node worker smoke run: bunx playwright install chromium - name: Run Windows-safe shard env: # Same policy as the required Linux lane: an attributed failure gets # one serial retry (cap 5 files, truncation veto) and a clean retry # is a loud flaky pass, appended to the ledger uploaded below and # listed in the aggregate job summary. GSTACK_FREE_RETRY_FLAKY: '1' GSTACK_FLAKE_LEDGER: ${{ runner.temp }}/flake-ledger.jsonl TEMP: ${{ runner.temp }} TMP: ${{ runner.temp }} run: bun run test:windows --ci-run "$RUNNER_TEMP/windows-plan.json" --shard ${{ matrix.shard }} --result "$RUNNER_TEMP/windows-results/shard-${{ matrix.shard }}.json" shell: bash - name: Upload strict shard result if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-result-${{ matrix.shard }} path: ${{ runner.temp }}/windows-results/*.json if-no-files-found: error # A red lane must carry the WHY: the runner's quiet console names files, # not causes, so complete shard logs are kept on every run. - name: Upload full shard logs if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: windows-free-test-shard-logs-${{ matrix.shard }} path: | .context/free-test-logs/gstack-free-test-*.log ${{ runner.temp }}/gstack-free-test-*.log include-hidden-files: true if-no-files-found: ignore - name: Upload flake ledger if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: flake-ledger-windows-${{ matrix.shard }} path: ${{ runner.temp }}/flake-ledger.jsonl if-no-files-found: ignore retention-days: 90 windows-free-tests: if: ${{ always() && !inputs.record_durations }} needs: [windows-plan, windows-free-shard] runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - name: Require the plan and every Windows shard env: PLAN_RESULT: ${{ needs.windows-plan.result }} SHARD_RESULT: ${{ needs.windows-free-shard.result }} run: | set -eu test "$PLAN_RESULT" = success test "$SHARD_RESULT" = success - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.4.2 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: windows-plan path: ${{ runner.temp }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: windows-result-* merge-multiple: true path: ${{ runner.temp }}/windows-results - name: Require exact coverage and complete strict results run: bun run scripts/test-free-shards.ts --windows-only --ci-verify "$RUNNER_TEMP/windows-plan.json" --results "$RUNNER_TEMP/windows-results" windows-record-durations: if: ${{ inputs.record_durations }} runs-on: windows-latest timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.4.2 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with: node-version: 24.18.0 - name: Prepare the same inputs as a Windows shard shell: bash run: | git config --global user.email "windows-ci@gstack.test" git config --global user.name "Windows CI" git config --global init.defaultBranch main bun install --frozen-lockfile bash browse/scripts/build-node-server.sh bun run gen:skill-docs --host all bunx playwright install chromium # Records into this checkout's default seed path: GSTACK_FREE_TEST_DURATIONS # would leak into every timed test, and seed-reading tests would then read # the half-written recording instead of their fixtures. - name: Time every Windows-safe file alone shell: bash env: TEMP: ${{ runner.temp }} TMP: ${{ runner.temp }} run: bun run test:windows --record-durations - name: Name the recording as the Windows seed if: always() shell: bash run: cp scripts/free-test-durations.json "$RUNNER_TEMP/free-test-durations-windows.json" - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 if: always() with: name: free-test-durations-windows path: ${{ runner.temp }}/free-test-durations-windows.json if-no-files-found: error