Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
# KeepKey python-keepkey CI
#
# Pulls the published emulator image (kktech/kkemu) from DockerHub
# and runs the full python integration test suite against it.
#
# Stage 1: GATE (seconds)
# └─ lint basic Python syntax check
#
# Stage 2: TEST (gated by Stage 1)
# └─ integration full pytest suite against emulator

name: CI

on:
push:
branches: [master, develop, 'feature/**', 'fix/**', 'hotfix/**']
pull_request:
branches: [master, develop]

jobs:
# ═══════════════════════════════════════════════════════════
# STAGE 1: GATE
# ═══════════════════════════════════════════════════════════

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Syntax check
run: python -m py_compile keepkeylib/*.py

- name: Lint summary
run: |
echo "## 🔑 KeepKey python-keepkey — Lint" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Syntax | ✅ PASS |" >> "$GITHUB_STEP_SUMMARY"

# ═══════════════════════════════════════════════════════════
# STAGE 2: TEST — pull published emulator, run pytest
# ═══════════════════════════════════════════════════════════

integration:
needs: [lint]
runs-on: ubuntu-latest
timeout-minutes: 30

services:
kkemu:
image: kktech/kkemu:latest
ports:
- 11044:11044/udp
- 11045:11045/udp
- 5000:5000

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
pip install --upgrade pip
pip install "protobuf>=3.20,<4"
pip install -e .
pip install pytest semver rlp requests

- name: Wait for emulator
run: |
echo "Waiting for emulator bridge on port 5000..."
for i in $(seq 1 30); do
if curl -sf -X POST http://localhost:5000/exchange/main \
-H 'Content-Type: application/json' \
-d '{"data":""}' > /dev/null 2>&1; then
echo "Emulator ready after ${i}s"
break
fi
sleep 1
done

- name: Run integration tests
env:
KK_TRANSPORT_MAIN: "127.0.0.1:11044"
KK_TRANSPORT_DEBUG: "127.0.0.1:11045"
PYTHONPATH: "${{ github.workspace }}/keepkeylib:${{ github.workspace }}"
run: |
cd tests
pytest -v --junitxml=junit.xml 2>&1 | tee pytest-output.txt
echo "${PIPESTATUS[0]}" > status

- name: Test summary
if: always()
run: |
XML="tests/junit.xml"
echo "## 🔑 KeepKey python-keepkey — Integration Tests" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

if [ ! -f "$XML" ]; then
echo "❌ **No test results found** — suite may have crashed before completion." >> "$GITHUB_STEP_SUMMARY"
else
TOTAL=$(grep -oP 'tests="\K[0-9]+' "$XML" | head -1)
FAILED=$(grep -oP 'failures="\K[0-9]+' "$XML" | head -1)
ERRORS=$(grep -oP 'errors="\K[0-9]+' "$XML" | head -1)
SKIPPED=$(grep -oP 'skipped="\K[0-9]+' "$XML" | head -1)
TIME=$(grep -oP 'time="\K[0-9.]+' "$XML" | head -1)

TOTAL=${TOTAL:-0}; FAILED=${FAILED:-0}; ERRORS=${ERRORS:-0}; SKIPPED=${SKIPPED:-0}
PASSED=$((TOTAL - FAILED - ERRORS - SKIPPED))

if [ "$FAILED" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then
echo "✅ **$PASSED of $TOTAL TESTS PASSED** in ${TIME}s" >> "$GITHUB_STEP_SUMMARY"
else
echo "❌ **$((FAILED + ERRORS)) of $TOTAL TESTS FAILED**" >> "$GITHUB_STEP_SUMMARY"
fi

echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Total | $TOTAL |" >> "$GITHUB_STEP_SUMMARY"
echo "| ✅ Passed | $PASSED |" >> "$GITHUB_STEP_SUMMARY"
echo "| ⏭️ Skipped | $SKIPPED |" >> "$GITHUB_STEP_SUMMARY"
echo "| ❌ Failed | $FAILED |" >> "$GITHUB_STEP_SUMMARY"
echo "| 💥 Errors | $ERRORS |" >> "$GITHUB_STEP_SUMMARY"

# Itemize skipped with reasons
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

if [ -s /tmp/skip_rows.txt ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Skipped Tests" >> "$GITHUB_STEP_SUMMARY"
echo "| Test | Reason |" >> "$GITHUB_STEP_SUMMARY"
echo "|------|--------|" >> "$GITHUB_STEP_SUMMARY"
cat /tmp/skip_rows.txt >> "$GITHUB_STEP_SUMMARY"
fi
fi

echo "" >> "$GITHUB_STEP_SUMMARY"
echo "---" >> "$GITHUB_STEP_SUMMARY"
echo "*KeepKey python-keepkey CI*" >> "$GITHUB_STEP_SUMMARY"

- name: Upload test results
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: tests/junit.xml
check_name: Integration Tests

- name: Fail on test failure
if: always()
run: |
STATUS=$(cat tests/status 2>/dev/null || echo "1")
[ "$STATUS" = "0" ] || exit 1
8 changes: 8 additions & 0 deletions keepkeylib/eth/uniswap_tokens.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -4542,5 +4542,13 @@
"contractAddress": "0x0Ae055097C6d159879521C384F1D2123D1f195e6",
"precision": 18,
"network": "ETH"
},
{
"symbol": "WETH",
"identifier": "weth",
"displayName": "Wrapped Ether",
"contractAddress": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"precision": 18,
"network": "ETH"
}
]
4 changes: 4 additions & 0 deletions tests/test_msg_mayachain_getaddress.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@

DEFAULT_BIP32_PATH = "m/44h/931h/0h/0/0"

from keepkeylib.client import KeepKeyClient
HAS_MAYACHAIN = hasattr(KeepKeyClient, 'mayachain_get_address')

@unittest.skipUnless(HAS_MAYACHAIN, "Client library does not support MAYAChain")
class TestMsgMayaChainGetAddress(common.KeepKeyTest):

def test_mayachain_get_address(self):
Expand Down
4 changes: 4 additions & 0 deletions tests/test_msg_mayachain_signtx.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,9 @@

DEFAULT_BIP32_PATH = "m/44h/931h/0h/0/0"

from keepkeylib.client import KeepKeyClient
HAS_MAYACHAIN = hasattr(KeepKeyClient, 'mayachain_sign_tx')

def make_send(from_address, to_address, amount):
return {
'type': 'mayachain/MsgSend',
Expand All@@ -23,6 +26,7 @@ def make_send(from_address, to_address, amount):
}
}

@unittest.skipUnless(HAS_MAYACHAIN, "Client library does not support MAYAChain")
class TestMsgThorChainSignTx(common.KeepKeyTest):

def test_mayachain_sign_tx(self):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_msg_signtx_p2tr.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,7 @@
from keepkeylib.client import CallException
from keepkeylib import tx_api

HAS_P2TR = hasattr(proto_types, 'PAYTOTAPROOT')

TXHASH_5c6661 = bytes.fromhex(
"5c66611fecd82c893305ea50ed3e94cd5404cb33a6cf4bf49d1330a95fd0a046"
Expand All@@ -56,6 +57,7 @@ def request_finished() -> proto.TxRequest:
return proto.TxRequest(request_type=proto_types.TXFINISHED)


@unittest.skipUnless(HAS_P2TR, "Protobuf types do not include PAYTOTAPROOT")
class TestMsgSigntx(common.KeepKeyTest):

def test_send_p2tr_only(self):
Expand Down