From d29876cb7dd43c939b47e74c247002d57788d3f7 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 20 Apr 2024 07:50:44 +0200 Subject: [PATCH 01/33] runapptests - Initial implementation for additional asserts and running from json --- apps/android/tests.json | 21 ++++++ bin/runapptests.js | 144 +++++++++++++++++++++++++++++++--------- 2 files changed, 134 insertions(+), 31 deletions(-) create mode 100644 apps/android/tests.json diff --git a/apps/android/tests.json b/apps/android/tests.json new file mode 100644 index 000000000..fdb836824 --- /dev/null +++ b/apps/android/tests.json @@ -0,0 +1,21 @@ +{ + "app" : "android", + "tests" : [ { + "steps" : [ + {"t":"wrap", "fn": "Bangle.setGPSPower", "id": "gpspower"}, + {"t":"cmd", "js": "NRF.getSecurityStatus = () => {}"}, + {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))"}, + {"t":"assert", "js": "!NRF.getSecurityStatus().connected", "is":"true", "text": "Not connected"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"false", "text": "isGPSOn is correct"}, + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"true", "text": "setGPSPower returns true when switching on"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"true", "text": "isGPSOn is correct"}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "internal GPS switched on"} + {"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"false", "text": "setGPSPower returns false when switching off"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"false", "text": "isGPSOn is correct"}, + {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ] , "text": "internal GPS switched off"} + ] + }] +} diff --git a/bin/runapptests.js b/bin/runapptests.js index 40a898fa6..21c96abcf 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -20,33 +20,6 @@ TODO: */ -// A simpletest -/*var TEST = { - app : "android", - tests : [ { - steps : [ - {t:"load", fn:"messagesgui.app.js"}, - {t:"gb", "obj":{"t":"notify","id":1234,"src":"Twitter","title":"A Name","body":"message contents"}}, - {t:"cmd", "js":"X='hello';"}, - {t:"eval", "js":"X", eq:"hello"} - ] - }] -};*/ -var TEST = { - app : "antonclk", - tests : [ { - steps : [ - {t:"cmd", "js": "Bangle.loadWidgets()"}, - {t:"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, - {t:"cmd", "js":"Bangle.setUI()"}, // load and free - {t:"saveMemoryUsage"}, - {t:"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, - {t:"cmd", "js":"Bangle.setUI()"}, // load and free - {t:"checkMemoryUsage"}, // check memory usage is the same - ] - }] -}; - var EMULATOR = "banglejs2"; var DEVICEID = "BANGLEJS2"; @@ -76,15 +49,115 @@ function ERROR(s) { process.exit(1); } +function getValue(js){ + //console.log(`> Getting value for "${js}"`); + emu.tx(`\x10print(JSON.stringify(${js}))\n`); + var result = emu.getLastLine(); + try { + return JSON.parse(result); + } catch (e) { + console.log("Error during getValue", e); + } +} + +function assertFail(text){ + console.log("> FAIL: " + text); + ok = false; +} + +function assertCondition(condition, text) { + if (!condition) { + assertFail(text); + } else console.log("OK: " + text); +} + +function assertArray(step){ + let array = step.value; + if (step.value === undefined) + array = getValue(step.js); + switch (step.is){ + case "notempty": assertCondition(array && array.length > 0, step.text); break; + case "undefinedorempty": assertCondition(array && array.length == 0, step.text); break; + } +} + +function assertValue(step){ + console.debug("assertValue", step); + let value = step.js; + if (value === undefined) + value = step.value; + switch (step.is){ + case "truthy": assertCondition(getValue(`!!${value}`), step.text); break; + case "falsy": assertCondition(getValue(`!${value}`), step.text); break; + case "true": assertCondition(getValue(`${value} === true`), step.text); break; + case "false": assertCondition(getValue(`${value} === false`), step.text); break; + case "equal": assertCondition(getValue(`${value} == ${step.to}`), step.text); break; + } +} + +function wrap(func, id){ + console.log(`> Wrapping "${func}"`); + + let wrappingCode = ` + if(!global.APPTESTS) global.APPTESTS={}; + if(!global.APPTESTS.funcCalls) global.APPTESTS.funcCalls={}; + if(!global.APPTESTS.funcArgs) global.APPTESTS.funcArgs={}; + global.APPTESTS.funcCalls.${id}=0; + (function(o) { + ${func} = function() { + global.APPTESTS.funcCalls.${id}++; + global.APPTESTS.funcArgs.${id}=arguments; + return o.apply(this, arguments); + }; + }(${func}));`; + + emu.tx(wrappingCode); +} + +function assertCall(step){ + let id = step.id; + let args = step.argAsserts; + let calls = getValue(`global.APPTESTS.funcCalls.${id}`); + if ((args.count && args.count == calls) || (!args.count && calls > 0)){ + if (args) { + let callArgs = getValue(`global.APPTESTS.funcArgs.${id}`); + for (let a of args){ + let current = { + value: callArgs[a.arg], + is: a.is, + to: a.to, + text: step.text + }; + switch(a.t){ + case "assertArray": + assertArray(current); + break; + case "assert": + assertValue(current); + break; + } + } + } else { + console.log("OK", step.text); + } + } else + assertFail(step.text) +} + function runTest(test) { var app = apploader.apps.find(a=>a.id==test.app); if (!app) ERROR(`App ${JSON.stringify(test.app)} not found`); if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + return apploader.getAppFilesString(app).then(command => { + console.log("Handling command", command); + // What about dependencies?? test.tests.forEach((subtest,subtestIdx) => { console.log(`==============================`); console.log(`"${test.app}" Test ${subtestIdx}`); + if (test.description) + console.log(`"${test.description}`); console.log(`==============================`); emu.factoryReset(); console.log("> Sending app "+test.app); @@ -103,6 +176,9 @@ function runTest(test) { console.log(`> Sending JS "${step.js}"`); emu.tx(`${step.js}\n`); break; + case "wrap" : + wrap(step.fn, step.id); + break; case "gb" : emu.tx(`GB(${JSON.stringify(step.obj)})\n`); break; case "tap" : emu.tx(`Bangle.emit(...)\n`); break; case "eval" : @@ -118,6 +194,15 @@ function runTest(test) { break; // tap/touch/drag/button press // delay X milliseconds? + case "assertArray": + assertArray(step); + break; + case "assertCall": + assertCall(step); + break; + case "assert": + assertValue(step); + break; case "screenshot" : console.log(`> Compare screenshots - UNIMPLEMENTED`); break; @@ -151,16 +236,13 @@ emu.init({ }).then(function() { // Emulator is now loaded console.log("Loading tests"); - var tests = []; apploader.apps.forEach(app => { var testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; var test = JSON.parse(require("fs").readFileSync(testFile).toString()); test.app = app.id; - tests.push(test); + runTest(test); }); - // Running tests - runTest(TEST); }); /* if (erroredApps.length) { From c66199c76bbd4a633f5f46a11ef8d7a4ad508101 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Fri, 3 May 2024 23:38:10 +0200 Subject: [PATCH 02/33] runapptests - Allow running setup steps --- bin/runapptests.js | 126 ++++++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 21c96abcf..c150e9894 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -144,6 +144,70 @@ function assertCall(step){ assertFail(step.text) } +function runStep(step, subtest, test, state){ + if (state.ok) switch(step.t) { + case "setup" : + test.setup.filter(e=>e.id==step.id)[0].steps.forEach(setupStep=>{ + runStep(setupStep, subtest, test, state); + }); + break; + case "load" : + console.log(`> Loading file "${step.fn}"`); + emu.tx(`load(${JSON.stringify(step.fn)})\n`); + break; + case "cmd" : + console.log(`> Sending JS "${step.js}"`); + emu.tx(`${step.js}\n`); + break; + case "wrap" : + wrap(step.fn, step.id); + break; + case "gb" : emu.tx(`GB(${JSON.stringify(step.obj)})\n`); break; + case "tap" : emu.tx(`Bangle.emit(...)\n`); break; + case "eval" : + console.log(`> Evaluate "${step.js}"`); + emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); + var result = emu.getLastLine(); + var expected = JSON.stringify(step.eq); + console.log("> GOT "+result); + if (result!=expected) { + console.log("> FAIL: EXPECTED "+expected); + state.ok = false; + } + break; + // tap/touch/drag/button press + // delay X milliseconds? + case "assertArray": + assertArray(step); + break; + case "assertCall": + assertCall(step); + break; + case "assert": + assertValue(step); + break; + case "screenshot" : + console.log(`> Compare screenshots - UNIMPLEMENTED`); + break; + case "saveMemoryUsage" : + emu.tx(`\x10print(process.memory().usage)\n`); + subtest.memUsage = parseInt( emu.getLastLine()); + console.log("> CURRENT MEMORY USAGE", subtest.memUsage); + break; + case "checkMemoryUsage" : + emu.tx(`\x10print(process.memory().usage)\n`); + var memUsage = emu.getLastLine(); + console.log("> CURRENT MEMORY USAGE", memUsage); + if (subtest.memUsage != memUsage ) { + console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); + state.ok = false; + } + break; + default: ERROR("Unknown step type "+step.t); + } + emu.idle(); +} + function runTest(test) { var app = apploader.apps.find(a=>a.id==test.app); if (!app) ERROR(`App ${JSON.stringify(test.app)} not found`); @@ -164,65 +228,11 @@ function runTest(test) { emu.tx(command); console.log("> Sent app"); emu.tx("reset()\n"); - console.log("> Reset."); - var ok = true; + console.log("> Reset"); + + var state = { ok: true}; subtest.steps.forEach(step => { - if (ok) switch(step.t) { - case "load" : - console.log(`> Loading file "${step.fn}"`); - emu.tx(`load(${JSON.stringify(step.fn)})\n`); - break; - case "cmd" : - console.log(`> Sending JS "${step.js}"`); - emu.tx(`${step.js}\n`); - break; - case "wrap" : - wrap(step.fn, step.id); - break; - case "gb" : emu.tx(`GB(${JSON.stringify(step.obj)})\n`); break; - case "tap" : emu.tx(`Bangle.emit(...)\n`); break; - case "eval" : - console.log(`> Evaluate "${step.js}"`); - emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); - var result = emu.getLastLine(); - var expected = JSON.stringify(step.eq); - console.log("> GOT "+result); - if (result!=expected) { - console.log("> FAIL: EXPECTED "+expected); - ok = false; - } - break; - // tap/touch/drag/button press - // delay X milliseconds? - case "assertArray": - assertArray(step); - break; - case "assertCall": - assertCall(step); - break; - case "assert": - assertValue(step); - break; - case "screenshot" : - console.log(`> Compare screenshots - UNIMPLEMENTED`); - break; - case "saveMemoryUsage" : - emu.tx(`\x10print(process.memory().usage)\n`); - subtest.memUsage = parseInt( emu.getLastLine()); - console.log("> CURRENT MEMORY USAGE", subtest.memUsage); - break; - case "checkMemoryUsage" : - emu.tx(`\x10print(process.memory().usage)\n`); - var memUsage = emu.getLastLine(); - console.log("> CURRENT MEMORY USAGE", memUsage); - if (subtest.memUsage != memUsage ) { - console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); - ok = false; - } - break; - default: ERROR("Unknown step type "+step.t); - } - emu.idle(); + runStep(step, subtest, test, state) }); }); emu.stopIdle(); From 8b354edf7cb6cd1371802aee666e7d590ca66db2 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 00:04:46 +0200 Subject: [PATCH 03/33] runapptests - Print results table --- bin/runapptests.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index c150e9894..71065ec62 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -208,7 +208,7 @@ function runStep(step, subtest, test, state){ emu.idle(); } -function runTest(test) { +function runTest(test, testState) { var app = apploader.apps.find(a=>a.id==test.app); if (!app) ERROR(`App ${JSON.stringify(test.app)} not found`); if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); @@ -234,11 +234,19 @@ function runTest(test) { subtest.steps.forEach(step => { runStep(step, subtest, test, state) }); + console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, "\n"+state.ok ? "OK": "FAIL"); + testState.push({ + app: test.app, + number: subtestIdx, + result: state.ok ? "SUCCESS": "FAILURE", + description: subtest.description + }); }); emu.stopIdle(); }); } +let testState = []; emu.init({ EMULATOR : EMULATOR, @@ -246,14 +254,22 @@ emu.init({ }).then(function() { // Emulator is now loaded console.log("Loading tests"); + let p = Promise.resolve(); apploader.apps.forEach(app => { var testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; var test = JSON.parse(require("fs").readFileSync(testFile).toString()); test.app = app.id; - runTest(test); + p = p.then(() => runTest(test, testState)); }); + p.finally(()=>{ + console.log("\n\n"); + console.log("Overall results:"); + console.table(testState); + }); + return p; }); + /* if (erroredApps.length) { erroredApps.forEach(app => { From a7f17f0f3c91974489a3bef62e98cfc2db813d0d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 00:05:11 +0200 Subject: [PATCH 04/33] android - Initial tests for GPS overwriting --- apps/android/test.json | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/android/test.json diff --git a/apps/android/test.json b/apps/android/test.json new file mode 100644 index 000000000..2fbe5f977 --- /dev/null +++ b/apps/android/test.json @@ -0,0 +1,40 @@ +{ + "app" : "android", + "setup" : [{ + "id": "default", + "steps" : [ + {"t":"cmd", "js": "Bangle.setGPSPower=(isOn, appID)=>{if (!appID) appID='?';if (!Bangle._PWR) Bangle._PWR={};if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);return Bangle._PWR.GPS.length>0;};", "text": "Fake the setGPSPower"}, + {"t":"wrap", "fn": "Bangle.setGPSPower", "id": "gpspower"}, + {"t":"cmd", "js": "NRF.getSecurityStatus = () => { return { connected: false };}", "text": "Control the security status"}, + {"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port println"}, + {"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"}, + {"t":"cmd", "js": "Bangle._PWR={}", "text": "Prepare an empty _PWR for following asserts"}, + {"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"}, + {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"} + ] + }], + "tests" : [{ + "description": "Check setGPSPower is replaced", + "steps" : [ + {"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port"}, + {"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"}, + {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"}, + {"t":"assert", "js": "Bangle.setGPSPower.toString().includes('native')", "is":"false", "text": "setGPSPower has been replaced"} + ] + },{ + "description": "Test switching hardware GPS on and off", + "steps" : [ + {"t":"setup", "id": "default"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"}, + {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"truthy", "text": "setGPSPower returns truthy when switching on"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"truthy", "text": "isGPSOn shows GPS as on"}, + {"t":"assertCall", "id": "gpspower", "count": 1, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "internal GPS switched on"}, + {"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"falsy", "text": "setGPSPower returns falsy when switching off"}, + {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, + {"t":"assert", "js": "Bangle.isGPSOn()", "is":"falsy", "text": "isGPSOn shows GPS as off"}, + {"t":"assertCall", "id": "gpspower", "count": 2, "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ] , "text": "internal GPS switched off"} + ] + }] +} From 98479df6194d50ab08b473e912d592a6f4c02c97 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 00:05:44 +0200 Subject: [PATCH 05/33] antonclk - Test for memory leaks --- apps/antonclk/test.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/antonclk/test.json diff --git a/apps/antonclk/test.json b/apps/antonclk/test.json new file mode 100644 index 000000000..0db6e27f7 --- /dev/null +++ b/apps/antonclk/test.json @@ -0,0 +1,14 @@ +{ + "app" : "antonclk", + "tests" : [{ + "steps" : [ + {"t":"cmd", "js": "Bangle.loadWidgets()"}, + {"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, + {"t":"cmd", "js": "Bangle.setUI()"}, + {"t":"saveMemoryUsage"}, + {"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, + {"t":"cmd", "js":"Bangle.setUI()"}, + {"t":"checkMemoryUsage"} + ] + }] +} From 97b5bf50395eb52f64519cd937ca2b13fac789a4 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 00:36:02 +0200 Subject: [PATCH 06/33] android - Remove duplicated tests file --- apps/android/tests.json | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 apps/android/tests.json diff --git a/apps/android/tests.json b/apps/android/tests.json deleted file mode 100644 index fdb836824..000000000 --- a/apps/android/tests.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "app" : "android", - "tests" : [ { - "steps" : [ - {"t":"wrap", "fn": "Bangle.setGPSPower", "id": "gpspower"}, - {"t":"cmd", "js": "NRF.getSecurityStatus = () => {}"}, - {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))"}, - {"t":"assert", "js": "!NRF.getSecurityStatus().connected", "is":"true", "text": "Not connected"}, - {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, - {"t":"assert", "js": "Bangle.isGPSOn()", "is":"false", "text": "isGPSOn is correct"}, - {"t":"assert", "js": "Bangle.setGPSPower(1, 'test')", "is":"true", "text": "setGPSPower returns true when switching on"}, - {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"notEmpty", "text": "GPS clients"}, - {"t":"assert", "js": "Bangle.isGPSOn()", "is":"true", "text": "isGPSOn is correct"}, - {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "internal GPS switched on"} - {"t":"assert", "js": "Bangle.setGPSPower(0, 'test')", "is":"false", "text": "setGPSPower returns false when switching off"}, - {"t":"assertArray", "js": "Bangle._PWR.GPS", "is":"undefinedOrEmpty", "text": "No GPS clients"}, - {"t":"assert", "js": "Bangle.isGPSOn()", "is":"false", "text": "isGPSOn is correct"}, - {"t":"assertCall", "id": "gpspower", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 0 } ] , "text": "internal GPS switched off"} - ] - }] -} From 5574a0e62b685d9ee8d628cfb205ee933e4e9adf Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 09:01:55 +0200 Subject: [PATCH 07/33] runapptests - Wrap every step in a promise to allow waiting --- bin/runapptests.js | 133 ++++++++++++++++++++++++++++++--------------- 1 file changed, 89 insertions(+), 44 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 71065ec62..892cf66fa 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -145,56 +145,84 @@ function assertCall(step){ } function runStep(step, subtest, test, state){ + let p = Promise.resolve(); if (state.ok) switch(step.t) { case "setup" : test.setup.filter(e=>e.id==step.id)[0].steps.forEach(setupStep=>{ - runStep(setupStep, subtest, test, state); + p = p.then(runStep(setupStep, subtest, test, state)); }); break; case "load" : + p = p.then(() => { console.log(`> Loading file "${step.fn}"`); emu.tx(`load(${JSON.stringify(step.fn)})\n`); - break; + }); + break; case "cmd" : - console.log(`> Sending JS "${step.js}"`); - emu.tx(`${step.js}\n`); + p = p.then(() => { + console.log(`> Sending JS "${step.js}"`); + emu.tx(`${step.js}\n`); + }); break; case "wrap" : - wrap(step.fn, step.id); + p = p.then(() => { + wrap(step.fn, step.id); + }); + break; + case "gb" : + p = p.then(() => { + emu.tx(`GB(${JSON.stringify(step.obj)})\n`); + }); + break; + case "tap" : + p = p.then(() => { + emu.tx(`Bangle.emit(...)\n`); + }); break; - case "gb" : emu.tx(`GB(${JSON.stringify(step.obj)})\n`); break; - case "tap" : emu.tx(`Bangle.emit(...)\n`); break; case "eval" : - console.log(`> Evaluate "${step.js}"`); - emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); - var result = emu.getLastLine(); - var expected = JSON.stringify(step.eq); - console.log("> GOT "+result); - if (result!=expected) { - console.log("> FAIL: EXPECTED "+expected); - state.ok = false; - } + p = p.then(() => { + console.log(`> Evaluate "${step.js}"`); + emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); + var result = emu.getLastLine(); + var expected = JSON.stringify(step.eq); + console.log("> GOT "+result); + if (result!=expected) { + console.log("> FAIL: EXPECTED "+expected); + state.ok = false; + } + }); break; // tap/touch/drag/button press // delay X milliseconds? case "assertArray": - assertArray(step); + p = p.then(() => { + assertArray(step); + }); break; case "assertCall": - assertCall(step); + p = p.then(() => { + assertCall(step); + }); break; case "assert": - assertValue(step); + p = p.then(() => { + assertValue(step); + }); break; case "screenshot" : - console.log(`> Compare screenshots - UNIMPLEMENTED`); + p = p.then(() => { + console.log(`> Compare screenshots - UNIMPLEMENTED`); + }); break; case "saveMemoryUsage" : - emu.tx(`\x10print(process.memory().usage)\n`); - subtest.memUsage = parseInt( emu.getLastLine()); - console.log("> CURRENT MEMORY USAGE", subtest.memUsage); + p = p.then(() => { + emu.tx(`\x10print(process.memory().usage)\n`); + subtest.memUsage = parseInt( emu.getLastLine()); + console.log("> CURRENT MEMORY USAGE", subtest.memUsage); + }); break; case "checkMemoryUsage" : + p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); var memUsage = emu.getLastLine(); console.log("> CURRENT MEMORY USAGE", memUsage); @@ -202,6 +230,13 @@ function runStep(step, subtest, test, state){ console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); state.ok = false; } + }); + break; + case "sleep" : + p = p.then(new Promise(resolve => { + console.log("Start waiting for", step.ms); + setTimeout(resolve, step.ms) + })); break; default: ERROR("Unknown step type "+step.t); } @@ -217,32 +252,42 @@ function runTest(test, testState) { console.log("Handling command", command); // What about dependencies?? + let p = Promise.resolve(); test.tests.forEach((subtest,subtestIdx) => { - console.log(`==============================`); - console.log(`"${test.app}" Test ${subtestIdx}`); - if (test.description) - console.log(`"${test.description}`); - console.log(`==============================`); - emu.factoryReset(); - console.log("> Sending app "+test.app); - emu.tx(command); - console.log("> Sent app"); - emu.tx("reset()\n"); - console.log("> Reset"); + let state = { ok: true}; + p = p.then(()=>{ + console.log(`==============================`); + console.log(`"${test.app}" Test ${subtestIdx}`); + if (test.description) + console.log(`"${test.description}`); + console.log(`==============================`); + emu.factoryReset(); + console.log("> Sending app "+test.app); + emu.tx(command); + console.log("> Sent app"); + emu.tx("reset()\n"); + console.log("> Reset"); - var state = { ok: true}; - subtest.steps.forEach(step => { - runStep(step, subtest, test, state) }); - console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, "\n"+state.ok ? "OK": "FAIL"); - testState.push({ - app: test.app, - number: subtestIdx, - result: state.ok ? "SUCCESS": "FAILURE", - description: subtest.description + + subtest.steps.forEach(step => { + p = p.then(runStep(step, subtest, test, state)); + }); + + p = p.then(() => { + console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, "\n"+state.ok ? "OK": "FAIL"); + testState.push({ + app: test.app, + number: subtestIdx, + result: state.ok ? "SUCCESS": "FAILURE", + description: subtest.description + }); }); }); - emu.stopIdle(); + p = p.then(()=>{ + emu.stopIdle(); + }); + return p; }); } From 412a14c2adc0f0892cbcb802bfb881af62c55fe0 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 10:45:54 +0200 Subject: [PATCH 08/33] runapptests - Allow uploading files --- bin/runapptests.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 892cf66fa..195040826 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -35,6 +35,7 @@ if (!require("fs").existsSync(DIR_IDE)) { process.exit(1); } +var AppInfo = require(BASE_DIR+"/core/js/appinfo.js"); var apploader = require(BASE_DIR+"/core/lib/apploader.js"); apploader.init({ DEVICEID : DEVICEID @@ -235,7 +236,13 @@ function runStep(step, subtest, test, state){ case "sleep" : p = p.then(new Promise(resolve => { console.log("Start waiting for", step.ms); - setTimeout(resolve, step.ms) + setTimeout(resolve, step.ms); + })); + break; + case "upload" : + p = p.then(new Promise(() => { + console.log("Uploading", step.file); + emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString())); })); break; default: ERROR("Unknown step type "+step.t); From e8dc8b9be8edb1fe2fc4f174f1b84525836baa5d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 10:46:25 +0200 Subject: [PATCH 09/33] runapptests - Set defaults for sending a GB event --- bin/runapptests.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 195040826..349aed530 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -172,7 +172,15 @@ function runStep(step, subtest, test, state){ break; case "gb" : p = p.then(() => { - emu.tx(`GB(${JSON.stringify(step.obj)})\n`); + let obj = Object.apply({ + src:'Messenger', + t: 'notify', + type: 'text', + id: Date.now().toFixed(0), + title:'title', + body:'body' + }, step.obj || {}); + emu.tx(`GB(${JSON.stringify(obj)})\n`); }); break; case "tap" : From a84b6d138866e03a75c379cd5f41b86c82e7eb3a Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 10:46:59 +0200 Subject: [PATCH 10/33] runapptests - Implement generic event emitter command --- bin/runapptests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 349aed530..1c025e715 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -183,9 +183,11 @@ function runStep(step, subtest, test, state){ emu.tx(`GB(${JSON.stringify(obj)})\n`); }); break; - case "tap" : + case "emit" : p = p.then(() => { - emu.tx(`Bangle.emit(...)\n`); + let parent = step.parent ? step.parent : "Bangle" + let args = JSON.stringify([step.event].concat(step.paramsArray)); + emu.tx(`${parent}.emit.apply(${parent}, ${args})\n`); }); break; case "eval" : From 7289d3fd2b847bc15152529d9568f99dc849f8bb Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 15:00:37 +0200 Subject: [PATCH 11/33] runapptests - Fix promise use for step running --- bin/runapptests.js | 47 +++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 1c025e715..ff49e1184 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -150,7 +150,9 @@ function runStep(step, subtest, test, state){ if (state.ok) switch(step.t) { case "setup" : test.setup.filter(e=>e.id==step.id)[0].steps.forEach(setupStep=>{ - p = p.then(runStep(setupStep, subtest, test, state)); + p = p.then(()=>{ + return runStep(setupStep, subtest, test, state); + }); }); break; case "load" : @@ -234,30 +236,37 @@ function runStep(step, subtest, test, state){ break; case "checkMemoryUsage" : p = p.then(() => { - emu.tx(`\x10print(process.memory().usage)\n`); - var memUsage = emu.getLastLine(); - console.log("> CURRENT MEMORY USAGE", memUsage); - if (subtest.memUsage != memUsage ) { - console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); - state.ok = false; - } + emu.tx(`\x10print(process.memory().usage)\n`); + var memUsage = emu.getLastLine(); + console.log("> CURRENT MEMORY USAGE", memUsage); + if (subtest.memUsage != memUsage ) { + console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); + state.ok = false; + } }); break; case "sleep" : - p = p.then(new Promise(resolve => { - console.log("Start waiting for", step.ms); - setTimeout(resolve, step.ms); - })); + p = p.then(()=>{ + return new Promise(resolve => { + setTimeout(()=>{ + console.log("Waited for", step.ms); + resolve(); + }, step.ms); + }) + }); break; case "upload" : - p = p.then(new Promise(() => { + p = p.then(()=>{ console.log("Uploading", step.file); emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString())); - })); + }); break; default: ERROR("Unknown step type "+step.t); } - emu.idle(); + p = p.then(()=> { + emu.idle(); + }); + return p; } function runTest(test, testState) { @@ -288,7 +297,9 @@ function runTest(test, testState) { }); subtest.steps.forEach(step => { - p = p.then(runStep(step, subtest, test, state)); + p = p.then(()=>{ + return runStep(step, subtest, test, state) + }); }); p = p.then(() => { @@ -322,7 +333,9 @@ emu.init({ if (!require("fs").existsSync(testFile)) return; var test = JSON.parse(require("fs").readFileSync(testFile).toString()); test.app = app.id; - p = p.then(() => runTest(test, testState)); + p = p.then(()=>{ + return runTest(test, testState) + }); }); p.finally(()=>{ console.log("\n\n"); From 9b01f82324e051f0ae95b447898a8f217c3dd673 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 18:35:57 +0200 Subject: [PATCH 12/33] runapptests - Better logging of emit --- bin/runapptests.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index ff49e1184..a5f439bc3 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -187,8 +187,11 @@ function runStep(step, subtest, test, state){ break; case "emit" : p = p.then(() => { - let parent = step.parent ? step.parent : "Bangle" + let parent = step.parent ? step.parent : "Bangle"; + if (!step.paramsArray) step.paramsArray = []; let args = JSON.stringify([step.event].concat(step.paramsArray)); + console.log(`> Emit "${step.event}" on ${parent} with parameters ${JSON.stringify(step.paramsArray)}`); + emu.tx(`${parent}.emit.apply(${parent}, ${args})\n`); }); break; From 842070928c1ac6149b6db27363e78deb88ac1e79 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 22:56:37 +0200 Subject: [PATCH 13/33] runapptests - Reset apploaders device installed apps list for each test --- bin/runapptests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/runapptests.js b/bin/runapptests.js index a5f439bc3..3107d1fd9 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -273,6 +273,7 @@ function runStep(step, subtest, test, state){ } function runTest(test, testState) { + apploader.reset(); var app = apploader.apps.find(a=>a.id==test.app); if (!app) ERROR(`App ${JSON.stringify(test.app)} not found`); if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); From 9f340dd8c323dcc90cb355f7745b9e7b813645d9 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 22:57:19 +0200 Subject: [PATCH 14/33] runapptests - Better result handling and logging --- bin/runapptests.js | 85 +++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 3107d1fd9..6fa6cc56e 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -51,49 +51,62 @@ function ERROR(s) { } function getValue(js){ - //console.log(`> Getting value for "${js}"`); + console.log(`> Getting value for "${js}"`); emu.tx(`\x10print(JSON.stringify(${js}))\n`); var result = emu.getLastLine(); - try { - return JSON.parse(result); - } catch (e) { - console.log("Error during getValue", e); - } + console.log(`> GOT "${result}"`); + return JSON.parse(result); } function assertFail(text){ console.log("> FAIL: " + text); - ok = false; + return false; } -function assertCondition(condition, text) { +function assertCondition(condition) { if (!condition) { - assertFail(text); - } else console.log("OK: " + text); + return false; + } + return true; } function assertArray(step){ + let isOK; let array = step.value; if (step.value === undefined) array = getValue(step.js); - switch (step.is){ - case "notempty": assertCondition(array && array.length > 0, step.text); break; - case "undefinedorempty": assertCondition(array && array.length == 0, step.text); break; + switch (step.is.toLowerCase()){ + case "notempty": isOK = assertCondition(array && array.length > 0, step.text); break; + case "undefinedorempty": isOK = assertCondition(array && array.length == 0 || !array, step.text); break; } + + if (isOK) + console.log("OK - ASSERT ARRAY " + step.is.toUpperCase(), step.text); + else + console.log("FAIL - ASSERT ARRAY " + step.is.toUpperCase(), step.text); + return isOK; } function assertValue(step){ console.debug("assertValue", step); + let isOK; let value = step.js; if (value === undefined) value = step.value; switch (step.is){ - case "truthy": assertCondition(getValue(`!!${value}`), step.text); break; - case "falsy": assertCondition(getValue(`!${value}`), step.text); break; - case "true": assertCondition(getValue(`${value} === true`), step.text); break; - case "false": assertCondition(getValue(`${value} === false`), step.text); break; - case "equal": assertCondition(getValue(`${value} == ${step.to}`), step.text); break; + case "truthy": isOK = assertCondition(getValue(`!!${value}`), step.text); break; + case "falsy": isOK = assertCondition(getValue(`!${value}`), step.text); break; + case "true": isOK = assertCondition(getValue(`${value} === true`), step.text); break; + case "false": isOK = assertCondition(getValue(`${value} === false`), step.text); break; + case "equal": isOK = assertCondition(getValue(`${value} == ${step.to}`), step.text); break; + case "function": isOK = assertCondition(getValue(`typeof ${value} === "function"`), step.text); break; } + + if (isOK) + console.log("OK - ASSERT " + step.is.toUpperCase(), step.text); + else + console.log("FAIL - ASSERT " + step.is.toUpperCase(), step.text); + return isOK; } function wrap(func, id){ @@ -116,6 +129,7 @@ function wrap(func, id){ } function assertCall(step){ + let isOK = false; let id = step.id; let args = step.argAsserts; let calls = getValue(`global.APPTESTS.funcCalls.${id}`); @@ -131,18 +145,22 @@ function assertCall(step){ }; switch(a.t){ case "assertArray": - assertArray(current); + isOK = assertArray(current); break; case "assert": - assertValue(current); + isOK = assertValue(current); break; } } } else { - console.log("OK", step.text); + isOK = true; } - } else - assertFail(step.text) + } + if (isOK) + console.log("OK", step.text); + else + console.log("FAIL", step.text); + return isOK; } function runStep(step, subtest, test, state){ @@ -151,7 +169,9 @@ function runStep(step, subtest, test, state){ case "setup" : test.setup.filter(e=>e.id==step.id)[0].steps.forEach(setupStep=>{ p = p.then(()=>{ - return runStep(setupStep, subtest, test, state); + let np = runStep(setupStep, subtest, test, state); + emu.idle(); + return np; }); }); break; @@ -212,17 +232,17 @@ function runStep(step, subtest, test, state){ // delay X milliseconds? case "assertArray": p = p.then(() => { - assertArray(step); + state.ok &= assertArray(step); }); break; case "assertCall": p = p.then(() => { - assertCall(step); + state.ok &= assertCall(step); }); break; case "assert": p = p.then(() => { - assertValue(step); + state.ok &= assertValue(step); }); break; case "screenshot" : @@ -302,12 +322,15 @@ function runTest(test, testState) { subtest.steps.forEach(step => { p = p.then(()=>{ - return runStep(step, subtest, test, state) + return runStep(step, subtest, test, state).catch((e)=>{ + console.log("STEP FAILED:", e, step); + state.ok = false; + }) }); }); - p = p.then(() => { - console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, "\n"+state.ok ? "OK": "FAIL"); + p = p.finally(()=>{ + console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, (state.ok ? "OK": "FAIL")); testState.push({ app: test.app, number: subtestIdx, @@ -338,7 +361,7 @@ emu.init({ var test = JSON.parse(require("fs").readFileSync(testFile).toString()); test.app = app.id; p = p.then(()=>{ - return runTest(test, testState) + return runTest(test, testState); }); }); p.finally(()=>{ From 24a40c88c9992c11d4a70db84e1372f1848e0a79 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 22:57:46 +0200 Subject: [PATCH 15/33] messagesoverlay - Adds initial tests --- apps/messagesoverlay/test.json | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 apps/messagesoverlay/test.json diff --git a/apps/messagesoverlay/test.json b/apps/messagesoverlay/test.json new file mode 100644 index 000000000..866a1dfa0 --- /dev/null +++ b/apps/messagesoverlay/test.json @@ -0,0 +1,34 @@ +{ + "app" : "messagesoverlay", + "tests" : [{ + "description": "Test handler backgrounding", + "steps" : [ + {"t":"upload", "file": "modules/widget_utils.js", "as": "widget_utils"}, + {"t":"cmd", "js": "Bangle.loadWidgets()", "text": "Load widgets"}, + {"t":"assertArray", "js": "Bangle['#onswipe']", "is":"undefinedOrEmpty", "text": "No swipe handlers"}, + {"t":"cmd", "js": "require('widget_utils').swipeOn(0)", "text": "Store widgets in overlay"}, + {"t":"assert", "js": "Bangle['#onswipe']", "is":"function", "text": "One swipe handler for widgets"}, + {"t":"emit", "event":"swipe", "paramsArray": [ 0, 1 ], "text": "Show widgets"}, + {"t":"assert", "js": "Bangle['#onswipe']", "is":"function", "text": "One swipe handler for widgets"}, + {"t":"cmd", "js": "require('messagesoverlay').message('text', {src:'Messenger',t:'add',type:'text',id:Date.now().toFixed(0),title:'title',body:'body'})", "text": "Show a message overlay"}, + {"t":"assertArray", "js": "Bangle['#onswipe']", "is":"undefinedOrEmpty", "text": "No swipe handlers while message overlay is on screen"}, + {"t":"emit", "event":"touch", "paramsArray": [ 1, { "x": 10, "y": 10, "type": 0 } ], "text": "Close message"}, + {"t":"assert", "js": "Bangle['#onswipe']", "is":"function", "text": "One swipe handler restored"} + ] + },{ + "description": "Test handler backgrounding with fastloading (setUI)", + "steps" : [ + {"t":"upload", "file": "modules/widget_utils.js", "as": "widget_utils"}, + {"t":"cmd", "js": "Bangle.loadWidgets()", "text": "Load widgets"}, + {"t":"cmd", "js": "Bangle.on('swipe',print)", "text": "Create listener for swipes"}, + {"t":"cmd", "js": "Bangle.setUI({mode: 'clock',remove: ()=>{require('widget_utils').show();}})", "text": "Init UI for clock"}, + {"t":"cmd", "js": "require('messagesoverlay').message('text', {src:'Messenger',t:'add',type:'text',id:Date.now().toFixed(0),title:'title',body:'body'})", "text": "Show a message overlay"}, + {"t":"assertArray", "js": "Bangle['#onswipe']", "is":"undefinedOrEmpty", "text": "No swipe handlers while message overlay is on screen"}, + {"t":"cmd", "js": "Bangle.setUI()", "text": "Trigger removal of UI"}, + {"t":"assertArray", "js": "Bangle['#onswipe']", "is":"undefinedOrEmpty", "text": "Still no swipe handlers"}, + {"t":"cmd", "js": "Bangle.on('touch', print)"}, + {"t":"emit", "event":"touch", "paramsArray": [ 1, { "x": 10, "y": 10, "type": 0 } ], "text": "Close message"}, + {"t":"assert", "js": "Bangle['#onswipe']", "is":"function", "text": "One swipe handler restored"} + ] + }] +} From b7babc46dffe81d33c63deaa427fcef0b0e011bc Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 22:58:17 +0200 Subject: [PATCH 16/33] android - Remove a bit of noise during the testrun (erros because of missing Bluetooth.println) --- apps/android/test.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/android/test.json b/apps/android/test.json index 2fbe5f977..809fd012c 100644 --- a/apps/android/test.json +++ b/apps/android/test.json @@ -17,6 +17,7 @@ "description": "Check setGPSPower is replaced", "steps" : [ {"t":"cmd", "js": "Serial1.println = () => { }", "text": "Fake the serial port"}, + {"t":"cmd", "js": "Bluetooth.println = () => { }", "text": "Fake the Bluetooth println"}, {"t":"cmd", "js": "require('Storage').writeJSON('android.settings.json', {overwriteGps: true})", "text": "Enable GPS overwrite"}, {"t":"cmd", "js": "eval(require('Storage').read('android.boot.js'))", "text": "Load the boot code"}, {"t":"assert", "js": "Bangle.setGPSPower.toString().includes('native')", "is":"false", "text": "setGPSPower has been replaced"} From 8c74a23549c204106ccc0b6c85fbd0b5301b869c Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sat, 4 May 2024 23:06:32 +0200 Subject: [PATCH 17/33] messagesoverlay - Remove not needed test steps regarding widgets --- apps/messagesoverlay/test.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/messagesoverlay/test.json b/apps/messagesoverlay/test.json index 866a1dfa0..40ac14a8b 100644 --- a/apps/messagesoverlay/test.json +++ b/apps/messagesoverlay/test.json @@ -18,10 +18,8 @@ },{ "description": "Test handler backgrounding with fastloading (setUI)", "steps" : [ - {"t":"upload", "file": "modules/widget_utils.js", "as": "widget_utils"}, - {"t":"cmd", "js": "Bangle.loadWidgets()", "text": "Load widgets"}, {"t":"cmd", "js": "Bangle.on('swipe',print)", "text": "Create listener for swipes"}, - {"t":"cmd", "js": "Bangle.setUI({mode: 'clock',remove: ()=>{require('widget_utils').show();}})", "text": "Init UI for clock"}, + {"t":"cmd", "js": "Bangle.setUI({mode: 'clock',remove: ()=>{}})", "text": "Init UI for clock"}, {"t":"cmd", "js": "require('messagesoverlay').message('text', {src:'Messenger',t:'add',type:'text',id:Date.now().toFixed(0),title:'title',body:'body'})", "text": "Show a message overlay"}, {"t":"assertArray", "js": "Bangle['#onswipe']", "is":"undefinedOrEmpty", "text": "No swipe handlers while message overlay is on screen"}, {"t":"cmd", "js": "Bangle.setUI()", "text": "Trigger removal of UI"}, From 00f66f31a08114b522955450a9d578f901ce6fa0 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 5 May 2024 08:59:43 +0200 Subject: [PATCH 18/33] runapptests - Add option for filtering tests by app id --- bin/runapptests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 6fa6cc56e..535fdb574 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -93,7 +93,7 @@ function assertValue(step){ let value = step.js; if (value === undefined) value = step.value; - switch (step.is){ + switch (step.is.toLowerCase()){ case "truthy": isOK = assertCondition(getValue(`!!${value}`), step.text); break; case "falsy": isOK = assertCondition(getValue(`!${value}`), step.text); break; case "true": isOK = assertCondition(getValue(`${value} === true`), step.text); break; @@ -355,7 +355,9 @@ emu.init({ // Emulator is now loaded console.log("Loading tests"); let p = Promise.resolve(); - apploader.apps.forEach(app => { + let apps = apploader.apps; + if (process.argv.includes("--id")) apps = apps.filter(e=>e.id==process.argv[process.argv.indexOf("--id") + 1]); + apps.forEach(app => { var testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; var test = JSON.parse(require("fs").readFileSync(testFile).toString()); From 5c5c89878bcf6ba7ab3b630e542f657ad030415d Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 5 May 2024 09:30:58 +0200 Subject: [PATCH 19/33] runapptests - Return number of failed tests as exit code --- bin/runapptests.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/runapptests.js b/bin/runapptests.js index 535fdb574..f83958a2b 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -370,6 +370,10 @@ emu.init({ console.log("\n\n"); console.log("Overall results:"); console.table(testState); + + process.exit(testState.reduce((a,c)=>{ + return a + ((c.result == "SUCCESS") ? 0 : 1); + }, 0)) }); return p; }); From 56f7dd192c8d613bb3047de56347650263b27a59 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Sun, 5 May 2024 09:31:52 +0200 Subject: [PATCH 20/33] anton - Add description to test --- apps/antonclk/test.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/antonclk/test.json b/apps/antonclk/test.json index 0db6e27f7..a719e0a14 100644 --- a/apps/antonclk/test.json +++ b/apps/antonclk/test.json @@ -1,6 +1,7 @@ { "app" : "antonclk", "tests" : [{ + "description": "Check memory usage after setUI", "steps" : [ {"t":"cmd", "js": "Bangle.loadWidgets()"}, {"t":"cmd", "js": "eval(require('Storage').read('antonclk.app.js'))"}, From 4f258f800b326accc96438760c8642eae406c717 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 19:33:53 +0200 Subject: [PATCH 21/33] runapptests - Unify log messages --- bin/runapptests.js | 88 ++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 50 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index f83958a2b..1ead38d86 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -45,67 +45,58 @@ var emu = require(BASE_DIR+"/core/lib/emulator.js"); // Last set of text received var lastTxt; +function getSanitizedLastLine(){ + return emu.getLastLine().replaceAll("\r", ""); +} + function ERROR(s) { console.error(s); process.exit(1); } function getValue(js){ - console.log(`> Getting value for "${js}"`); + if (verbose) + console.log(`> GETTING VALUE FOR \`${js}\``); emu.tx(`\x10print(JSON.stringify(${js}))\n`); - var result = emu.getLastLine(); - console.log(`> GOT "${result}"`); + var result = getSanitizedLastLine(); + + if (verbose) + console.log(` GOT \`${result}\``); return JSON.parse(result); } -function assertFail(text){ - console.log("> FAIL: " + text); - return false; -} - -function assertCondition(condition) { - if (!condition) { - return false; - } - return true; -} - function assertArray(step){ let isOK; - let array = step.value; - if (step.value === undefined) - array = getValue(step.js); switch (step.is.toLowerCase()){ - case "notempty": isOK = assertCondition(array && array.length > 0, step.text); break; - case "undefinedorempty": isOK = assertCondition(array && array.length == 0 || !array, step.text); break; + case "notempty": isOK = getValue(`${step.js} && ${step.js}.length > 0`); break; + case "undefinedorempty": isOK = getValue(`!${step.js} || (${step.js} && ${step.js}.length === 0)`); break; } if (isOK) - console.log("OK - ASSERT ARRAY " + step.is.toUpperCase(), step.text); + console.log("> OK - ASSERT ARRAY " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); else - console.log("FAIL - ASSERT ARRAY " + step.is.toUpperCase(), step.text); + console.log("> FAIL - ASSERT ARRAY " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); return isOK; } function assertValue(step){ - console.debug("assertValue", step); let isOK; let value = step.js; if (value === undefined) value = step.value; switch (step.is.toLowerCase()){ - case "truthy": isOK = assertCondition(getValue(`!!${value}`), step.text); break; - case "falsy": isOK = assertCondition(getValue(`!${value}`), step.text); break; - case "true": isOK = assertCondition(getValue(`${value} === true`), step.text); break; - case "false": isOK = assertCondition(getValue(`${value} === false`), step.text); break; - case "equal": isOK = assertCondition(getValue(`${value} == ${step.to}`), step.text); break; - case "function": isOK = assertCondition(getValue(`typeof ${value} === "function"`), step.text); break; + case "truthy": isOK = getValue(`!!${value}`); break; + case "falsy": isOK = getValue(`!${value}`); break; + case "true": isOK = getValue(`${value} === true`); break; + case "false": isOK = getValue(`${value} === false`); break; + case "equal": isOK = getValue(`${value} == ${step.to}`); break; + case "function": isOK = getValue(`typeof ${value} === "function"`); break; } if (isOK) - console.log("OK - ASSERT " + step.is.toUpperCase(), step.text); + console.log("> OK - ASSERT " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); else - console.log("FAIL - ASSERT " + step.is.toUpperCase(), step.text); + console.log("> FAIL - ASSERT " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); return isOK; } @@ -157,9 +148,9 @@ function assertCall(step){ } } if (isOK) - console.log("OK", step.text); + console.log("OK - ASSERT CALL", step.text ? "- " + step.text : ""); else - console.log("FAIL", step.text); + console.log("FAIL - ASSERT CALL", step.text ? "- " + step.text : ""); return isOK; } @@ -177,13 +168,13 @@ function runStep(step, subtest, test, state){ break; case "load" : p = p.then(() => { - console.log(`> Loading file "${step.fn}"`); + console.log(`> LOADING FILE "${step.fn}"`); emu.tx(`load(${JSON.stringify(step.fn)})\n`); }); break; case "cmd" : p = p.then(() => { - console.log(`> Sending JS "${step.js}"`); + console.log(`> SENDING JS \`${step.js}\``, step.text ? "- " + step.text : ""); emu.tx(`${step.js}\n`); }); break; @@ -210,7 +201,7 @@ function runStep(step, subtest, test, state){ let parent = step.parent ? step.parent : "Bangle"; if (!step.paramsArray) step.paramsArray = []; let args = JSON.stringify([step.event].concat(step.paramsArray)); - console.log(`> Emit "${step.event}" on ${parent} with parameters ${JSON.stringify(step.paramsArray)}`); + console.log(`> EMIT "${step.event}" on ${parent} with parameters ${JSON.stringify(step.paramsArray, null, null)}`); emu.tx(`${parent}.emit.apply(${parent}, ${args})\n`); }); @@ -219,9 +210,9 @@ function runStep(step, subtest, test, state){ p = p.then(() => { console.log(`> Evaluate "${step.js}"`); emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); - var result = emu.getLastLine(); + var result = getSanitizedLastLine(); var expected = JSON.stringify(step.eq); - console.log("> GOT "+result); + console.log("> GOT `"+result+"`"); if (result!=expected) { console.log("> FAIL: EXPECTED "+expected); state.ok = false; @@ -253,14 +244,14 @@ function runStep(step, subtest, test, state){ case "saveMemoryUsage" : p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); - subtest.memUsage = parseInt( emu.getLastLine()); + subtest.memUsage = parseInt( getSanitizedLastLine()); console.log("> CURRENT MEMORY USAGE", subtest.memUsage); }); break; case "checkMemoryUsage" : p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); - var memUsage = emu.getLastLine(); + var memUsage = getSanitizedLastLine(); console.log("> CURRENT MEMORY USAGE", memUsage); if (subtest.memUsage != memUsage ) { console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); @@ -272,7 +263,7 @@ function runStep(step, subtest, test, state){ p = p.then(()=>{ return new Promise(resolve => { setTimeout(()=>{ - console.log("Waited for", step.ms); + console.log("> WAITED FOR", step.ms); resolve(); }, step.ms); }) @@ -280,7 +271,7 @@ function runStep(step, subtest, test, state){ break; case "upload" : p = p.then(()=>{ - console.log("Uploading", step.file); + console.log("> UPLOADING" + (step.load ? " AND LOADING" : ""), step.file); emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString())); }); break; @@ -299,9 +290,6 @@ function runTest(test, testState) { if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); return apploader.getAppFilesString(app).then(command => { - console.log("Handling command", command); - - // What about dependencies?? let p = Promise.resolve(); test.tests.forEach((subtest,subtestIdx) => { let state = { ok: true}; @@ -312,25 +300,25 @@ function runTest(test, testState) { console.log(`"${test.description}`); console.log(`==============================`); emu.factoryReset(); - console.log("> Sending app "+test.app); + console.log("> SENDING APP "+test.app); emu.tx(command); - console.log("> Sent app"); + console.log("> SENT APP"); emu.tx("reset()\n"); - console.log("> Reset"); + console.log("> RESET"); }); subtest.steps.forEach(step => { p = p.then(()=>{ return runStep(step, subtest, test, state).catch((e)=>{ - console.log("STEP FAILED:", e, step); + console.log("> STEP FAILED:", e, step); state.ok = false; }) }); }); p = p.finally(()=>{ - console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, (state.ok ? "OK": "FAIL")); + console.log("> RESULT -", (state.ok ? "OK": "FAIL") , "- " + test.app + (subtest.description ? (" - " + subtest.description) : "")); testState.push({ app: test.app, number: subtestIdx, From 305694ad2f2bd344bb9067162b78222d01923cc0 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 19:37:31 +0200 Subject: [PATCH 22/33] runapptests - Add verbose option --- bin/runapptests.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 1ead38d86..3ea2a9b55 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -35,6 +35,8 @@ if (!require("fs").existsSync(DIR_IDE)) { process.exit(1); } +const verbose = process.argv.includes("--verbose") || process.argv.includes("-v"); + var AppInfo = require(BASE_DIR+"/core/js/appinfo.js"); var apploader = require(BASE_DIR+"/core/lib/apploader.js"); apploader.init({ @@ -55,7 +57,7 @@ function ERROR(s) { } function getValue(js){ - if (verbose) + if(verbose) console.log(`> GETTING VALUE FOR \`${js}\``); emu.tx(`\x10print(JSON.stringify(${js}))\n`); var result = getSanitizedLastLine(); @@ -344,7 +346,15 @@ emu.init({ console.log("Loading tests"); let p = Promise.resolve(); let apps = apploader.apps; - if (process.argv.includes("--id")) apps = apps.filter(e=>e.id==process.argv[process.argv.indexOf("--id") + 1]); + if (process.argv.includes("--id")) { + let f = process.argv[process.argv.indexOf("--id") + 1]; + apps = apps.filter(e=>e.id==f); + if (apps.length == 0){ + console.log("No apps left after filtering for " + f); + process.exitCode(255); + } + } + apps.forEach(app => { var testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; From 162aa57dc2835e7b8b61394c083eca3582b3f43b Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 19:46:01 +0200 Subject: [PATCH 23/33] runapptests - Allow directly loading from upload step --- bin/runapptests.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/runapptests.js b/bin/runapptests.js index 3ea2a9b55..6e581d663 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -275,6 +275,9 @@ function runStep(step, subtest, test, state){ p = p.then(()=>{ console.log("> UPLOADING" + (step.load ? " AND LOADING" : ""), step.file); emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString())); + if (step.load){ + emu.tx(`\x10load("${step.as}")`); + } }); break; default: ERROR("Unknown step type "+step.t); From 7d4489ef7b71fd320d2e1c4e85e8dc66f827f508 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 19:46:49 +0200 Subject: [PATCH 24/33] runapptests - Adds a step type for starting an interactive console --- bin/runapptests.js | 60 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 6e581d663..ffb6a4c30 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -280,6 +280,49 @@ function runStep(step, subtest, test, state){ } }); break; + case "console" : + p = p.then(()=>{ + return new Promise(resolve => { + if (process.stdin.isTTY){ + console.log("> STARTING INTERACTIVE CONSOLE"); + + let shutdownHandler = function (code) { + console.log(" STOPPING INTERACTIVE CONSOLE"); + process.stdin.removeListener("readable", stdinlistener) + process.stdin.setRawMode(false); + handleRx = ()=>{}; + handleConsoleOutput = handleConsoleOutputCurrent; + resolve(); + } + + let stdinlistener = () => { + while ((chunk = process.stdin.read()) !== null) { + if (chunk === '\x03') { + shutdownHandler(); + } + emu.tx(chunk.toString()); + } + }; + + handleRx = (c) => { + process.stdout.write(String.fromCharCode(c)); + } + + let handleConsoleOutputCurrent = handleConsoleOutput; + handleConsoleOutput = () => {}; + + process.stdin.setRawMode(true); + process.stdin.setEncoding('ASCII'); + process.stdin.on("readable", stdinlistener); + + process.stdout.write(">"); + } else { + console.log("> TERMINAL NEEDS TO BE A TTY FOR INTERACTIVE CONSOLE"); + resolve(); + } + }) + }); + break; default: ERROR("Unknown step type "+step.t); } p = p.then(()=> { @@ -339,11 +382,26 @@ function runTest(test, testState) { }); } + +let handleRx = ()=>{}; +let handleConsoleOutput = () => {}; +if (verbose){ + handleConsoleOutput = (d) => { + console.log("<", d); + } +} + let testState = []; emu.init({ EMULATOR : EMULATOR, - DEVICEID : DEVICEID + DEVICEID : DEVICEID, + rxCallback : (ch)=>{ + handleRx(ch); + }, + consoleOutputCallback: (d)=>{ + handleConsoleOutput(d); + } }).then(function() { // Emulator is now loaded console.log("Loading tests"); From 42cd2bfbcbe623210f552273616ce2e7cc3c26c5 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 21:56:49 +0200 Subject: [PATCH 25/33] runapptests - Refine assertValue --- bin/runapptests.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index ffb6a4c30..409695edd 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -83,16 +83,13 @@ function assertArray(step){ function assertValue(step){ let isOK; - let value = step.js; - if (value === undefined) - value = step.value; switch (step.is.toLowerCase()){ - case "truthy": isOK = getValue(`!!${value}`); break; - case "falsy": isOK = getValue(`!${value}`); break; - case "true": isOK = getValue(`${value} === true`); break; - case "false": isOK = getValue(`${value} === false`); break; - case "equal": isOK = getValue(`${value} == ${step.to}`); break; - case "function": isOK = getValue(`typeof ${value} === "function"`); break; + case "truthy": isOK = getValue(`!!${step.js}`); break; + case "falsy": isOK = getValue(`!${step.js}`); break; + case "true": isOK = getValue(`${step.js} === true`); break; + case "false": isOK = getValue(`${step.js} === false`); break; + case "equal": isOK = getValue(`${step.js} == ${step.to}`); break; + case "function": isOK = getValue(`typeof ${step.js} === "function"`); break; } if (isOK) From acd910a864367cff08f6e443357ceb3185e64ec6 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 21:58:59 +0200 Subject: [PATCH 26/33] runapptests - Add example test as documentation --- bin/runapptests.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/bin/runapptests.js b/bin/runapptests.js index 409695edd..0eb10f570 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -8,6 +8,8 @@ IT IS UNFINISHED It searches for `test.json` in each app's directory and will run them in sequence. +The return code is the number of failed tests. + TODO: * more code to test with @@ -20,6 +22,44 @@ TODO: */ +const DEMOAPP = { + "id":"demoappfortestformat", + "name":"demo", + "version":"0.01", + "type":"app", + "supports":["BANGLEJS2"], + "storage":[], +}; +const DEMOTEST = { + "app" : "demoappfortestformat", + "setup" : [{ + "id": "arbitraryid", + "steps" : [ + {"t":"cmd", "js": "global.testfunction = ()=>{}", "text": "Runs some code on the device"}, + {"t":"wrap", "fn": "global.testfunction", "id": "testfunc", text:"Wraps a function to count calls and store the last set of arguments on the device"} + ] + }], + "tests" : [{ + "description": "Optional description of the test, will be shown in results table", + "steps" : [ + {"t":"setup", "id": "arbitraryid", "text": "Calls a set of predefined steps"}, + {"t":"eval", "js": "'test' + 'value'", "eq": "testvalue", "text": "Evals code on the device and optionally compares the resulting string to the value in 'eq'"}, + {"t":"saveMemoryUsage", "text": "Gets and stores the current memory usage"}, + {"t":"checkMemoryUsage", "text": "Checks the current memory to be equal to the stored value"}, + {"t":"assert", "js": "0", "is":"falsy", "text": "Evaluates the content of 'js' on the device and asserts if the result is falsy"}, + {"t":"assert", "js": "1", "is":"truthy", "text": "Evaluates the content of 'js' on the device and asserts if the result is truthy"}, + {"t":"assert", "js": "false", "is":"false", "text": "Evaluates the content of 'js' on the device and asserts if the result is false"}, + {"t":"assert", "js": "true", "is":"true", "text": "Evaluates the content of 'js' on the device and asserts if the result is true"}, + {"t":"assert", "js": "()=>{}", "is":"function", "text": "Evaluates the content of 'js' and on the device and asserts if the result is a function"}, + {"t":"assert", "js": "test", "is":"equal", "to": "test", "text": "Evaluates the content of 'js' and 'to' on the device and asserts if the result is equal"}, + {"t":"assertArray", "js": "[]", "is":"undefinedOrEmpty", "text": "Evaluates the content of 'js' on the device and asserts if the result is undefined or an empty array"}, + {"t":"assertArray", "js": "[1,2,3]", "is":"notEmpty", "text": "Evaluates the content of 'js' on the device and asserts if the result is an array with more than 0 entries"},, + {"t":"assertCall", "id": "testfunc", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "Asserts if a wrapped function has been called with the expected arguments"}, + {"t":"assertCall", "id": "testfunc", "count": 1 , "text": "Asserts if a wrapped function has been called the expected number of times"} + ] + }] +} + var EMULATOR = "banglejs2"; var DEVICEID = "BANGLEJS2"; @@ -413,6 +453,12 @@ emu.init({ } } + apps.push(DEMOAPP); + + p = p.then(()=>{ + return runTest(DEMOTEST, testState); + }); + apps.forEach(app => { var testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; From 52c17dffbf8dce88e3c125a631041d70f3f0bb62 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 23:17:36 +0200 Subject: [PATCH 27/33] runapptests - Fix demo app tests --- bin/runapptests.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 0eb10f570..4f75f83e7 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -43,7 +43,9 @@ const DEMOTEST = { "description": "Optional description of the test, will be shown in results table", "steps" : [ {"t":"setup", "id": "arbitraryid", "text": "Calls a set of predefined steps"}, - {"t":"eval", "js": "'test' + 'value'", "eq": "testvalue", "text": "Evals code on the device and optionally compares the resulting string to the value in 'eq'"}, +// {"t":"eval", "js": "'test' + 'value'", "eq": "testvalue", "text": "Evals code on the device and compares the resulting string to the value in 'eq'"}, +// {"t":"console", "text": "Starts an interactive console for debugging"} + {"t":"saveMemoryUsage", "text": "Gets and stores the current memory usage"}, {"t":"saveMemoryUsage", "text": "Gets and stores the current memory usage"}, {"t":"checkMemoryUsage", "text": "Checks the current memory to be equal to the stored value"}, {"t":"assert", "js": "0", "is":"falsy", "text": "Evaluates the content of 'js' on the device and asserts if the result is falsy"}, @@ -51,9 +53,10 @@ const DEMOTEST = { {"t":"assert", "js": "false", "is":"false", "text": "Evaluates the content of 'js' on the device and asserts if the result is false"}, {"t":"assert", "js": "true", "is":"true", "text": "Evaluates the content of 'js' on the device and asserts if the result is true"}, {"t":"assert", "js": "()=>{}", "is":"function", "text": "Evaluates the content of 'js' and on the device and asserts if the result is a function"}, - {"t":"assert", "js": "test", "is":"equal", "to": "test", "text": "Evaluates the content of 'js' and 'to' on the device and asserts if the result is equal"}, + {"t":"assert", "js": "123", "is":"equal", "to": "123", "text": "Evaluates the content of 'js' and 'to' on the device and asserts if the result is equal"}, {"t":"assertArray", "js": "[]", "is":"undefinedOrEmpty", "text": "Evaluates the content of 'js' on the device and asserts if the result is undefined or an empty array"}, - {"t":"assertArray", "js": "[1,2,3]", "is":"notEmpty", "text": "Evaluates the content of 'js' on the device and asserts if the result is an array with more than 0 entries"},, + {"t":"assertArray", "js": "[1,2,3]", "is":"notEmpty", "text": "Evaluates the content of 'js' on the device and asserts if the result is an array with more than 0 entries"}, + {"t":"cmd", "js": "global.testfunction(1)", "text": "Call function for the following asserts"}, {"t":"assertCall", "id": "testfunc", "argAsserts": [ { "t": "assert", "arg": "0", "is": "equal", "to": 1 } ] , "text": "Asserts if a wrapped function has been called with the expected arguments"}, {"t":"assertCall", "id": "testfunc", "count": 1 , "text": "Asserts if a wrapped function has been called the expected number of times"} ] From a597489c69191da8ae70a2f54d1d494bd40ba17a Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 23:21:40 +0200 Subject: [PATCH 28/33] runapptests - Fix call assertions --- bin/runapptests.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 4f75f83e7..4e6a8ff37 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -162,32 +162,31 @@ function wrap(func, id){ } function assertCall(step){ - let isOK = false; + let isOK = true; let id = step.id; let args = step.argAsserts; + if (step.count !== undefined){ let calls = getValue(`global.APPTESTS.funcCalls.${id}`); - if ((args.count && args.count == calls) || (!args.count && calls > 0)){ - if (args) { + isOK = step.count == calls + } + if (args && args.length > 0){ let callArgs = getValue(`global.APPTESTS.funcArgs.${id}`); for (let a of args){ let current = { - value: callArgs[a.arg], + js: callArgs[a.arg], is: a.is, to: a.to, text: step.text }; switch(a.t){ case "assertArray": - isOK = assertArray(current); + isOK = isOK && assertArray(current); break; case "assert": - isOK = assertValue(current); + isOK = isOK && assertValue(current); break; } } - } else { - isOK = true; - } } if (isOK) console.log("OK - ASSERT CALL", step.text ? "- " + step.text : ""); From c51ffbedd2ef0aeab59ca9fa2694fdae46b5f207 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Tue, 7 May 2024 23:22:06 +0200 Subject: [PATCH 29/33] runapptests - Allow filterung for demo app --- bin/runapptests.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 4e6a8ff37..28884247b 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -446,6 +446,9 @@ emu.init({ console.log("Loading tests"); let p = Promise.resolve(); let apps = apploader.apps; + + apps.push(DEMOAPP); + if (process.argv.includes("--id")) { let f = process.argv[process.argv.indexOf("--id") + 1]; apps = apps.filter(e=>e.id==f); @@ -455,17 +458,14 @@ emu.init({ } } - apps.push(DEMOAPP); - - p = p.then(()=>{ - return runTest(DEMOTEST, testState); - }); - apps.forEach(app => { - var testFile = APP_DIR+"/"+app.id+"/test.json"; + let test = DEMOTEST; + if (app.id != DEMOAPP.id){ + let testFile = APP_DIR+"/"+app.id+"/test.json"; if (!require("fs").existsSync(testFile)) return; - var test = JSON.parse(require("fs").readFileSync(testFile).toString()); + test = JSON.parse(require("fs").readFileSync(testFile).toString()); test.app = app.id; + } p = p.then(()=>{ return runTest(test, testState); }); From d8b28e5d6ada20b1724efa62559df2178a86c300 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Thu, 9 May 2024 14:00:06 +0200 Subject: [PATCH 30/33] runapptests - Fix things not working after a wrap command --- bin/runapptests.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 28884247b..184a5ea20 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -43,9 +43,8 @@ const DEMOTEST = { "description": "Optional description of the test, will be shown in results table", "steps" : [ {"t":"setup", "id": "arbitraryid", "text": "Calls a set of predefined steps"}, -// {"t":"eval", "js": "'test' + 'value'", "eq": "testvalue", "text": "Evals code on the device and compares the resulting string to the value in 'eq'"}, -// {"t":"console", "text": "Starts an interactive console for debugging"} - {"t":"saveMemoryUsage", "text": "Gets and stores the current memory usage"}, + {"t":"eval", "js": "'test' + 'value'", "eq": "testvalue", "text": "Evals code on the device and compares the resulting string to the value in 'eq'"}, +// {"t":"console", "text": "Starts an interactive console for debugging"}, {"t":"saveMemoryUsage", "text": "Gets and stores the current memory usage"}, {"t":"checkMemoryUsage", "text": "Checks the current memory to be equal to the stored value"}, {"t":"assert", "js": "0", "is":"falsy", "text": "Evaluates the content of 'js' on the device and asserts if the result is falsy"}, @@ -156,7 +155,7 @@ function wrap(func, id){ global.APPTESTS.funcArgs.${id}=arguments; return o.apply(this, arguments); }; - }(${func}));`; + }(${func}));\n`; emu.tx(wrappingCode); } From 1b4af29529f2ea75a1da822b9184a008a06952a5 Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Thu, 9 May 2024 14:18:50 +0200 Subject: [PATCH 31/33] runapptests - Fix missing newline for loading after uploading --- bin/runapptests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index 184a5ea20..f0c5280c2 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -285,7 +285,7 @@ function runStep(step, subtest, test, state){ p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); subtest.memUsage = parseInt( getSanitizedLastLine()); - console.log("> CURRENT MEMORY USAGE", subtest.memUsage); + console.log("> SAVED MEMORY USAGE", subtest.memUsage); }); break; case "checkMemoryUsage" : @@ -314,7 +314,7 @@ function runStep(step, subtest, test, state){ console.log("> UPLOADING" + (step.load ? " AND LOADING" : ""), step.file); emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString())); if (step.load){ - emu.tx(`\x10load("${step.as}")`); + emu.tx(`\x10load("${step.as}")\n`); } }); break; From 816313e1363337eded0d0b3f70119bd65b42302c Mon Sep 17 00:00:00 2001 From: Martin Boonk Date: Thu, 9 May 2024 22:44:55 +0200 Subject: [PATCH 32/33] runapptests - Tweak some of the logging --- bin/runapptests.js | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index f0c5280c2..d09481a1d 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -110,20 +110,23 @@ function getValue(js){ } function assertArray(step){ + console.log(`> ASSERT ARRAY ${step.js} IS`,step.is.toUpperCase(), step.text ? "- " + step.text : ""); let isOK; switch (step.is.toLowerCase()){ case "notempty": isOK = getValue(`${step.js} && ${step.js}.length > 0`); break; case "undefinedorempty": isOK = getValue(`!${step.js} || (${step.js} && ${step.js}.length === 0)`); break; } - if (isOK) - console.log("> OK - ASSERT ARRAY " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); - else - console.log("> FAIL - ASSERT ARRAY " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); + if (isOK) { + if (verbose) + console.log("> OK -", `\`${step.js}\``); + } else + console.log("> FAIL -", `\`${step.js}\``); return isOK; } function assertValue(step){ + console.log("> ASSERT " + `\`${step.js}\``, "IS", step.is.toUpperCase(), step.to ? "TO " + `\`${step.js}\`` : "", step.text ? "- " + step.text : ""); let isOK; switch (step.is.toLowerCase()){ case "truthy": isOK = getValue(`!!${step.js}`); break; @@ -134,15 +137,16 @@ function assertValue(step){ case "function": isOK = getValue(`typeof ${step.js} === "function"`); break; } - if (isOK) - console.log("> OK - ASSERT " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); - else - console.log("> FAIL - ASSERT " + step.is.toUpperCase(), step.text ? "- " + step.text : ""); + if (isOK){ + if (verbose) + console.log("> OK - " + `\`${step.js}\``, "IS", step.is.toUpperCase(), step.to ? "TO " + `\`${step.js}\`` : ""); + } else + console.log("> FAIL - " + `\`${step.js}\``, "IS", step.is.toUpperCase(), step.to ? "TO " + `\`${step.js}\`` : ""); return isOK; } function wrap(func, id){ - console.log(`> Wrapping "${func}"`); + console.log(`> WRAPPING \`${func}\` AS ${id}`); let wrappingCode = ` if(!global.APPTESTS) global.APPTESTS={}; @@ -161,35 +165,37 @@ function wrap(func, id){ } function assertCall(step){ + console.log("> ASSERT CALL", step.id, step.text ? "- " + step.text : ""); let isOK = true; let id = step.id; let args = step.argAsserts; if (step.count !== undefined){ - let calls = getValue(`global.APPTESTS.funcCalls.${id}`); + let calls = getValue(`global.APPTESTS.funcCalls.${id}`); isOK = step.count == calls } if (args && args.length > 0){ - let callArgs = getValue(`global.APPTESTS.funcArgs.${id}`); - for (let a of args){ - let current = { + let callArgs = getValue(`global.APPTESTS.funcArgs.${id}`); + for (let a of args){ + let current = { js: callArgs[a.arg], - is: a.is, - to: a.to, - text: step.text - }; - switch(a.t){ - case "assertArray": + is: a.is, + to: a.to, + text: step.text + }; + switch(a.t){ + case "assertArray": isOK = isOK && assertArray(current); - break; - case "assert": + break; + case "assert": isOK = isOK && assertValue(current); - break; - } + break; } + } } - if (isOK) - console.log("OK - ASSERT CALL", step.text ? "- " + step.text : ""); - else + if (isOK){ + if (verbose) + console.log("OK - ASSERT CALL", step.text ? "- " + step.text : ""); + } else console.log("FAIL - ASSERT CALL", step.text ? "- " + step.text : ""); return isOK; } @@ -248,14 +254,17 @@ function runStep(step, subtest, test, state){ break; case "eval" : p = p.then(() => { - console.log(`> Evaluate "${step.js}"`); + console.log(`> EVAL \`${step.js}\``); emu.tx(`\x10print(JSON.stringify(${step.js}))\n`); var result = getSanitizedLastLine(); var expected = JSON.stringify(step.eq); - console.log("> GOT `"+result+"`"); + if (verbose) + console.log("> GOT `"+result+"`"); if (result!=expected) { console.log("> FAIL: EXPECTED "+expected); state.ok = false; + } else if (verbose) { + console.log("> OK: EXPECTED "+expected); } }); break; @@ -284,15 +293,15 @@ function runStep(step, subtest, test, state){ case "saveMemoryUsage" : p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); - subtest.memUsage = parseInt( getSanitizedLastLine()); + subtest.memUsage = parseInt(getSanitizedLastLine()); console.log("> SAVED MEMORY USAGE", subtest.memUsage); }); break; case "checkMemoryUsage" : p = p.then(() => { emu.tx(`\x10print(process.memory().usage)\n`); - var memUsage = getSanitizedLastLine(); - console.log("> CURRENT MEMORY USAGE", memUsage); + var memUsage = parseInt(getSanitizedLastLine()); + console.log("> COMPARE MEMORY USAGE", memUsage); if (subtest.memUsage != memUsage ) { console.log("> FAIL: EXPECTED MEMORY USAGE OF "+subtest.memUsage); state.ok = false; @@ -388,7 +397,8 @@ function runTest(test, testState) { emu.factoryReset(); console.log("> SENDING APP "+test.app); emu.tx(command); - console.log("> SENT APP"); + if (verbose) + console.log("> SENT APP"); emu.tx("reset()\n"); console.log("> RESET"); @@ -461,9 +471,9 @@ emu.init({ let test = DEMOTEST; if (app.id != DEMOAPP.id){ let testFile = APP_DIR+"/"+app.id+"/test.json"; - if (!require("fs").existsSync(testFile)) return; + if (!require("fs").existsSync(testFile)) return; test = JSON.parse(require("fs").readFileSync(testFile).toString()); - test.app = app.id; + test.app = app.id; } p = p.then(()=>{ return runTest(test, testState); From b1e7c1b4ea6ef9d663d9aecee59be8a2568fe51d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 10 May 2024 09:25:56 +0100 Subject: [PATCH 33/33] Update bin/runapptests.js Co-authored-by: Rob Pilling --- bin/runapptests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/runapptests.js b/bin/runapptests.js index d09481a1d..17d2e9ce2 100755 --- a/bin/runapptests.js +++ b/bin/runapptests.js @@ -485,7 +485,7 @@ emu.init({ console.table(testState); process.exit(testState.reduce((a,c)=>{ - return a + ((c.result == "SUCCESS") ? 0 : 1); + return a || ((c.result == "SUCCESS") ? 0 : 1); }, 0)) }); return p;