Skip to content

Commit bf96906

Browse files
BitHighlanderclaude
andcommitted
feat(ci): migrate from CircleCI to GitHub Actions
Pulls published kktech/kkemu:latest emulator from DockerHub instead of rebuilding from firmware source. Runs full pytest integration suite against it with KeepKey-branded step summaries. 2-stage pipeline: - Stage 1 GATE: Python syntax check - Stage 2 TEST: Full integration suite against published emulator Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c24db05 commit bf96906

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# KeepKey python-keepkey CI
2+
#
3+
# Pulls the published emulator image (kktech/kkemu) from DockerHub
4+
# and runs the full python integration test suite against it.
5+
#
6+
# Stage 1: GATE (seconds)
7+
# └─ lint basic Python syntax check
8+
#
9+
# Stage 2: TEST (gated by Stage 1)
10+
# └─ integration full pytest suite against emulator
11+
12+
name: CI
13+
14+
on:
15+
push:
16+
branches: [master, develop, 'feature/**', 'fix/**', 'hotfix/**']
17+
pull_request:
18+
branches: [master, develop]
19+
20+
jobs:
21+
# ═══════════════════════════════════════════════════════════
22+
# STAGE 1: GATE
23+
# ═══════════════════════════════════════════════════════════
24+
25+
lint:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- uses: actions/setup-python@v5
31+
with:
32+
python-version: '3.11'
33+
34+
- name: Syntax check
35+
run: python -m py_compile keepkeylib/*.py
36+
37+
- name: Lint summary
38+
run: |
39+
echo "## 🔑 KeepKey python-keepkey — Lint" >> "$GITHUB_STEP_SUMMARY"
40+
echo "" >> "$GITHUB_STEP_SUMMARY"
41+
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
42+
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
43+
echo "| Syntax | ✅ PASS |" >> "$GITHUB_STEP_SUMMARY"
44+
45+
# ═══════════════════════════════════════════════════════════
46+
# STAGE 2: TEST — pull published emulator, run pytest
47+
# ═══════════════════════════════════════════════════════════
48+
49+
integration:
50+
needs: [lint]
51+
runs-on: ubuntu-latest
52+
timeout-minutes: 30
53+
54+
services:
55+
kkemu:
56+
image: kktech/kkemu:latest
57+
ports:
58+
- 11044:11044/udp
59+
- 11045:11045/udp
60+
- 5000:5000
61+
62+
steps:
63+
- uses: actions/checkout@v4
64+
with:
65+
submodules: recursive
66+
67+
- uses: actions/setup-python@v5
68+
with:
69+
python-version: '3.11'
70+
71+
- name: Install dependencies
72+
run: |
73+
pip install --upgrade pip
74+
pip install -e ".[ethereum]"
75+
pip install pytest semver
76+
77+
- name: Wait for emulator
78+
run: |
79+
echo "Waiting for emulator bridge on port 5000..."
80+
for i in $(seq 1 30); do
81+
if curl -sf -X POST http://localhost:5000/exchange/main \
82+
-H 'Content-Type: application/json' \
83+
-d '{"data":""}' > /dev/null 2>&1; then
84+
echo "Emulator ready after ${i}s"
85+
break
86+
fi
87+
sleep 1
88+
done
89+
90+
- name: Run integration tests
91+
env:
92+
KK_TRANSPORT_MAIN: "127.0.0.1:11044"
93+
KK_TRANSPORT_DEBUG: "127.0.0.1:11045"
94+
run: |
95+
cd tests
96+
pytest -v --junitxml=junit.xml 2>&1 | tee pytest-output.txt
97+
echo "${PIPESTATUS[0]}" > status
98+
99+
- name: Test summary
100+
if: always()
101+
run: |
102+
XML="tests/junit.xml"
103+
echo "## 🔑 KeepKey python-keepkey — Integration Tests" >> "$GITHUB_STEP_SUMMARY"
104+
echo "" >> "$GITHUB_STEP_SUMMARY"
105+
106+
if [ ! -f "$XML" ]; then
107+
echo "❌ **No test results found** — suite may have crashed before completion." >> "$GITHUB_STEP_SUMMARY"
108+
else
109+
TOTAL=$(grep -oP 'tests="\K[0-9]+' "$XML" | head -1)
110+
FAILED=$(grep -oP 'failures="\K[0-9]+' "$XML" | head -1)
111+
ERRORS=$(grep -oP 'errors="\K[0-9]+' "$XML" | head -1)
112+
SKIPPED=$(grep -oP 'skipped="\K[0-9]+' "$XML" | head -1)
113+
TIME=$(grep -oP 'time="\K[0-9.]+' "$XML" | head -1)
114+
115+
TOTAL=${TOTAL:-0}; FAILED=${FAILED:-0}; ERRORS=${ERRORS:-0}; SKIPPED=${SKIPPED:-0}
116+
PASSED=$((TOTAL - FAILED - ERRORS - SKIPPED))
117+
118+
if [ "$FAILED" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then
119+
echo "✅ **$PASSED of $TOTAL TESTS PASSED** in ${TIME}s" >> "$GITHUB_STEP_SUMMARY"
120+
else
121+
echo "❌ **$((FAILED + ERRORS)) of $TOTAL TESTS FAILED**" >> "$GITHUB_STEP_SUMMARY"
122+
fi
123+
124+
echo "" >> "$GITHUB_STEP_SUMMARY"
125+
echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY"
126+
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
127+
echo "| Total | $TOTAL |" >> "$GITHUB_STEP_SUMMARY"
128+
echo "| ✅ Passed | $PASSED |" >> "$GITHUB_STEP_SUMMARY"
129+
echo "| ⏭️ Skipped | $SKIPPED |" >> "$GITHUB_STEP_SUMMARY"
130+
echo "| ❌ Failed | $FAILED |" >> "$GITHUB_STEP_SUMMARY"
131+
echo "| 💥 Errors | $ERRORS |" >> "$GITHUB_STEP_SUMMARY"
132+
133+
# Itemize skipped with reasons
134+
python3 -c "import xml.etree.ElementTree as ET,sys;tree=ET.parse(sys.argv[1]);[print(f'| \`{tc.get(\"classname\",\"\")}.{tc.get(\"name\",\"\")}\` | {tc.find(\"skipped\").get(\"message\",tc.find(\"skipped\").text or \"No reason given\")} |') for tc in tree.iter('testcase') if tc.find('skipped') is not None]" "$XML" > /tmp/skip_rows.txt 2>/dev/null || true
135+
136+
if [ -s /tmp/skip_rows.txt ]; then
137+
echo "" >> "$GITHUB_STEP_SUMMARY"
138+
echo "### Skipped Tests" >> "$GITHUB_STEP_SUMMARY"
139+
echo "| Test | Reason |" >> "$GITHUB_STEP_SUMMARY"
140+
echo "|------|--------|" >> "$GITHUB_STEP_SUMMARY"
141+
cat /tmp/skip_rows.txt >> "$GITHUB_STEP_SUMMARY"
142+
fi
143+
fi
144+
145+
echo "" >> "$GITHUB_STEP_SUMMARY"
146+
echo "---" >> "$GITHUB_STEP_SUMMARY"
147+
echo "*KeepKey python-keepkey CI*" >> "$GITHUB_STEP_SUMMARY"
148+
149+
- name: Upload test results
150+
uses: mikepenz/action-junit-report@v4
151+
if: always()
152+
with:
153+
report_paths: tests/junit.xml
154+
check_name: Integration Tests
155+
156+
- name: Fail on test failure
157+
if: always()
158+
run: |
159+
STATUS=$(cat tests/status 2>/dev/null || echo "1")
160+
[ "$STATUS" = "0" ] || exit 1

0 commit comments

Comments
 (0)