From 64337190d67c08820871ead1f72495a9cfa2fa87 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Fri, 1 Mar 2024 17:27:37 +1300 Subject: [PATCH] feature: supertape: add per-test timeout option (#9) --- README.md | 2 ++ packages/supertape/README.md | 17 ++++++++---- packages/supertape/lib/run-tests.js | 28 +++++++++++++++----- packages/supertape/lib/run-tests.spec.js | 33 +++++++++++++++++++++++- packages/supertape/lib/supertape.d.ts | 1 + packages/supertape/lib/supertape.js | 11 ++++++-- packages/supertape/test/errors.ts | 5 ++++ 7 files changed, 83 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e25f0bf..d82ae85 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Options - `SUPERTAPE_CHECK_DUPLICATES` - toggle check duplicates; - `SUPERTAPE_CHECK_SCOPES` - check that test message has a scope: `scope: subject`; - `SUPERTAPE_CHECK_ASSERTIONS_COUNT` - check that assertion count is no more then 1; +- `SUPERTAPE_CHECK_SKIPED` - check that skiped count equal to `0`, exit with status code; +- `SUPERTAPE_LOAD_LOOP_TIMEOUT` - timeout for load tests, defaults to `5ms`, when mocha used as runner - `50ms` optimal; ```js test('tape: error', (t) => { diff --git a/packages/supertape/README.md b/packages/supertape/README.md index 4fcf6d1..88116ee 100644 --- a/packages/supertape/README.md +++ b/packages/supertape/README.md @@ -211,19 +211,26 @@ test('calc: sum', (t) => { }); ``` -## test(name, cb) +## test(message: string, fn: (t: Test) => void, options?: TestOptions) -Create a new test with `name` string. -`cb(t)` fires with the new test object `t` once all preceding tests have +Create a new test with `message` string. +`fn(t)` fires with the new test object `t` once all preceding tests have finished. Tests execute serially. -## test.only(name, cb) +Here is Possible `options` similar to [Environment Variables](#environment-variables) but relates to one test: + +- `checkDuplicates`; +- `checkScopes`;- +- `checkAssertionsCount`; +- `timeout`; + +## test.only(message, fn, options?) Like `test(name, cb)` except if you use `.only` this is the only test case that will run for the entire process, all other test cases using `tape` will be ignored. -## test.skip(name, cb) +## test.skip(message, fn, options?) Generate a new test that will be skipped over. diff --git a/packages/supertape/lib/run-tests.js b/packages/supertape/lib/run-tests.js index 87f217c..f09b8d2 100644 --- a/packages/supertape/lib/run-tests.js +++ b/packages/supertape/lib/run-tests.js @@ -1,6 +1,5 @@ 'use strict'; -const process = require('process'); const fullstore = require('fullstore'); const wraptile = require('wraptile'); const tryToCatch = require('try-to-catch'); @@ -15,10 +14,9 @@ const notSkip = ({skip}) => !skip; const getInitOperators = async () => await import('./operators.mjs'); -const {SUPERTAPE_TIMEOUT = 3000} = process.env; const DEBUG_TIME = 3000 * 1000; -const timeout = (time, value) => { +const doTimeout = (time, value) => { let stop; if (isDebug) @@ -73,7 +71,7 @@ async function runTests(tests, {formatter, operators, skiped, isStop}) { tests, }); - for (const {fn, message, extensions, at, validations} of tests) { + for (const {fn, message, timeout, extensions, at, validations} of tests) { if (wasStop()) break; @@ -95,6 +93,7 @@ async function runTests(tests, {formatter, operators, skiped, isStop}) { incPassed, getValidationMessage, validations, + timeout, extensions: { ...operators, @@ -118,7 +117,24 @@ async function runTests(tests, {formatter, operators, skiped, isStop}) { }; } -async function runOneTest({message, at, fn, extensions, formatter, count, total, failed, incCount, incPassed, incFailed, getValidationMessage, validations}) { +async function runOneTest(options) { + const { + message, + at, + fn, + extensions, + formatter, + count, + total, + failed, + incCount, + incPassed, + incFailed, + getValidationMessage, + validations, + timeout, + } = options; + const isReturn = fullstore(false); const assertionsCount = fullstore(0); const isEnded = fullstore(false); @@ -145,7 +161,7 @@ async function runOneTest({message, at, fn, extensions, formatter, count, total, }); if (!isReturn()) { - const [timer, stopTimer] = timeout(SUPERTAPE_TIMEOUT, ['timeout']); + const [timer, stopTimer] = doTimeout(timeout, ['timeout']); const [error] = await Promise.race([tryToCatch(fn, t), timer]); stopTimer(); diff --git a/packages/supertape/lib/run-tests.spec.js b/packages/supertape/lib/run-tests.spec.js index d04a275..c1e41e1 100644 --- a/packages/supertape/lib/run-tests.spec.js +++ b/packages/supertape/lib/run-tests.spec.js @@ -487,7 +487,7 @@ test('supertape: runTests: fail: at', async (t) => { t.end(); }); -test('supertape: runTests: fail: timeout', async (t) => { +test('supertape: runTests: fail: timeout: SUPERTAPE_TIMEOUT', async (t) => { const fn = async (t) => { await once(new EventEmitter(), 'end'); t.end(); @@ -525,6 +525,37 @@ test('supertape: runTests: fail: timeout', async (t) => { t.end(); }); +test('supertape: runTests: fail: timeout', async (t) => { + const fn = async (t) => { + await once(new EventEmitter(), 'end'); + t.end(); + }; + + const message = 'hello world'; + + const supertape = reRequire('..'); + supertape(message, fn, { + quiet: true, + timeout: 1, + }); + + const [result] = await Promise.all([ + pull(supertape.createStream(), 5), + once(supertape.run(), 'end'), + ]); + + const expected = montag` + TAP version 13 + # hello world + not ok 1 timeout + --- + operator: fail + `; + + t.equal(result, expected); + t.end(); +}); + test('supertape: runTests: equal', async (t) => { const fn = (t) => { t.equal('hello', 'hello'); diff --git a/packages/supertape/lib/supertape.d.ts b/packages/supertape/lib/supertape.d.ts index 43bbe94..50b50c9 100644 --- a/packages/supertape/lib/supertape.d.ts +++ b/packages/supertape/lib/supertape.d.ts @@ -34,6 +34,7 @@ type TestOptions = { checkAssertionsCount?: boolean; checkScopes?: boolean; checkDuplicates?: boolean; + timeout?: number; }; declare function test(message: string, fn: (t: Test) => void, options?: TestOptions): void; diff --git a/packages/supertape/lib/supertape.js b/packages/supertape/lib/supertape.js index 1937a20..3e13a03 100644 --- a/packages/supertape/lib/supertape.js +++ b/packages/supertape/lib/supertape.js @@ -20,7 +20,10 @@ const {assign} = Object; const {stdout} = process; // 5ms ought to be enough for anybody -const {SUPERTAPE_LOAD_LOOP_TIMEOUT = 5} = process.env; +const { + SUPERTAPE_LOAD_LOOP_TIMEOUT = 5, + SUPERTAPE_TIMEOUT = 3000, +} = process.env; let mainEmitter; @@ -44,13 +47,14 @@ const defaultOptions = { checkIfEnded: true, checkAssertionsCount: true, checkScopes: true, + timeout: SUPERTAPE_TIMEOUT, }; function _createEmitter({quiet, stream = stdout, format, getOperators, isStop, readyFormatter, workerFormatter}) { const tests = []; const emitter = new EventEmitter(); - emitter.on('test', (message, fn, {skip, only, extensions, at, validations}) => { + emitter.on('test', (message, fn, {skip, only, extensions, at, validations, timeout}) => { tests.push({ message, fn, @@ -59,6 +63,7 @@ function _createEmitter({quiet, stream = stdout, format, getOperators, isStop, r extensions, at, validations, + timeout, }); }); @@ -154,6 +159,7 @@ function test(message, fn, options = {}) { checkAssertionsCount, checkIfEnded, workerFormatter, + timeout, } = { ...defaultOptions, ...initedOptions, @@ -186,6 +192,7 @@ function test(message, fn, options = {}) { extensions, at, validations, + timeout, }); if (run) diff --git a/packages/supertape/test/errors.ts b/packages/supertape/test/errors.ts index 94f6e3f..f5dd2be 100644 --- a/packages/supertape/test/errors.ts +++ b/packages/supertape/test/errors.ts @@ -49,6 +49,11 @@ test('hello', (t: Test) => { t.end(); }, {checkScopes: false}); +test.only('hello', (t: Test) => { + t.end(); +// THROWS Type 'string' is not assignable to type 'number' +}, {timeout: 'hello'}); + test('hello', (t: Test) => { t.end(); // THROWS Object literal may only specify known properties, and 'checkUnknown' does not exist in type 'TestOptions'