|
7 | 7 | # └─ lint Python syntax + deterministic protocol contract tests |
8 | 8 | # |
9 | 9 | # Stage 2: TEST (gated by Stage 1) |
10 | | -# ├─ integration full pytest suite against the regular emulator |
11 | | -# └─ integration-btc bitcoin-only product boundary against a |
12 | | -# -DKK_BITCOIN_ONLY=ON emulator |
| 10 | +# ├─ integration full pytest suite against the regular emulator (7.16) |
| 11 | +# ├─ integration-btc bitcoin-only product boundary against a |
| 12 | +# │ -DKK_BITCOIN_ONLY=ON emulator (7.16) |
| 13 | +# └─ integration-rc18 the same suite against RC18/7.15.0, NON-BLOCKING -- |
| 14 | +# reports the release-target result without gating |
13 | 15 |
|
14 | 16 | name: CI |
15 | 17 |
|
@@ -312,6 +314,185 @@ jobs: |
312 | 314 | STATUS=$(cat keepkey-firmware/deps/python-keepkey/tests/status 2>/dev/null || echo "1") |
313 | 315 | [ "$STATUS" = "0" ] || exit 1 |
314 | 316 |
|
| 317 | + # ═══════════════════════════════════════════════════════════ |
| 318 | + # STAGE 2c: TEST — the RELEASE TARGET (RC18 / 7.15.0) |
| 319 | + # ═══════════════════════════════════════════════════════════ |
| 320 | + |
| 321 | + integration-rc18: |
| 322 | + needs: [lint] |
| 323 | + runs-on: ubuntu-latest |
| 324 | + timeout-minutes: 15 |
| 325 | + |
| 326 | + # NON-BLOCKING BY DESIGN, and that is a statement about evidence, not a |
| 327 | + # way to hide failures. |
| 328 | + # |
| 329 | + # The other two integration jobs pin firmware 7.16.0, because parts of |
| 330 | + # this suite need firmware that postdates RC18: variant_getName() |
| 331 | + # returning "EmulatorBTC", and the Ironwood known-answer vectors. A green |
| 332 | + # run there says nothing about the release this PR targets. |
| 333 | + # |
| 334 | + # This job closes that gap by actually running the suite against |
| 335 | + # RC18/7.15.0. It does not gate the merge yet: the 7.15-vs-7.16 delta has |
| 336 | + # never been measured at current head, so a first red run is information, |
| 337 | + # not a verdict. Promote to blocking (delete continue-on-error) once it is |
| 338 | + # green, and treat a regression from green as a real failure. |
| 339 | + # |
| 340 | + # Tests that need post-RC18 firmware skip themselves here rather than |
| 341 | + # failing: the bitcoin-only module gates on requires_bitcoinOnly(), which |
| 342 | + # keys on a variant name RC18 does not report, and the Ironwood tests gate |
| 343 | + # on 7.16.0 explicitly. |
| 344 | + continue-on-error: true |
| 345 | + |
| 346 | + steps: |
| 347 | + - uses: actions/checkout@v4 |
| 348 | + with: |
| 349 | + submodules: recursive |
| 350 | + path: python-keepkey |
| 351 | + |
| 352 | + - name: Checkout firmware (RC18) |
| 353 | + uses: actions/checkout@v4 |
| 354 | + with: |
| 355 | + repository: BitHighlander/keepkey-firmware |
| 356 | + # v7.15.0-rc18 final audit candidate -- the head of |
| 357 | + # audit/7.15.0-rc18-final, merged as firmware PR #320. This is the |
| 358 | + # release this PR is cut against. |
| 359 | + ref: fd1012c5adbacf88e2ca521a95b335a2849d528f |
| 360 | + path: keepkey-firmware |
| 361 | + |
| 362 | + - name: Init the submodules the emulator build needs |
| 363 | + working-directory: keepkey-firmware |
| 364 | + run: | |
| 365 | + git submodule update --init --depth 1 deps/crypto/trezor-firmware |
| 366 | + git submodule update --init --depth 1 deps/device-protocol |
| 367 | + git submodule update --init --depth 1 deps/googletest |
| 368 | + git submodule update --init --depth 1 deps/qrenc/QR-Code-generator |
| 369 | + git submodule update --init --depth 1 deps/sca-hardening/SecAESSTM32 |
| 370 | +
|
| 371 | + - name: Overlay this python-keepkey onto the firmware tree |
| 372 | + run: | |
| 373 | + rm -rf keepkey-firmware/deps/python-keepkey |
| 374 | + cp -a python-keepkey keepkey-firmware/deps/python-keepkey |
| 375 | +
|
| 376 | + - name: Build the RC18 emulator |
| 377 | + timeout-minutes: 20 |
| 378 | + working-directory: keepkey-firmware |
| 379 | + run: | |
| 380 | + docker build -t kkemu-rc18-ci -f scripts/emulator/Dockerfile . |
| 381 | +
|
| 382 | + - name: Start the emulator |
| 383 | + run: | |
| 384 | + docker run -d --name kkemu-rc18 \ |
| 385 | + -p 11044:11044/udp -p 11045:11045/udp -p 5000:5000 kkemu-rc18-ci |
| 386 | + sleep 3 |
| 387 | + docker logs kkemu-rc18 | head -5 |
| 388 | +
|
| 389 | + - uses: actions/setup-python@v5 |
| 390 | + with: |
| 391 | + python-version: '3.11' |
| 392 | + |
| 393 | + - name: Install dependencies |
| 394 | + working-directory: python-keepkey |
| 395 | + run: | |
| 396 | + pip install --upgrade pip |
| 397 | + pip install "protobuf>=3.20,<4" |
| 398 | + pip install -e . |
| 399 | + pip install pytest semver rlp requests eth-keys pycryptodome |
| 400 | +
|
| 401 | + - name: Wait for emulator |
| 402 | + run: | |
| 403 | + echo "Waiting for emulator bridge on port 5000..." |
| 404 | + for i in $(seq 1 30); do |
| 405 | + if curl -sf -X POST http://localhost:5000/exchange/main \ |
| 406 | + -H 'Content-Type: application/json' \ |
| 407 | + -d '{"data":""}' > /dev/null 2>&1; then |
| 408 | + echo "Emulator ready after ${i}s" |
| 409 | + break |
| 410 | + fi |
| 411 | + sleep 1 |
| 412 | + done |
| 413 | +
|
| 414 | + # Assert this really is 7.15.0. A silently-wrong ref would make this job |
| 415 | + # a duplicate of `integration` and quietly retire the gap it exists to |
| 416 | + # measure. |
| 417 | + - name: Assert the emulator is RC18 |
| 418 | + timeout-minutes: 2 |
| 419 | + env: |
| 420 | + KK_TRANSPORT_MAIN: "127.0.0.1:11044" |
| 421 | + KK_TRANSPORT_DEBUG: "127.0.0.1:11045" |
| 422 | + KK_UDP_TIMEOUT: "20" |
| 423 | + working-directory: keepkey-firmware/deps/python-keepkey/tests |
| 424 | + run: | |
| 425 | + python - <<'PY' |
| 426 | + import sys |
| 427 | + sys.path.insert(0, '..') |
| 428 | + import config |
| 429 | + from keepkeylib.client import KeepKeyDebuglinkClient |
| 430 | + c = KeepKeyDebuglinkClient(config.TRANSPORT(*config.TRANSPORT_ARGS, |
| 431 | + **config.TRANSPORT_KWARGS)) |
| 432 | + c.set_debuglink(config.DEBUG_TRANSPORT(*config.DEBUG_TRANSPORT_ARGS, |
| 433 | + **config.DEBUG_TRANSPORT_KWARGS)) |
| 434 | + c.init_device() |
| 435 | + f = c.features |
| 436 | + got = (f.major_version, f.minor_version, f.patch_version) |
| 437 | + print('emulator firmware %d.%d.%d, variant %r' % (got + (f.firmware_variant,))) |
| 438 | + if got != (7, 15, 0): |
| 439 | + sys.exit('FATAL: expected RC18 (7.15.0), got %d.%d.%d -- the pinned ' |
| 440 | + 'firmware ref is not the release target.' % got) |
| 441 | + PY |
| 442 | +
|
| 443 | + - name: Run the suite against RC18 |
| 444 | + timeout-minutes: 10 |
| 445 | + env: |
| 446 | + KK_TRANSPORT_MAIN: "127.0.0.1:11044" |
| 447 | + KK_TRANSPORT_DEBUG: "127.0.0.1:11045" |
| 448 | + PYTHONPATH: "${{ github.workspace }}/keepkey-firmware/deps/python-keepkey" |
| 449 | + KK_UDP_TIMEOUT: "45" |
| 450 | + run: | |
| 451 | + cd keepkey-firmware/deps/python-keepkey/tests |
| 452 | + pytest -v --junitxml=junit-rc18.xml 2>&1 | tee pytest-rc18-output.txt |
| 453 | + echo "${PIPESTATUS[0]}" > status-rc18 |
| 454 | +
|
| 455 | + - name: RC18 summary |
| 456 | + if: always() |
| 457 | + run: | |
| 458 | + XML="keepkey-firmware/deps/python-keepkey/tests/junit-rc18.xml" |
| 459 | + echo "## 🔑 python-keepkey — RC18 / 7.15.0 (non-blocking)" >> "$GITHUB_STEP_SUMMARY" |
| 460 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 461 | + echo "Release-target result. Does not gate the merge; see the job comment." >> "$GITHUB_STEP_SUMMARY" |
| 462 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 463 | + if [ ! -f "$XML" ]; then |
| 464 | + echo "❌ **No test results** — the suite crashed before completion." >> "$GITHUB_STEP_SUMMARY" |
| 465 | + else |
| 466 | + TOTAL=$(grep -oP 'tests="\K[0-9]+' "$XML" | head -1) |
| 467 | + FAILED=$(grep -oP 'failures="\K[0-9]+' "$XML" | head -1) |
| 468 | + ERRORS=$(grep -oP 'errors="\K[0-9]+' "$XML" | head -1) |
| 469 | + SKIPPED=$(grep -oP 'skipped="\K[0-9]+' "$XML" | head -1) |
| 470 | + TOTAL=${TOTAL:-0}; FAILED=${FAILED:-0}; ERRORS=${ERRORS:-0}; SKIPPED=${SKIPPED:-0} |
| 471 | + PASSED=$((TOTAL - FAILED - ERRORS - SKIPPED)) |
| 472 | + echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY" |
| 473 | + echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY" |
| 474 | + echo "| Total | $TOTAL |" >> "$GITHUB_STEP_SUMMARY" |
| 475 | + echo "| ✅ Passed | $PASSED |" >> "$GITHUB_STEP_SUMMARY" |
| 476 | + echo "| ⏭️ Skipped | $SKIPPED |" >> "$GITHUB_STEP_SUMMARY" |
| 477 | + echo "| ❌ Failed | $FAILED |" >> "$GITHUB_STEP_SUMMARY" |
| 478 | + echo "| 💥 Errors | $ERRORS |" >> "$GITHUB_STEP_SUMMARY" |
| 479 | + echo "" >> "$GITHUB_STEP_SUMMARY" |
| 480 | + if [ "$FAILED" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then |
| 481 | + echo "RC18 is green at current head — this job can be promoted to blocking by deleting \`continue-on-error\`." >> "$GITHUB_STEP_SUMMARY" |
| 482 | + else |
| 483 | + echo "RC18 differs from 7.16 at current head. Each failure is either a real RC18 regression or a test that needs a version gate." >> "$GITHUB_STEP_SUMMARY" |
| 484 | + fi |
| 485 | + fi |
| 486 | +
|
| 487 | + - name: Annotate RC18 results |
| 488 | + uses: mikepenz/action-junit-report@v4 |
| 489 | + if: always() |
| 490 | + with: |
| 491 | + report_paths: keepkey-firmware/deps/python-keepkey/tests/junit-rc18.xml |
| 492 | + annotate_only: true |
| 493 | + require_tests: true |
| 494 | + fail_on_failure: false |
| 495 | + |
315 | 496 | # ═══════════════════════════════════════════════════════════ |
316 | 497 | # STAGE 2b: TEST — the OTHER shipping product |
317 | 498 | # ═══════════════════════════════════════════════════════════ |
|
0 commit comments