Skip to content

Migrate tests to native node:test, remove Babel/Jest tooling, and clarify long-stack-trace behavior#66

Merged
niemyjski merged 6 commits intomasterfrom
chore/node-test-migration
May 2, 2026
Merged

Migrate tests to native node:test, remove Babel/Jest tooling, and clarify long-stack-trace behavior#66
niemyjski merged 6 commits intomasterfrom
chore/node-test-migration

Conversation

@niemyjski
Copy link
Copy Markdown
Collaborator

Summary

This PR migrates the test suite from Jest+Babel to native Node.js test APIs, removes unmaintained/unused test tooling dependencies, keeps CI on Node 24.x and 25.x only, and updates docs to reflect current long-stack-trace behavior.

Primary goals addressed:

  • Keep behavior and assertion intent equivalent while switching test runner APIs
  • Minimize non-essential changes
  • Remove unmaintained long-stack-traces dependency from tests
  • Keep CI matrix on Node 24.x and 25.x only
  • Document long stack trace findings in README

Why These Changes

1) Move to native Node test APIs

  • Jest/Babel were only being used as test infrastructure; package runtime already ships native ESM.
  • Native node --test reduces toolchain complexity and maintenance overhead.
  • Converted tests from test/expect to node:test + node:assert/strict with equivalent assertions.

2) Remove Babel/Jest and related artifacts

  • Babel transform and Jest config were no longer needed after native test migration.
  • Removing these packages materially reduces dependency surface and avoids stale infrastructure.

3) Remove long-stack-traces dependency from tests

  • long-stack-traces is unmaintained, and its suggested replacement (longjohn) is also effectively stale.
  • The parser behavior we care about is support for dashed async boundary markers in stack strings.
  • Test now validates this behavior deterministically using synthetic stack input, without monkeypatching runtime APIs.

4) Keep CI matrix exactly as requested: Node 24.x and 25.x

  • Matrix remains on 24.x and 25.x only.

5) README clarification for long stack traces

  • Updated docs to clarify that this module does not depend on long-stack-traces and still supports dashed boundary marker parsing when present.

File-by-File Rationale

Tests migrated to native APIs

  • __tests__/get-test.js

    • Replaced Jest imports with node:test and node:assert/strict.
    • Preserved test coverage and assertions.
    • ESM stack filename expectation uses import.meta.url because modern Node ESM stacks report file URLs.
  • __tests__/parse-test.js

    • Replaced Jest APIs with native APIs while preserving assertion intent.
    • Preserved the real-vs-parsed comparison test semantics.
    • Kept line/column comparison excluded for this specific test because get() and new Error() capture at different source positions.
  • __tests__/long-stack-trace-test.js

    • Removed runtime dependency usage on unmaintained long-stack-traces.
    • Replaced with deterministic synthetic stack containing dashed marker.
    • Preserved original behavioral intent: parser detects dashed event-loop boundary markers.

Tooling and dependency cleanup

  • package.json

    • Test script changed from jest to node --test.
    • Removed Jest/Babel/long-stack-traces dependency config.
    • Set engines to >=20.0.0.
  • package-lock.json

    • Regenerated to match dependency removal (drastically reduced lockfile footprint).
  • .babelrc

    • Removed because Babel is no longer used.
  • .npmignore

    • Removed stale references to .babelrc and .travis.yml.
    • Kept package payload clean.

CI + docs

  • .github/workflows/test.yml

    • Matrix explicitly set to Node 24.x and 25.x only.
    • Removed unnecessary install/cache steps for now-empty dependency setup.
  • Readme.md

    • Updated Long stack traces section:
      • clarifies parser behavior for dashed boundary markers
      • clarifies module no longer depends on unmaintained long-stack-traces package
    • Also cleaned one markdown link-text lint issue.

Validation Performed

  1. Baseline before migration
  • Original suite: 3 suites / 16 tests passed.
  1. Post-migration test verification
  • npm test: 16/16 passing.
  • npx -y node@24 --test: 16/16 passing.
  • npx -y -p node@25 node --test: 16/16 passing.
  1. Package verification
  • npm pack --dry-run includes only expected files:
    • License
    • Readme.md
    • index.js
    • package.json

Commits (logical steps)

  1. test: migrate suites to node:test and assert
  2. chore: drop jest babel and long-stack-traces dependencies
  3. ci/docs: keep node 24/25 matrix and clarify long-stack trace support

