Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/skills/tests/references/test-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Device: **Yes**
| Test Area | Project | Notes |
|-----------|---------|-------|
| **runtime** (all) | `tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj` | Core runtime tests |
| **JNI reference leaks** | `tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj` | Dedicated serial NUnit/MTP app for JNI reference and peer leak checks |
| **networking** | Same project — tests in `Xamarin.Android.Net/` and `System.Net/` | `AndroidMessageHandlerTests`, `AndroidMessageHandlerIntegrationTests` |
| **java interop (on-device)** | Same project — tests in `Java.Interop/` | `JnienvTest`, `JavaListTest` |
| **android app** | Same project — tests in `Android.App/` | `Application`, `Activity` tests |
Expand Down Expand Up @@ -154,6 +155,15 @@ Command:

Results are `.trx` files under `bin/TestDebug/TestResults/` and are published as VSTest results in CI. Always pass the same configuration and MSBuild properties to the install and `dotnet test --no-build` commands (for example, `-c Release -p:UseMonoRuntime=false`).

For the dedicated JNI reference leak tests:
```bash
./dotnet-local.sh build -t:Install -c Debug tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
(
cd tests/JniReferenceLeakTests
../../dotnet-local.sh test JniReferenceLeakTests.csproj --no-build -c Debug --report-trx --results-directory ../../bin/TestDebug/TestResults
)
```

