xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.42k stars 504 forks source link

Initializing the ObjCRuntime within NUnit's Console Runner fails due to a null EntryAssembly file path #7524

Open Difegue opened 4 years ago

Difegue commented 4 years ago

Steps to Reproduce

  1. Build a NUnit test project referencing Xamarin.Mac.dll (and libxammac.dylib) in order to run tests consuming Cocoa objects
  2. Initialize ObjCRuntime through NSApplication.Init() in the test's AssemblySetupFixture
  3. Run said test in both Visual Studio for Mac and NUnit's Console Runner through Mono
  4. Tests run properly in Visual Studio, but not in NUnit.

Expected Behavior

The Runtime initializes properly, and the test runs in NUnit.

Actual Behavior

NUnit passes a null EntryAssembly when running the test .dll, leading Xamarin.Mac to try guessing one from the dll's path when the runtime gets initialized.

The guess is done in the native part here, and stored in the options' xamarin_entry_assembly_path field.

Said path is then normally unmarshaled here, but that seems to fail and return null, leading to a crash during initialization:

1) SetUp Error : AssemblySetupFixture
System.ArgumentNullException : Value cannot be null.
Parameter name: path
  at System.Reflection.Assembly.LoadFile (System.String path, System.Security.Policy.Evidence securityEvidence) [0x00003] in <f4b7e76e6f194c6e967f1482e5a25ebb>:0 
  at System.Reflection.Assembly.LoadFile (System.String path) [0x00000] in <f4b7e76e6f194c6e967f1482e5a25ebb>:0 
  at ObjCRuntime.Runtime.GetEntryAssembly () [0x00021] in <dd87740fbb6340a2b184810a7b4411e5>:0 
  at ObjCRuntime.Runtime.RegisterAssemblies () [0x00001] in <dd87740fbb6340a2b184810a7b4411e5>:0 
  at AppKit.NSApplication.Init () [0x00022] in <dd87740fbb6340a2b184810a7b4411e5>:0 
  at AssemblySetupFixture.OneTimeSetup () [0x00001] in <1a7ee69ba04b4ee488e6c676bf9f4bfd>:0 
  at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&)
  at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <f4b7e76e6f194c6e967f1482e5a25ebb>:0 

I feel a simple nullcheck when unmarshalling the entry_assembly_path would fix the issue, but it seems weird that the native part would fail like that.

Environment

=== Visual Studio Community 2019 for Mac ===

Version 8.1 (build 2742)
Installation UUID: ad12cd9c-7830-4352-b3c7-44ad204b97f5
    GTK+ 2.24.23 (Raleigh theme)
    Xamarin.Mac 5.6.0.25 (d16-0 / 50f75273)

    Package version: 606000117

=== Mono Framework MDK ===

Runtime:
    Mono 6.6.0.117 (2019-08/617f399efca) (64-bit)
    Package version: 606000117

=== NuGet ===

Version : 5.0.2.5988

=== .NET Core ===

Runtime : /usr/local/share/dotnet/dotnet
Versions du runtime :
    3.0.0
    2.1.13
    2.1.12
SDK : /usr/local/share/dotnet/sdk/3.0.100/Sdks
Versions du SDK :
    3.0.100
    2.1.701
SDK MSBuild : /Library/Frameworks/Mono.framework/Versions/6.6.0/lib/mono/msbuild/Current/bin/Sdks

=== Xamarin.Profiler ===

Version : 1.6.12
Emplacement : /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler

=== Updater ===

Version : 11

=== Apple Developer Tools ===

Xcode 11.2.1 (15526.1)
Build 11B500

=== Xamarin.Mac ===

Version: 6.6.0.12 (Visual Studio Community)
Hash: e3c2b406d
Branch: xcode11.2
Build date: 2019-11-01 00:12:07-0400

=== Xamarin.Android ===

Non installé

=== Microsoft Mobile OpenJDK ===

Java SDK: Introuvable

Code EPL d'Android Designer disponible ici :
https://github.com/xamarin/AndroidDesigner.EPL

=== Android Device Manager ===

Version: 1.2.0.44
Hash: aac645b
Branch: remotes/origin/d16-1
Build date: 2019-11-29 09:52:30 UTC

=== Xamarin Inspector ===

Version: 1.4.3
Hash: db27525
Branch: 1.4-release
Build date: Mon, 09 Jul 2018 21:20:18 GMT
Client compatibility: 1

=== Xamarin Designer ===

Version: 16.1.0.464
Hash: 66bb7b43f
Branch: remotes/origin/d16-1-new-document-model
Build date: 2019-11-29 09:51:49 UTC

