Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
nits
  • Loading branch information
cjihrig committed Jan 15, 2025
commit 531921e91be0c68bef7833100a2b03cf6d872f0d
4 changes: 2 additions & 2 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -3620,8 +3620,8 @@ added: REPLACEME
function does not accept any arguments, and is allowed to return any value.
* `options` {Object} An optional configuration object for the polling operation.
The following properties are supported:
* `interval` {number} The polling period in milliseconds. The `condition`
function is invoked according to this interval. **Default:** `50`.
* `interval` {number} The number of milliseconds to wait after an unsuccessful
invocation of `condition` before trying again. **Default:** `50`.
* `timeout` {number} The poll timeout in milliseconds. If `condition` has not
succeeded by the time this elapses, an error occurs. **Default:** `1000`.
* Returns: {Promise} Fulfilled with the value returned by `condition`.
Expand Down
12 changes: 6 additions & 6 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ const {
validateUint32,
} = require('internal/validators');
const {
clearInterval,
clearTimeout,
setInterval,
setTimeout,
} = require('timers');
const { TIMEOUT_MAX } = require('internal/timers');
Expand Down Expand Up @@ -364,10 +362,10 @@ class TestContext {
const { promise, resolve, reject } = PromiseWithResolvers();
const noError = Symbol();
let cause = noError;
let intervalId;
let pollerId;
let timeoutId;
const done = (err, result) => {
clearInterval(intervalId);
clearTimeout(pollerId);
clearTimeout(timeoutId);

if (err === noError) {
Expand All @@ -388,16 +386,18 @@ class TestContext {
done(err);
}, timeout);

intervalId = setInterval(async () => {
const poller = async () => {
try {
const result = await condition();

done(noError, result);
} catch (err) {
cause = err;
pollerId = setTimeout(poller, interval);
}
}, interval);
};

poller();
return promise;
}
}
Expand Down
79 changes: 51 additions & 28 deletions test/parallel/test-runner-wait-for.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
'use strict';
require('../common');
const { test } = require('node:test');
const { suite, test } = require('node:test');

test('throws if condition is not a function', (t) => {
t.assert.throws(() => {
t.waitFor(5);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "condition" argument must be of type function/,
suite('input validation', () => {
test('throws if condition is not a function', (t) => {
t.assert.throws(() => {
t.waitFor(5);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "condition" argument must be of type function/,
});
});
});

test('throws if options is not an object', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, null);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options" argument must be of type object/,
test('throws if options is not an object', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, null);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options" argument must be of type object/,
});
});
});

test('throws if options.interval is not a number', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, { interval: 'foo' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.interval" property must be of type number/,
test('throws if options.interval is not a number', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, { interval: 'foo' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.interval" property must be of type number/,
});
});
});

test('throws if options.timeout is not a number', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, { timeout: 'foo' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.timeout" property must be of type number/,
test('throws if options.timeout is not a number', (t) => {
t.assert.throws(() => {
t.waitFor(() => {}, { timeout: 'foo' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.timeout" property must be of type number/,
});
});
});

Expand Down Expand Up @@ -99,3 +101,24 @@ test('sets last failure as error cause on timeouts', async (t) => {
return true;
});
});

test('limits polling if condition takes longer than interval', async (t) => {
let count = 0;

function condition() {
count++;
return new Promise((resolve) => {
setTimeout(() => {
resolve('success');
}, 200);
});
}

const result = await t.waitFor(condition, {
interval: 1,
timeout: 60_000,
});

t.assert.strictEqual(result, 'success');
t.assert.strictEqual(count, 1);
});