mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-22 07:28:10 +00:00
ci: add hardware tests for T1
This enables to run device tests against an actual device connected to our CI. It uses https://github.com/mmahut/tpmb to enter bootloader and then it uploads a debug firmware to the device. The debug mode is the used to "click" the buttons but we might improve this and actually use the hardware buttons instead.
This commit is contained in:
parent
bc4e8eaa16
commit
4264e87319
8
ci/hardware_tests/README.md
Normal file
8
ci/hardware_tests/README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Hardware tests
|
||||||
|
|
||||||
|
Hardware tests are device tests that run against an actual device instead of an emulator.
|
||||||
|
This works thanks to [tpmb](https://github.com/mmahut/tpmb), which is a small arduino
|
||||||
|
device capable of pushing an actual buttons on the device. Currently T1 is supported
|
||||||
|
but TT might follow.
|
||||||
|
|
||||||
|
See `ci/test.yml` "hardware legacy device test" what exactly is run.
|
20
ci/hardware_tests/bootstrap.py
Executable file
20
ci/hardware_tests/bootstrap.py
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
import configparser
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from device.t1 import TrezorOne
|
||||||
|
|
||||||
|
|
||||||
|
def main(file: str = None):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read_file(open("hardware.cfg"))
|
||||||
|
t1 = TrezorOne(
|
||||||
|
config["t1"]["location"], config["t1"]["port"], config["t1"]["arduino_serial"],
|
||||||
|
)
|
||||||
|
t1.update_firmware(file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
file = None
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
file = sys.argv[1]
|
||||||
|
main(file)
|
14
ci/hardware_tests/default.nix
Normal file
14
ci/hardware_tests/default.nix
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
with import <nixpkgs> {};
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
name = "trezor-firmware-hardware-tests";
|
||||||
|
buildInputs = [
|
||||||
|
uhubctl
|
||||||
|
ffmpeg
|
||||||
|
pipenv
|
||||||
|
libusb1
|
||||||
|
dejavu_fonts
|
||||||
|
];
|
||||||
|
LD_LIBRARY_PATH = "${libusb1}/lib";
|
||||||
|
NIX_ENFORCE_PURITY = 0;
|
||||||
|
}
|
46
ci/hardware_tests/device/device.py
Normal file
46
ci/hardware_tests/device/device.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
|
||||||
|
class Device:
|
||||||
|
def __init__(self, uhub_location, uhub_port, arduino_serial):
|
||||||
|
self.uhub_location = uhub_location
|
||||||
|
self.uhub_port = uhub_port
|
||||||
|
self.arduino_serial = arduino_serial
|
||||||
|
self.serial = serial.Serial(arduino_serial, 9600)
|
||||||
|
|
||||||
|
def power_on(self):
|
||||||
|
self.now()
|
||||||
|
print("[hardware/usb] Turning power on...")
|
||||||
|
os.system(
|
||||||
|
"uhubctl -l {} -p {} -a on > /dev/null".format(
|
||||||
|
self.uhub_location, self.uhub_port
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.wait(3)
|
||||||
|
|
||||||
|
def power_off(self):
|
||||||
|
self.now()
|
||||||
|
print("[hardware/usb] Turning power off...")
|
||||||
|
os.system(
|
||||||
|
"uhubctl -l {} -p {} -r 100 -a off > /dev/null".format(
|
||||||
|
self.uhub_location, self.uhub_port
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.wait(3)
|
||||||
|
|
||||||
|
def touch(self, location, action):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def wait(seconds):
|
||||||
|
Device.now()
|
||||||
|
print("[software] Waiting for {} seconds...".format(seconds))
|
||||||
|
time.sleep(seconds)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def now():
|
||||||
|
print("\n[timestamp] {}".format(datetime.datetime.now()))
|
47
ci/hardware_tests/device/t1.py
Normal file
47
ci/hardware_tests/device/t1.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from .device import Device
|
||||||
|
|
||||||
|
|
||||||
|
class TrezorOne(Device):
|
||||||
|
def touch(self, location, action):
|
||||||
|
self.now()
|
||||||
|
print(
|
||||||
|
"[hardware/trezor] Touching the {} button by {}...".format(location, action)
|
||||||
|
)
|
||||||
|
self.serial.write(("{} {}\n".format(location, action)).encode())
|
||||||
|
|
||||||
|
def update_firmware(self, file=None):
|
||||||
|
if file:
|
||||||
|
unofficial = True
|
||||||
|
trezorctlcmd = "trezorctl firmware-update -s -f {} &".format(file)
|
||||||
|
print("[software/trezorctl] Updating the firmware to {}...".format(file))
|
||||||
|
else:
|
||||||
|
unofficial = False
|
||||||
|
trezorctlcmd = "trezorctl firmware-update &"
|
||||||
|
print("[software/trezorctl] Updating the firmware to latest...")
|
||||||
|
self._enter_bootloader()
|
||||||
|
|
||||||
|
os.system(trezorctlcmd)
|
||||||
|
self.wait(3)
|
||||||
|
self.touch("right", "click")
|
||||||
|
self.wait(20)
|
||||||
|
if unofficial:
|
||||||
|
self.touch("right", "click")
|
||||||
|
self.wait(10)
|
||||||
|
self.power_off()
|
||||||
|
self.power_on()
|
||||||
|
if unofficial:
|
||||||
|
self.touch("right", "click")
|
||||||
|
self.wait(5)
|
||||||
|
self.touch("right", "click")
|
||||||
|
self.wait(5)
|
||||||
|
os.system("trezorctl get-features|grep version")
|
||||||
|
|
||||||
|
def _enter_bootloader(self):
|
||||||
|
self.power_off()
|
||||||
|
self.touch("all", "press")
|
||||||
|
self.wait(2)
|
||||||
|
self.power_on()
|
||||||
|
self.wait(2)
|
||||||
|
self.touch("all", "unpress")
|
4
ci/hardware_tests/hardware.cfg
Normal file
4
ci/hardware_tests/hardware.cfg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[t1]
|
||||||
|
location = 3-1.4
|
||||||
|
port = 3
|
||||||
|
arduino_serial = /dev/ttyACM0
|
20
ci/hardware_tests/record_video.sh
Executable file
20
ci/hardware_tests/record_video.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
if [ $# -ne 2 ]
|
||||||
|
then
|
||||||
|
echo "Usage: $0 commit_id [start|stop]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUTPUTFILE=video_$1_$(date +%s).mp4
|
||||||
|
INPUTDEVICE=/dev/video0
|
||||||
|
|
||||||
|
if [ "$2" == "start" ]; then
|
||||||
|
echo "[software/video] Starting record to $OUTPUTFILE"
|
||||||
|
ffmpeg -loglevel panic -f oss -f video4linux2 -i $INPUTDEVICE \
|
||||||
|
-vf "drawtext=font=Dejavu Sans: \
|
||||||
|
text='$1 | %{localtime} | %{pts}': x=(w-tw)/2: y=h-(2*lh): fontcolor=white: box=1: boxcolor=0x00000000@1: fontsize=15" $OUTPUTFILE &
|
||||||
|
export VPID=$!
|
||||||
|
elif [ "$2" == "stop" ]; then
|
||||||
|
echo "[software/video] Stopping the recording of $OUTPUTFILE"
|
||||||
|
pkill ffmpeg
|
||||||
|
sync
|
||||||
|
fi
|
22
ci/test.yml
22
ci/test.yml
@ -319,3 +319,25 @@ storage test:
|
|||||||
junit: tests/junit.xml
|
junit: tests/junit.xml
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
|
# Hardware
|
||||||
|
hardware legacy device test:
|
||||||
|
stage: test
|
||||||
|
extends: .legacy_job
|
||||||
|
tags:
|
||||||
|
- tpmb
|
||||||
|
dependencies:
|
||||||
|
- legacy fw debug build
|
||||||
|
script:
|
||||||
|
- cd ci/hardware_tests
|
||||||
|
- nix-shell --run "./record_video.sh ${CI_COMMIT_SHORT_SHA} start"
|
||||||
|
- nix-shell --run "cd ../.. && pipenv sync"
|
||||||
|
- nix-shell --run "pipenv run python bootstrap.py"
|
||||||
|
- nix-shell --run "pipenv run python bootstrap.py ../../trezor-*.bin"
|
||||||
|
- nix-shell --run "pipenv run pytest ../../tests/device_tests"
|
||||||
|
- nix-shell --run "./record_video.sh ${CI_COMMIT_SHORT_SHA} stop"
|
||||||
|
artifacts:
|
||||||
|
name: "$CI_JOB_NAME-$CI_COMMIT_SHORT_SHA"
|
||||||
|
paths:
|
||||||
|
- ci/hardware_tests/video*.mp4
|
||||||
|
expire_in: 2 days
|
||||||
|
Loading…
Reference in New Issue
Block a user