-
-
Notifications
You must be signed in to change notification settings - Fork 12
assert.throws: harness test doesn't cover object-matcher and validator-function forms #42
Description
Background
The CTS assert.throws global delegates to node:assert/strict in the Node.js implementor, which supports three forms beyond the basic no-matcher call:
- Regex —
assert.throws(fn, /pattern/)✅ covered by harness test - Constructor —
assert.throws(fn, TypeError)✅ covered by harness test - Object matcher —
assert.throws(fn, { code: 'X', message: 'Y' })❌ not covered - Validator function —
assert.throws(fn, (err) => { ...; return true; })❌ not covered
Problem
Forms 3 and 4 are already relied on by ported tests:
- Object matcher:
tests/js-native-api/test_bigint/test.js(lines 41–50), andtests/js-native-api/test_error/test.js(feat: port test_error to CTS #40) - Validator function:
tests/js-native-api/test_error/test.js(feat: port test_error to CTS #40)
The harness test at tests/harness/assert.js doesn't verify these forms, so a future runtime implementor who only covers the regex and constructor forms would silently pass the harness test but then fail on the actual ported tests.
Root cause: globals without type contracts
The broader issue is that the CTS allow-lists globals (via --import flags) but doesn't enforce a type contract on them. The harness test files (tests/harness/*.js) are the only mechanism ensuring implementors provide the right shape — and they only go as far as the author thought to test.
For assert.throws specifically, this means the four call signatures are silently assumed to be supported without anything preventing a test file from using an uncovered form.
If the test files (and ideally the harness implementations) were subject to TypeScript type-checking, this class of problem would be caught statically:
- The CTS could publish a
.d.tsdeclaration file for the injected globals (e.g.globals.d.ts) that precisely defines which overloads ofassert.throwsare guaranteed. - Any test file using an uncovered overload would be a type error at check time, before it ever reaches a runtime implementor.
- The harness
.d.tswould become the canonical contract — tighter than prose documentation and automatically enforced.
Node.js test files are plain .js (CJS), so this gap doesn't exist upstream — but it's inherent to the CTS's multi-runtime design.
Alternative: ESLint-based enforcement
If full TypeScript checking is too heavy, a lighter option is a custom ESLint rule (or a no-restricted-syntax pattern) that flags assert.throws calls whose second argument is neither a regex literal, a constructor reference, nor an explicit allowlist of known-good forms. This is less precise than types but requires no build step and works on plain .js files.
Possible resolutions
- Short term: Add coverage for forms 3 and 4 to
tests/harness/assert.jsso implementors are forced to support them. - Medium term: Introduce a
globals.d.tsand runtsc --noEmit(orts-checkvia JSDoc@typecomments) over the test files to enforce the contract statically. - Alternative: Add an ESLint rule restricting
assert.throwsto covered call forms.
Surfaced during review of #40.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status