diff --git a/src/cmds/test.js b/src/cmds/test.js index 3106ab2db..7dbf96930 100644 --- a/src/cmds/test.js +++ b/src/cmds/test.js @@ -100,6 +100,11 @@ export default { describe: 'Enable coverage output. Output is already in Istanbul JSON format and can be uploaded directly to codecov.', type: 'boolean', default: userConfig.test.cov + }, + covTimeout: { + describe: 'How long to wait for coverage collection. Workaround for https://github.com/ipfs/aegir/issues/1206', + type: 'number', + default: userConfig.test.covTimeout } }) }, diff --git a/src/config/user.js b/src/config/user.js index 6f7fa4fe6..c4835703d 100644 --- a/src/config/user.js +++ b/src/config/user.js @@ -25,6 +25,7 @@ const defaults = { bail: false, progress: false, cov: false, + covTimeout: 60000, browser: { config: { buildConfig: { diff --git a/src/test/node.js b/src/test/node.js index 0e9a63c39..f1b672571 100644 --- a/src/test/node.js +++ b/src/test/node.js @@ -1,6 +1,7 @@ import path from 'path' import { fileURLToPath } from 'url' import { execa } from 'execa' +import kleur from 'kleur' import merge from 'merge-options' import * as tempy from 'tempy' @@ -68,7 +69,7 @@ export default async function testNode (argv, execaOptions) { const beforeEnv = before && before.env ? before.env : {} // run mocha - await execa(exec, args, + const proc = execa(exec, args, merge( { env: { @@ -78,11 +79,58 @@ export default async function testNode (argv, execaOptions) { }, preferLocal: true, localDir: path.join(__dirname, '../..'), - stdio: 'inherit' + stdio: argv.cov ? 'pipe' : 'inherit' }, execaOptions ) ) + + let killedWhileCollectingCoverage = false + + /** @type {ReturnType | undefined} */ + let timeout + + if (argv.cov) { + proc.stderr?.addListener('data', (data) => { + process.stderr.write(data) + }) + + let lastLine = '' + proc.stdout?.addListener('data', (data) => { + process.stdout.write(data) + + lastLine = data.toString() + + if (lastLine.trim() !== '') { + // more output has been sent, reset timer + clearTimeout(timeout) + } + + if (lastLine.match(/^ {2}\d+ (passing|pending)/m) != null) { + // if we see something that looks like the successful end of a mocha + // run, set a timer - if the process does not exit before the timer + // fires, kill it and log a warning, though don't cause the test run + // to fail + timeout = setTimeout(() => { + console.warn(kleur.red('!!! Collecting coverage has hung, killing process')) // eslint-disable-line no-console + console.warn(kleur.red('!!! See https://github.com/ipfs/aegir/issues/1206 for more information')) // eslint-disable-line no-console + killedWhileCollectingCoverage = true + proc.kill() + }, argv.covTimeout).unref() + } + }) + } + + try { + await proc + } catch (err) { + if (!killedWhileCollectingCoverage) { + throw err + } + } finally { + clearTimeout(timeout) + } + // after hook await argv.fileConfig.test.after(argv, before) } diff --git a/src/types.ts b/src/types.ts index ce7de3180..bba8f388b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -236,6 +236,10 @@ interface TestOptions { * Enable coverage output */ cov: boolean + /** + * How long to wait for collecting code coverage. Workaround for @see https://github.com/ipfs/aegir/issues/1206 + */ + covTimeout: number /** * Runner enviroment */