Skip to content

Commit

Permalink
[Java.Interop-Tests] Initial dotnet test support
Browse files Browse the repository at this point in the history
Context: dotnet/android#5592

With introductory support for `Java.Interop.dll` on .NET Core via
commit 2a299eb, what about running the unit tests within
`Java.Interop-Tests.dll` on .NET Core?

Update `src/java-interop` so that the `java-interop` native library
is copied to `$(OutputPath)` of referencing projects.  This allows
e.g. `libjava-interop.dylib` to be implicitly copied into the
appropriate output dir, e.g. `bin/TestDebug-netcoreapp3.1`.
Further update `src/java-interop` so that `$(IntermediateOutputPath)`
correctly differs between net472 and netcoreapp3.1 builds, ensuring
that e.g. `libjava-interop.dylib` reliably uses Mono on net472, and
generate `jni.c` into `$(IntermediateOutputPath)` to avoid
unnecessary rebuilds.

Update `tests/Java.Interop-Tests` and `tests/TestJVM/TestJVM.csproj`
to multitarget between net472 and netcoreapp3.1.

Update `Java.Runtime.Environment.csproj` to reference
`java-interop.csproj`, which causes all projects which reference
`Java.Runtime.Environment.csproj` to get the `java-interop` native
library copied to the appropriate `$(OutputPath)`.

Update `JreRuntime` to use "dummy" `JniRuntime.JniValueManager` and
`JniRuntime.JniObjectReferenceManager` subclasses when *not* running
under Mono.  This avoids an `ArgumentException` when creating the
`Java.InteropTests.JavaVMFixture` instance used by the unit tests.

Update `tests/TestJVM` to reference
`Xamarin.Android.Tools.AndroidSdk.csproj`, allowing the `TestJVM` type
to use `JdkInfo` to find a JVM to use.  This allows us to avoid a
requirement to set the `JI_JVM_PATH` environment variable in order to
create `TestJVM` instances.

Update `src/Java.Interop` so that generic delegate types are not
marshaled via P/Invoke.  .NET Core doesn't support this.

Update `**/*.java` to remove Version, Culture, and PublicKeyToken
information from the assembly qualified name, for two reasons:

 1. The default Version value appears to differ between
    net472 (0.0.0.0) and netcoreapp3.1 (1.0.0.0).

 2. Wrt dotnet/android#5592, certain "security products"
    believe that the Version number is an *IP address*, and emit a
    warning about "IP Address disclosure".

Since we don't strong-name any of these assemblies (unit tests!)
*or* the expected execution environment is a "self-contained app"
in which you can't have multiple versions of the same assembly
(Xamarin.Android), remove the Version, Culture, and PublicKeyToken
information from these Java Callable Wrappers.
  • Loading branch information
jonpryor committed Feb 17, 2021
1 parent a4a2c13 commit 827c627
Show file tree
Hide file tree
Showing 24 changed files with 133 additions and 92 deletions.
8 changes: 0 additions & 8 deletions build-tools/scripts/RunNUnitTests.targets
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@
</ItemGroup>
<Target Name="RunTests"
Outputs="$(_TopDir)\TestResult-%(_TestAssembly.Filename).xml">
<ItemGroup>
<_JavaInteropNativeLibrary Include="$(_TopDir)\bin\$(Configuration)\libjava-interop.*" />
</ItemGroup>
<Copy
SourceFiles="@(_JavaInteropNativeLibrary)"
DestinationFolder="$(_TopDir)\bin\Test$(Configuration)"
SkipUnchangedFiles="True"
/>
<SetEnvironmentVariable Name="ANDROID_SDK_PATH" Value="$(AndroidSdkDirectory)" Condition=" '$(AndroidSdkDirectory)' != '' " />
<SetEnvironmentVariable Name="MONO_TRACE_LISTENER" Value="Console.Out" />
<SetEnvironmentVariable Name="JAVA_INTEROP_GREF_LOG" Value="bin\Test$(Configuration)\g-%(_TestAssembly.Filename).txt" />
Expand Down
9 changes: 6 additions & 3 deletions src/Java.Interop/Java.Interop/JavaProxyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ sealed class JavaProxyObject : JavaObject, IEquatable<JavaProxyObject>
[JniAddNativeMethodRegistrationAttribute]
static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
{
args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (Func<IntPtr, IntPtr, IntPtr, bool>)Equals));
args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", (Func<IntPtr, IntPtr, int>)GetHashCode));
args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (Func<IntPtr, IntPtr, IntPtr>)ToString));
args.Registrations.Add (new JniNativeMethodRegistration ("equals", "(Ljava/lang/Object;)Z", (EqualsMarshalMethod)Equals));
args.Registrations.Add (new JniNativeMethodRegistration ("hashCode", "()I", (GetHashCodeMarshalMethod)GetHashCode));
args.Registrations.Add (new JniNativeMethodRegistration ("toString", "()Ljava/lang/String;", (ToStringMarshalMethod)ToString));
}