=== Xamarin.iOS ===

Xamarin.iOS not installed.
Can't find mtouch or the Version file at /Library/Frameworks/Xamarin.iOS.framework/Versions/Current.

=== Build Information ===

Release ID: 801002742
Git revision: ed27233de1b33083ea2234bc66a3b7824b99bbc7
Build date: 2019-06-12 10:18:51+00
Build branch: release-8.1
Xamarin extensions: 98dc4d7de2257c9b7342dee023910e16d02f8e65

=== Operating System ===

Mac OS X 10.14.6
Darwin 18.7.0 Darwin Kernel Version 18.7.0
    Tue Aug 20 16:57:14 PDT 2019
    root:xnu-4903.271.2~2/RELEASE_X86_64 x86_64

Build Logs

Example Project (If Possible)

XamarinNUnitTestProject.zip

Build the solution, then run the XamarinTestRunner project.
The test project is XamarinMacTestProject and can be ran as-is in VS for Mac.

The runner project is in .NET Core and basically just runs the NUnit console runner(provided through the matching nuGet package) with mono.

(I've seen a few forum posts encouraging the use of GuiUnit and the like, but it very much seems the base NUnit console runner would work here if that hurdle with the entryAssembly were to be fixed, since those tests don't need a message loop implementation.)

chamons commented 4 years ago

So I'm not sure things are as straight forward as you think unfortunately.

The initialization code in question starts here where we setup some things from our native launcher.

Visual Studio for Mac uses Xamarin.Mac, so it's already setup all of the setup, such as libxammac.dylib.

However, getting past initialization is only the first hurdle. Xamarin iOS and Mac depend runtime support (called toggle ref) to correctly handle memory with the native runtime engine. Mono provides us those APIs, but .NET core does not (yet) to my knowledge.

A member of our community, @filipnavara, did some work at some point looking at the integration and might be able to provide some tails.

As this is at least tangentially related to .NET 5, I'd like to also suggest you take a look at the meta tracking issue.

Please, let me know if that makes sense.

filipnavara commented 4 years ago

@chamons is right on point. There's currently no way to run Xamarin.Mac applications under .NET Core, or the CoreCLR runtime specifically. Attempts were made to do that but all of them were abandoned for one reason or another.

There is, however, a special "netcore" flavor of Mono runtime. This is currently hosted in the Mono repository (look for the netcore directory for sample and build scripts) and slated to be moved into dotnet/runtime alongside CoreCLR. This special runtime offers all the classic Mono runtime APIs necessary for loading libxammac.dylib. It also offers the CoreCLR API entrypoints and can act as drop-in replacement for libcoreclr.dylib and System.Private.CoreLib.dll. You can look for the patch-app (or link-mono) target in netcore/Makefile.am to see how the replacement is done.

This special runtime is designed to work with Xamarin.Mac eventually but at this point it does not work out of the box yet. There are at least two or three dependencies on the managed side of Xamarin.Mac that rely on private APIs not available on .NET Core (TLS provider initialization, injecting NSAutoreleasePool in thread pool threads, etc.).

Difegue commented 4 years ago

Oh, I'm afraid the example project is a bit misleading, my apologies. It uses .NET Core, but that's only to grab the nunit exe from nuget and start processes.
(I wasn't aware of the work on the CoreCLR/Mono hybrid though, very interesting!)

The call to the test runner itself uses mono, so I think it should be fine? Let me know if I'm mistaken :

/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono ~/.nuget/packages/nunit.consolerunner/3.10.0/tools/nunit3-console.exe XamarinMacTestProject/bin/Debug/XamarinMacTestProject.dll --config=Debug --result=TestResults.xml;format=nunit2
chamons commented 4 years ago

I looked into it, and I believe there is a mismatch between how guiunit-ng and nunit is setting things up.

Guiunit-ng: Before loading any test assemblies [setup Xamarin.Mac])(https://github.com/garuma/GuiUnitNg/blob/master/GuiUnitNg/GuiUnit/MonoMacMainLoopIntegration.cs#L34). This appears to make the check pass, as we've setup state.

nuint: I added the same setup before calling NSApplication.Init and it wasn't being hit.

I do not understand why however with my initial research.

Playing nicer with nunit proper and not requiring guiunit-ng is a reasonable enhancement request.

Difegue commented 4 years ago

Thanks for looking into it!

I've switched the runner to GuiUnitNg for our Xamarin.Mac tests in the meantime and it seems to work fine.