Skip to content

Commit

Permalink
ci: move hardware tests over to github actions
Browse files Browse the repository at this point in the history
[skip_ci]
  • Loading branch information
mmilata committed Feb 13, 2024
1 parent 9414082 commit 1acd8f6
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 209 deletions.
137 changes: 137 additions & 0 deletions .github/workflows/core-hw.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Hardware tests

on:
schedule:
- cron: '15 23 * * *' # every day @ 23:15
workflow_dispatch:
pull_request: # TODO remove

# [Device tests](../tests/device-tests.md) that run against an actual physical Trezors.
# The device needs to have special bootloader, found in `core/embed/bootloader_ci`, that
# makes it possible to flash firmware without confirmation on the touchscreen.
#
# All hardware tests are run nightly on the `main` branch,
# and also can be started manually.
#
# Currently it's not possible to run all regular TT tests without getting into
# a state where the micropython heap is too fragmented and allocations fail
# (often manifesting as a stuck test case). For that reason some tests are
# skipped.
# See also: https://github.com/trezor/trezor-firmware/issues/1371
jobs:
core_device_test:
name: Device tests
runs-on:
- self-hosted
- ${{ matrix.model == 'T2B1' && 'hw-t2b1' || 'hw-t2t1' }}
strategy:
fail-fast: false
matrix:
model: [T2T1, T2B1]
coins: [universal, btconly]
env:
TREZOR_MODEL: ${{ matrix.model == 'T2T1' && 'T' || 'R' }}
TREZOR_PYTEST_SKIP_ALTCOINS: ${{ matrix.coins == 'btconly' && '1' || '0' }}
PYTEST_TIMEOUT: 1200
PYOPT: 0
DISABLE_OPTIGA: 1
BOOTLOADER_DEVEL: ${{ matrix.model == 'T2B1' && '1' || '0' }}
TESTOPTS: "-k 'not authenticate and not recovery and not lots'"
TT_UHUB_PORT: 1
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: ./.github/actions/environment
- run: nix-shell --arg hardwareTest true --run uhubctl
- run: nix-shell --run "poetry run make -C core build_firmware"
- run: nix-shell --arg hardwareTest true --run "poetry run python ci/hardware_tests/bootstrap.py ${{ matrix.model }} core/build/firmware/firmware.bin"
- run: nix-shell --run "poetry run trezorctl list"
- run: nix-shell --run "poetry run trezorctl get-features"
- run: |
nix-shell --arg hardwareTest true --run "ls -l /dev/tty*"
# log serial console to file; sleep is used because tio needs stdin that is not /dev/null
nix-shell --arg hardwareTest true --run "sleep 8h | tio --no-autoconnect /dev/ttyTREZOR &> trezor.log" &
nix-shell --run "poetry run pytest -v tests/device_tests"
- run: tail -n50 trezor.log || true
if: failure()
- uses: actions/upload-artifact@v3
with:
name: core-hardware-${{ matrix.model }}
path: trezor.log
retention-days: 7
if: always()

core_monero_test:
name: Monero tests
runs-on:
- self-hosted
- ${{ matrix.model == 'T2B1' && 'hw-t2b1' || 'hw-t2t1' }}
strategy:
fail-fast: false
matrix:
model: [T2T1, T2B1]
env:
TREZOR_MODEL: ${{ matrix.model == 'T2T1' && 'T' || 'R' }}
PYTEST_TIMEOUT: 1200
PYOPT: 0
DISABLE_OPTIGA: 1
BOOTLOADER_DEVEL: ${{ matrix.model == 'T2B1' && '1' || '0' }}
TT_UHUB_PORT: 1
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: ./.github/actions/environment
- run: nix-shell --arg hardwareTest true --run uhubctl
- run: nix-shell --run "poetry run make -C core build_firmware"
- run: nix-shell --arg hardwareTest true --run "poetry run python ci/hardware_tests/bootstrap.py ${{ matrix.model }} core/build/firmware/firmware.bin"
- run: nix-shell --run "poetry run trezorctl list"
- run: nix-shell --run "poetry run trezorctl get-features"
- run: |
nix-shell --arg hardwareTest true --run "ls -l /dev/tty*"
# log serial console to file; sleep is used because tio needs stdin that is not /dev/null
nix-shell --arg hardwareTest true --run "sleep 8h | tio --no-autoconnect /dev/ttyTREZOR &> trezor.log" &
nix-shell --arg fullDeps true --run "cd ../../core/tests && ./run_tests_device_emu_monero.sh $TESTOPTS"
- run: tail -n50 trezor.log || true
if: failure()
- uses: actions/upload-artifact@v3
with:
name: core-hardware-${{ matrix.model }}
path: trezor.log
retention-days: 7
if: always()

