[tests] Isolate JNI reference leak tests#12038
Conversation
Move JNI reference and peer leak checks into a dedicated serial MSTest/MTP app and run it under CoreCLR and Mono in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7be7d34c-aff3-4338-b15b-6061bc7a3ba9
There was a problem hiding this comment.
Pull request overview
This PR isolates JNI reference/peer leak checks into a dedicated on-device MSTest/Microsoft Testing Platform (MTP) test app (tests/JniReferenceLeakTests) to avoid flaky process-wide assertions caused by unrelated background activity in shared device test processes.
Changes:
- Added a dedicated
JniReferenceLeakTestsAndroid test app with serialized MSTest execution ([assembly: DoNotParallelize]) and focused leak/peer assertions. - Migrated/removed flaky or process-global JNI reference/peer-count assertions from shared device test suites; narrowed remaining assertions to object-specific checks or restricted them to non-Android runs.
- Wired the new test app into solution/test documentation and into CI (package tests for CoreCLR + Mono; nightly emulator coverage).
Show a summary per file
| File | Description |
|---|---|
| Xamarin.Android-Tests.slnx | Adds the new JniReferenceLeakTests project to the tests solution structure. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs | Removes the weak-peer surfaced-object drift test from the shared device suite. |
| tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs | Removes the previously ignored/flaky custom-widget GREF leak test from the shared suite. |
| tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj | New dedicated Android MSTest/MTP app project for leak/peer accounting tests. |
| tests/JniReferenceLeakTests/TestInstrumentation.cs | Implements an MTP-based Android Instrumentation runner for MSTest with TRX reporting. |
| tests/JniReferenceLeakTests/AssemblyInfo.cs | Disables MSTest parallelism at the assembly level to stabilize process-wide accounting. |
| tests/JniReferenceLeakTests/ReferenceTestHelpers.cs | Adds helpers to amplify leak detection and synchronize GC/peer collection. |
| tests/JniReferenceLeakTests/GlobalReferenceTests.cs | Adds amplified GREF leak checks (TryFindClass, activation, object arrays). |
| tests/JniReferenceLeakTests/LocalReferenceTests.cs | Adds thread-local LREF accounting checks in an isolated process. |
| tests/JniReferenceLeakTests/PeerReferenceTests.cs | Adds object-specific surfaced-peer assertions + weak-peer collection check in isolation. |
| tests/JniReferenceLeakTests/WidgetReferenceTests.cs | Reintroduces the custom-view inflate leak check in the isolated harness. |
| tests/JniReferenceLeakTests/Resources/layout/leak_test_widget.axml | Layout used to inflate a custom managed widget for the leak test. |
| tests/JniReferenceLeakTests/java/net/dot/jni/referenceleaktests/ActivationProbeFactory.java | Java-side factory to exercise Java-side activation paths. |
| tests/JniReferenceLeakTests/AndroidManifest.xml | Manifest for the new test app. |
| tests/JniReferenceLeakTests/global.json | Forces Microsoft Testing Platform runner behavior when invoking dotnet test. |
| tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs | Removes process-global GREF equality assertion from Java-side activation test. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniTypeUtf8Test.cs | Restricts process-global GREF leak assertions to non-Android runs. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs | Changes peer assertions from total-count based to peer-specific checks; restricts GREF leak test to non-Android runs. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniEnvironmentTests.cs | Restricts process-global reference-count assertions to non-Android runs. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JavaObjectTest.cs | Restricts runtime-global surfaced-peer count assertion to non-Android runs. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JavaObjectArrayTest.cs | Restricts fixture-wide process-global GREF baseline checks to non-Android runs. |
| external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JavaArrayContract.cs | Restricts fixture-wide LREF baseline checks to non-Android runs (covered in new isolated app). |
| build-tools/automation/yaml-templates/stage-package-tests.yaml | Adds CI legs to run the new app under CoreCLR and Mono in package tests. |
| build-tools/automation/azure-pipelines-nightly.yaml | Adds nightly emulator coverage for the new isolated leak test app. |
| .github/skills/tests/references/test-catalog.md | Documents how to build/run the new dedicated leak test app locally. |
Copilot's findings
- Files reviewed: 25/25 changed files
- Comments generated: 0
Keep the dedicated on-device JNI reference leak checks on CoreCLR only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7be7d34c-aff3-4338-b15b-6061bc7a3ba9
Treat warnings as errors in the leak-test project locally so unavailable package versions fail before CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7be7d34c-aff3-4338-b15b-6061bc7a3ba9
Track the stable TestFX release centrally and keep the leak-test project on MSTestPackageVersion with local warnings treated as errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7be7d34c-aff3-4338-b15b-6061bc7a3ba9
✅ New
|
…icated-jni-reference-leak-tests # Conflicts: # eng/Version.Details.xml # eng/Versions.props
|
/review |
|
✅ Android PR Reviewer completed successfully! |
There was a problem hiding this comment.
⚠️ Review — Isolate JNI reference leak tests
Solid, well-motivated PR. Moving process-wide GREF/surfaced-peer accounting out of the shared multi-fixture test apps into a dedicated [assembly: DoNotParallelize] MSTest/MTP app is the right fix for the flakiness described in #12031: the refactored contract tests now count only the specific peer under test (GetSurfacedPeerCount(peer)) instead of process-global totals, and the warmup+measurement amplification in AssertNoGlobalReferenceLeak is a sound way to turn per-op leaks into detectable deltas. Guarding the process-wide assertions in the Java.Interop suite with #if !__ANDROID__ cleanly leaves JVM-only accounting where it belongs.
Verdict:
Findings
- 💡 Unreachable
return;afterAssert.InconclusiveinPeerReferenceTests. - 💡 Surfaced-peer tolerance/batch duplicated inline instead of reusing the
ReferenceTestHelpersamplification helper. - 💡 MTP run exceptions in
TestInstrumentationare only surfaced via the resultBundle; also log toadb logcat. ⚠️ Nightly + package lanes build this app asDebugonly, so full trimming (Release-only) leaks aren't exercised — please confirm intent.
Notes
- CI status is still pending on the head commit — the Azure DevOps
dotnet-androidpipeline hadn't reported at review time. This isn't LGTM-able until those checks are green; please re-check the pipeline (and the newly-addedJniReferenceLeakTestslanes) before merge. - Nice touches: per-thread exact LREF accounting retained,
[MethodImpl(NoInlining)]on the weak-reference/array creators to keep locals from being kept alive, and the migrated custom-widget test (#11201) now running in isolation.
Positive callout: the diff meaningfully reduces ambient-noise coupling rather than just papering over flakes with wider tolerances. 👍
Generated by Android PR Reviewer for #12038 · 116.1 AIC · ⌖ 13.3 AIC · ⊞ 6.8K
Comment /review to run again
Addresses the Android PR Reviewer feedback on #12038: - Remove the unreachable `return;` after `Assert.Inconclusive` in PeerReferenceTests (Assert.Inconclusive always throws). - Log MTP run exceptions to logcat in TestInstrumentation so failures remain observable via `adb logcat` even if the result Bundle is missed or truncated. - Add a Release JniReferenceLeakTests lane to the nightly pipeline so full trimming (TrimMode=full, Release-only) reference leaks are exercised. The PR package-tests lane stays Debug to keep PR CI fast. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ad688158-19a2-49f4-a9d3-09a9457d0dfd
JniReferenceLeakTests is the first project to restore the Maestro-managed MSTest package ($(MSTestPackageVersion)) in the public dnceng-public pipeline. That version (currently 4.4.0-preview.26360.5, flowed from microsoft/testfx) is published to the public `dotnet-tools` feed, which was missing from NuGet.config. Restore therefore failed with: error NU1102: Unable to find package MSTest with version (>= 4.4.0-preview.26360.5) [nearest on dotnet-public: 4.3.2] Add the `dotnet-tools` feed so the darc-managed MSTest preview resolves in the public pipeline, fixing the JniReferenceLeakTests build lanes (and any other $(MSTestPackageVersion) consumer) without pinning away from the centrally-managed version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ad688158-19a2-49f4-a9d3-09a9457d0dfd
Summary
[assembly: DoNotParallelize]Why
GREF and surfaced-peer counts are process-wide. The existing Android test apps load many unrelated fixtures into one process, and runtime or test-infrastructure work on background threads can change those totals while a test is taking before/after snapshots. Serializing NUnit workers did not prevent these failures.
The new app contains only reference-accounting tests and disables MSTest parallelism at the assembly level. Process-wide checks execute equal 100-operation warmup and measurement batches, allowing only a small fixed amount of ambient drift, so a per-operation leak is strongly amplified. Thread-local LREF checks retain exact accounting, while registry tests count only the specific peer under test.
Functional coverage remains in the original suites, and JVM-only accounting tests continue to run there. The custom-widget test previously quarantined under #11201 now passes reliably in the isolated process.
Validation
make prepare && make allwith serialized MSBuildTreatWarningsAsErrors=true: 0 warningsJniReferenceLeakTests: 12 passed, 0 failed, 0 skipped under CoreCLRJava.Interop-Tests: 672 passed, 4 existing skipsMono.Android.NET-Tests.csprojbuildgit diff --checkFixes #12031.
Supersedes #12037.