Skip to content

Commit

Permalink
test(transport-test): introduce new dedicated testing package
Browse files Browse the repository at this point in the history
  • Loading branch information
mroz22 authored and szymonlesisz committed Aug 13, 2024
1 parent d168d2b commit f0a0601
Show file tree
Hide file tree
Showing 19 changed files with 411 additions and 176 deletions.
22 changes: 14 additions & 8 deletions .github/workflows/test-transport.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@ on:
pull_request:
paths:
- "packages/transport/**"
- "packages/transport-bridge/**"
- "packages/transport-test/**"
- "packages/protobuf/**"
- "packages/protocol/**"
- "packages/trezor-user-env-link/**"
- "packages/utils/**"
- "docker/docker-transport-test.sh"
- "docker/docker-compose.transport-test.yml"
- ".github/workflows/connect-transport-e2e-test.yml"
- "docker/docker-compose.transport-test-ci.yml"
- "yarn.lock"
workflow_dispatch:

jobs:
transport-e2e:
if: github.repository == 'trezor/trezor-suite'
runs-on: ubuntu-latest
env:
COMPOSE_FILE: ./docker/docker-compose.transport-test.yml
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -39,7 +37,15 @@ jobs:
- name: Install dependencies
run: |
echo -e "\nenableScripts: false" >> .yarnrc.yml
yarn workspaces focus @trezor/transport
yarn workspaces focus @trezor/transport-test
- name: Run E2E tests
run: ./docker/docker-transport-test.sh
- name: Setup containers
run: |
docker compose -f ./docker/docker-compose.transport-test-ci.yml pull
docker compose -f ./docker/docker-compose.transport-test-ci.yml up -d
- name: Run E2E tests (old-bridge:emu)
run: yarn workspace @trezor/transport-test test:e2e:old-bridge:emu

- name: Run E2E tests (new-bridge:emu)
run: yarn workspace @trezor/transport-test test:e2e:new-bridge:emu
14 changes: 14 additions & 0 deletions docker/docker-compose.transport-test-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "3.9"
services:
trezor-user-env-unix:
image: ghcr.io/trezor/trezor-user-env
environment:
- SDL_VIDEODRIVER=dummy
- XDG_RUNTIME_DIR=/var/tmp
network_mode: host
# in case local developement on mac is needed, these ports will be useful
# ports:
# - "9002:9002"
# - "9001:9001"
# - "21326:21326"
# - "21325:21326"
15 changes: 0 additions & 15 deletions docker/docker-compose.transport-test.yml

This file was deleted.

5 changes: 0 additions & 5 deletions docker/docker-transport-test.sh

This file was deleted.

5 changes: 5 additions & 0 deletions packages/transport-test/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
rules: {
'no-nested-ternary': 'off', // useful in tests..
},
};
10 changes: 10 additions & 0 deletions packages/transport-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
This package contains a collection of end-to-end tests of the transport layer using both emulators and real devices.

### Bridge tests

| | needs tenv | manually connect device | manually start bridge |
| ------------------------------------------------------------- | ---------- | ----------------------- | --------------------- |
| yarn workspace @trezor/transport-test test:e2e:old-bridge:hw | no | yes | yes |
| yarn workspace @trezor/transport-test test:e2e:old-bridge:emu | yes | no | no |
| yarn workspace @trezor/transport-test test:e2e:new-bridge:hw | no | yes | no |
| yarn workspace @trezor/transport-test test:e2e:new-bridge:emu | yes | no | no |
175 changes: 175 additions & 0 deletions packages/transport-test/e2e/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* eslint no-console: 0 */

import { WebUSB } from 'usb';

import { TrezorUserEnvLinkClass } from '@trezor/trezor-user-env-link';
import { scheduleAction, Log } from '@trezor/utils';
import { TrezordNode } from '@trezor/transport-bridge/src';

export const env = {
USE_HW: process.env.USE_HW === 'true',
USE_NODE_BRIDGE: process.env.USE_NODE_BRIDGE === 'true',
};

console.log('env', env);

const webusb = new WebUSB({
allowAllDevices: true, // return all devices, not only authorized
});

