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

418 строки
12 KiB
C#
Исходник Постоянная ссылка Обычный вид История

using System;
[ObjCRuntime] Don't double-retain blocks. (#3717) * [ObjCRuntime] Don't double-retain blocks. First there was darkness; no blocks were retained. Then came the light; and all blocks were retained [1] Forever. But all that once is, must one day not be, and thus the light gave way to darkness, and blocks were only retained as long as need be [2]. But before there was a balance, there was a crossroad. In some places the light shone forever, and all blocks were retained. In other places there was a balance, and the light shone only as long as needed. A desire to unify arose. Alas, it could not be. It was a bright and sunny day When a merge failed [3]. And all blocks were retained. Twice. Once [here][4] and once [there][5]. For many years we could not see. Until a dark and rainy night, when an awareness arose. And the desire to unify the balance could finally be fulfilled. [1]: https://github.com/xamarin/maccore/commit/6efca92acb5d0f2b1b495a40ac2e89586200d206 [2]: https://github.com/xamarin/maccore/commit/a22f877539058b889555ae4decae0f2e9aca029c [3]: https://github.com/xamarin/maccore/commit/befa0477cf9f3c9602d3fda6b9dee65e5bba506f [4]: https://github.com/xamarin/xamarin-macios/blob/5158a3c00138ae9bc45979487ca6a0fbc3fccc4d/src/ObjCRuntime/Runtime.cs#L858 [5]: https://github.com/xamarin/xamarin-macios/blob/5158a3c00138ae9bc45979487ca6a0fbc3fccc4d/runtime/runtime.m#L2091 * [tests] Fix test builds. * [monotouch-test] RegistrarTest.BlockCollection: allocate more and wait longer for the GC. Allocate more objects and wait longer for the GC to run. Hopefully fixes this problem: [FAIL] RegistrarTest.BlockCollection : freed blocks Expected: greater than 0 But was: 0 The blocks are freed if we just wait long enough... The problem is that we don't want to wait very long (makes the tests slow to run), so try to speed things up by allocating more.
2018-03-13 14:30:32 +03:00
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; }
}
}
}