From ba0a61f422dda11363e1c0befac6815aee7a94e2 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 2 Oct 2024 10:17:41 +0200 Subject: [PATCH] Add test for marshal methods hang (#9352) Context: https://github.com/dotnet/android/pull/9343 Context: https://github.com/dotnet/android/issues/8253#issuecomment-2380714158 Test is based on the original repro from https://github.com/filipnavara/mm-deadlock-repro/ --- .../Tests/InstallAndRunTests.cs | 5 - .../Tests/MarshalMethodsGCHangTests.cs | 115 ++++++++++++++++++ 2 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 tests/MSBuildDeviceIntegration/Tests/MarshalMethodsGCHangTests.cs diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 0ab31c828e3..c6516e8f7f1 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -30,11 +30,6 @@ public void Teardown () [Test] public void NativeAssemblyCacheWithSatelliteAssemblies ([Values (true, false)] bool enableMarshalMethods) { - // TODO: enable when marshal methods are fixed - if (enableMarshalMethods) { - Assert.Ignore ("Test is skipped when marshal methods are enabled, pending fixes to MM for .NET9"); - } - var path = Path.Combine ("temp", TestName); var lib = new XamarinAndroidLibraryProject { ProjectName = "Localization", diff --git a/tests/MSBuildDeviceIntegration/Tests/MarshalMethodsGCHangTests.cs b/tests/MSBuildDeviceIntegration/Tests/MarshalMethodsGCHangTests.cs new file mode 100644 index 00000000000..cd24f8028ac --- /dev/null +++ b/tests/MSBuildDeviceIntegration/Tests/MarshalMethodsGCHangTests.cs @@ -0,0 +1,115 @@ +using System; +using NUnit.Framework; +using Xamarin.ProjectTools; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using System.Text; +using System.Xml.Linq; +using System.Collections.Generic; + +namespace Xamarin.Android.Build.Tests; + +[TestFixture] +[Category ("UsesDevice")] +[Category ("MayHang")] +public class MarshalMethodsGCHangTests : DeviceTest +{ + static readonly string MarshalMethodsAppRuns_PermissionManifest = @" + + + + + +"; + + static readonly string MarshalMethodsAppRuns_MainActivity = @"using Android.Media; + +namespace marshal2; + +[Activity (Label = ""@string/app_name"", MainLauncher = true)] +public class MainActivity : Activity +{ + protected override void OnCreate (Bundle? savedInstanceState) + { + base.OnCreate (savedInstanceState); + SetContentView (Resource.Layout.Main); + } + + protected override void OnStart () + { + base.OnStart (); + + try { + var mp = new MediaPlayer (); + mp.SetDataSource (new StreamMediaDataSource (new MemoryStream (new byte[65536]))); + mp.Prepare (); + } catch (Java.IO.IOException) { + GC.Collect (); + } + } + + class StreamMediaDataSource (System.IO.Stream data) : MediaDataSource + { + public override long Size => data.Length; + + public override int ReadAt (long position, byte[]? buffer, int offset, int size) + { + try { + Console.WriteLine ($""XXX:START StreamMediaDataSource.ReadAt {position} {buffer} {buffer?.Length ?? 0} {offset} {size}""); + + // Allocate enough to trigger GC + for (int i = 0; i < 1000; i++) { + _ = new byte[8192]; + } + + if (data.CanSeek) { + data.Seek (position, SeekOrigin.Begin); + } + return data.Read (buffer ?? [], offset, size); + } finally { + Console.WriteLine ($""XXX:END //StreamMediaDataSource.ReadAt {position} {buffer} {buffer?.Length ?? 0} {offset} {size}""); + } + } + + public override void Close () + { + data.Dispose (); + data = System.IO.Stream.Null; + } + } +} +"; + + // All Tests here require the emulator to be started with -writable-system + [Test] + public void MarshalMethodsAppRuns () + { + var proj = new XamarinAndroidApplicationProject (packageName: "marshal2") { + IsRelease = true, + EnableMarshalMethods = true, + TargetFramework = "net9.0-android", + SupportedOSPlatformVersion = "23", + TrimModeRelease = TrimMode.Full, + ProjectName = "marshal2", + }; + + proj.SetAndroidSupportedAbis (DeviceAbi); + proj.AndroidManifest = String.Format (MarshalMethodsAppRuns_PermissionManifest, proj.PackageName); + proj.MainActivity = MarshalMethodsAppRuns_MainActivity; + proj.SetDefaultTargetDevice (); + + using var apkBuilder = CreateApkBuilder (Path.Combine ("temp", TestName)); + Assert.True (apkBuilder.Install (proj), "Project should have installed."); + RunProjectAndAssert (proj, apkBuilder); + + const string expectedLogcatOutput = "XXX:END //StreamMediaDataSource.ReadAt"; + Assert.IsTrue ( + MonitorAdbLogcat ( + InstallAndRunTests.CreateLineChecker (expectedLogcatOutput), + logcatFilePath: Path.Combine (Root, apkBuilder.ProjectDirectory, "startup-logcat.log"), timeout: 60 + ), + $"Output did not contain {expectedLogcatOutput}!" + ); + } +}