/**
* Controller based on TrezorUserEnvLink its main purpose is:
* - to bypass communication with trezor-user-env and allow using local hw devices
* - start and stop node bridge (node bridge should be implemented into trezor-user-env however)
*/
class Controller extends TrezorUserEnvLinkClass {
private logger: Console;
private nodeBridge: TrezordNode | undefined = undefined;

private originalApi: {
connect: typeof TrezorUserEnvLinkClass.prototype.connect;
startBridge: typeof TrezorUserEnvLinkClass.prototype.startBridge;
stopBridge: typeof TrezorUserEnvLinkClass.prototype.stopBridge;
startEmu: typeof TrezorUserEnvLinkClass.prototype.startEmu;
stopEmu: typeof TrezorUserEnvLinkClass.prototype.stopEmu;
};

constructor() {
super();

this.logger = console;

this.originalApi = {
connect: super.connect.bind(this),
startBridge: super.startBridge.bind(this),
stopBridge: super.stopBridge.bind(this),
startEmu: super.startEmu.bind(this),
stopEmu: super.stopEmu.bind(this),
};

this.connect = !env.USE_HW
? this.originalApi.connect
: () => {
return Promise.resolve(null);
};

this.startBridge =
!env.USE_HW && !env.USE_NODE_BRIDGE
? (version?: string) => this.originalApi.startBridge(version)
: env.USE_NODE_BRIDGE
? async () => {
this.nodeBridge = new TrezordNode({
port: 21325,
api: !env.USE_HW ? 'udp' : 'usb',
logger: new Log('test-bridge', false),
});

await this.nodeBridge.start();

// todo: this shouldn't be here, nodeBridge should be started when start resolves
await this.waitForBridgeIsRunning(true);

return null;
}
: () => this.waitForBridgeIsRunning(true);

this.stopBridge =
!env.USE_HW && !env.USE_NODE_BRIDGE
? this.originalApi.stopBridge
: env.USE_NODE_BRIDGE
? async () => {
await this.nodeBridge?.stop();

// todo: this shouldn't be here, nodeBridge should be stopped when stop resolves
await this.waitForBridgeIsRunning(false);

return null;
}
: () => this.waitForBridgeIsRunning(false);

this.startEmu = !env.USE_HW
? this.originalApi.startEmu
: env.USE_HW
? () => this.waitForNumberOfDevices(1)
: () => {
console.log('todo: ping local emu');

return Promise.resolve(null);
};

this.stopEmu = !env.USE_HW
? this.originalApi.stopEmu
: env.USE_HW
? () => this.waitForNumberOfDevices(0)
: () => {
console.log('todo: ping local emu');

return Promise.resolve(null);
};
}

private waitForNumberOfDevices = (expected: number) => {
this.logger.log(
`${env.USE_HW ? '[MANUAL ACTION REQUIRED] ' : ''} waiting for ${expected} device to be connected`,
);

return scheduleAction(
async () => {
const devices = await webusb.getDevices();
if (devices.filter(d => d.productName === 'TREZOR').length === expected) {
return null;
}
throw new Error('Condition not met');
},
{
deadline: Date.now() + 60_000,
gap: 1000,
},
);
};

private waitForBridgeIsRunning = (expected: boolean) => {
this.logger.log(
`${env.USE_HW && !env.USE_NODE_BRIDGE ? '[MANUAL ACTION REQUIRED] ' : ''} waiting for bridge ${expected ? 'start' : 'stop'}`,
);

return scheduleAction(
() => {
return fetch('http://localhost:21325/', {
method: 'POST',
headers: {
['Origin']: 'https://wallet.trezor.io',
},
})
.then(res => {
if (res.ok !== expected) {
throw new Error('Condition not met');
}

return res.text();
})
.then((text: string) => {
console.log(`running bridge: ${text}`);

return null;
})
.catch(err => {
if (err.message === 'Condition not met') {
throw err;
}
if (expected) {
throw err;
}

return null;
});
},
{
deadline: Date.now() + 60_000,
gap: 1000,
},
);
};
}

export const controller = new Controller();
26 changes: 26 additions & 0 deletions packages/transport-test/e2e/expect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { DEVICE_TYPE } from '@trezor/transport/src/api/abstract';

import { env } from './controller';

const { USE_HW, USE_NODE_BRIDGE } = env;

const debug = USE_NODE_BRIDGE ? undefined : USE_HW ? false : true;
const debugSession = USE_NODE_BRIDGE ? undefined : null;
const path = USE_NODE_BRIDGE ? expect.any(String) : '1';
const product = USE_HW ? 21441 : USE_NODE_BRIDGE ? undefined : 0;
const vendor = USE_NODE_BRIDGE ? undefined : USE_HW ? 4617 : 0;
const type =
USE_NODE_BRIDGE && USE_HW
? expect.any(Number)
: USE_NODE_BRIDGE && !USE_HW
? DEVICE_TYPE.TypeEmulator
: undefined;

/**
* emu '127.0.0.1:21324' (15)
* hw old bridge '1' (1)
* hw new bridge '185B982B5F37F9D96706EC49' (24)
*/
export const pathLength = USE_HW && USE_NODE_BRIDGE ? 24 : !USE_HW && USE_NODE_BRIDGE ? 15 : 1;

export const descriptor = { debug, debugSession, path, product, vendor, type };
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default {
import type { Config } from 'jest';

export const config: Config = {
rootDir: './',
moduleFileExtensions: ['ts', 'js'],
modulePathIgnorePatterns: ['node_modules'],
Expand All @@ -20,3 +22,6 @@ export default {
testEnvironment: 'node',
globals: {},
};

// eslint-disable-next-line import/no-default-export
export default config;
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { runCLI } from 'jest';
import { Config } from '@jest/types';

import { TrezorUserEnvLink } from '@trezor/trezor-user-env-link';

import argv from './jest.config';
import { controller as TrezorUserEnvLink } from './controller';
import { config } from './jest.config';

(async () => {
// Before actual tests start, establish connection with trezor-user-env
await TrezorUserEnvLink.connect();

// @ts-expect-error
argv.runInBand = true;
const argv: Config.Argv = {
...config,
runInBand: true,
testPathPattern: [process.argv[2] || ''],
};

// @ts-expect-error
const { results } = await runCLI(argv, [__dirname]);

process.exit(results.numFailedTests);
Expand Down
Loading

0 comments on commit f0a0601

Please sign in to comment.