diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/PackageUtils.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/PackageUtils.cs new file mode 100644 index 00000000000..c9e3e85bb29 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/PackageUtils.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.CompilerServices; +using System.Text; +using Xamarin.Android.Tasks; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests; + +class PackageUtils +{ + /// + /// Constructs Android package name parameter for the which includes + /// the runtime used to build and run the application. A unique per-runtime package name is necessary so that elements + /// of different runtimes don't mix when running the same test for several of them. + /// + public static string MakePackageName (AndroidRuntime runtime, [CallerMemberName] string packageName = "") + { + if (String.IsNullOrEmpty (packageName)) { + throw new ArgumentException ("Must not be null or empty", nameof (packageName)); + } + + var sb = new StringBuilder (packageName); + sb.Append ('_'); + sb.Append (runtime.ToString ().ToLowerInvariant ()); + + return sb.ToString (); + } +} diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index b0a1594b404..717abe5925f 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -29,17 +29,46 @@ public void Teardown () proj = null; } + static IEnumerable Get_DotNetRun_Data () + { + var ret = new List (); + + foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { + AddTestData (true, "llvm-ir", runtime); + AddTestData (false, "llvm-ir", runtime); + AddTestData (true, "managed", runtime); + // NOTE: TypeMappingStep is not yet setup for Debug mode + //AddTestData (false, "managed", runtime); + } + + return ret; + + void AddTestData (bool isRelease, string typemapImplementation, AndroidRuntime runtime) + { + ret.Add (new object[] { + isRelease, + typemapImplementation, + runtime, + }); + } + } + [Test] - [TestCase (true, "llvm-ir")] - [TestCase (false, "llvm-ir")] - [TestCase (true, "managed")] - // NOTE: TypeMappingStep is not yet setup for Debug mode - //[TestCase (false, "managed")] - public void DotNetRun (bool isRelease, string typemapImplementation) + [TestCaseSource (nameof (Get_DotNetRun_Data))] + public void DotNetRun (bool isRelease, string typemapImplementation, AndroidRuntime runtime) { - var proj = new XamarinAndroidApplicationProject { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + if (runtime == AndroidRuntime.NativeAOT && typemapImplementation == "llvm-ir") { + Assert.Ignore ("NativeAOT doesn't work with LLVM-IR typemaps"); + } + + var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease }; + proj.SetRuntime (runtime); proj.SetProperty ("_AndroidTypeMapImplementation", typemapImplementation); using var builder = CreateApkBuilder (); builder.Save (proj); @@ -54,13 +83,16 @@ public void DotNetRun (bool isRelease, string typemapImplementation) } [Test] - [TestCase (true)] - [TestCase (false)] - public void DeployToDevice (bool isRelease) + public void DeployToDevice ([Values] bool isRelease, [Values] AndroidRuntime runtime) { - var proj = new XamarinAndroidApplicationProject { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease }; + proj.SetRuntime (runtime); using var builder = CreateApkBuilder (); builder.Save (proj); @@ -90,11 +122,16 @@ public void DeployToDevice (bool isRelease) } [Test] - public void ActivityAliasRuns ([Values (true, false)] bool isRelease) + public void ActivityAliasRuns ([Values] bool isRelease, [Values] AndroidRuntime runtime) { - var proj = new XamarinAndroidApplicationProject { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease }; + proj.SetRuntime (runtime); proj.AndroidManifest = proj.AndroidManifest.Replace ("", @" Get_SmokeTestBuildAndRunWithSpecialCharacters_Data () + { + var ret = new List (); + + foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { + AddTestData ("テスト", runtime); + AddTestData ("随机生成器", runtime); + AddTestData ("中国", runtime); + } + + return ret; + + void AddTestData (string testName, AndroidRuntime runtime) + { + ret.Add (new object[] { + testName, + runtime, + }); + } + } + [Test] [Category ("UsesDevice")] - [TestCase ("テスト")] - [TestCase ("随机生成器")] - [TestCase ("中国")] - public void SmokeTestBuildAndRunWithSpecialCharacters (string testName) + [TestCaseSource (nameof (Get_SmokeTestBuildAndRunWithSpecialCharacters_Data))] + public void SmokeTestBuildAndRunWithSpecialCharacters (string testName, AndroidRuntime runtime) { + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + // TODO: fix NativeAOT builds. Despite the .so library being preset in the apk (and named correctly) + // all the tests fail with one of: + // + // java.lang.UnsatisfiedLinkError: dlopen failed: library "libテスト.so" not found + // java.lang.UnsatisfiedLinkError: dlopen failed: library "lib中国.so" not found + // java.lang.UnsatisfiedLinkError: dlopen failed: library "lib随机生成器.so" not found + // + // It might be an issue with the Android shared libary loader or name encoding in the archive. It might + // be a good idea to limit .so names to ASCII. + if (runtime == AndroidRuntime.NativeAOT) { + Assert.Ignore ("NativeAOT doesn't work well with diacritics in the application library name"); + } + var rootPath = Path.Combine (Root, "temp", TestName); - var proj = new XamarinFormsAndroidApplicationProject () { + var proj = new XamarinFormsAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { ProjectName = testName, - IsRelease = true, + IsRelease = isRelease, }; + proj.SetRuntime (runtime); proj.SetAndroidSupportedAbis (DeviceAbi); proj.SetDefaultTargetDevice (); using (var builder = CreateApkBuilder (Path.Combine (rootPath, proj.ProjectName))){ @@ -326,10 +418,28 @@ public void SmokeTestBuildAndRunWithSpecialCharacters (string testName) [Test] public void CustomLinkDescriptionPreserve ( [Values (AndroidLinkMode.SdkOnly, AndroidLinkMode.Full)] AndroidLinkMode linkMode, - [Values (AndroidRuntime.MonoVM)] AndroidRuntime runtime + [Values] AndroidRuntime runtime ) { + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + if (runtime == AndroidRuntime.CoreCLR) { + Assert.Ignore ("Currently broken on CoreCLR"); + } + + // TODO: NativeAOT perhaps should work here (ignoring all the MonoAOT settings?), but for now it fails with + // + // Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(120,5): error NETSDK1207: Ahead-of-time compilation is not supported for the target framework. + // + if (runtime == AndroidRuntime.NativeAOT) { + Assert.Ignore ("NativeAOT is currently broken here"); + } + var lib1 = new XamarinAndroidLibraryProject () { + IsRelease = isRelease, ProjectName = "Library1", Sources = { new BuildItem.Source ("SomeClass.cs") { @@ -367,6 +477,7 @@ public class LinkModeFullClass { lib1.SetRuntime (runtime); var lib2 = new DotNetStandard { + IsRelease = isRelease, ProjectName = "LinkTestLib", Sdk = "Microsoft.NET.Sdk", TargetFramework = "netstandard2.0", @@ -393,8 +504,8 @@ public class LinkModeFullClass { }; lib2.SetRuntime (runtime); - proj = new XamarinFormsAndroidApplicationProject () { - IsRelease = true, + proj = new XamarinFormsAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { + IsRelease = isRelease, AndroidLinkModeRelease = linkMode, References = { new BuildItem ("ProjectReference", "..\\Library1\\Library1.csproj"), @@ -473,11 +584,15 @@ string getResource (string name) } [Test] - public void JsonDeserializationCreatesJavaHandle ([Values (false, true)] bool isRelease) + public void JsonDeserializationCreatesJavaHandle ([Values] bool isRelease, [Values] AndroidRuntime runtime) { - proj = new XamarinAndroidApplicationProject () { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease, }; + proj.SetRuntime (runtime); // error SYSLIB0011: 'BinaryFormatter.Serialize(Stream, object)' is obsolete: 'BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.' proj.SetProperty ("NoWarn", "SYSLIB0011"); @@ -503,7 +618,7 @@ void TestJsonDeserialization (Person p) StreamReader sr = new StreamReader (stream); Console.WriteLine ($""JSON Person representation: {sr.ReadToEnd ()}""); - +/ stream.Position = 0; Person p2 = (Person) serializer.ReadObject (stream); @@ -565,14 +680,22 @@ public override Type BindToType (string assemblyName, string typeName) } [Test] - public void RunWithInterpreterEnabled ([Values (false, true)] bool isRelease) + public void RunWithInterpreterEnabled ([Values] bool isRelease, [Values] AndroidRuntime runtime) { - proj = new XamarinAndroidApplicationProject () { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + // MonoVM-only test, for now (until CoreCLR has interpreter we can use) + if (runtime != AndroidRuntime.MonoVM) { + Assert.Ignore ("MonoVM-only test for the moment"); + } + + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease, AotAssemblies = false, // Release defaults to Profiled AOT for .NET 6 }; - // MonoVM-only test - proj.SetRuntime (Android.Tasks.AndroidRuntime.MonoVM); + proj.SetRuntime (runtime); var abis = new string[] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }; proj.SetAndroidSupportedAbis (abis); proj.SetProperty (proj.CommonProperties, "UseInterpreter", "True"); @@ -644,11 +767,19 @@ bool SeenFailedToLoad (string line) } [Test] - public void SingleProject_ApplicationId ([Values (false, true)] bool testOnly) + public void SingleProject_ApplicationId ([Values] bool testOnly, [Values] AndroidRuntime runtime) { + bool isRelease = runtime == AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + AssertCommercialBuild (); - proj = new XamarinAndroidApplicationProject (); + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { + IsRelease = isRelease, + }; + proj.SetRuntime (runtime); proj.SetProperty ("ApplicationId", "com.i.should.get.overridden.by.the.manifest"); if (testOnly) proj.AndroidManifest = proj.AndroidManifest.Replace (" System.Reflection.TargetInvocationException: Arg_TargetInvocationException + // AndroidRuntime: ---> System.IO.FileNotFoundException: IO_FileNotFound_FileName, _Microsoft.Android.Resource.Designer + // AndroidRuntime: IO_FileName_Name, _Microsoft.Android.Resource.Designer + // DOTNET : FATAL UNHANDLED EXCEPTION: Java.Lang.Exception: Unable to start activity ComponentInfo{com.xamarin.appwithstyleableusageruns_nativeaot/com.xamarin.appwithstyleableusageruns_nativeaot.MainActivity} + // DOTNET : ---> Java.Lang.Exception: Binary XML file line #1 in com.xamarin.appwithstyleableusageruns_nativeaot:layout/main: Binary XML file line #1 in com.xamarin.appwithstyleableusageruns_nativeaot:layout/main: Error inflating class crc64f75eeacfa0ca1368.MyLayout + // DOTNET : ---> Java.Lang.Exception: Binary XML file line #1 in com.xamarin.appwithstyleableusageruns_nativeaot:layout/main: Error inflating class crc64f75eeacfa0ca1368.MyLayout + // DOTNET : ---> Java.Lang.ReflectiveOperationException: Exception_WasThrown, Java.Lang.ReflectiveOperationException + // DOTNET : ---> System.NotSupportedException: Could not activate { PeerReference=0x7fe9706698/I IdentityHashCode=0xd12aeee Java.Type=crc64f75eeacfa0ca1368/MyLayout } for managed type 'UnnamedProject.MyLayout'. + // DOTNET : ---> System.Reflection.TargetInvocationException: Arg_TargetInvocationException + // DOTNET : ---> System.IO + // eruns_nativeaot: No implementation found for void mono.android.Runtime.propagateUncaughtException(java.lang.Thread, java.lang.Throwable) (tried Java_mono_android_Runtime_propagateUncaughtException and Java_mono_android_Runtime_propagateUncaughtException__Ljava_lang_Thread_2Ljava_lang_Throwable_2) - is the library loaded, e.g. System.loadLibrary? + Assert.Ignore ("NativeAOT is broken without string-based typemaps"); + } + var rootPath = Path.Combine (Root, "temp", TestName); var lib = new XamarinAndroidLibraryProject () { + IsRelease = isRelease, ProjectName = "Styleable.Library" }; + lib.SetRuntime (runtime); lib.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\values\\styleables.xml") { TextContent = () => @" @@ -721,10 +880,10 @@ public MyLibraryLayout (Android.Content.Context context, Android.Util.IAttribute }" }); - proj = new XamarinAndroidApplicationProject () { + proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) { IsRelease = isRelease, }; - proj.SetProperty ("UseMonoRuntime", useCLR ? "false" : "true"); + proj.SetRuntime (runtime); proj.AddReference (lib); proj.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\values\\styleables.xml") { @@ -762,9 +921,11 @@ public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet att } "); - string[] abis = useCLR switch { - true => new string [] { "arm64-v8a", "x86_64" }, - false => new string [] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }, + string[] abis = runtime switch { + AndroidRuntime.CoreCLR => new string [] { "arm64-v8a", "x86_64" }, + AndroidRuntime.NativeAOT => new string [] { "arm64-v8a", "x86_64" }, + AndroidRuntime.MonoVM => new string [] { "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }, + _ => throw new NotSupportedException ($"Unsupported runtime {runtime}") }; proj.SetAndroidSupportedAbis (abis); @@ -775,7 +936,7 @@ public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet att Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); Dictionary? environmentVariables = null; - if (useCLR && !isRelease && useStringTypeMaps) { + if (runtime == AndroidRuntime.CoreCLR && !isRelease && useStringTypeMaps) { // The variable must have content to enable string-based typemaps environmentVariables = new (StringComparer.Ordinal) { {"CI_TYPEMAP_DEBUG_USE_STRINGS", "yes"} @@ -790,16 +951,33 @@ public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet att } [Test] - public void CheckXamarinFormsAppDeploysAndAButtonWorks () + public void CheckXamarinFormsAppDeploysAndAButtonWorks ([Values] AndroidRuntime runtime) { - var proj = new XamarinFormsAndroidApplicationProject (); + bool isRelease = runtime == AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + // TODO: fix for NativeAOT. Currently fails with: + // + // DOTNET : FATAL UNHANDLED EXCEPTION: System.InvalidCastException: Unable to convert instance of type 'AndroidX.AppCompat.Widget.AppCompatImageButton' to type 'AndroidX.AppCompat.Widget.Toolbar'. + if (runtime == AndroidRuntime.NativeAOT) { + Assert.Ignore ("NativeAOT type mapping fails"); + } + + string packageName = PackageUtils.MakePackageName (runtime); + var proj = new XamarinFormsAndroidApplicationProject (packageName: packageName) { + IsRelease = isRelease, + }; + proj.SetRuntime (runtime); proj.SetAndroidSupportedAbis (DeviceAbi); - var builder = CreateApkBuilder (); + var builder = CreateApkBuilder (packageName: packageName); Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); builder.BuildLogFile = "install.log"; Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); + ClearAdbLogcat (); AdbStartActivity ($"{proj.PackageName}/{proj.JavaPackageName}.MainActivity"); WaitForActivityToStart (proj.PackageName, "MainActivity", Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log"), 15); @@ -812,11 +990,14 @@ public void CheckXamarinFormsAppDeploysAndAButtonWorks () } [Test] - public void SkiaSharpCanvasBasedAppRuns ([Values (true, false)] bool isRelease, [Values (true, false)] bool addResource) + public void SkiaSharpCanvasBasedAppRuns ([Values] bool isRelease, [Values] bool addResource, [Values] AndroidRuntime runtime) { - var app = new XamarinAndroidApplicationProject () { + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + var app = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime, "SkiaSharpCanvasTest")) { IsRelease = isRelease, - PackageName = "Xamarin.SkiaSharpCanvasTest", PackageReferences = { KnownPackages.SkiaSharp, KnownPackages.SkiaSharp_Views, @@ -824,6 +1005,7 @@ public void SkiaSharpCanvasBasedAppRuns ([Values (true, false)] bool isRelease, KnownPackages.AndroidXAppCompatResources, }, }; + app.SetRuntime (runtime); app.AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\values\\styles.xml") { TextContent = () => @"