name: Hardware tests

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

# [Device tests](../tests/device-tests.md) that run against an actual physical Trezors.
# The device needs to have special bootloader, found in `core/embed/projects/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' && 'runner3' || 'hw-t2t1' }}
      # runner4 does not work at the moment
      # - ${{ matrix.model == 'T2B1' && 'hw-t2b1' || 'hw-t2t1' }}
    strategy:
      fail-fast: false
      matrix:
        model: [T2T1, T2B1]
        coins: [universal, btconly]
    env:
      TREZOR_MODEL: ${{ matrix.model }}
      BITCOIN_ONLY: ${{ matrix.coins == 'btconly' && '1' || '0' }}
      PYTEST_TIMEOUT: 600  # 10m single test timeout
      PYOPT: 0
      DISABLE_OPTIGA: 1
      STORAGE_INSECURE_TESTING_MODE: 1
      BOOTLOADER_DEVEL: ${{ matrix.model == 'T2B1' && '1' || '0' }}
      # TODO: enable SD-related tests after fixing #4924
      # Disable authenticity test since it requires DISABLE_OPTIGA=0
      # 5.5h pytest global timeout
      TESTOPTS: >-
        -m 'not sd_card'
        -k 'not test_authenticate_device'
        --durations=50
        --session-timeout 19800
        --retries=5
        --retry-delay=1
      TT_UHUB_PORT: 1
      QUIET_MODE: 1
    timeout-minutes: 360  # 6h CI job timeout
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
      - uses: ./.github/actions/environment
      - run: nix-shell --arg hardwareTest true --run uhubctl
      - run: nix-shell --run "uv run make -C core build_firmware"
      - run: nix-shell --arg hardwareTest true --run "uv run python ci/hardware_tests/bootstrap.py ${{ matrix.model }} core/build/firmware/firmware.bin"
      - run: nix-shell --run "uv run trezorctl list"
      - run: nix-shell --run "uv run trezorctl get-features"
      - run: |
          nix-shell --arg hardwareTest true --run "./tools/read-device-log.sh /dev/ttyTREZOR &> trezor.log" &
          nix-shell --run "uv run pytest -v --verbose-log-file pytest.log tests/device_tests $TESTOPTS"
      - run: tail -n50 trezor.log || true
        if: failure()
      - uses: actions/upload-artifact@v4
        with:
          name: core-hardware-${{ matrix.model }}-${{ matrix.coins }}
          path: |
            trezor.log
            pytest.log
          retention-days: 7
        if: always()

  core_monero_test:
    name: Monero tests
    runs-on:
      - self-hosted
      - ${{ matrix.model == 'T2B1' && 'runner3' || 'hw-t2t1' }}
      # runner4 does not work at the moment
      # - ${{ matrix.model == 'T2B1' && 'hw-t2b1' || 'hw-t2t1' }}
    strategy:
      fail-fast: false
      matrix:
        model: [T2T1, T2B1]
    env:
      TREZOR_MODEL: ${{ matrix.model }}
      PYTEST_TIMEOUT: 1200
      PYOPT: 0
      DISABLE_OPTIGA: 1
      BOOTLOADER_DEVEL: ${{ matrix.model == 'T2B1' && '1' || '0' }}
      TT_UHUB_PORT: 1
      QUIET_MODE: 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 "uv run make -C core build_firmware"
      - run: nix-shell --arg hardwareTest true --run "uv run python ci/hardware_tests/bootstrap.py ${{ matrix.model }} core/build/firmware/firmware.bin"
      - run: nix-shell --run "uv run trezorctl list"
      - run: nix-shell --run "uv run trezorctl get-features"
      - run: |
          nix-shell --arg hardwareTest true --run "./tools/read-device-log.sh /dev/ttyTREZOR &> trezor.log" &
          nix-shell --arg fullDeps true --run "./core/tests/run_tests_device_emu_monero.sh --trezor-path webusb:"
      - run: tail -n50 trezor.log || true
        if: failure()
      - uses: actions/upload-artifact@v4
        with:
          name: core-hardware-monero-${{ 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:
      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 "uv run legacy/script/setup"
      - run: nix-shell --run "export PRODUCTION=0 && uv run legacy/script/cibuild"
      - run: nix-shell --arg hardwareTest true --run "ci/hardware_tests/t1_hw_test.sh"
      - uses: actions/upload-artifact@v4
        with:
          name: legacy-hardware-${{ matrix.coins }}
          path: ci/hardware_tests/*.mp4
          retention-days: 7
        if: always()
