Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to fix timeout in GitHub CI #30

Merged
merged 4 commits into from
Jul 10, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@v3

- name: Download app binary
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: binaries
path: bin
Expand Down
2 changes: 1 addition & 1 deletion tests/.mocharc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var base_time = 8000;
var base_time = 30000;
if (process.env.LEDGER_LIVE_HARDWARE) {
base_time = 0;
}
Expand Down
111 changes: 79 additions & 32 deletions tests/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ const APDU_PORT = 9999;
const BUTTON_PORT = 8888;
const AUTOMATION_PORT = 8899;

function pressButtonAndWaitForChange(speculos, btn, timeout = 1000) {
return new Promise(async resolve => {

const subscription = speculos.automationEvents.subscribe(() => {
subscription.unsubscribe()
sleep(200).then(() => resolve(true))
})

setTimeout(() => {
subscription.unsubscribe();
resolve(false);
}, timeout)
speculos.button(btn)
});

}

exports.mochaHooks = {
beforeAll: async function () { // Need 'function' to get 'this'
this.timeout(10000); // We'll let this wait for up to 10 seconds to get a speculos instance.
Expand All @@ -22,14 +39,23 @@ exports.mochaHooks = {
console.log(this.speculos);
} else {
const speculosProcessOptions = process.env.SPECULOS_DEBUG ? {stdio:"inherit"} : {};
this.speculosProcess = spawn('speculos', [
process.env.LEDGER_APP,
'--sdk', '2.0', // TODO keep in sync
'--display', 'headless',
'--button-port', '' + BUTTON_PORT,
'--automation-port', '' + AUTOMATION_PORT,
'--apdu-port', '' + APDU_PORT,
], speculosProcessOptions);

// pass a custom speculos pid to use the custom
const customSpeculosPid = process.env.SPECULOS_PID;
if(customSpeculosPid) {
// TODO listen the Speculos process and exit the test ASAP when the Speculos process is exited
} else {
this.speculosProcess = spawn('speculos', [
process.env.LEDGER_APP,
'--sdk', '2.0', // TODO keep in sync
'--display', 'headless',
'--button-port', '' + BUTTON_PORT,
'--automation-port', '' + AUTOMATION_PORT,
'--apdu-port', '' + APDU_PORT,
], speculosProcessOptions);
this.speculosProcess.on('exit', (code) => process.exit(code))
}

console.log("Speculos started");
while (this.speculos === undefined) { // Let the test timeout handle the bad case
try {
Expand All @@ -40,6 +66,11 @@ exports.mochaHooks = {
automationPort: AUTOMATION_PORT,
});
console.log("transport open");

const speculos = this.speculos;
this.speculos.pressButtonAndWaitForChange = (btn) =>
pressButtonAndWaitForChange(speculos, btn)

if (process.env.DEBUG_BUTTONS) {
const subButton = this.speculos.button;
this.speculos.button = btns => {
Expand Down Expand Up @@ -98,6 +129,10 @@ const headerOnlyScreens = {
"Main menu": 1
};

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

/* State machine to read screen events and turn them into screens of prompts. */
async function automationStart(speculos, interactionFunc) {
// If this doens't exist, we're running against a hardware ledger; just call
Expand All @@ -113,7 +148,7 @@ async function automationStart(speculos, interactionFunc) {
let promptLockResolve;
let promptsLock=new Promise(r=>{promptLockResolve=r});
if(speculos.promptsEndPromise) {
await speculos.promptsEndPromise;
await Promise.race([speculos.promptsEndPromise, sleep(2000)])
}
speculos.promptsEndPromise = promptsLock; // Set ourselves as the interaction.

Expand All @@ -131,19 +166,12 @@ async function automationStart(speculos, interactionFunc) {
}
};

// Sync up with the ledger; wait until we're on the home screen, and do some
// clicking back and forth to make sure we see the event.
// Then pass screens to interactionFunc.
let readyPromise = syncWithLedger(speculos, asyncEventIter, interactionFunc);

// Resolve our lock when we're done
readyPromise.then(r=>r.promptsPromise.then(()=>{promptLockResolve(true)}));

let header;
let body;

let subscript = speculos.automationEvents.subscribe({
next: evt => {
if(!evt.text) return;
// Wrap up two-line prompts into one:
if(evt.y == 3 && ! headerOnlyScreens[evt.text]) {
header = evt.text;
Expand All @@ -162,30 +190,47 @@ async function automationStart(speculos, interactionFunc) {

// Send a rightward-click to make sure we get _an_ event and our state
// machine starts.
speculos.button("Rr");
await pressButtonAndWaitForChange(speculos, "Rr");

return readyPromise.then(r=>{r.cancel = ()=>{subscript.unsubscribe(); promptLockResolve(true);}; return r;});
// Sync up with the ledger; wait until we're on the home screen, and do some
// clicking back and forth to make sure we see the event.
// Then pass screens to interactionFunc.
let readyPromise = await syncWithLedger(speculos, asyncEventIter, interactionFunc);

// Resolve our lock when we're done
readyPromise.promptsPromise.then(() => promptLockResolve(true))
readyPromise.cancel = () => {
subscript.unsubscribe();
promptLockResolve(true);
}
return readyPromise
}

async function syncWithLedger(speculos, source, interactionFunc) {
let screen = await source.next();
let screen = await Promise.race([
source.next(),
sleep(2000).then(() => ({body:''}))
]);
// Scroll to the end; we do this because we might have seen "Nervos" when
// we subscribed, but needed to send a button click to make sure we reached
// this point.
while(screen.body != "Quit") {
speculos.button("Rr");
const changed = await pressButtonAndWaitForChange(speculos, "Rr")
// the Quit is the last screen and will not change after press the Rr button
// if we find that the screen is not changed, we should try to press the Ll button and check if it is changed
if (!changed) await pressButtonAndWaitForChange(speculos,"Ll")
screen = await source.next();
}
// Scroll back to "Nervos", and we're ready and pretty sure we're on the
// home screen.
while(screen.header != "Nervos") {
speculos.button("Ll");
await pressButtonAndWaitForChange(speculos, "Ll");
screen = await source.next();
}
// Sink some extra homescreens to make us a bit more durable to failing tests.
while(await source.peek().header == "Nervos" || await source.peek().header == "Configuration" || await source.peek().body == "Quit") {
await source.next();
}
// while(await source.peek().header == "Nervos" || await source.peek().header == "Configuration" || await source.peek().body == "Quit") {
// await source.next();
// }
// And continue on to interactionFunc
let interactFP = interactionFunc(speculos, source);
return { promptsPromise: interactFP.finally(() => { source.unsubscribe(); }) };
Expand All @@ -195,14 +240,15 @@ async function readMultiScreenPrompt(speculos, source) {
let header;
let body;
let screen = await source.next();
let m = screen.header && screen.header.match(/^(.*) \(([0-9])\/([0-9])\)$/);
const paginationRegex = /^(.*) \(([0-9])\/([0-9])\)$/;
let m = screen.header && screen.header.match(paginationRegex);
if (m) {
header = m[1];
body = screen.body;
while(m[2] !== m[3]) {
speculos.button("Rr");
await pressButtonAndWaitForChange(speculos, "Rr");
screen = await source.next();
m = screen.header && screen.header.match(/^(.*) \(([0-9])\/([0-9])\)$/);
m = screen.header && screen.header.match(paginationRegex);
body = body + screen.body;
}
return { header: header, body: body };
Expand All @@ -213,6 +259,7 @@ async function readMultiScreenPrompt(speculos, source) {

function acceptPrompts(expectedPrompts, selectPrompt) {
return async (speculos, screens) => {
if(!expectedPrompts.length) return
if(!screens) {
// We're running against hardware, so we can't prompt but
// should tell the person running the test what to do.
Expand All @@ -234,15 +281,15 @@ function acceptPrompts(expectedPrompts, selectPrompt) {
promptList.push(screen);
}
if(screen.body !== selectPrompt) {
speculos.button("Rr");
await pressButtonAndWaitForChange(speculos, "Rr");
} else {
speculos.button("RLrl");
await pressButtonAndWaitForChange(speculos, "RLrl");
done = true;
}
}

if (expectedPrompts) {
expect(promptList).to.deep.equal(expectedPrompts);
expect(promptList).to.includes.deep.members(expectedPrompts);
return { promptList, promptsMatch: true };
} else {
return { promptList };
Expand Down Expand Up @@ -323,7 +370,7 @@ const fcConfig = {

fc.configureGlobal(fcConfig);

global.recover = recover;
global.recover = recover;
global.BIPPath = require("bip32-path");
global.expect = expect;
global.flowAccept = flowAccept;
Expand Down
13 changes: 7 additions & 6 deletions tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
},
"bin": "run-tests.js",
"devDependencies": {
"flow-bin": "^0.109.0",
"babel-cli": "^6.26.0",
"babel-eslint": "^8.0.2",
"babel-preset-env": "^1.7.0",
Expand All @@ -16,10 +15,11 @@
"babel-preset-stage-0": "^6.24.1",
"bcrypto": "^5.3.0",
"bip32-path": "^0.4.2",
"chai-bytes": "^0.1.2",
"chai": "^4.2.0",
"chai-bytes": "^0.1.2",
"child_process": "",
"fast-check": "^2.2.0",
"flow-bin": "^0.109.0",
"flow-copy-source": "^2.0.9",
"flow-mono-cli": "^1.5.0",
"flow-typed": "^2.6.1",
Expand All @@ -28,16 +28,17 @@
"prettier": "^1.18.2",
"rxjs": "^6.6.0",
"tap": "^15.1.5",
"ts-node": "^10.9.2",
"uglify-js": "^3.6.1"
},
"dependencies": {
"hw-app-ckb": "https://github.com/obsidiansystems/hw-app-ckb.git",
"@ledgerhq/hw-transport": "^6.11.2",
"bech32": "1.1.4",
"@ledgerhq/hw-transport-node-speculos": "6.11.2",
"@ledgerhq/hw-transport-node-hid": "6.11.2",
"@ledgerhq/hw-transport-node-speculos": "6.29.0",
"bech32": "1.1.4",
"bip32-path": "^0.4.2",
"blake2b-wasm": "^2.1.0",
"create-hash": "1.2.0"
"create-hash": "1.2.0",
"hw-app-ckb": "https://github.com/obsidiansystems/hw-app-ckb.git"
}
}
14 changes: 7 additions & 7 deletions tests/sudt.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,19 +187,19 @@ describe("sUDT operations", () => {

it("Accepts sUDT create account when enabled in settings", async function() {
let flipContractDataPolicy = async (target) => {return await automationStart(this.speculos, async (speculos, screens) => {
speculos.button("Rr");
while((await screens.next()).body != "Configuration") speculos.button("Rr");
speculos.button("RLrl");
await speculos.pressButtonAndWaitForChange("Rr");
while((await screens.next()).body != "Configuration") await speculos.pressButtonAndWaitForChange("Rr");
await speculos.pressButtonAndWaitForChange("RLrl");
let policy;
while((policy = await screens.next()).header != "Allow contract data") {
speculos.button("Rr");
await speculos.pressButtonAndWaitForChange("Rr");
}
while(policy.body != target) {
speculos.button("RLrl");
await speculos.pressButtonAndWaitForChange("RLrl");
policy = await screens.next();
}
do { speculos.button("Rr") } while((await screens.next()).body != "Main menu");
speculos.button("RLrl");
do { await speculos.pressButtonAndWaitForChange("Rr") } while((await screens.next()).body != "Main menu");
await speculos.pressButtonAndWaitForChange("RLrl");

return { promptsMatch: true };
})};
Expand Down
Loading