2022-12-16 10:04:27 +00:00
|
|
|
#!/usr/bin/node
|
|
|
|
/*
|
|
|
|
|
|
|
|
This allows us to test apps using the Bangle.js emulator
|
|
|
|
|
|
|
|
IT IS UNFINISHED
|
|
|
|
|
|
|
|
It searches for `test.json` in each app's directory and will
|
|
|
|
run them in sequence.
|
|
|
|
|
|
|
|
TODO:
|
|
|
|
|
|
|
|
* more code to test with
|
|
|
|
* run tests that we have found and loaded (currently we just use TEST)
|
|
|
|
* documentation
|
|
|
|
* actual tests
|
|
|
|
* detecting 'Uncaught Error'
|
|
|
|
* logging of success/fail
|
|
|
|
* ...
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
var EMULATOR = "banglejs2";
|
|
|
|
var DEVICEID = "BANGLEJS2";
|
|
|
|
|
|
|
|
var BASE_DIR = __dirname + "/..";
|
|
|
|
var APP_DIR = BASE_DIR + "/apps";
|
|
|
|
var DIR_IDE = BASE_DIR + "/../EspruinoWebIDE";
|
|
|
|
|
|
|
|
|
|
|
|
if (!require("fs").existsSync(DIR_IDE)) {
|
|
|
|
console.log("You need to:");
|
|
|
|
console.log(" git clone https://github.com/espruino/EspruinoWebIDE");
|
|
|
|
console.log("At the same level as this project");
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2024-05-04 08:45:54 +00:00
|
|
|
var AppInfo = require(BASE_DIR+"/core/js/appinfo.js");
|
2023-07-07 14:19:32 +00:00
|
|
|
var apploader = require(BASE_DIR+"/core/lib/apploader.js");
|
2022-12-16 10:04:27 +00:00
|
|
|
apploader.init({
|
|
|
|
DEVICEID : DEVICEID
|
|
|
|
});
|
2023-07-07 14:19:32 +00:00
|
|
|
var emu = require(BASE_DIR+"/core/lib/emulator.js");
|
2022-12-16 10:04:27 +00:00
|
|
|
|
|
|
|
// Last set of text received
|
|
|
|
var lastTxt;
|
|
|
|
|
|
|
|
function ERROR(s) {
|
|
|
|
console.error(s);
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2024-04-20 05:50:44 +00:00
|
|
|
function getValue(js){
|
2024-05-04 20:57:19 +00:00
|
|
|
console.log(`> Getting value for "${js}"`);
|
2024-04-20 05:50:44 +00:00
|
|
|
emu.tx(`\x10print(JSON.stringify(${js}))\n`);
|
|
|
|
var result = emu.getLastLine();
|
2024-05-04 20:57:19 +00:00
|
|
|
console.log(`> GOT "${result}"`);
|
|
|
|
return JSON.parse(result);
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function assertFail(text){
|
|
|
|
console.log("> FAIL: " + text);
|
2024-05-04 20:57:19 +00:00
|
|
|
return false;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
2024-05-04 20:57:19 +00:00
|
|
|
function assertCondition(condition) {
|
2024-04-20 05:50:44 +00:00
|
|
|
if (!condition) {
|
2024-05-04 20:57:19 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function assertArray(step){
|
2024-05-04 20:57:19 +00:00
|
|
|
let isOK;
|
2024-04-20 05:50:44 +00:00
|
|
|
let array = step.value;
|
|
|
|
if (step.value === undefined)
|
|
|
|
array = getValue(step.js);
|
2024-05-04 20:57:19 +00:00
|
|
|
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;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
2024-05-04 20:57:19 +00:00
|
|
|
|
|
|
|
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;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function assertValue(step){
|
|
|
|
console.debug("assertValue", step);
|
2024-05-04 20:57:19 +00:00
|
|
|
let isOK;
|
2024-04-20 05:50:44 +00:00
|
|
|
let value = step.js;
|
|
|
|
if (value === undefined)
|
|
|
|
value = step.value;
|
|
|
|
switch (step.is){
|
2024-05-04 20:57:19 +00:00
|
|
|
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;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
2024-05-04 20:57:19 +00:00
|
|
|
|
|
|
|
if (isOK)
|
|
|
|
console.log("OK - ASSERT " + step.is.toUpperCase(), step.text);
|
|
|
|
else
|
|
|
|
console.log("FAIL - ASSERT " + step.is.toUpperCase(), step.text);
|
|
|
|
return isOK;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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){
|
2024-05-04 20:57:19 +00:00
|
|
|
let isOK = false;
|
2024-04-20 05:50:44 +00:00
|
|
|
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":
|
2024-05-04 20:57:19 +00:00
|
|
|
isOK = assertArray(current);
|
2024-04-20 05:50:44 +00:00
|
|
|
break;
|
|
|
|
case "assert":
|
2024-05-04 20:57:19 +00:00
|
|
|
isOK = assertValue(current);
|
2024-04-20 05:50:44 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2024-05-04 20:57:19 +00:00
|
|
|
isOK = true;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
2024-05-04 20:57:19 +00:00
|
|
|
}
|
|
|
|
if (isOK)
|
|
|
|
console.log("OK", step.text);
|
|
|
|
else
|
|
|
|
console.log("FAIL", step.text);
|
|
|
|
return isOK;
|
2024-04-20 05:50:44 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 21:38:10 +00:00
|
|
|
function runStep(step, subtest, test, state){
|
2024-05-04 07:01:55 +00:00
|
|
|
let p = Promise.resolve();
|
2024-05-03 21:38:10 +00:00
|
|
|
if (state.ok) switch(step.t) {
|
|
|
|
case "setup" :
|
|
|
|
test.setup.filter(e=>e.id==step.id)[0].steps.forEach(setupStep=>{
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=>{
|
2024-05-04 20:57:19 +00:00
|
|
|
let np = runStep(setupStep, subtest, test, state);
|
|
|
|
emu.idle();
|
|
|
|
return np;
|
2024-05-04 13:00:37 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
case "load" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-03 21:38:10 +00:00
|
|
|
console.log(`> Loading file "${step.fn}"`);
|
|
|
|
emu.tx(`load(${JSON.stringify(step.fn)})\n`);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
|
|
|
break;
|
2024-05-03 21:38:10 +00:00
|
|
|
case "cmd" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
|
|
|
console.log(`> Sending JS "${step.js}"`);
|
|
|
|
emu.tx(`${step.js}\n`);
|
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "wrap" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
|
|
|
wrap(step.fn, step.id);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
case "gb" :
|
|
|
|
p = p.then(() => {
|
2024-05-04 08:46:25 +00:00
|
|
|
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`);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
|
|
|
break;
|
2024-05-04 08:46:59 +00:00
|
|
|
case "emit" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-04 16:35:57 +00:00
|
|
|
let parent = step.parent ? step.parent : "Bangle";
|
|
|
|
if (!step.paramsArray) step.paramsArray = [];
|
2024-05-04 08:46:59 +00:00
|
|
|
let args = JSON.stringify([step.event].concat(step.paramsArray));
|
2024-05-04 16:35:57 +00:00
|
|
|
console.log(`> Emit "${step.event}" on ${parent} with parameters ${JSON.stringify(step.paramsArray)}`);
|
|
|
|
|
2024-05-04 08:46:59 +00:00
|
|
|
emu.tx(`${parent}.emit.apply(${parent}, ${args})\n`);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "eval" :
|
2024-05-04 07:01:55 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
// tap/touch/drag/button press
|
|
|
|
// delay X milliseconds?
|
|
|
|
case "assertArray":
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-04 20:57:19 +00:00
|
|
|
state.ok &= assertArray(step);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "assertCall":
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-04 20:57:19 +00:00
|
|
|
state.ok &= assertCall(step);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "assert":
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-04 20:57:19 +00:00
|
|
|
state.ok &= assertValue(step);
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "screenshot" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
|
|
|
console.log(`> Compare screenshots - UNIMPLEMENTED`);
|
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "saveMemoryUsage" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
|
|
|
emu.tx(`\x10print(process.memory().usage)\n`);
|
|
|
|
subtest.memUsage = parseInt( emu.getLastLine());
|
|
|
|
console.log("> CURRENT MEMORY USAGE", subtest.memUsage);
|
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
case "checkMemoryUsage" :
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(() => {
|
2024-05-04 13:00:37 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-05-04 07:01:55 +00:00
|
|
|
});
|
|
|
|
break;
|
|
|
|
case "sleep" :
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=>{
|
|
|
|
return new Promise(resolve => {
|
|
|
|
setTimeout(()=>{
|
|
|
|
console.log("Waited for", step.ms);
|
|
|
|
resolve();
|
|
|
|
}, step.ms);
|
|
|
|
})
|
|
|
|
});
|
2024-05-04 08:45:54 +00:00
|
|
|
break;
|
|
|
|
case "upload" :
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=>{
|
2024-05-04 08:45:54 +00:00
|
|
|
console.log("Uploading", step.file);
|
|
|
|
emu.tx(AppInfo.getFileUploadCommands(step.as, require("fs").readFileSync(BASE_DIR + "/" + step.file).toString()));
|
2024-05-04 13:00:37 +00:00
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
break;
|
|
|
|
default: ERROR("Unknown step type "+step.t);
|
|
|
|
}
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=> {
|
|
|
|
emu.idle();
|
|
|
|
});
|
|
|
|
return p;
|
2024-05-03 21:38:10 +00:00
|
|
|
}
|
|
|
|
|
2024-05-03 22:04:46 +00:00
|
|
|
function runTest(test, testState) {
|
2024-05-04 20:56:37 +00:00
|
|
|
apploader.reset();
|
2022-12-16 10:04:27 +00:00
|
|
|
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`);
|
2024-04-20 05:50:44 +00:00
|
|
|
|
2022-12-16 10:04:27 +00:00
|
|
|
return apploader.getAppFilesString(app).then(command => {
|
2024-04-20 05:50:44 +00:00
|
|
|
console.log("Handling command", command);
|
|
|
|
|
2022-12-16 10:04:27 +00:00
|
|
|
// What about dependencies??
|
2024-05-04 07:01:55 +00:00
|
|
|
let p = Promise.resolve();
|
2022-12-16 10:04:27 +00:00
|
|
|
test.tests.forEach((subtest,subtestIdx) => {
|
2024-05-04 07:01:55 +00:00
|
|
|
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");
|
|
|
|
|
|
|
|
});
|
2024-05-03 21:38:10 +00:00
|
|
|
|
2022-12-16 10:04:27 +00:00
|
|
|
subtest.steps.forEach(step => {
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=>{
|
2024-05-04 20:57:19 +00:00
|
|
|
return runStep(step, subtest, test, state).catch((e)=>{
|
|
|
|
console.log("STEP FAILED:", e, step);
|
|
|
|
state.ok = false;
|
|
|
|
})
|
2024-05-04 13:00:37 +00:00
|
|
|
});
|
2022-12-16 10:04:27 +00:00
|
|
|
});
|
2024-05-04 07:01:55 +00:00
|
|
|
|
2024-05-04 20:57:19 +00:00
|
|
|
p = p.finally(()=>{
|
|
|
|
console.log("RESULT for", test.app + (subtest.description ? (": " + subtest.description) : ""), "test", subtestIdx, (state.ok ? "OK": "FAIL"));
|
2024-05-04 07:01:55 +00:00
|
|
|
testState.push({
|
|
|
|
app: test.app,
|
|
|
|
number: subtestIdx,
|
|
|
|
result: state.ok ? "SUCCESS": "FAILURE",
|
|
|
|
description: subtest.description
|
|
|
|
});
|
2024-05-03 22:04:46 +00:00
|
|
|
});
|
2022-12-16 10:04:27 +00:00
|
|
|
});
|
2024-05-04 07:01:55 +00:00
|
|
|
p = p.then(()=>{
|
|
|
|
emu.stopIdle();
|
|
|
|
});
|
|
|
|
return p;
|
2022-12-16 10:04:27 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-05-03 22:04:46 +00:00
|
|
|
let testState = [];
|
2022-12-16 10:04:27 +00:00
|
|
|
|
|
|
|
emu.init({
|
|
|
|
EMULATOR : EMULATOR,
|
|
|
|
DEVICEID : DEVICEID
|
|
|
|
}).then(function() {
|
|
|
|
// Emulator is now loaded
|
|
|
|
console.log("Loading tests");
|
2024-05-03 22:04:46 +00:00
|
|
|
let p = Promise.resolve();
|
2022-12-16 10:04:27 +00:00
|
|
|
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;
|
2024-05-04 13:00:37 +00:00
|
|
|
p = p.then(()=>{
|
2024-05-04 20:57:19 +00:00
|
|
|
return runTest(test, testState);
|
2024-05-04 13:00:37 +00:00
|
|
|
});
|
2024-05-03 22:04:46 +00:00
|
|
|
});
|
|
|
|
p.finally(()=>{
|
|
|
|
console.log("\n\n");
|
|
|
|
console.log("Overall results:");
|
|
|
|
console.table(testState);
|
2022-12-16 10:04:27 +00:00
|
|
|
});
|
2024-05-03 22:04:46 +00:00
|
|
|
return p;
|
2022-12-16 10:04:27 +00:00
|
|
|
});
|
2024-05-03 22:04:46 +00:00
|
|
|
|
2022-12-16 10:04:27 +00:00
|
|
|
/*
|
|
|
|
if (erroredApps.length) {
|
|
|
|
erroredApps.forEach(app => {
|
|
|
|
console.log(`::error file=${app.id}::${app.id}`);
|
|
|
|
console.log("::group::Log");
|
|
|
|
app.log.split("\n").forEach(line => console.log(`\u001b[38;2;255;0;0m${line}`));
|
|
|
|
console.log("::endgroup::");
|
|
|
|
});
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
*/
|