Skip to content

Run the ReactAndroid JNI gtests as instrumentation tests - #57976

Closed
GijsWeterings wants to merge 2 commits into
react:mainfrom
GijsWeterings:export-D116286866
Closed

Run the ReactAndroid JNI gtests as instrumentation tests#57976
GijsWeterings wants to merge 2 commits into
react:mainfrom
GijsWeterings:export-D116286866

Conversation

@GijsWeterings

Copy link
Copy Markdown
Contributor

Summary:
The ReactAndroid JNI gtests in react/fabric/test and react/jni/test were configured as plain host C++ tests, but they link against the Android-only JNI libraries. The resulting binary is an Android aarch64 ELF that needs /system/bin/linker64 and so cannot run on a Linux host:

qemu-aarch64: Could not open '/system/bin/linker64': No such file or directory
Test failed to produce the expected output! ... IO error: No result xml files found.

They were not failing their assertions — they were never executing. The runner enumerated case names statically out of the binary without running it, so all 6 cases across the two targets were known and reported as failing, with no stack traces at all. The absence of stack traces was itself the tell: the process never started, so no gtest XML was ever produced.

Both targets move to the established pattern for gtests against Android-only native libs: build them as Android instrumentation tests, packaged into an APK and run on a device or emulator.

FabricMountingManagerTest can drop its deliberate leak as a result. It previously allocated the manager with a no-op deleter, because ~FabricMountingManager() calls jni::ThreadScope::WithClassLoader, which throws without an attached JavaVM. Under instrumentation the gtest runs inside a native method registered via fbjni makeNativeMethod, so cachedOrNull() is non-null and the closure runs inline — destruction is safe. The fixture now holds a default-constructed (null) jni::global_ref member and returns a std::unique_ptr; releasing a null global_ref is a no-op. All four test bodies are byte-identical. ModuleRegistryBuilderTest.cpp is a comment-only correction.

Changelog: [Internal]

Differential Revision: D116286866

…eact#57975)

Summary:

`HighResDuration::fromDOMHighResTimeStamp` and `HighResTimeStamp::fromDOMHighResTimeStamp` converted milliseconds back to nanoseconds with `static_cast<int64_t>(units * 1e6)`, which truncates toward zero.

`toDOMHighResTimeStamp()` divides the nanosecond count by 1e6 (one rounding) and multiplying back by 1e6 rounds again, so the product frequently lands a hair below the original integer (e.g. `537648854729249.97`). Truncation then chops off a whole nanosecond, so `fromDOMHighResTimeStamp(toDOMHighResTimeStamp(x)) != x` for roughly 2% of random `now()` values.

That is the source of an intermittent `BridgingTest/highResTimeStampTest` failure, which reported:

```
Expected equality of these values:
  timestamp
    Which is: 8-byte object <22-02 00-21 FD-E8 01-00>
  bridging::fromJs<HighResTimeStamp>( rt, bridging::toJs(rt, timestamp), invoker)
    Which is: 8-byte object <21-02 00-21 FD-E8 01-00>
```

Round to the nearest nanosecond instead of truncating. Nanosecond values below 2^53 are exactly representable in a double, so the round trip is now exact for every value below 2.25e15 ns (26 days of monotonic clock); beyond that the double's ULP exceeds 0.5 ns and residual error is at most 2 ns, which is a floor of the DOM representation itself.

Rounding is also the correct semantic independently of the round trip — truncation gives a systematic downward bias and is asymmetric across zero, which affects the other callers (`RCTHighResTimeStampFromSeconds` for touch timestamps, `RuntimeTargetConsole` `console.timeStamp`, and `PerformanceTracer`). It is implemented without `<cmath>` so the header stays `constexpr`-safe and C++17/20-portable.

`HighResTimeStamp::fromDOMHighResTimeStamp` now delegates to `HighResDuration`'s so there is a single implementation.

`highResTimeStampTest` previously asserted on `HighResTimeStamp::now()`, whose magnitude is host-uptime-dependent, so it only tripped the bug on about 2% of runs. It now round-trips five fixed nanosecond values (including the exact value from the failing run), which exercises the bug on every run. No test was skipped, disabled, or loosened.

Changelog:
[General][Fixed] - Round instead of truncate when converting a `DOMHighResTimeStamp` back to nanoseconds, so `HighResTimeStamp` and `HighResDuration` round trips are exact

Differential Revision: D116286868
Summary:
The `ReactAndroid` JNI gtests in `react/fabric/test` and `react/jni/test` were configured as plain host C++ tests, but they link against the Android-only JNI libraries. The resulting binary is an Android aarch64 ELF that needs `/system/bin/linker64` and so cannot run on a Linux host:

```
qemu-aarch64: Could not open '/system/bin/linker64': No such file or directory
Test failed to produce the expected output! ... IO error: No result xml files found.
```

They were not failing their assertions — they were never executing. The runner enumerated case names statically out of the binary without running it, so all 6 cases across the two targets were known and reported as failing, with no stack traces at all. The absence of stack traces was itself the tell: the process never started, so no gtest XML was ever produced.

Both targets move to the established pattern for gtests against Android-only native libs: build them as Android instrumentation tests, packaged into an APK and run on a device or emulator.

`FabricMountingManagerTest` can drop its deliberate leak as a result. It previously allocated the manager with a no-op deleter, because `~FabricMountingManager()` calls `jni::ThreadScope::WithClassLoader`, which throws without an attached `JavaVM`. Under instrumentation the gtest runs inside a native method registered via fbjni `makeNativeMethod`, so `cachedOrNull()` is non-null and the closure runs inline — destruction is safe. The fixture now holds a default-constructed (null) `jni::global_ref` member and returns a `std::unique_ptr`; releasing a null `global_ref` is a no-op. All four test bodies are byte-identical. `ModuleRegistryBuilderTest.cpp` is a comment-only correction.

Changelog: [Internal]

Differential Revision: D116286866
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 17, 2026
@meta-codesync

meta-codesync Bot commented Aug 17, 2026

Copy link
Copy Markdown

@GijsWeterings has exported this pull request. If you are a Meta employee, you can view the originating Diff in D116286866.

@meta-codesync meta-codesync Bot closed this in 37dd404 Aug 17, 2026
@meta-codesync meta-codesync Bot added the Merged This PR has been merged. label Aug 17, 2026
@meta-codesync

meta-codesync Bot commented Aug 17, 2026

Copy link
Copy Markdown

This pull request has been merged in 37dd404.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant