2021-08-11 11:06:46 +03:00
using System ;
2018-03-13 14:30:32 +03:00
using System.Threading ;
2024-08-09 15:32:19 +03:00
#if NET
using System.Runtime.InteropServices ;
#endif
2018-03-02 16:30:18 +03:00
using Foundation ;
using ObjCRuntime ;
using NUnit.Framework ;
using Bindings.Test ;
2024-08-09 15:32:19 +03:00
#nullable enable
2022-12-05 10:23:34 +03:00
namespace Xamarin.BindingTests {
2018-03-02 16:30:18 +03:00
[TestFixture]
[Preserve (AllMembers = true)]
2022-12-05 10:23:34 +03:00
public class RegistrarBindingTest {
2018-03-02 16:30:18 +03:00
[Test]
public void BlockCallback ( )
{
using ( var obj = new BlockCallbackTester ( ) ) {
obj . CallClassCallback ( ) ;
obj . TestObject = new BlockCallbackClass ( ) ;
obj . CallOptionalCallback ( ) ;
obj . CallRequiredCallback ( ) ;
2018-03-17 01:01:56 +03:00
ObjCBlockTester . TestClass = new Class ( typeof ( BlockCallbackClass ) ) ;
ObjCBlockTester . CallRequiredStaticCallback ( ) ;
ObjCBlockTester . CallOptionalStaticCallback ( ) ;
2018-03-02 16:30:18 +03:00
obj . TestObject = new BlockCallbackClassExplicit ( ) ;
obj . CallOptionalCallback ( ) ;
obj . CallRequiredCallback ( ) ;
2018-03-17 01:01:56 +03:00
ObjCBlockTester . TestClass = new Class ( typeof ( BlockCallbackClassExplicit ) ) ;
ObjCBlockTester . CallRequiredStaticCallback ( ) ;
ObjCBlockTester . CallOptionalStaticCallback ( ) ;
2018-03-02 16:30:18 +03:00
}
}
2022-12-05 10:23:34 +03:00
class BlockCallbackClass : NSObject , IObjCProtocolBlockTest {
2018-03-02 16:30:18 +03:00
public void RequiredCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
[Export ("optionalCallback:")]
public void OptionalCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
2018-03-17 01:01:56 +03:00
[Export ("requiredStaticCallback:")]
public static void RequiredStaticCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
[Export ("optionalStaticCallback:")]
public static void OptionalStaticCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
2018-09-06 17:20:23 +03:00
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" ) ;
} ) ;
}
2018-03-02 16:30:18 +03:00
}
2022-04-26 14:47:18 +03:00
[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 ;
2024-08-09 15:32:19 +03:00
#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
2022-04-26 14:47:18 +03:00
}
}
2022-12-05 10:23:34 +03:00
abstract class BaseBlockCallbackClass : NSObject , IObjCProtocolBlockTest {
2022-04-26 14:47:18 +03:00
public abstract void RequiredCallback ( Action < int > completionHandler ) ;
public abstract Action < int > RequiredReturnValue ( ) ;
}
2022-12-05 10:23:34 +03:00
class DerivedBlockCallbackClass : BaseBlockCallbackClass {
2022-04-26 14:47:18 +03:00
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 ) = > {
2022-12-05 10:23:34 +03:00
Console . WriteLine ( "OptionalReturnValue" ) ;
2022-04-26 14:47:18 +03:00
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 ) = > {
2022-12-05 10:23:34 +03:00
Console . WriteLine ( "OptionalStaticReturnValue" ) ;
2022-04-26 14:47:18 +03:00
Assert . AreEqual ( Answer , v , "RequiredReturnValue" ) ;
} ) ;
}
}
2022-12-05 10:23:34 +03:00
class BlockCallbackClassExplicit : NSObject , IObjCProtocolBlockTest {
2018-03-02 16:30:18 +03:00
// Explicitly implemented interface member
void IObjCProtocolBlockTest . RequiredCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
[Export ("optionalCallback:")]
public void OptionalCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
2018-03-17 01:01:56 +03:00
[Export ("requiredStaticCallback:")]
public static void RequiredStaticCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
[Export ("optionalStaticCallback:")]
public static void OptionalRequiredCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
2018-09-06 17:20:23 +03:00
// 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" ) ;
} ) ;
}
2018-03-02 16:30:18 +03:00
}
2022-12-05 10:23:34 +03:00
public class BlockCallbackTester : ObjCBlockTester {
2018-03-02 16:30:18 +03:00
public override void ClassCallback ( Action < int > completionHandler )
{
completionHandler ( 42 ) ;
}
2024-08-09 15:32:19 +03:00
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
2018-03-02 16:30:18 +03:00
}
2018-09-06 17:20:23 +03:00
public class PropertyBlock : NSObject , IProtocolWithBlockProperties {
[Export ("myOptionalProperty")]
2024-08-09 15:32:19 +03:00
public SimpleCallback MyOptionalProperty { get ; set ; } = ( ) = > { Assert . Fail ( "No block set?" ) ; } ;
2018-09-06 17:20:23 +03:00
2024-08-09 15:32:19 +03:00
public SimpleCallback MyRequiredProperty { get ; set ; } = ( ) = > { Assert . Fail ( "No block set?" ) ; } ;
2018-09-06 17:20:23 +03:00
[Export ("myOptionalStaticProperty")]
2024-08-09 15:32:19 +03:00
public static SimpleCallback ? MyOptionalStaticProperty { get ; set ; }
2018-09-06 17:20:23 +03:00
[Export ("myRequiredStaticProperty")]
2024-08-09 15:32:19 +03:00
public static SimpleCallback ? MyRequiredStaticProperty { get ; set ; }
2018-09-06 17:20:23 +03:00
}
[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 ) {
2024-08-09 15:32:19 +03:00
pb . MyRequiredProperty ! ( ) ;
2018-09-06 17:20:23 +03:00
} else {
2024-08-09 15:32:19 +03:00
PropertyBlock . MyRequiredStaticProperty ! ( ) ;
2018-09-06 17:20:23 +03:00
}
} else {
if ( instance ) {
2024-08-09 15:32:19 +03:00
pb . MyOptionalProperty ! ( ) ;
2018-09-06 17:20:23 +03:00
} else {
2024-08-09 15:32:19 +03:00
PropertyBlock . MyOptionalStaticProperty ! ( ) ;
2018-09-06 17:20:23 +03:00
}
}
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 ) ;
}
}
2018-09-10 11:56:02 +03:00
[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 ) {
2023-04-26 17:38:27 +03:00
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" ) ;
}
2018-09-10 11:56:02 +03:00
}
}
}
// 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")]
2024-08-09 15:32:19 +03:00
public SimpleCallback ? MyOptionalProperty { get ; set ; }
2018-09-10 11:56:02 +03:00
[Export ("myRequiredProperty")]
2024-08-09 15:32:19 +03:00
public SimpleCallback ? MyRequiredProperty { get ; set ; }
2018-09-10 11:56:02 +03:00
[Export ("myOptionalStaticProperty")]
2024-08-09 15:32:19 +03:00
public static SimpleCallback ? MyOptionalStaticProperty { get ; set ; }
2018-09-10 11:56:02 +03:00
[Export ("myRequiredStaticProperty")]
2024-08-09 15:32:19 +03:00
public static SimpleCallback ? MyRequiredStaticProperty { get ; set ; }
2018-09-10 11:56:02 +03:00
}
2018-03-02 16:30:18 +03:00
}
}