Skip to content

fix(test-harness): surface addon load failures instead of silence#20

Open
gmaclennan wants to merge 2 commits into
mainfrom
fix/test-diagnostics
Open

fix(test-harness): surface addon load failures instead of silence#20
gmaclennan wants to merge 2 commits into
mainfrom
fix/test-diagnostics

Conversation

@gmaclennan

Copy link
Copy Markdown
Member

Both changes came out of debugging a rocksdb-native prebuild run that failed with Cannot find addon while the file was sitting on the device the whole time. Neither changes what passes or fails — they only make a failing run explain itself.

"Cannot find addon" when the addon is right there

require-addon tries to require() every candidate and raises Cannot find addon when they all fail, so a prebuild that exists but can't dlopen reports identically to one that was never installed. Both runners printed only err.stack, which omits cause.

Printing cause isn't enough: the resolver assigns it per candidate, so it ends up holding the last candidate's error — normally a Cannot find module for a root-level path that was never going to exist. The actual dlopen error gets overwritten.

The error does carry candidates, so this retries the ones that exist on disk and reports why each failed. The rocksdb-native run now says:

Addon diagnostics: candidate(s) present but failed to load:
  .../fs-native-extensions/prebuilds/android-arm64/fs-native-extensions.node:
    dlopen failed: cannot locate symbol "napi_create_function"

which is the answer (holepunch's npm android .node links no libnode.so), rather than a wrong hint to go looking for a missing file. Applied to default-smoke-test.js too, since smoke is the default runner and this is exactly the failure it exists to catch.

Failures logged under tags the runner silenced

run-test.sh streamed only NODEJS-MOBILE (node's stdout/stderr, pumped by native-lib.cpp). Node's fatal-error reports go to tag nodejs instead, and TestActivity's asset copy printStackTraces per-file failures to System.err — both dropped, so a broken run looked like silence. Finding Error: ENOENT ... open 'test/fixtures/lock' needed unfiltered logcat on a local emulator. Adds nodejs, System.err, AndroidRuntime; keeps -v raw so TAP output and sentinel parsing are unchanged.

Verification

  • present-but-broken: android-arm64 emulator, real dlopen error surfaced
  • genuinely missing / corrupt / wrong-arch: exercised on the host
  • node --check, bash -n, shellcheck clean

Not included: the APPLE_CLANG=ON edit sitting uncommitted in my working copy of prebuild/action.yml — that's a separate change and still unreleased, so iOS prebuilds built from v2 don't have it.

The runner only streamed NODEJS-MOBILE, the tag native-lib.cpp pumps node's
stdout/stderr to. Two sources of failure log elsewhere and were dropped, so a
broken run showed as unexplained silence:

- node's own fatal-error reports go to tag `nodejs`, not through our
  stdout/stderr pipe, so an uncaught exception's message never appeared.
- TestActivity's asset copy catches per-file errors and printStackTraces them
  to System.err, so a file missing on device was invisible.

Diagnosing a failed rocksdb-native run needed unfiltered logcat on a local
emulator to find `Error: ENOENT ... open 'test/fixtures/lock'` under `nodejs`.
Add those tags plus AndroidRuntime. `-v raw` stays, so TAP output is unchanged
and the sentinel parsing still works.
require-addon raises "Cannot find addon" when every candidate fails to *load*,
not only when none exist, so a prebuild that is present but cannot dlopen —
built without linking libnode.so so napi_* never resolves, wrong ABI — is
indistinguishable from one that was never installed. Both runners printed only
err.stack, which omits the cause.

Printing the cause is not enough either: the resolver assigns `cause` per
candidate, so it holds the last one's error, normally "Cannot find module" for
a root-level path that never existed. The real dlopen error is overwritten.

The error does carry the candidate list, so retry the candidates that exist on
disk and report why each failed, separating "no prebuild for this target" from
"prebuild present but broken". On a rocksdb-native run this turns a misleading
"Cannot find addon" into:

  Addon diagnostics: candidate(s) present but failed to load:
    .../fs-native-extensions/prebuilds/android-arm64/fs-native-extensions.node:
      dlopen failed: cannot locate symbol "napi_create_function"

Verified on an android-arm64 emulator for the present-but-broken case, and on
the host for the missing and corrupt cases.
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.

1 participant