Skip to content
Merged
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 build-tools/automation/yaml-templates/stage-package-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ stages:
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab
artifactFolder: $(DotNetTargetFramework)-NativeAOT

# Process-wide JNI reference counts need a fresh process without unrelated JcwGen tests.
- template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml
parameters:
configuration: $(XA.Build.Configuration)
testName: Xamarin.Android.JcwGen_Tests-JniReferenceLeaks
project: tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj
extraBuildArgs: -p:IncludeCategories=JniReferenceLeak
artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Xamarin.Android.JcwGen_Tests-Signed.apk
artifactFolder: $(DotNetTargetFramework)-JcwGen-JniReferenceLeaks

- 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 @@ -15,6 +15,9 @@ namespace Xamarin.Android.JcwGenTests {
[TestFixture]
public class BindingTests {

const string JniReferenceLeakCategory = "JniReferenceLeak";
const int LeakCheckIterations = 100;

[Test]
public void TestTimingCreateTimingIsCorrectType ()
{
Expand Down Expand Up @@ -95,22 +98,77 @@ public void Arrays ()
}

[Test]
[Explicit ("Run only in isolated JniReferenceLeak test runs.")]
[Category (JniReferenceLeakCategory)]
public void JavaSideActivation ()
{
using (var i = new ConstructorTest ()) {
// 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);
AssertNoSustainedGlobalReferenceGrowth (() => AssertJavaSideActivation (c));
}
}

[Test]
[Explicit ("Run only in isolated JniReferenceLeak test runs.")]
[Category (JniReferenceLeakCategory)]
public void AssertNoSustainedGlobalReferenceGrowth_DetectsRetainedGlobalReference ()
{
var objectClass = Java.Interop.JniEnvironment.Types.FindClass ("java/lang/Object");
var retainedReferences = new List<Java.Interop.JniObjectReference> ();
try {
Assert.Throws<AssertionException> (() => AssertNoSustainedGlobalReferenceGrowth (() => {
retainedReferences.Add (objectClass.NewGlobalRef ());
}));
} finally {
foreach (var retainedReference in retainedReferences) {
var reference = retainedReference;
Java.Interop.JniObjectReference.Dispose (ref reference);
}
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));
Java.Interop.JniObjectReference.Dispose (ref objectClass);
}
}

static void AssertJavaSideActivation (Java.Lang.Class c)
{
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);
}
Comment thread
simonrozsival marked this conversation as resolved.
}

static void AssertNoSustainedGlobalReferenceGrowth (Action action)
{
for (int i = 0; i < LeakCheckIterations; i++) {
action ();
}
CollectPeers ();

int grefsBefore = Java.Interop.Runtime.GlobalReferenceCount;
for (int i = 0; i < LeakCheckIterations; i++) {
action ();
}
CollectGarbage ();
int grefsAfter = Java.Interop.Runtime.GlobalReferenceCount;

Assert.LessOrEqual (grefsAfter, grefsBefore,
$"Operation should not leak global references after {LeakCheckIterations} iterations. " +
$"Before={grefsBefore}, After={grefsAfter}, Delta={grefsAfter - grefsBefore}");
}

static void CollectPeers ()
{
CollectGarbage ();
Java.Interop.JniEnvironment.Runtime.ValueManager.CollectPeers ();
Java.Interop.JniEnvironment.Runtime.ValueManager.WaitForGCBridgeProcessing ();
CollectGarbage ();
}

static void CollectGarbage ()
{
for (int i = 0; i < 3; i++) {
GC.Collect ();
GC.WaitForPendingFinalizers ();
}
}
Comment thread
simonrozsival marked this conversation as resolved.

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

Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ public TestInstrumentation (IntPtr handle, JniHandleOwnership transfer)
{
}

protected override IEnumerable<string>? IncludedCategories {
get {
var value = AppContext.GetData ("IncludeCategories") as string;
if (string.IsNullOrWhiteSpace (value))
return null;

var categories = value.Split (new [] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return categories.Length > 0 ? categories : null;
}
}

protected override IEnumerable<Assembly> GetTestAssemblies ()
{
return [Assembly.GetExecutingAssembly ()];
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

<ItemGroup>
<PackageReference Include="NUnit" Version="$(NUnitVersion)" />
<RuntimeHostConfigurationOption Include="IncludeCategories" Value="$(IncludeCategories)" Condition=" '$(IncludeCategories)' != '' " />
</ItemGroup>

<ItemGroup>
Expand Down
Loading