For `Xamarin.Android.JcwGen-Tests`, use the same pattern from `tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/`:
```bash
./dotnet-local.sh build -t:Install -c Debug tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj
Expand Down
1 change: 1 addition & 0 deletions Xamarin.Android-Tests.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Project Path="samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj" />
<Project Path="tests/StartupHook/StartupHook.csproj" />
</Folder>
<Folder Name="/tests/Mono.Android-Tests/" />
Expand Down
24 changes: 24 additions & 0 deletions build-tools/automation/azure-pipelines-nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,30 @@ stages:
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: Mono.Android-$(XA.Build.Configuration)-$(avdApiLevel)

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
buildConfiguration: $(XA.Build.Configuration)
configuration: Debug
Comment thread
simonrozsival marked this conversation as resolved.
testName: JniReferenceLeakTests-$(avdApiLevel)
project: tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
extraBuildArgs: -p:UseMonoRuntime=false -p:TestAvdApiLevel=$(avdApiLevel) -p:TestAvdAbi=$(avdAbi) -p:TestAvdType=$(avdType)
artifactSource: bin/TestDebug/$(DotNetTargetFramework)-android/net.dot.jni.referenceleaktests-Signed.apk
artifactFolder: JniReferenceLeakTests-$(avdApiLevel)

# Also run in Release so full trimming (TrimMode=full, Release-only) is
# exercised - trimming can remove code paths that keep peers/references
# alive, exactly the regressions this app exists to catch. Kept in nightly
# (not PR package-tests) to avoid adding an emulator run to every PR.
- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
buildConfiguration: $(XA.Build.Configuration)
configuration: Release
testName: JniReferenceLeakTests-Release-$(avdApiLevel)
project: tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
extraBuildArgs: -p:UseMonoRuntime=false -p:TestAvdApiLevel=$(avdApiLevel) -p:TestAvdAbi=$(avdAbi) -p:TestAvdType=$(avdType)
artifactSource: bin/TestRelease/$(DotNetTargetFramework)-android/net.dot.jni.referenceleaktests-Signed.apk
artifactFolder: JniReferenceLeakTests-Release-$(avdApiLevel)

- template: /build-tools/automation/yaml-templates/upload-results.yaml
parameters:
configuration: $(XA.Build.Configuration)
Expand Down
28 changes: 28 additions & 0 deletions build-tools/automation/yaml-templates/stage-package-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,34 @@ stages:

- template: /build-tools/automation/yaml-templates/start-stop-emulator.yaml

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
buildConfiguration: $(XA.Build.Configuration)
configuration: Debug
testName: JniReferenceLeakTests-CoreCLR
project: tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
extraBuildArgs: -p:UseMonoRuntime=false
Comment thread
simonrozsival marked this conversation as resolved.
artifactSource: bin/TestDebug/$(DotNetTargetFramework)-android/net.dot.jni.referenceleaktests-Signed.apk
artifactFolder: $(DotNetTargetFramework)-JniReferenceLeakTests-CoreCLR

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: JniReferenceLeakTests-CoreCLRTrimmable
project: tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
extraBuildArgs: -p:_AndroidTypeMapImplementation=trimmable -p:UseMonoRuntime=false
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/net.dot.jni.referenceleaktests-Signed.apk
artifactFolder: $(DotNetTargetFramework)-JniReferenceLeakTests-CoreCLRTrimmable

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: JniReferenceLeakTests-NativeAOT
project: tests/JniReferenceLeakTests/JniReferenceLeakTests.csproj
extraBuildArgs: -p:PublishAot=true
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/net.dot.jni.referenceleaktests-Signed.apk
artifactFolder: $(DotNetTargetFramework)-JniReferenceLeakTests-NativeAOT

- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Java.InteropTests
{
public abstract class JavaArrayContract<T> : ListContract<T>
{
#if !__ANDROID__
Comment thread
simonrozsival marked this conversation as resolved.
int lrefStartCount;

[OneTimeSetUp]
Expand All @@ -24,6 +25,7 @@ public void EndArrayTests ()
int lref = JniEnvironment.LocalReferenceCount;
Assert.AreEqual (lrefStartCount, lref, "JNI local references");
}
#endif // !__ANDROID__

[Test]
public void ToArray ()
Expand All @@ -41,4 +43,3 @@ public void ToArray ()
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public class JavaObjectArray_object_ContractTest : JavaObjectArrayContractTest<o
protected override object CreateValueA () {return a;}
protected override object CreateValueB () {return 42;}

#if !__ANDROID__
int grefStartCount;

[OneTimeSetUp]
Expand All @@ -176,6 +177,7 @@ public void EndCheckGlobalRefCount ()
string.Format ("JNI global references: grefStartCount={0}; gref={1}", grefStartCount, gref));
JniEnvironment.Runtime.ValueManager.CollectPeers ();
}
#endif // !__ANDROID__

[Test]
public void ObjectArrayType ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public void JavaReferencedInstanceSurvivesCollection ()
}
#endif // !NO_GC_BRIDGE_SUPPORT

#if !__ANDROID__
[Test]
[Ignore ("Frequently failing: https://github.com/dotnet/android/issues/12031")]
public void UnregisterFromRuntime ()
Expand All @@ -63,6 +64,7 @@ public void UnregisterFromRuntime ()
JniObjectReference.Dispose (ref l);
Assert.Throws<ObjectDisposedException> (() => o.UnregisterFromRuntime ());
}
#endif // !__ANDROID__

[Test]
public void RegisterWithVM_PermitsAliases ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public unsafe void Handles_NewReturnToJniRef ()
}
}

#if !__ANDROID__
Comment thread
simonrozsival marked this conversation as resolved.
[Test]
public void References_CreatedReference_InvalidRef ()
{
Expand Down Expand Up @@ -142,6 +143,6 @@ public void References_CreatedReference_GlobalRef ()
GetDeleteRefFunc ("java_interop_jnienv_delete_global_ref")?.Invoke (JniEnvironment.EnvironmentPointer, o.Handle);
}
}
#endif // !__ANDROID__
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -61,75 +61,51 @@ public void AddPeer ()
{
}

int GetSurfacedPeersCount ()
int GetSurfacedPeerCount (IJavaPeerable peer)
{
return valueManager.GetSurfacedPeers ().Count;
return valueManager.GetSurfacedPeers ().Count (candidate => {
return candidate.SurfacedPeer.TryGetTarget (out var target) && object.ReferenceEquals (target, peer);
});
}

[Test]
public void AddPeer_NoDuplicates ()
{
int startPeerCount = GetSurfacedPeersCount ();
using (var v = new MyDisposableObject ()) {
// MyDisposableObject ctor implicitly calls AddPeer();
Assert.AreEqual (startPeerCount + 1, GetSurfacedPeersCount (), DumpPeers ());
Assert.AreEqual (1, GetSurfacedPeerCount (v), DumpPeers ());
valueManager.AddPeer (v);
Assert.AreEqual (startPeerCount + 1, GetSurfacedPeersCount (), DumpPeers ());
Assert.AreEqual (1, GetSurfacedPeerCount (v), DumpPeers ());
}
}

[Test]
public void ConstructPeer_ImplicitViaBindingConstructor_PeerIsInSurfacedPeers ()
{
int startPeerCount = GetSurfacedPeersCount ();

var g = new GetThis ();
var surfaced = valueManager.GetSurfacedPeers ();
Assert.AreEqual (startPeerCount + 1, surfaced.Count);

var found = false;
foreach (var pr in surfaced) {
if (!pr.SurfacedPeer.TryGetTarget (out var p))
continue;
if (object.ReferenceEquals (g, p)) {
found = true;
}
}
Assert.IsTrue (found);
Assert.AreEqual (1, GetSurfacedPeerCount (g), DumpPeers ());

var localRef = g.PeerReference.NewLocalRef ();
g.Dispose ();
Assert.AreEqual (startPeerCount, GetSurfacedPeersCount ());
Assert.AreEqual (0, GetSurfacedPeerCount (g));
Assert.IsNull (valueManager.PeekPeer (localRef));
JniObjectReference.Dispose (ref localRef);
}

[Test]
public void ConstructPeer_ImplicitViaBindingMethod_PeerIsInSurfacedPeers ()
{
int startPeerCount = GetSurfacedPeersCount ();

var g = new GetThis ();
var surfaced = valueManager.GetSurfacedPeers ();
Assert.AreEqual (startPeerCount + 1, surfaced.Count);

var found = false;
foreach (var pr in surfaced) {
if (!pr.SurfacedPeer.TryGetTarget (out var p))
continue;
if (object.ReferenceEquals (g, p)) {
found = true;
}
}
Assert.IsTrue (found);
Assert.AreEqual (1, GetSurfacedPeerCount (g), DumpPeers ());

var localRef = g.PeerReference.NewLocalRef ();
g.Dispose ();
Assert.AreEqual (startPeerCount, GetSurfacedPeersCount ());
Assert.AreEqual (0, GetSurfacedPeerCount (g));
Assert.IsNull (valueManager.PeekPeer (localRef));
JniObjectReference.Dispose (ref localRef);
}

#if !__ANDROID__
// https://github.com/dotnet/android/issues/11101
[Test]
public void ConstructPeer_CalledMultipleTimes_ShouldNotLeakGlobalRefs ()
Expand Down Expand Up @@ -169,6 +145,7 @@ public void ConstructPeer_CalledMultipleTimes_ShouldNotLeakGlobalRefs ()
}
}
}
#endif // !__ANDROID__


[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public void TryFindClass_Utf8 ()
Assert.IsFalse (notFound.IsValid);
}

#if !__ANDROID__
[Test]
[Ignore ("Frequently failing: https://github.com/dotnet/android/issues/12031")]
public void TryFindClass_Utf8_DoesNotLeakGlobalRefs ()
{
int grefsBefore = JniEnvironment.Runtime.GlobalReferenceCount;
Expand All @@ -110,6 +110,7 @@ public void TryFindClass_String_DoesNotLeakGlobalRefs ()
Assert.AreEqual (grefsBefore, grefsAfter,
"TryFindClass for non-existent classes should not leak global references");
}
#endif // !__ANDROID__

[Test]
public void GetMethodID_Utf8_MatchesStringOverload ()
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ public static void EnsureLocalCapacity (int capacity)

internal static void DeleteRef (IntPtr handle, JniHandleOwnership transfer)
{
switch (transfer) {
switch (transfer & ~JniHandleOwnership.DoNotRegister) {
case JniHandleOwnership.DoNotTransfer:
break;
case JniHandleOwnership.TransferLocalRef:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,12 @@ public void JavaSideActivation ()
// To ensure that CallMethodFromCtor.class_ref is initialized
}
using (var c = Java.Lang.Class.FromType (typeof (ConstructorTest))) {
int initGref = Java.Interop.Runtime.GlobalReferenceCount;
using (var j = Com.Xamarin.Android.CallMethodFromCtor.NewInstance (c)) {
var instance = j.JavaCast<ConstructorTest>();
Assert.AreSame (j, instance);
Assert.IsTrue (instance.DefaultConstructorInvoked);
Assert.IsTrue (instance.ActivationConstructorInvoked);
}
int finiGref = Java.Interop.Runtime.GlobalReferenceCount;
Assert.AreEqual (initGref, finiGref,
string.Format ("Initial grefc={0}; final gref={1}; No GREFs should be lost!", initGref, finiGref));
}
}

Expand Down Expand Up @@ -391,4 +387,3 @@ public class B : Java.Lang.Object {
}
}
}

6 changes: 6 additions & 0 deletions tests/JniReferenceLeakTests/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:label="JniReferenceLeakTests">
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
Loading
Loading