Risk Assessment

  • Runtime library code (index.js) is unchanged.
  • Risk is limited to test harness migration and documentation updates.
  • Cross-version verification on Node 24 and 25 completed successfully.

@niemyjski niemyjski requested a review from Copilot May 2, 2026 14:58
@niemyjski niemyjski self-assigned this May 2, 2026
@niemyjski niemyjski added the dependencies Pull requests that update a dependency file label May 2, 2026
@niemyjski
Copy link
Copy Markdown
Collaborator Author

closes #54

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the repository’s test infrastructure by moving from Jest/Babel-based testing to Node’s native node:test runner, while cleaning up now-unused tooling and clarifying the README’s description of long stack trace parsing. In the broader codebase, it keeps the published runtime module unchanged and focuses the changes on tests, CI, packaging metadata, and docs.

Changes:

  • Migrates the existing test suites from Jest assertions/APIs to node:test and node:assert/strict.
  • Removes obsolete test tooling/config (jest, babel-jest, Babel config, and long-stack-traces) and updates package metadata/lockfile accordingly.
  • Simplifies CI and updates README text around dashed async-boundary marker parsing.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
package.json Switches the test script to node --test, removes test-tooling deps/config, and updates engine metadata.
package-lock.json Lockfile is reduced to root package metadata after dependency cleanup.
tests/parse-test.js Converts parser behavior tests from Jest to native Node test/assert APIs.
tests/long-stack-trace-test.js Replaces the dependency-based async stack test with a deterministic synthetic stack test.
tests/get-test.js Converts get() behavior tests to native Node test/assert APIs.
Readme.md Clarifies long-stack-trace behavior and cleans up V8 API link wording.
.npmignore Removes stale ignore entries and excludes .github/ from the package payload.
.github/workflows/test.yml Simplifies the CI job to just set up Node and run npm test.
.babelrc Deletes the now-unused Babel configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Readme.md Outdated
Comment thread package.json
Comment thread .github/workflows/test.yml
Comment thread __tests__/parse-test.js
niemyjski and others added 3 commits May 2, 2026 10:08
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread package.json
},
"engines": {
"node": ">=18.14.0"
"node": ">=20.0.0"
Comment thread Readme.md
Comment on lines +44 to +45
indentation. All other methods of the event loop boundary call site return
`null`.
Comment on lines +33 to +34
assert.notStrictEqual(boundary, undefined);
assert.match(boundary.getFileName(), /-----/);

setTimeout(badFn, 10);
assert.notStrictEqual(boundary, undefined);
assert.match(boundary.getFileName(), /-----/);
Comment thread __tests__/parse-test.js
Comment on lines 120 to 126
@@ -113,29 +125,24 @@ describe("parse", () => {
realValue = exceptions[i];
}
@niemyjski niemyjski merged commit e19d06c into master May 2, 2026
6 checks passed
@niemyjski niemyjski deleted the chore/node-test-migration branch May 2, 2026 15:16
niemyjski added a commit that referenced this pull request May 2, 2026
- Fix issue #29: parse() now captures source location from SyntaxError
  stacks where V8 puts the file:line(:col) as the first line instead of
  an error message. This is returned as the first CallSite frame.
- Revert engines.node to >=20.0.0
- Fix README: 'All other methods' -> 'The other getter-style methods'
  (boolean predicates return false, not null)
- Add full boundary-frame contract assertions to long-stack-trace test
- Remove dead 'exceptions' parameter from compare() in parse-test
- Add regression test for issue #13 ([object Object] in function name)
  -- confirmed already working with current regex
- Add verification test for issue #25 (async/await) -- confirmed
  resolved on modern Node via V8 async stack traces
- Comprehensive test coverage for #29: SyntaxError with/without column,
  Windows paths, and non-regression for normal Error/TypeError/RangeError

Closes #13
Closes #25
Closes #29
niemyjski added a commit that referenced this pull request May 2, 2026
- Fix issue #29: parse() now captures source location from SyntaxError
  stacks where V8 puts the file:line(:col) as the first line instead of
  an error message. This is returned as the first CallSite frame.
- Source location detection uses robust guards:
  * Excludes lines with ': ' (colon+space) which indicates error messages
  * Excludes URL schemes (http://, https://, etc.)
  * Regex is safe from ReDoS (non-greedy with anchored terminus)
- Refactored parse() internals from map+filter to forEach+push:
  * Identical output behavior (verified with tests)
  * Slightly more memory efficient (1 array vs 2 intermediate arrays)
  * Fully compatible with Node 20+
- Keep engines.node at >=20.0.0
- Fix README: 'All other methods' -> 'The other getter-style methods'
  (boolean predicates return false, not null)
- Add full boundary-frame contract assertions to long-stack-trace test
- Remove dead 'exceptions' parameter from compare() in parse-test
- Add regression test for issue #13 ([object Object] in function name)
  -- confirmed already working with current regex
- Add verification test for issue #25 (async/await) -- confirmed
  resolved on modern Node via V8 async stack traces
- Comprehensive test coverage for #29: SyntaxError with/without column,
  Windows paths, non-regression for Error/TypeError/RangeError, custom
  error types, URL schemes, adversarial ReDoS inputs, forEach output
  equivalence, and memory/shared-state safety

Closes #13
Closes #25
Closes #29
niemyjski added a commit that referenced this pull request May 2, 2026
ISSUE #29: parse() now captures leading source-location lines
- CJS SyntaxError stacks (require() on invalid files) begin with
  '/path/to/file.cjs:line[:col]' before the error message. This line
  was previously lost; it is now returned as the first CallSite frame.
- Detection guards (both required to identify a source loc):
    1. /:\s/ — all error messages contain ': ' (colon+space); source
       location lines never do.
    2. /^(?:https?|ftp|data|blob):\/\// — excludes web URL schemes
       but explicitly permits file:// (valid source loc in some envs).
- Verified against actual Node 25.9 stacks; format stable since Node 10.
- ESM SyntaxErrors produce a standard 'SyntaxError: message' first line
  (no source location prepended) — no change needed for that path.

BEFORE/AFTER VERIFICATION:
- Against master index.js: exactly 5 [REQUIRES FIX] tests fail.
- Against patched index.js: all 35 tests pass.

CODE CHANGE: map+filter → forEach+push
- forEach+push produces identical output (no falsy entries ever pushed;
  non-matching lines return early without pushing — equivalent to the
  prior .filter(Boolean) step).
- Verified by test 'non-parseable lines produce no entries in output array'.
- Eliminates one intermediate array allocation per parse() call.
- All forEach/push/Array APIs stable since ES5; fully Node 20 compatible.

SECURITY: ReDoS analysis
- /at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/
  No nested quantifiers. Non-greedy .+? with concrete anchors.
- /^(.+?):(\d+)(?::(\d+))?$/
  Non-greedy .+? anchored by $ terminus. Tested at 50KB: <1ms.
- Both regexes have dedicated adversarial timing tests.

OTHER CHANGES:
- engines.node: >=20.0.0 (unchanged)
- README: 'All other methods' -> 'The other getter-style methods'
  (bool predicates return false, not null, for boundary frames)
- long-stack-trace test: full boundary-frame contract assertions
- parse-test: remove dead 'exceptions' param from compare()
- Regression test for #13 ([object Object] in function name)
- Verification test for #25 (async/await) — resolved since Node 12

Closes #13
Closes #25
Closes #29
niemyjski added a commit that referenced this pull request May 2, 2026
* fix: address PR #66/#67 review, fix #29, verify #13 and #25

ISSUE #29: parse() now captures leading source-location lines
- CJS SyntaxError stacks (require() on invalid files) begin with
  '/path/to/file.cjs:line[:col]' before the error message. This line
  was previously lost; it is now returned as the first CallSite frame.
- Detection guards (both required to identify a source loc):
    1. /:\s/ — all error messages contain ': ' (colon+space); source
       location lines never do.
    2. /^(?:https?|ftp|data|blob):\/\// — excludes web URL schemes
       but explicitly permits file:// (valid source loc in some envs).
- Verified against actual Node 25.9 stacks; format stable since Node 10.
- ESM SyntaxErrors produce a standard 'SyntaxError: message' first line
  (no source location prepended) — no change needed for that path.

BEFORE/AFTER VERIFICATION:
- Against master index.js: exactly 5 [REQUIRES FIX] tests fail.
- Against patched index.js: all 35 tests pass.

CODE CHANGE: map+filter → forEach+push
- forEach+push produces identical output (no falsy entries ever pushed;
  non-matching lines return early without pushing — equivalent to the
  prior .filter(Boolean) step).
- Verified by test 'non-parseable lines produce no entries in output array'.
- Eliminates one intermediate array allocation per parse() call.
- All forEach/push/Array APIs stable since ES5; fully Node 20 compatible.

SECURITY: ReDoS analysis
- /at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/
  No nested quantifiers. Non-greedy .+? with concrete anchors.
- /^(.+?):(\d+)(?::(\d+))?$/
  Non-greedy .+? anchored by $ terminus. Tested at 50KB: <1ms.
- Both regexes have dedicated adversarial timing tests.

OTHER CHANGES:
- engines.node: >=20.0.0 (unchanged)
- README: 'All other methods' -> 'The other getter-style methods'
  (bool predicates return false, not null, for boundary frames)
- long-stack-trace test: full boundary-frame contract assertions
- parse-test: remove dead 'exceptions' param from compare()
- Regression test for #13 ([object Object] in function name)
- Verification test for #25 (async/await) — resolved since Node 12

Closes #13
Closes #25
Closes #29

* fix: allow file:// source locations in parse(); fix CI on Node 24

RCA: The source-location URL guard /^\w+:\/\// was too broad — it excluded
'file://' scheme URLs which are valid source location prefixes. This caused
the 'file:// source location is captured correctly' test to fail on Node 24
(and 25 in CI) because file:///path:line was rejected before being captured
as a source-loc frame.

Fix: Change the URL exclusion from /^\w+:\/\// to /^(?:https?|ftp|data|blob):\/\//
so that file:// is intentionally permitted while network/data URL schemes are
still excluded. node: scheme paths (node:internal/...) do not use '://' and
are already handled correctly.

Also: expand the source-location comment block to document:
- All supported path formats (POSIX, Windows, file://)
- The two detection guards and why each works
- ESM vs CJS SyntaxError behaviour differences
- Tested on Node 24.x and 25.x (matches CI matrix)
- Verified against @exceptionless/node package test suite

* updated git ignore

* fix: address Copilot review #9 — node: scheme guard, async throw, comment accuracy, ReDoS threshold

- index.js: add 'node:' to URL scheme exclusion guard so paths like
  'node:internal/modules/...' are never misclassified as source locations
- index.js: tighten comment wording — 'always' was inaccurate for empty-message
  errors (new Error().stack → 'Error' with no colon+space; the source-loc regex
  already rejects such lines, but the comment was misleading)
- get-test.js: fix async/await test to genuinely cross an async suspension point
  (await Promise.resolve() before throw) and use try/catch so V8 attaches the
  async context; match 'async outerAsync' prefix via .includes()
- parse-test.js: relax ReDoS timing threshold from 100ms → 1000ms; 100ms was
  brittle on loaded CI runners — true ReDoS takes seconds to minutes

* fix: use Number.isNaN check instead of || null for parseInt results

parseInt(...) || null coerces 0 to null because 0 is falsy. Column numbers
can legitimately be 0 in some tooling. Replace with explicit NaN guard:
  Number.isNaN(n) ? null : n

Added regression test: 'SyntaxError CJS: column number 0 is preserved'.

* fix: correctly exclude node: specifiers from source-loc detection

The guard !firstLine.match(/^(?:https?|ftp|data|blob|node):\\/\\//) only matched
'node://' (with slashes) but Node built-in specifiers use 'node:' without '//'.
Fix: add separate !firstLine.match(/^node:/) check.
Update comment to clarify that node: and node:// are different formats.
Add regression test: 'node: specifier is not treated as source loc'.

* chore: clean up test markers and use startsWith for node: guard

- index.js: replace !firstLine.match(/^node:/) with !firstLine.startsWith('node:')
  for clarity and performance; update comment for guard #2
- parse-test.js: remove [REQUIRES FIX], [REGRESSION], [SECURITY]/ReDoS
  markers — tests are verified and shipping; remove '— possible ReDoS'
  from timing assert messages

* Apply suggestion from @niemyjski

* Apply suggestion from @niemyjski

* fix: consistent Number.isNaN guard for at-frame parseInt; trim comment block

- Apply Number.isNaN(n) ? null : n to lineNumber and columnNumber in the
  at-frame parsing path for consistency with the source-loc frame fix.
  parseInt() || null coerces 0 to null; 0 is a valid column number.
- Trim the 20-line source-loc comment block to 6 lines — this package
  ships source without minification, so large comments go to consumers.

* fix: use node:test built-in timeout option instead of wall-clock assertions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants