xamarin-macios/tests/bindings-test/RegistrarBindingTest.cs

418 строки
12 KiB
C#

using System;
using System.Threading;
#if NET
using System.Runtime.InteropServices;
#endif
using Foundation;
using ObjCRuntime;
using NUnit.Framework;
using Bindings.Test;
#nullable enable
namespace Xamarin.BindingTests {
[TestFixture]
[Preserve (AllMembers = true)]
public class RegistrarBindingTest {
[Test]
public void BlockCallback ()
{
using (var obj = new BlockCallbackTester ()) {
obj.CallClassCallback ();
obj.TestObject = new BlockCallbackClass ();
obj.CallOptionalCallback ();
obj.CallRequiredCallback ();
ObjCBlockTester.TestClass = new Class (typeof (BlockCallbackClass));
ObjCBlockTester.CallRequiredStaticCallback ();
ObjCBlockTester.CallOptionalStaticCallback ();
obj.TestObject = new BlockCallbackClassExplicit ();
obj.CallOptionalCallback ();
obj.CallRequiredCallback ();
ObjCBlockTester.TestClass = new Class (typeof (BlockCallbackClassExplicit));
ObjCBlockTester.CallRequiredStaticCallback ();
ObjCBlockTester.CallOptionalStaticCallback ();
}
}
class BlockCallbackClass : NSObject, IObjCProtocolBlockTest {
public void RequiredCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("optionalCallback:")]
public void OptionalCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("requiredStaticCallback:")]
public static void RequiredStaticCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("optionalStaticCallback:")]
public static void OptionalStaticCallback (Action<int> completionHandler)
{
completionHandler (42);
}
public Action<int> RequiredReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalReturnValue")]
public Action<int> OptionalReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("requiredStaticReturnValue")]
public static Action<int> RequiredStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalStaticReturnValue")]
public static Action<int> OptionalStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
}
[Test]
public void DerivedClassBlockCallback ()
{
using (var obj = new BlockCallbackTester ()) {
DerivedBlockCallbackClass.Answer = 42;
obj.TestObject = new DerivedBlockCallbackClass ();
obj.CallOptionalCallback ();
// obj.CallRequiredCallback ();
ObjCBlockTester.TestClass = new Class (typeof (DerivedBlockCallbackClass));
ObjCBlockTester.CallRequiredStaticCallback ();
ObjCBlockTester.CallOptionalStaticCallback ();
DerivedBlockCallbackClass.Answer = 2;
#if NET
Assert.IsFalse (obj.InvokeNullableCallbackNatively (null), "NullableCallback A rv");
int nullableResult = -1;
Assert.IsTrue (obj.InvokeNullableCallbackNatively ((v) => nullableResult = v), "NullableCallback B rv");
Assert.AreEqual (24, nullableResult, "NullableCallback result");
#endif
}
}
abstract class BaseBlockCallbackClass : NSObject, IObjCProtocolBlockTest {
public abstract void RequiredCallback (Action<int> completionHandler);
public abstract Action<int> RequiredReturnValue ();
}
class DerivedBlockCallbackClass : BaseBlockCallbackClass {
public static int Answer = 42;
public override void RequiredCallback (Action<int> completionHandler)
{
completionHandler (Answer);
}
[Export ("optionalCallback:")]
public void OptionalCallback (Action<int> completionHandler)
{
Console.WriteLine ("OptionalCallback");
completionHandler (Answer);
}
[Export ("requiredStaticCallback:")]
public static void RequiredStaticCallback (Action<int> completionHandler)
{
completionHandler (Answer);
}
[Export ("optionalStaticCallback:")]
public static void OptionalStaticCallback (Action<int> completionHandler)
{
Console.WriteLine ("OptionalStaticCallback");
completionHandler (Answer);
}
public override Action<int> RequiredReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (Answer, v, "RequiredReturnValue");
});
}
[Export ("optionalReturnValue")]
public Action<int> OptionalReturnValue ()
{
return new Action<int> ((v) => {
Console.WriteLine ("OptionalReturnValue");
Assert.AreEqual (Answer, v, "RequiredReturnValue");
});
}
[Export ("requiredStaticReturnValue")]
public static Action<int> RequiredStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (Answer, v, "RequiredReturnValue");
});
}
[Export ("optionalStaticReturnValue")]
public static Action<int> OptionalStaticReturnValue ()
{
return new Action<int> ((v) => {
Console.WriteLine ("OptionalStaticReturnValue");
Assert.AreEqual (Answer, v, "RequiredReturnValue");
});
}
}
class BlockCallbackClassExplicit : NSObject, IObjCProtocolBlockTest {
// Explicitly implemented interface member
void IObjCProtocolBlockTest.RequiredCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("optionalCallback:")]
public void OptionalCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("requiredStaticCallback:")]
public static void RequiredStaticCallback (Action<int> completionHandler)
{
completionHandler (42);
}
[Export ("optionalStaticCallback:")]
public static void OptionalRequiredCallback (Action<int> completionHandler)
{
completionHandler (42);
}
// Explicitly implemented interface member
Action<int> IObjCProtocolBlockTest.RequiredReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalReturnValue")]
public Action<int> OptionalReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("requiredStaticReturnValue")]
public static Action<int> RequiredStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
[Export ("optionalStaticReturnValue")]
public static Action<int> OptionalStaticReturnValue ()
{
return new Action<int> ((v) => {
Assert.AreEqual (42, v, "RequiredReturnValue");
});
}
}
public class BlockCallbackTester : ObjCBlockTester {
public override void ClassCallback (Action<int> completionHandler)
{
completionHandler (42);
}
public override bool NullableCallback (Action<int>? completionHandler)
{
if (completionHandler is not null) {
completionHandler (24);
return true;
}
return false;
}
#if NET
[BindingImpl (BindingImplOptions.Optimizable)]
public bool InvokeNullableCallbackNatively (Action<int>? completionHandler)
{
byte rv;
if (completionHandler is null) {
rv = Messaging.byte_objc_msgSend_IntPtr (Handle, Selector.GetHandle ("nullableCallback:"), IntPtr.Zero);
} else {
unsafe {
delegate* unmanaged<IntPtr, int, void> trampoline = &NullableCallbackHandler;
using var block = new BlockLiteral (trampoline, completionHandler, typeof (BlockCallbackTester), nameof (NullableCallbackHandler));
BlockLiteral* block_ptr = &block;
rv = Messaging.byte_objc_msgSend_IntPtr (Handle, Selector.GetHandle ("nullableCallback:"), (IntPtr) block_ptr);
}
}
return rv != 0;
}
[UnmanagedCallersOnly]
static void NullableCallbackHandler (IntPtr block, int magicNumber)
{
var del = BlockLiteral.GetTarget<Action<int>> (block);
if (del is not null) {
del (magicNumber);
}
}
#endif
}
public class PropertyBlock : NSObject, IProtocolWithBlockProperties {
[Export ("myOptionalProperty")]
public SimpleCallback MyOptionalProperty { get; set; } = () => { Assert.Fail ("No block set?"); };
public SimpleCallback MyRequiredProperty { get; set; } = () => { Assert.Fail ("No block set?"); };
[Export ("myOptionalStaticProperty")]
public static SimpleCallback? MyOptionalStaticProperty { get; set; }
[Export ("myRequiredStaticProperty")]
public static SimpleCallback? MyRequiredStaticProperty { get; set; }
}
[Test]
[TestCase (true, true)]
[TestCase (true, false)]
[TestCase (false, true)]
[TestCase (false, false)]
public void ProtocolWithBlockProperties (bool required, bool instance)
{
using (var pb = new PropertyBlock ()) {
var callbackCalled = false;
SimpleCallback action = () => {
callbackCalled = true;
};
if (required) {
if (instance) {
pb.MyRequiredProperty = action;
} else {
PropertyBlock.MyRequiredStaticProperty = action;
}
} else {
if (instance) {
pb.MyOptionalProperty = action;
} else {
PropertyBlock.MyOptionalStaticProperty = action;
}
}
ObjCBlockTester.CallProtocolWithBlockProperties (pb, required, instance);
Assert.IsTrue (callbackCalled, "Callback");
}
}
[Test]
[TestCase (true, true)]
[TestCase (true, false)]
[TestCase (false, true)]
[TestCase (false, false)]
public void ProtocolWithNativeBlockProperties (bool required, bool instance)
{
using (var pb = new PropertyBlock ()) {
var calledCounter = ObjCBlockTester.CalledBlockCount;
ObjCBlockTester.SetProtocolWithBlockProperties (pb, required, instance);
if (required) {
if (instance) {
pb.MyRequiredProperty! ();
} else {
PropertyBlock.MyRequiredStaticProperty! ();
}
} else {
if (instance) {
pb.MyOptionalProperty! ();
} else {
PropertyBlock.MyOptionalStaticProperty! ();
}
}
Assert.AreEqual (calledCounter + 1, ObjCBlockTester.CalledBlockCount, "Blocks called");
}
}
[Test]
[TestCase (true, true)]
[TestCase (true, false)]
[TestCase (false, true)]
[TestCase (false, false)]
public void ProtocolWithReturnValues (bool required, bool instance)
{
using (var pb = new BlockCallbackClass ()) {
ObjCBlockTester.CallProtocolWithBlockReturnValue (pb, required, instance);
}
}
[Test]
[TestCase (true, true)]
[TestCase (true, false)]
[TestCase (false, true)]
[TestCase (false, false)]
public void LinkedAway (bool required, bool instance)
{
if (!(TestRuntime.IsLinkAll && TestRuntime.IsOptimizeAll))
Assert.Ignore ("This test is only applicable if optimized & linking all assemblies.");
using (var pb = new FakePropertyBlock ()) {
try {
Messaging.void_objc_msgSend_IntPtr_bool_bool (Class.GetHandle (typeof (ObjCBlockTester)), Selector.GetHandle ("setProtocolWithBlockProperties:required:instance:"), pb.Handle, required, instance);
Assert.Fail ("Expected an MT8028 error");
} catch (RuntimeException re) {
Assert.That (re.Code, Is.EqualTo (8009).Or.EqualTo (8028), "Code");
if (re.Code == 8009) {
Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message");
} else {
Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message");
}
}
}
}
// Each method here will show a warning like this:
// MT4174: Unable to locate the block to delegate conversion method for the method ...
// This is expected.
// This is 'fake' because it doesn't implement the ProtocolWithBlockProperties protocol,
// which means that XI won't be able to find the delegate<->block conversion methods
// (either at runtime or build time).
// The point of this test is to ensure that we don't crash when the runtime tries
// to find those conversion methods, and instead finds that many required functions
// have been linked away (which happen when _forcing_ the dynamic registrar to be linked away).
public class FakePropertyBlock : NSObject {
[Export ("myOptionalProperty")]
public SimpleCallback? MyOptionalProperty { get; set; }
[Export ("myRequiredProperty")]
public SimpleCallback? MyRequiredProperty { get; set; }
[Export ("myOptionalStaticProperty")]
public static SimpleCallback? MyOptionalStaticProperty { get; set; }
[Export ("myRequiredStaticProperty")]
public static SimpleCallback? MyRequiredStaticProperty { get; set; }
}
}
}