public override JniPeerMembers JniPeerMembers {
Expand Down Expand Up @@ -72,6 +72,7 @@ public override bool Equals (object? obj)
}

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
delegate bool EqualsMarshalMethod (IntPtr jnienv, IntPtr n_self, IntPtr n_value);
static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
{
var envp = new JniTransition (jnienv);
Expand All @@ -91,6 +92,7 @@ static bool Equals (IntPtr jnienv, IntPtr n_self, IntPtr n_value)
}

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
delegate int GetHashCodeMarshalMethod (IntPtr jnienv, IntPtr n_self);
static int GetHashCode (IntPtr jnienv, IntPtr n_self)
{
var envp = new JniTransition (jnienv);
Expand All @@ -107,6 +109,7 @@ static int GetHashCode (IntPtr jnienv, IntPtr n_self)
}
}

delegate IntPtr ToStringMarshalMethod (IntPtr jnienv, IntPtr n_self);
static IntPtr ToString (IntPtr jnienv, IntPtr n_self)
{
var envp = new JniTransition (jnienv);
Expand Down
27 changes: 13 additions & 14 deletions src/Java.Interop/Java.Interop/ManagedPeer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@ namespace Java.Interop {
[JniTypeSignature (JniTypeName)]
/* static */ sealed class ManagedPeer : JavaObject {

delegate void ConstructDelegate (IntPtr jnienv,
IntPtr klass,
IntPtr n_self,
IntPtr n_assemblyQualifiedName,
IntPtr n_constructorSignature,
IntPtr n_constructorArguments);
delegate void RegisterDelegate (IntPtr jnienv,
IntPtr klass,
IntPtr n_nativeClass,
IntPtr n_assemblyQualifiedName,
IntPtr n_methods);

internal const string JniTypeName = "com/xamarin/java_interop/ManagedPeer";


Expand All @@ -37,11 +25,11 @@ static ManagedPeer ()
new JniNativeMethodRegistration (
"construct",
ConstructSignature,
(ConstructDelegate) Construct),
(ConstructMarshalMethod) Construct),
new JniNativeMethodRegistration (
"registerNativeMembers",
RegisterNativeMembersSignature,
(RegisterDelegate) RegisterNativeMembers)
(RegisterMarshalMethod) RegisterNativeMembers)
);
}

