|
| 1 | +name: test262 |
| 2 | + |
| 3 | +on: |
| 4 | + push: |
| 5 | + pull_request: |
| 6 | + |
| 7 | + # 允許手動觸發,可以選擇跑哪些測試 |
| 8 | + workflow_dispatch: |
| 9 | + inputs: |
| 10 | + test_pattern: |
| 11 | + description: 'Glob pattern for tests to run' |
| 12 | + required: false |
| 13 | + default: 'test/built-ins/**/*.js test/language/**/*.js' |
| 14 | + skip_features: |
| 15 | + description: 'Comma-separated features to skip' |
| 16 | + required: false |
| 17 | + default: '' |
| 18 | + |
| 19 | +# 同一 PR 只保留最新一次 CI |
| 20 | +concurrency: |
| 21 | + group: ${{ github.workflow }}-${{ github.ref }} |
| 22 | + cancel-in-progress: true |
| 23 | + |
| 24 | +env: |
| 25 | + CARGO_TERM_COLOR: always |
| 26 | + # ↓↓↓ 修改這裡:你的引擎 cargo package 名稱 ↓↓↓ |
| 27 | + ENGINE_BIN_NAME: js |
| 28 | + |
| 29 | +jobs: |
| 30 | + build-engine: |
| 31 | + name: Build Engine |
| 32 | + runs-on: ubuntu-latest |
| 33 | + steps: |
| 34 | + - name: Checkout engine |
| 35 | + uses: actions/checkout@v6 |
| 36 | + |
| 37 | + - name: Setup Rust toolchain |
| 38 | + uses: dtolnay/rust-toolchain@stable |
| 39 | + with: |
| 40 | + components: rustfmt, clippy |
| 41 | + |
| 42 | + - name: Cache cargo |
| 43 | + uses: actions/cache@v4 |
| 44 | + with: |
| 45 | + path: | |
| 46 | + ~/.cargo/bin/ |
| 47 | + ~/.cargo/registry/index/ |
| 48 | + ~/.cargo/registry/cache/ |
| 49 | + ~/.cargo/git/db/ |
| 50 | + target/ |
| 51 | + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} |
| 52 | + restore-keys: ${{ runner.os }}-cargo- |
| 53 | + |
| 54 | + - name: Build engine (release) |
| 55 | + run: cargo build --release -p ${{ env.ENGINE_BIN_NAME }} |
| 56 | + |
| 57 | + - name: Stage artifacts |
| 58 | + run: | |
| 59 | + mkdir -p staging |
| 60 | + cp target/release/${{ env.ENGINE_BIN_NAME }} staging/ |
| 61 | + cp run.py staging/ |
| 62 | +
|
| 63 | + - name: Upload engine binary + runner |
| 64 | + uses: actions/upload-artifact@v4 |
| 65 | + with: |
| 66 | + name: engine-binary |
| 67 | + path: staging/ |
| 68 | + retention-days: 1 |
| 69 | + |
| 70 | + test262: |
| 71 | + name: test262 |
| 72 | + needs: build-engine |
| 73 | + runs-on: ubuntu-latest |
| 74 | + # 不讓測試失敗阻擋整個 workflow,這樣能看到完整報告 |
| 75 | + continue-on-error: true |
| 76 | + strategy: |
| 77 | + fail-fast: false |
| 78 | + matrix: |
| 79 | + # ── 按模組拆分並行跑,加速 CI ── |
| 80 | + suite: |
| 81 | + # language 拆成 4 個任務:2 個慢的 dstr 單獨跑,其餘分兩組 |
| 82 | + - name: lang-expr-dstr |
| 83 | + pattern: "test/language/expressions/class/dstr/**/*.js" |
| 84 | + exclude: "" |
| 85 | + - name: lang-stmt-dstr |
| 86 | + pattern: "test/language/statements/class/dstr/**/*.js" |
| 87 | + exclude: "" |
| 88 | + - name: lang-expressions |
| 89 | + pattern: "test/language/expressions/**/*.js" |
| 90 | + exclude: "test/language/expressions/class/dstr/**/*.js" |
| 91 | + - name: lang-other |
| 92 | + pattern: "test/language/**/*.js" |
| 93 | + exclude: "test/language/expressions/**/*.js,test/language/statements/class/dstr/**/*.js" |
| 94 | + - name: built-ins-A-E |
| 95 | + pattern: "test/built-ins/[A-E]*/**/*.js" |
| 96 | + exclude: "" |
| 97 | + - name: built-ins-F-O |
| 98 | + pattern: "test/built-ins/[F-O]*/**/*.js" |
| 99 | + exclude: "" |
| 100 | + - name: built-ins-P-Z |
| 101 | + pattern: "test/built-ins/[P-Z]*/**/*.js" |
| 102 | + exclude: "" |
| 103 | + - name: annexB |
| 104 | + pattern: "test/annexB/**/*.js" |
| 105 | + exclude: "" |
| 106 | + |
| 107 | + steps: |
| 108 | + - name: Download engine binary |
| 109 | + uses: actions/download-artifact@v4 |
| 110 | + with: |
| 111 | + name: engine-binary |
| 112 | + path: ./engine |
| 113 | + |
| 114 | + - name: Make engine executable |
| 115 | + run: chmod +x ./engine/${{ env.ENGINE_BIN_NAME }} |
| 116 | + |
| 117 | + - name: Checkout test262 |
| 118 | + uses: actions/checkout@v6 |
| 119 | + with: |
| 120 | + repository: tc39/test262 |
| 121 | + path: test262 |
| 122 | + fetch-depth: 1 |
| 123 | + |
| 124 | + - name: Copy runner script |
| 125 | + run: cp ./engine/run.py test262/run.py |
| 126 | + |
| 127 | + - name: Setup Python |
| 128 | + uses: actions/setup-python@v5 |
| 129 | + with: |
| 130 | + python-version: '3.12' |
| 131 | + |
| 132 | + - name: Install Python dependencies |
| 133 | + run: pip install pyyaml |
| 134 | + |
| 135 | + - name: Run test262 — ${{ matrix.suite.name }} |
| 136 | + id: run-tests |
| 137 | + working-directory: test262 |
| 138 | + env: |
| 139 | + # 你的引擎不支持的特性,在這裡跳過 |
| 140 | + SKIP_FEATURES: >- |
| 141 | + Atomics,SharedArrayBuffer, |
| 142 | + IsHTMLDDA, |
| 143 | + resizable-arraybuffer, |
| 144 | + Temporal, |
| 145 | + import-defer, |
| 146 | + source-phase-imports, |
| 147 | + source-phase-imports-module-source, |
| 148 | + tail-call-optimization |
| 149 | + TEST_PATTERN: ${{ github.event.inputs.test_pattern || matrix.suite.pattern }} |
| 150 | + EXCLUDE_PATTERN: ${{ matrix.suite.exclude }} |
| 151 | + SKIP_OVERRIDE: ${{ github.event.inputs.skip_features }} |
| 152 | + run: | |
| 153 | + SKIP="${SKIP_OVERRIDE:-$SKIP_FEATURES}" |
| 154 | + EXCLUDE_ARG="" |
| 155 | + if [ -n "$EXCLUDE_PATTERN" ]; then |
| 156 | + EXCLUDE_ARG="--exclude $EXCLUDE_PATTERN" |
| 157 | + fi |
| 158 | + python3 run.py \ |
| 159 | + --engine "${{ github.workspace }}/engine/${{ env.ENGINE_BIN_NAME }}" \ |
| 160 | + --jobs 4 \ |
| 161 | + --timeout 30 \ |
| 162 | + --failures-only \ |
| 163 | + --gh-annotations \ |
| 164 | + --json results-${{ matrix.suite.name }}.json \ |
| 165 | + --summary summary-${{ matrix.suite.name }}.md \ |
| 166 | + --skip-features "$SKIP" \ |
| 167 | + $EXCLUDE_ARG \ |
| 168 | + "$TEST_PATTERN" |
| 169 | + # 即使測試失敗也繼續,這樣能上傳報告 |
| 170 | + continue-on-error: true |
| 171 | + |
| 172 | + - name: Write job summary |
| 173 | + if: always() |
| 174 | + working-directory: test262 |
| 175 | + run: | |
| 176 | + if [ -f summary-${{ matrix.suite.name }}.md ]; then |
| 177 | + cat summary-${{ matrix.suite.name }}.md >> "$GITHUB_STEP_SUMMARY" |
| 178 | + fi |
| 179 | +
|
| 180 | + - name: Upload test results |
| 181 | + if: always() |
| 182 | + uses: actions/upload-artifact@v4 |
| 183 | + with: |
| 184 | + name: test262-results-${{ matrix.suite.name }} |
| 185 | + path: | |
| 186 | + test262/results-*.json |
| 187 | + test262/summary-*.md |
| 188 | + retention-days: 30 |
| 189 | + |
| 190 | + # ── 匯總所有分片結果 ── |
| 191 | + report: |
| 192 | + name: Aggregate Results |
| 193 | + needs: test262 |
| 194 | + if: always() |
| 195 | + runs-on: ubuntu-latest |
| 196 | + steps: |
| 197 | + - name: Download all results |
| 198 | + uses: actions/download-artifact@v4 |
| 199 | + with: |
| 200 | + pattern: test262-results-* |
| 201 | + merge-multiple: true |
| 202 | + |
| 203 | + - name: Aggregate and report |
| 204 | + run: | |
| 205 | + echo "## test262 Overall Results" >> "$GITHUB_STEP_SUMMARY" |
| 206 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 207 | +
|
| 208 | + TOTAL_PASS=0 |
| 209 | + TOTAL_FAIL=0 |
| 210 | + TOTAL_SKIP=0 |
| 211 | + TOTAL_TIMEOUT=0 |
| 212 | +
|
| 213 | + for f in results-*.json; do |
| 214 | + if [ -f "$f" ]; then |
| 215 | + SUITE=$(basename "$f" .json | sed 's/results-//') |
| 216 | + PASS=$(python3 -c "import json; d=json.load(open('$f')); print(d['summary']['pass'])") |
| 217 | + FAIL=$(python3 -c "import json; d=json.load(open('$f')); print(d['summary']['fail'])") |
| 218 | + SKIP=$(python3 -c "import json; d=json.load(open('$f')); print(d['summary']['skip'])") |
| 219 | + TMO=$(python3 -c "import json; d=json.load(open('$f')); print(d['summary']['timeout'])") |
| 220 | + TOTAL_PASS=$((TOTAL_PASS + PASS)) |
| 221 | + TOTAL_FAIL=$((TOTAL_FAIL + FAIL)) |
| 222 | + TOTAL_SKIP=$((TOTAL_SKIP + SKIP)) |
| 223 | + TOTAL_TIMEOUT=$((TOTAL_TIMEOUT + TMO)) |
| 224 | + echo " $SUITE: $PASS pass, $FAIL fail, $SKIP skip, $TMO timeout" |
| 225 | + fi |
| 226 | + done |
| 227 | +
|
| 228 | + TOTAL=$((TOTAL_PASS + TOTAL_FAIL + TOTAL_SKIP + TOTAL_TIMEOUT)) |
| 229 | + TESTED=$((TOTAL - TOTAL_SKIP)) |
| 230 | + if [ "$TESTED" -gt 0 ]; then |
| 231 | + RATE=$(python3 -c "print(f'{$TOTAL_PASS/$TESTED*100:.1f}')") |
| 232 | + else |
| 233 | + RATE="N/A" |
| 234 | + fi |
| 235 | +
|
| 236 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 237 | + echo "| | Count |" >> "$GITHUB_STEP_SUMMARY" |
| 238 | + echo "|---|---|" >> "$GITHUB_STEP_SUMMARY" |
| 239 | + echo "| :white_check_mark: Pass | $TOTAL_PASS |" >> "$GITHUB_STEP_SUMMARY" |
| 240 | + echo "| :x: Fail | $TOTAL_FAIL |" >> "$GITHUB_STEP_SUMMARY" |
| 241 | + echo "| :fast_forward: Skip | $TOTAL_SKIP |" >> "$GITHUB_STEP_SUMMARY" |
| 242 | + echo "| :hourglass: Timeout | $TOTAL_TIMEOUT |" >> "$GITHUB_STEP_SUMMARY" |
| 243 | + echo "| **Total** | **$TOTAL** |" >> "$GITHUB_STEP_SUMMARY" |
| 244 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 245 | + echo "**Overall pass rate: ${RATE}%**" >> "$GITHUB_STEP_SUMMARY" |
| 246 | +
|
| 247 | + echo "" |
| 248 | + echo "=============================" |
| 249 | + echo " PASS: $TOTAL_PASS" |
| 250 | + echo " FAIL: $TOTAL_FAIL" |
| 251 | + echo " SKIP: $TOTAL_SKIP" |
| 252 | + echo " TIMEOUT: $TOTAL_TIMEOUT" |
| 253 | + echo " Pass rate: ${RATE}%" |
| 254 | + echo "=============================" |
| 255 | +
|
| 256 | + # 如果有失敗,設定 exit code |
| 257 | + if [ "$TOTAL_FAIL" -gt 0 ]; then |
| 258 | + echo "::warning::$TOTAL_FAIL test(s) failed" |
| 259 | + fi |
0 commit comments