From 4ff96800d28908d04d162254bf28c61e28129136 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Fri, 12 Jun 2026 14:53:51 +0800 Subject: [PATCH] ci: pin android-emulator-runner by SHA + shard the slow PR test job (#4547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci(security): pin android-emulator-runner action by commit SHA Scorecard Pinned-Dependencies flagged the two reactivecircus/android-emulator-runner@v2 usages in android-e2e.yml as third-party actions not pinned by hash (code-scanning alerts #116, #117). Pin both to the full commit SHA the v2 tag currently resolves to (e89f39f = v2.37.0), matching the @ # convention already used by every other action in this workflow. Co-Authored-By: Claude Opus 4.8 (1M context) * ci(pull-request): shard web unit tests and split out test_extensions The jsdom unit suite was ~115s of the 280s test_web_app job — the slowest check on every PR. Split it across two parallel shards with vitest --shard, and move the browser-extension + koplugin tests into a new test_extensions job. - test_web_app: matrix shard [1, 2] running `vitest run --shard=N/2`; Playwright + browser tests run on shard 1 only. - test_extensions: extension tests + browser-ext build run always; the koplugin Lua tests (and their ~45s LuaJIT/busted install) run only when apps/readest.koplugin/** changed, detected via dorny/paths-filter (pinned by SHA; needs pull-requests: read to list PR files). - package.json: add test:pr:web:unit so CI can append --shard; test:pr:web still runs the full sequence locally. Cuts the PR critical path from ~280s toward ~188s (now build_tauri_app). Co-Authored-By: Claude Opus 4.8 (1M context) * ci(pull-request): isolate koplugin lint + LuaJIT install into test_extensions build_web_app was the slowest PR job and installed LuaJIT + ran the koplugin syntax check on every PR. Move all koplugin tooling into test_extensions, gated on apps/readest.koplugin/** like the koplugin Lua tests already are: - build_web_app drops the LuaJIT install. - test_extensions installs LuaJIT/busted and runs `pnpm lint:lua` + `pnpm test:lua` only when the koplugin sources changed. - `pnpm lint` is now web-only (tsgo + biome); `lint:lua` stays a standalone script that test_extensions (and local koplugin work) calls directly. This also drops koplugin lint from the pre-push hook. - verification rule updated to match. Most PRs now skip the koplugin toolchain entirely. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/android-e2e.yml | 4 +- .github/workflows/pull-request.yml | 117 ++++++++++++++---- .../readest-app/.claude/rules/verification.md | 4 +- apps/readest-app/package.json | 5 +- 4 files changed, 100 insertions(+), 30 deletions(-) diff --git a/.github/workflows/android-e2e.yml b/.github/workflows/android-e2e.yml index 32725339..b1b6df22 100644 --- a/.github/workflows/android-e2e.yml +++ b/.github/workflows/android-e2e.yml @@ -107,7 +107,7 @@ jobs: - name: create AVD snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 with: api-level: 34 arch: x86_64 @@ -118,7 +118,7 @@ jobs: script: echo "AVD snapshot created" - name: run Android e2e lane - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 with: api-level: 34 arch: x86_64 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 25ee4805..d00a1ba2 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -77,13 +77,13 @@ jobs: run: | pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors - - name: install LuaJIT (for koplugin lint) - run: sudo apt-get update && sudo apt-get install -y luajit - - name: run format check run: | pnpm format:check || (pnpm format && git diff && exit 1) + # pnpm lint here is web-only (tsgo + biome). The koplugin syntax check + # (lint:lua) runs in the test_extensions job, which installs LuaJIT only + # when the koplugin sources changed. - name: run lint working-directory: apps/readest-app run: | @@ -123,8 +123,16 @@ jobs: path: apps/readest-app/playwright-report/ retention-days: 7 + # The jsdom unit suite is the slowest part of the PR checks, so it is split + # across two parallel shards (vitest --shard). The browser tests need + # Playwright and run only on shard 1; koplugin + browser-extension tests + # moved to the test_extensions job. test_web_app: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shard: [1, 2] steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: @@ -144,16 +152,91 @@ jobs: run: | pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors + # Playwright is only needed by the browser tests, which run on shard 1. + - name: cache playwright browsers + if: matrix.shard == 1 + id: playwright-cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + + - name: install playwright browsers + if: matrix.shard == 1 + working-directory: apps/readest-app + run: | + if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then + npx playwright install-deps chromium + else + npx playwright install --with-deps chromium + fi + + - name: run web unit tests (shard ${{ matrix.shard }}/2) + working-directory: apps/readest-app + run: pnpm test:pr:web:unit --shard=${{ matrix.shard }}/2 + + - name: run web browser tests + if: matrix.shard == 1 + working-directory: apps/readest-app + run: pnpm test:browser + + # Browser-extension tests + build always run. The koplugin lint + Lua tests + # (and the ~45s LuaJIT/busted install they need) only run when the koplugin + # sources changed, so most PRs skip that cost entirely. + test_extensions: + runs-on: ubuntu-latest + # pull-requests: read lets dorny/paths-filter list a PR's changed files + # via the REST API (the top-level grant is contents: read only). + permissions: + contents: read + pull-requests: read + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + submodules: 'true' + + - name: detect koplugin changes + id: changes + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4 + with: + filters: | + koplugin: + - 'apps/readest.koplugin/**' + + - name: setup pnpm + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6 + + - name: setup node + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + cache: pnpm + + - name: install Dependencies + working-directory: apps/readest-app + run: | + pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors + + - name: run extension tests + working-directory: apps/readest-app + run: pnpm test:extension + + - name: build browser extension + working-directory: apps/readest-app + run: pnpm build-browser-ext + - name: cache apt packages + if: steps.changes.outputs.koplugin == 'true' uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: /var/cache/apt/archives - key: apt-test-web-${{ runner.os }} + key: apt-test-koplugin-${{ runner.os }} - - name: install LuaJIT + busted (for koplugin tests) + - name: install LuaJIT + busted (for koplugin lint + tests) + if: steps.changes.outputs.koplugin == 'true' run: | sudo apt-get update - # luajit — pnpm test:lua + # luajit — pnpm lint:lua + pnpm test:lua # luarocks/libsqlite3-dev — required to build lsqlite3complete sudo apt-get install -y luajit luarocks libsqlite3-dev # Install busted + the SQLite binding the LibraryStore specs use, @@ -163,27 +246,13 @@ jobs: sudo luarocks --lua-version=5.1 install busted sudo luarocks --lua-version=5.1 install lsqlite3complete - - name: cache playwright browsers - id: playwright-cache - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 - with: - path: ~/.cache/ms-playwright - key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - - - name: install playwright browsers + - name: lint koplugin + if: steps.changes.outputs.koplugin == 'true' working-directory: apps/readest-app - run: | - if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then - npx playwright install-deps chromium - else - npx playwright install --with-deps chromium - fi - - - name: run web tests - working-directory: apps/readest-app - run: pnpm test:pr:web + run: pnpm lint:lua - name: run koplugin tests + if: steps.changes.outputs.koplugin == 'true' working-directory: apps/readest-app run: pnpm test:lua diff --git a/apps/readest-app/.claude/rules/verification.md b/apps/readest-app/.claude/rules/verification.md index 90f1ba49..cd74bd92 100644 --- a/apps/readest-app/.claude/rules/verification.md +++ b/apps/readest-app/.claude/rules/verification.md @@ -3,8 +3,8 @@ Before marking work complete, all applicable checks must pass: 1. `pnpm test` — unit tests (vitest) -2. `pnpm lint` — Biome + tsgo (also runs `pnpm lint:lua` if luajit is installed) -3. `pnpm test:lua` — busted unit tests for `apps/readest.koplugin/spec/` (only when koplugin Lua files changed; soft-skips when busted/luajit not installed) +2. `pnpm lint` — Biome + tsgo (web only) +3. `pnpm lint:lua` + `pnpm test:lua` — koplugin LuaJIT syntax check + busted unit tests for `apps/readest.koplugin/spec/` (only when koplugin Lua files changed; soft-skip when luajit/busted not installed) 4. `pnpm fmt:check` — Rust format check (only when `src-tauri/` files changed) 5. `pnpm clippy:check` — Rust lint (only when `src-tauri/` files changed) 6. `pnpm test:rust` — Rust unit tests (`cargo test -p Readest --lib`; only when `src-tauri/` files changed); also run in the CI `rust_lint` job diff --git a/apps/readest-app/package.json b/apps/readest-app/package.json index db38425d..bd8d2d91 100644 --- a/apps/readest-app/package.json +++ b/apps/readest-app/package.json @@ -19,7 +19,7 @@ "dev-ios": "tauri ios build -- --features devtools && ideviceinstaller install src-tauri/gen/apple/build/arm64/Readest.ipa", "i18n:extract": "i18next-scanner --config i18next-scanner.config.cjs", "i18n:extract:ext": "pnpm --filter @readest/send-to-readest-extension i18n:extract", - "lint": "tsgo --noEmit && biome lint . && pnpm lint:lua", + "lint": "tsgo --noEmit && biome lint .", "lint:lua": "node ../readest.koplugin/scripts/lint-koplugin.mjs", "test:lua": "node ../readest.koplugin/scripts/test-koplugin.mjs", "test": "dotenv -e .env -e .env.test.local -- vitest", @@ -28,7 +28,8 @@ "test:browser": "dotenv -e .env -e .env.test.local -- vitest run --config vitest.browser.config.mts", "test:tauri": "bash scripts/test-tauri.sh", "test:android": "bash scripts/test-android.sh", - "test:pr:web": "dotenv -e .env -e .env.test.local -- vitest run --maxWorkers=4 && pnpm test:browser && pnpm test:extension && pnpm build-browser-ext", + "test:pr:web:unit": "dotenv -e .env -e .env.test.local -- vitest run --maxWorkers=4", + "test:pr:web": "pnpm test:pr:web:unit && pnpm test:browser && pnpm test:extension && pnpm build-browser-ext", "test:pr:tauri": "bash scripts/test-tauri.sh", "test:all": "pnpm test -- --watch=false && pnpm test:browser && bash scripts/test-tauri.sh", "bench": "node --experimental-strip-types --no-warnings bench/index.ts",