Skip to content

fix(report-file): surface write stream errors instead of a TypeError - #2058

Merged
johanneswuerbach merged 1 commit into
testem:masterfrom
MaxFreedomPollard:fix-report-file-error-handling
Sep 7, 2026
Merged

johanneswuerbach merged 1 commit into
testem:masterfrom
MaxFreedomPollard:fix-report-file-error-handling

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

testem ci dies with TypeError: Cannot read properties of undefined (reading 'end') when it cannot open the report file, and the real filesystem error never reaches the user. I hit it with a testem.json whose report_file points at an existing directory, so opening it for writing fails with EISDIR.

The cause is in lib/utils/report-file.js. Line 26 registers finish as an error listener on the write stream, but finish is a plain function, so this inside it is the stream that emitted the event, not the ReportFile instance. this.outputStream is undefined there and line 21 throws. That throw happens inside the error emit, before the second error listener on line 30 can reject closePromise, so it escapes as an uncaught exception and the open error is lost. Line 25 registers the same handler for end, which a writable stream never emits. Both are leftovers from when ReportFile wrapped two separate streams, an output stream and the file stream.

This removes both listeners. close() already ends the stream, and closePromise already resolves on finish and rejects on error, so the fs error now reaches the caller and testem shuts down through its normal path with exit code 1, the same as it already does when the report file's directory cannot be created. closePromise also gets a no-op catch, because an open failure rejects on the next tick, long before close() attaches its handler, and node would otherwise kill the process for an unhandled rejection.

Verification on macOS, node 22.23.2:

# the new test against unmodified master
$ npx mocha tests/utils/report-file_tests.js
  1 passing
  1 failing
  1) ReportFile close rejects with the write stream error when the file cannot be opened:
     Uncaught TypeError: Cannot read properties of undefined (reading 'end')
      at WriteStream.finish (lib/utils/report-file.js:21:27)

# with this change
$ npx mocha tests/utils/report-file_tests.js
  2 passing

$ npm run lint
  no output, exit 0

$ npm test
  832 passing (12m)
  2 pending
  8 failing

All 8 failures are 90 second timeouts in tests/ci/ci_tests.js and tests/ci/dev_tests.js, which launch Headless Firefox, and Firefox is not installed on the machine I ran this on. npx mocha tests/ci/ci_tests.js tests/ci/dev_tests.js -g "handles todos correctly|keeps runner association" fails identically on unmodified master. Every report file test passes with the change: the four in tests/ci/report_file_tests.js and the two in tests/utils/report-file_tests.js.

When the report file could not be opened, testem died with
"TypeError: Cannot read properties of undefined (reading 'end')"
and the real filesystem error was never shown.

lib/utils/report-file.js registered `finish` as an 'error' listener on the
write stream. `finish` is a plain function, so `this` inside it is the
stream that emitted the event, not the ReportFile instance, and
`this.outputStream` is undefined. The listener threw before the second
'error' listener could reject closePromise, so the throw escaped as an
uncaught exception and the underlying error was lost. The same handler was
also registered for 'end', which a Writable never emits.

Drop both listeners. close() already ends the stream, and closePromise
already resolves on 'finish' and rejects on 'error', so the fs error now
reaches the caller. Attach a no-op catch to closePromise as well, because
an open failure rejects on the next tick, long before close() attaches its
handler, and that would otherwise be reported as an unhandled rejection.
@johanneswuerbach
johanneswuerbach merged commit 5382bbc into testem:master Sep 7, 2026
10 checks passed
Gaurav0 pushed a commit to Gaurav0/testem that referenced this pull request Sep 9, 2026
…estem#2058)

When the report file could not be opened, testem died with
"TypeError: Cannot read properties of undefined (reading 'end')"
and the real filesystem error was never shown.

lib/utils/report-file.js registered `finish` as an 'error' listener on the
write stream. `finish` is a plain function, so `this` inside it is the
stream that emitted the event, not the ReportFile instance, and
`this.outputStream` is undefined. The listener threw before the second
'error' listener could reject closePromise, so the throw escaped as an
uncaught exception and the underlying error was lost. The same handler was
also registered for 'end', which a Writable never emits.

Drop both listeners. close() already ends the stream, and closePromise
already resolves on 'finish' and rejects on 'error', so the fs error now
reaches the caller. Attach a no-op catch to closePromise as well, because
an open failure rejects on the next tick, long before close() attaches its
handler, and that would otherwise be reported as an unhandled rejection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants