Skip to content

Commit

Permalink
add failsafe for when app crashes
Browse files Browse the repository at this point in the history
Signed-off-by: Hanno J. Gödecke <[email protected]>
  • Loading branch information
hannojg committed Sep 29, 2022
1 parent ff6a063 commit c88b0e5
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 26 deletions.
45 changes: 20 additions & 25 deletions e2e/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ const getPostJSONRequestData = async (req, res) => {
}
};

const createListenerState = () => {
const listeners = [];
const addListener = (listener) => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
if (index !== -1) {
listeners.splice(index, 1);
}
};
};

return [listeners, addListener];
};

/**
* The test result object that a client might submit to the server.
* @typedef TestResult
Expand All @@ -63,31 +78,9 @@ const getPostJSONRequestData = async (req, res) => {
* It returns an instance to which you can add listeners for the test results, and test done events.
*/
const createServerInstance = () => {
const testResultListeners = [];
const testDoneListeners = [];

/**
* Add a callback that will be called when receiving test results.
* @param {listener} listener
*/
const addTestResultListener = (listener) => {
testResultListeners.push(listener);
};

/**
* Will be called when a test signals that it's done.
* @param {Function} listener
* @returns {Function} A function to remove the listener.
*/
const addTestDoneListener = (listener) => {
testDoneListeners.push(listener);
return () => {
const index = testDoneListeners.indexOf(listener);
if (index !== -1) {
testDoneListeners.splice(index, 1);
}
};
};
const [testStartedListeners, addTestStartedListener] = createListenerState();
const [testResultListeners, addTestResultListener] = createListenerState();
const [testDoneListeners, addTestDoneListener] = createListenerState();

let activeTestConfig;

Expand All @@ -102,6 +95,7 @@ const createServerInstance = () => {
res.statusCode = 200;
switch (req.url) {
case Routes.testConfig: {
testStartedListeners.forEach(listener => listener(activeTestConfig));
if (activeTestConfig == null) {
throw new Error('No test config set');
}
Expand Down Expand Up @@ -137,6 +131,7 @@ const createServerInstance = () => {

return {
setTestConfig,
addTestStartedListener,
addTestResultListener,
addTestDoneListener,
start: () => new Promise(resolve => server.listen(PORT, resolve)),
Expand Down
34 changes: 34 additions & 0 deletions e2e/testRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const math = require('./measure/math');
const writeTestStats = require('./measure/writeTestStats');
const withFailTimeout = require('./utils/withFailTimeout');
const startRecordingVideo = require('./utils/startRecordingVideo');
const isAppRunning = require('./utils/isAppRunning');

const args = process.argv.slice(2);

Expand Down Expand Up @@ -104,7 +105,40 @@ const runTestsOnBranch = async (branch, baselineOrCompare) => {

const stopVideoRecording = startRecordingVideo();

// create a promise that will resolve once the app fetched
// the test config, which signals us that the app really started.
// Especially on weak systems it can happen that the app crashes during startup.
const waitForAppStarted = new Promise((resolve, reject) => {
// check every X seconds if the app is still app
// it can happen that the app crashes during startup, e.g.
// on CI. If this happens we want to try restarting the app.
let retries = 0;
const intervalId = setInterval(async () => {
try {
await isAppRunning();
} catch (e) {
// !!! the app doesn't seem to be active anymore, try to start it again
if (retries > 10) {
stopVideoRecording(true);
clearInterval(intervalId);
reject(new Error('App crashed during startup'));
}
retries++;

Logger.log(`App crashed during startup, restarting app (attempt ${retries}/10)`);
await launchApp('android');
}
}, 8000);

server.addTestStartedListener(() => {
Logger.log(`Test '${config.name}' started!`);
clearInterval(intervalId);
resolve();
});
});

await restartApp();
await waitForAppStarted;

// wait for a test to finish by waiting on its done call to the http server
try {
Expand Down
2 changes: 1 addition & 1 deletion e2e/utils/execAsync.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module.exports = (command) => {
}
} else {
Logger.log(stdout);
resolve();
resolve(stdout);
}
});
});
Expand Down
8 changes: 8 additions & 0 deletions e2e/utils/isAppRunning.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const {APP_PACKAGE} = require('../config');
const execAsync = require('./execAsync');

module.exports = function () {
// adb shell pidof
const cmd = `adb shell pidof ${APP_PACKAGE}`;
return execAsync(cmd);
};

0 comments on commit c88b0e5

Please sign in to comment.