legacy_device_test:
name: Device tests T1B1
runs-on:
- self-hosted
- hw-t1b1
strategy:
fail-fast: false
matrix:
coins: [universal, btconly]
env:
TREZOR_PYTEST_SKIP_ALTCOINS: ${{ matrix.coins == 'btconly' && '1' || '0' }}
PYTEST_TIMEOUT: 1200
T1_UHUB_LOCATION: 3-1
T1_UHUB_PORT: 2
T1_CAMERA: /dev/video0 # camera device
T1_ARDUINO_SERIAL: /dev/ttyTPMB # arduino that pushes T1 buttons
BITCOIN_ONLY: ${{ matrix.coins == 'universal' && '0' || '1' }}
DEBUG_LINK: 1
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: ./.github/actions/environment
- run: nix-shell --arg hardwareTest true --run uhubctl
- run: nix-shell --run "poetry run legacy/script/setup"
- run: nix-shell --run "export PRODUCTION=0 && poetry run legacy/script/cibuild"
- run: nix-shell --arg hardwareTest true --run "ci/hardware_tests/t1_hw_test.sh"
- uses: actions/[email protected]
with:
name: legacy-hardware-${{ matrix.model }}
path: ci/hardware_tests/*.mp4
retention-days: 7
if: always()
1 change: 0 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ include:
- ci/prebuild.yml
- ci/build.yml
- ci/test.yml
- ci/test-hw.yml
- ci/test-nonenglish.yml
- ci/posttest.yml
- ci/deploy.yml
28 changes: 17 additions & 11 deletions ci/hardware_tests/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import os
import sys

from device.t1 import TrezorOne
from device.tt import TrezorT
from device.legacy import TrezorOne
from device.core import TrezorCore

# https://www.uugear.com/product/mega4-4-port-usb-3-ppps-hub-for-raspberry-pi-4b/
# as long as every runner has this hub we don't have to configure a per-runner hub location
HUB_VENDOR = "2109:2817"


def main(model: str, file: str = None):
t1 = TrezorOne(
os.environ["T1_UHUB_LOCATION"],
os.environ["T1_ARDUINO_SERIAL"],
os.environ["T1_UHUB_PORT"],
os.getenv("T1_UHUB_LOCATION"),
os.getenv("T1_ARDUINO_SERIAL"),
os.getenv("T1_UHUB_PORT"),
)
tt = TrezorT(os.environ["TT_UHUB_LOCATION"], os.environ["TT_UHUB_PORT"])
tt = TrezorCore(hub_vendor=HUB_VENDOR, device_port=os.getenv("TT_UHUB_PORT"))

if model == "t1":
tt.power_off()
if model == "T1B1":
# tt.power_off()
path = t1.update_firmware(file)
elif model == "tt":
t1.power_off()
path = tt.update_firmware(file)
elif model == "T2T1":
# t1.power_off()
path = tt.update_firmware(file, "Trezor T")
elif model == "T2B1":
path = tt.update_firmware(file, "Safe 3")
else:
raise ValueError("Unknown Trezor model.")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from .device import Device


class TrezorT(Device):
def update_firmware(self, file=None):
class TrezorCore(Device):
def update_firmware(self, file=None, model_name="Trezor T"):
if not file:
raise ValueError(
"Uploading production firmware will replace the bootloader, it is not allowed!"
)

# reset to enter bootloader again
self.power_off()
self.wait(5)
self.power_on()

self.wait(5)
self.check_model("Trezor T bootloader")
self.wait(10)
self.check_model("bootloader")

self.run_trezorctl("device wipe --bootloader || true")
self.wait(5)
Expand All @@ -26,4 +27,4 @@ def update_firmware(self, file=None):

# after firmware-update finishes wait for reboot
self.wait(15)
return self.check_model("Trezor T")
return self.check_model(model_name)
13 changes: 10 additions & 3 deletions ci/hardware_tests/device/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@


class Device:
def __init__(self, uhub_location, device_port):
def __init__(self, *, uhub_location=None, hub_vendor=None, device_port=None):
self.uhub_location = uhub_location
self.hub_vendor = hub_vendor
self.device_port = device_port

@staticmethod
Expand Down Expand Up @@ -35,11 +36,17 @@ def reboot(self):
self.power_off()
self.power_on()

def _hub(self):
if self.hub_vendor:
return f"--vendor {self.hub_vendor}"
else:
return f"-l {self.uhub_location}"

def power_on(self):
self.now()
self.log("[hardware/usb] Turning power on...")
run(
f"uhubctl -l {self.uhub_location} -p {self.device_port} -a on",
f"uhubctl {self._hub()} -p {self.device_port} -a on",
shell=True,
check=True,
)
Expand All @@ -49,7 +56,7 @@ def power_off(self):
self.now()
self.log("[hardware/usb] Turning power off...")
run(
f"uhubctl -l {self.uhub_location} -p {self.device_port} -r 100 -a off",
f"uhubctl {self._hub()} -p {self.device_port} -r 5 -a off",
shell=True,
check=True,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

class TrezorOne(Device):
def __init__(self, uhub_location, arduino_serial, device_port):
super().__init__(uhub_location, device_port)
super().__init__(uhub_location=uhub_location, device_port=device_port)
self.serial = serial.Serial(arduino_serial, 9600)

def touch(self, location, action):
Expand Down Expand Up @@ -41,7 +41,7 @@ def update_firmware(self, file=None):
self.touch("right", "click")
self.wait(5)
self.touch("right", "click")
self.wait(10)
self.wait(15)
return self.check_model("Trezor 1")

def _enter_bootloader(self):
Expand Down
16 changes: 0 additions & 16 deletions ci/hardware_tests/hardware.cfg

This file was deleted.

2 changes: 1 addition & 1 deletion ci/hardware_tests/record_video.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ OUTPUTFILE=video_${COMMIT}_$(date +%s).mp4

if [ "$ACTION" == "start" ]; then
echo "[software/video] Starting record to $OUTPUTFILE"
ffmpeg -loglevel warning -f oss -f video4linux2 -i $INPUTDEVICE \
ffmpeg -loglevel warning -f oss -f v4l2 -i $INPUTDEVICE \
-flush_packets 1 \
-vf "drawtext=font=Dejavu Sans: \
text='$COMMIT | %{localtime} | %{pts}': x=(w-tw)/2: y=h-(2*lh): fontcolor=white: box=1: boxcolor=0x00000000@1: fontsize=15" $OUTPUTFILE &
Expand Down
18 changes: 8 additions & 10 deletions ci/hardware_tests/t1_hw_test.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
#!/usr/bin/env bash

HERE=`dirname "$0"`
SHA=${GITHUB_SHA:-unknown}
cd $HERE

function finish {
./record_video.sh ${T1_CAMERA} ${CI_COMMIT_SHORT_SHA} stop
ls -l *.mp4
./record_video.sh ${T1_CAMERA} ${SHA} stop
}
trap finish EXIT

set -e # exit on nonzero exitcode
set -x # trace commands

# export variables defined in the file
set -a
source hardware.cfg
set +a

./record_video.sh ${T1_CAMERA} ${CI_COMMIT_SHORT_SHA} start
./record_video.sh ${T1_CAMERA} ${SHA} start
(cd ../.. && poetry install)
poetry run python bootstrap.py t1
poetry run python bootstrap.py t1 ../../firmware-T1*.bin
#poetry run python bootstrap.py T1B1 # install official firmware first
poetry run python bootstrap.py T1B1 ../../firmware-T1*.bin
poetry run pytest ../../tests/device_tests
3 changes: 2 additions & 1 deletion ci/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ stdenvNoCC.mkDerivation ({
libiconv
] ++ lib.optionals hardwareTest [
uhubctl
ffmpeg
tio
ffmpeg_5-full
dejavu_fonts
] ++ lib.optionals devTools [
shellcheck
Expand Down
Loading

0 comments on commit 1acd8f6

Please sign in to comment.