Expand All @@ -62,6 +50,12 @@ public override JniPeerMembers JniPeerMembers {
const string ConstructSignature = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V";

// TODO: Keep in sync with the code generated by ExportedMemberBuilder
delegate void ConstructMarshalMethod (IntPtr jnienv,
IntPtr klass,
IntPtr n_self,
IntPtr n_assemblyQualifiedName,
IntPtr n_constructorSignature,
IntPtr n_constructorArguments);
static void Construct (
IntPtr jnienv,
IntPtr klass,
Expand Down Expand Up @@ -183,6 +177,11 @@ static Type[] GetParameterTypes (string? signature)

const string RegisterNativeMembersSignature = "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)V";

delegate void RegisterMarshalMethod (IntPtr jnienv,
IntPtr klass,
IntPtr n_nativeClass,
IntPtr n_assemblyQualifiedName,
IntPtr n_methods);
static void RegisterNativeMembers (
IntPtr jnienv,
IntPtr klass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
extends java.lang.Object
implements GCUserPeerable
{
static final String assemblyQualifiedName = "Java.Interop.JavaProxyObject, Java.Interop, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null";
static final String assemblyQualifiedName = "Java.Interop.JavaProxyObject, Java.Interop";
static {
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
JavaProxyObject.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
extends java.lang.Error
implements GCUserPeerable
{
static final String assemblyQualifiedName = "Java.Interop.JavaProxyThrowable, Java.Interop, Version=0.1.0.0, Culture=neutral, PublicKeyToken=null";
static final String assemblyQualifiedName = "Java.Interop.JavaProxyThrowable, Java.Interop";
static {
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
JavaProxyThrowable.class,
Expand Down
52 changes: 52 additions & 0 deletions src/Java.Runtime.Environment/Java.Interop/JreRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;

Expand Down Expand Up @@ -44,6 +45,10 @@ public JreRuntimeOptions ()
ValueManager = ValueManager ?? new MonoRuntimeValueManager ();
ObjectReferenceManager = ObjectReferenceManager ?? new MonoRuntimeObjectReferenceManager ();
}
else {
ValueManager = ValueManager ?? new DummyValueManager ();
ObjectReferenceManager = ObjectReferenceManager ?? new DummyObjectReferenceManager ();
}
}

public JreRuntimeOptions AddOption (string option)
Expand Down Expand Up @@ -161,5 +166,52 @@ partial class NativeMethods {
[DllImport (JavaInteropLib, CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
internal static extern int java_interop_jvm_create (out IntPtr javavm, out IntPtr jnienv, ref JavaVMInitArgs args);
}

class DummyValueManager : JniRuntime.JniValueManager {

public override void WaitForGCBridgeProcessing ()
{
}

public override void CollectPeers ()
{
}

public override void AddPeer (IJavaPeerable reference)
{
}

public override void RemovePeer (IJavaPeerable reference)
{
}

public override void FinalizePeer (IJavaPeerable reference)
{
}

public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
{
return null;
}

public override IJavaPeerable PeekPeer (global::Java.Interop.JniObjectReference reference)
{
return null;
}

public override void ActivatePeer (IJavaPeerable self, JniObjectReference reference, ConstructorInfo cinfo, object [] argumentValues)
{
}
}

class DummyObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
public override int GlobalReferenceCount {
get {return 0;}
}

public override int WeakGlobalReferenceCount {
get {return 0;}
}
}
}

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

<ItemGroup>
<ProjectReference Include="..\Java.Interop\Java.Interop.csproj" />
<ProjectReference Include="..\..\src\java-interop\java-interop.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/java-interop/java-interop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
</ItemDefinitionGroup>

<ItemGroup>
<ClCompile Include="jni.c" />
<ClCompile Include="$(IntermediateOutputPath)jni.c" />
<ClCompile Include="java-interop.cc" />
<ClCompile Include="java-interop-dlfcn.cc" />
<ClCompile Include="java-interop-jvm.cc" />
Expand Down
21 changes: 16 additions & 5 deletions src/java-interop/java-interop.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@
<Project>
<Target Name="BuildJni_c"
Inputs="$(_JNIEnvGenPath)"
Outputs="jni.c">
Outputs="$(IntermediateOutputPath)jni.c">
<MakeDir Directories="$(OutputPath)" />
<Exec Command="$(_RunJNIEnvGen) jni.g.cs jni.c" />
<Exec Command="$(_RunJNIEnvGen) $(IntermediateOutputPath)jni.g.cs $(IntermediateOutputPath)jni.c" />
</Target>

<PropertyGroup>
<_MacLib>$(OutputPath)/lib$(OutputName).dylib</_MacLib>
<_UnixLib>$(OutputPath)/lib$(OutputName).so</_UnixLib>
</PropertyGroup>

<ItemGroup Condition=" '$(OS)' != 'Windows_NT' And Exists ('/Library/Frameworks/') ">
<None Include="$(_MacLib)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup Condition=" '$(OS)' != 'Windows_NT' And !Exists ('/Library/Frameworks/') ">
<None Include="$(_UnixLib)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>$([MSBuild]::Unescape($(DefineSymbols.Replace(' ', ';'))))</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$([MSBuild]::Unescape($(_MonoIncludePath)));$([MSBuild]::Unescape($(_JdkIncludePath)))</AdditionalIncludeDirectories>
<Obj Condition=" '$(OS)' != 'Windows_NT' ">obj/$(Configuration)/%(Filename).o</Obj>
<Obj Condition=" '$(OS)' != 'Windows_NT' ">$(IntermediateOutputPath)/%(Filename).o</Obj>
</ClCompile>
</ItemDefinitionGroup>

Expand All @@ -25,7 +37,7 @@
DependsOnTargets="BuildJni_c"
Inputs="@(ClCompile);@(ClInclude)"
Outputs="%(ClCompile.Obj)">
<MakeDir Directories="obj\$(Configuration)" />
<MakeDir Directories="$(IntermediateOutputPath)" />
<ItemGroup>
<_Cl Include="@(ClCompile)">
<Compiler Condition=" '%(Extension)' == '.c' ">gcc -std=c99 -fPIC</Compiler>
Expand Down Expand Up @@ -86,7 +98,6 @@

<Target Name="Clean">
<RemoveDir Directories="obj" />
<Delete Files="jni.c" />
<Delete
Files="$(_MacLib);$(_UnixLib)"
Condition=" '$(OS)' != 'Windows_NT' "
Expand Down
2 changes: 1 addition & 1 deletion tests/Java.Interop-Tests/Java.Interop-Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<TargetFrameworks>net472;netcoreapp3.1</TargetFrameworks>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class CallVirtualFromConstructorDerived : CallVirtualFromConstructorBase
[JniAddNativeMethodRegistrationAttribute]
static void RegisterNativeMembers (JniNativeMethodRegistrationArguments args)
{
args.Registrations.Add (new JniNativeMethodRegistration ("calledFromConstructor", "(I)V", (Action<IntPtr, IntPtr, int>)CalledFromConstructorHandler));
args.Registrations.Add (new JniNativeMethodRegistration ("calledFromConstructor", "(I)V", (CalledFromConstructorMarshalMethod)CalledFromConstructorHandler));
}

public override JniPeerMembers JniPeerMembers {
Expand Down Expand Up @@ -57,6 +57,7 @@ public static unsafe CallVirtualFromConstructorDerived NewInstance (int value)
return JniEnvironment.Runtime.ValueManager.GetValue<CallVirtualFromConstructorDerived> (ref o, JniObjectReferenceOptions.CopyAndDispose);
}

delegate void CalledFromConstructorMarshalMethod (IntPtr jnienv, IntPtr n_self, int value);
static void CalledFromConstructorHandler (IntPtr jnienv, IntPtr n_self, int value)
{
var envp = new JniTransition (jnienv);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

public class CallNonvirtualBase implements GCUserPeerable {

static final String assemblyQualifiedName = "Java.InteropTests.CallNonvirtualBase, Java.Interop-Tests";

ArrayList<Object> managedReferences = new ArrayList<Object>();

public CallNonvirtualBase () {
if (CallNonvirtualBase.class == getClass ()) {
com.xamarin.java_interop.ManagedPeer.construct (
this,
"Java.InteropTests.CallNonvirtualBase, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
assemblyQualifiedName,
""
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ public class CallNonvirtualDerived
extends CallNonvirtualBase
implements GCUserPeerable
{
static final String assemblyQualifiedName = "Java.InteropTests.CallNonvirtualDerived, Java.Interop-Tests";

ArrayList<Object> managedReferences = new ArrayList<Object>();

public CallNonvirtualDerived () {
if (CallNonvirtualDerived.class == getClass ()) {
com.xamarin.java_interop.ManagedPeer.construct (
this,
"Java.InteropTests.CallNonvirtualDerived, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
assemblyQualifiedName,
""
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ public class CallNonvirtualDerived2
extends CallNonvirtualDerived
implements GCUserPeerable
{
static final String assemblyQualifiedName = "Java.InteropTests.CallNonvirtualDerived2, Java.Interop-Tests";

ArrayList<Object> managedReferences = new ArrayList<Object>();

public CallNonvirtualDerived2 () {
if (CallNonvirtualDerived2.class == getClass ()) {
com.xamarin.java_interop.ManagedPeer.construct (
this,
"Java.InteropTests.CallNonvirtualDerived2, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
assemblyQualifiedName,
""
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

public class CallVirtualFromConstructorBase implements GCUserPeerable {

static final String assemblyQualifiedName = "Java.InteropTests.CallVirtualFromConstructorBase, Java.Interop-Tests";

ArrayList<Object> managedReferences = new ArrayList<Object>();

public CallVirtualFromConstructorBase (int value) {
if (CallVirtualFromConstructorBase.class == getClass ()) {
com.xamarin.java_interop.ManagedPeer.construct (
this,
"Java.InteropTests.CallVirtualFromConstructorBase, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
assemblyQualifiedName,
"System.Int32",
value
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class CallVirtualFromConstructorDerived
extends CallVirtualFromConstructorBase
implements GCUserPeerable
{
static final String assemblyQualifiedName = "Java.InteropTests.CallVirtualFromConstructorDerived, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
static final String assemblyQualifiedName = "Java.InteropTests.CallVirtualFromConstructorDerived, Java.Interop-Tests";
static {
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
CallVirtualFromConstructorDerived.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

public class CrossReferenceBridge implements GCUserPeerable {

static final String assemblyQualifiedName = "Java.InteropTests.CrossReferenceBridge, Java.Interop-Tests";

ArrayList<Object> managedReferences = new ArrayList<Object>();

public CrossReferenceBridge () {
if (CrossReferenceBridge.class == getClass ()) {
com.xamarin.java_interop.ManagedPeer.construct (
this,
"Java.InteropTests.CrossReferenceBridge, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
assemblyQualifiedName,
""
);
}
Expand Down
Loading

0 comments on commit 827c627

Please sign in to comment.