From cd6840a81788225f52202ea9fb7faef6202a9110 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Wed, 8 Apr 2020 15:24:44 +0300 Subject: [PATCH] Implement multi-file Obj-C interop tests (interop_objc_tests task) Extract most of interop_objc_smoke tests --- backend.native/tests/build.gradle | 36 +- .../tests/interop/objc/objcTests.def | 6 + backend.native/tests/interop/objc/smoke.h | 171 -------- backend.native/tests/interop/objc/smoke.kt | 390 +----------------- backend.native/tests/interop/objc/smoke.m | 260 ------------ .../tests/interop/objc/tests/allocNoRetain.h | 5 + .../tests/interop/objc/tests/allocNoRetain.kt | 27 ++ .../tests/interop/objc/tests/allocNoRetain.m | 14 + .../tests/interop/objc/tests/blocks.h | 27 ++ .../tests/interop/objc/tests/blocks.kt | 21 + .../tests/interop/objc/tests/blocks.m | 20 + .../interop/objc/tests/callableReferences.h | 7 + .../interop/objc/tests/callableReferences.kt | 28 ++ .../interop/objc/tests/callableReferences.m | 11 + .../interop/objc/tests/clashingWithAny.h | 22 + .../interop/objc/tests/clashingWithAny.kt | 27 ++ .../interop/objc/tests/clashingWithAny.m | 68 +++ .../objc/tests/constructorReturnsNull.h | 5 + .../objc/tests/constructorReturnsNull.kt | 9 + .../objc/tests/constructorReturnsNull.m | 7 + .../tests/interop/objc/tests/conversions.kt | 18 + .../tests/interop/objc/tests/customString.h | 16 + .../tests/interop/objc/tests/customString.kt | 21 + .../tests/interop/objc/tests/customString.m | 32 ++ .../tests/interop/objc/tests/exceptions.h | 9 + .../tests/interop/objc/tests/exceptions.kt | 15 + .../tests/interop/objc/tests/exceptions.m | 7 + .../objc/tests/initWithCustomSelector.h | 8 + .../objc/tests/initWithCustomSelector.kt | 25 ++ .../objc/tests/initWithCustomSelector.m | 23 ++ .../tests/interop/objc/tests/main.kt | 11 + .../tests/interop/objc/tests/mangling.h | 25 ++ .../tests/interop/objc/tests/mangling.kt | 18 + .../tests/interop/objc/tests/mangling.m | 6 + .../objc/tests/multipleInheritanceClash.h | 17 + .../objc/tests/multipleInheritanceClash.kt | 18 + .../objc/tests/multipleInheritanceClash.m | 10 + .../interop/objc/tests/nsOutputStream.kt | 8 + .../tests/interop/objc/tests/objcWeakRefs.h | 13 + .../tests/interop/objc/tests/objcWeakRefs.kt | 45 ++ .../tests/interop/objc/tests/objcWeakRefs.m | 13 + .../tests/interop/objc/tests/overrideInit.h | 6 + .../tests/interop/objc/tests/overrideInit.kt | 11 + .../tests/interop/objc/tests/overrideInit.m | 11 + .../tests/interop/objc/tests/sharing.kt | 36 ++ .../tests/interop/objc/tests/utils.kt | 17 + .../tests/interop/objc/tests/varargs.h | 20 + .../tests/interop/objc/tests/varargs.kt | 53 +++ .../tests/interop/objc/tests/varargs.m | 51 +++ .../tests/interop/objc/tests/weakRefs.h | 5 + .../tests/interop/objc/tests/weakRefs.kt | 35 ++ 51 files changed, 942 insertions(+), 822 deletions(-) create mode 100644 backend.native/tests/interop/objc/objcTests.def create mode 100644 backend.native/tests/interop/objc/tests/allocNoRetain.h create mode 100644 backend.native/tests/interop/objc/tests/allocNoRetain.kt create mode 100644 backend.native/tests/interop/objc/tests/allocNoRetain.m create mode 100644 backend.native/tests/interop/objc/tests/blocks.h create mode 100644 backend.native/tests/interop/objc/tests/blocks.kt create mode 100644 backend.native/tests/interop/objc/tests/blocks.m create mode 100644 backend.native/tests/interop/objc/tests/callableReferences.h create mode 100644 backend.native/tests/interop/objc/tests/callableReferences.kt create mode 100644 backend.native/tests/interop/objc/tests/callableReferences.m create mode 100644 backend.native/tests/interop/objc/tests/clashingWithAny.h create mode 100644 backend.native/tests/interop/objc/tests/clashingWithAny.kt create mode 100644 backend.native/tests/interop/objc/tests/clashingWithAny.m create mode 100644 backend.native/tests/interop/objc/tests/constructorReturnsNull.h create mode 100644 backend.native/tests/interop/objc/tests/constructorReturnsNull.kt create mode 100644 backend.native/tests/interop/objc/tests/constructorReturnsNull.m create mode 100644 backend.native/tests/interop/objc/tests/conversions.kt create mode 100644 backend.native/tests/interop/objc/tests/customString.h create mode 100644 backend.native/tests/interop/objc/tests/customString.kt create mode 100644 backend.native/tests/interop/objc/tests/customString.m create mode 100644 backend.native/tests/interop/objc/tests/exceptions.h create mode 100644 backend.native/tests/interop/objc/tests/exceptions.kt create mode 100644 backend.native/tests/interop/objc/tests/exceptions.m create mode 100644 backend.native/tests/interop/objc/tests/initWithCustomSelector.h create mode 100644 backend.native/tests/interop/objc/tests/initWithCustomSelector.kt create mode 100644 backend.native/tests/interop/objc/tests/initWithCustomSelector.m create mode 100644 backend.native/tests/interop/objc/tests/main.kt create mode 100644 backend.native/tests/interop/objc/tests/mangling.h create mode 100644 backend.native/tests/interop/objc/tests/mangling.kt create mode 100644 backend.native/tests/interop/objc/tests/mangling.m create mode 100644 backend.native/tests/interop/objc/tests/multipleInheritanceClash.h create mode 100644 backend.native/tests/interop/objc/tests/multipleInheritanceClash.kt create mode 100644 backend.native/tests/interop/objc/tests/multipleInheritanceClash.m create mode 100644 backend.native/tests/interop/objc/tests/nsOutputStream.kt create mode 100644 backend.native/tests/interop/objc/tests/objcWeakRefs.h create mode 100644 backend.native/tests/interop/objc/tests/objcWeakRefs.kt create mode 100644 backend.native/tests/interop/objc/tests/objcWeakRefs.m create mode 100644 backend.native/tests/interop/objc/tests/overrideInit.h create mode 100644 backend.native/tests/interop/objc/tests/overrideInit.kt create mode 100644 backend.native/tests/interop/objc/tests/overrideInit.m create mode 100644 backend.native/tests/interop/objc/tests/sharing.kt create mode 100644 backend.native/tests/interop/objc/tests/utils.kt create mode 100644 backend.native/tests/interop/objc/tests/varargs.h create mode 100644 backend.native/tests/interop/objc/tests/varargs.kt create mode 100644 backend.native/tests/interop/objc/tests/varargs.m create mode 100644 backend.native/tests/interop/objc/tests/weakRefs.h create mode 100644 backend.native/tests/interop/objc/tests/weakRefs.kt diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 9cb596077fe..fc6fc8410cb 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -3445,6 +3445,11 @@ if (PlatformInfo.isAppleTarget(project)) { it.headers "$projectDir/interop/objc/smoke.h" } + createInterop("objcTests") { + it.defFile 'interop/objc/objcTests.def' + it.headers fileTree("$projectDir/interop/objc/tests") { include '**/*.h' } + } + createInterop("objcMisc") { it.defFile 'interop/objc_with_initializer/objc_misc.def' it.headers "$projectDir/interop/objc_with_initializer/objc_misc.h" @@ -3475,7 +3480,7 @@ createInterop("withSpaces") { /** * Creates a task for the interop test. Configures runner and adds library and test build tasks. */ -Task interopTest(String name, Closure configureClosure) { +Task interopTestBase(String name, boolean multiFile, Closure configureClosure) { return KotlinNativeTestKt.createTest(project, name, KonanInteropTest) { task -> task.configure(configureClosure) if (task.enabled) { @@ -3487,7 +3492,7 @@ Task interopTest(String name, Closure configureClosure) { libraries { artifact lib } - srcFiles task.getSources() + srcFiles multiFile ? fileTree(task.source) { include '**/*.kt' } : task.getSources() baseDir "$testOutputLocal/$name" linkerOpts "-L$buildDir" extraOpts task.flags @@ -3498,6 +3503,13 @@ Task interopTest(String name, Closure configureClosure) { } } +Task interopTest(String name, Closure configureClosure) { + return interopTestBase(name, false, configureClosure) +} + +Task interopTestMultifile(String name, Closure configureClosure) { + return interopTestBase(name, true, configureClosure) +} standaloneTest("interop_objc_allocException") { dependsOnPlatformLibs(it) @@ -3721,6 +3733,26 @@ if (PlatformInfo.isAppleTarget(project)) { } } + interopTestMultifile("interop_objc_tests") { + source = "interop/objc/tests/" + interop = 'objcTests' + flags = ['-tr', '-e', 'main'] + + doBeforeBuild { + mkdir(buildDir) + execKonanClang(project.target) { + args fileTree("$projectDir/interop/objc/tests/") { include '**/*.m' } + args "-lobjc", '-fobjc-arc' + args '-fPIC', '-shared', '-o', "$buildDir/libobjctests.dylib" + // Enable ARC optimizations to prevent some objects from leaking in Obj-C code due to exceptions: + args '-O2' + } + if (project.target instanceof KonanTarget.IOS_X64) { + UtilsKt.codesign(project, "$buildDir/libobjctests.dylib") + } + } + } + interopTest("interop_objc_global_initializer") { goldValue = "OK\n" source = "interop/objc_with_initializer/objc_test.kt" diff --git a/backend.native/tests/interop/objc/objcTests.def b/backend.native/tests/interop/objc/objcTests.def new file mode 100644 index 00000000000..66090941d9a --- /dev/null +++ b/backend.native/tests/interop/objc/objcTests.def @@ -0,0 +1,6 @@ +language = Objective-C +headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h Foundation/NSBundle.h +headerFilter = **/interop/objc/tests/**.h \ + Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h \ + objc/objc.h Foundation/NSBundle.h +linkerOpts = -lobjctests diff --git a/backend.native/tests/interop/objc/smoke.h b/backend.native/tests/interop/objc/smoke.h index dd427a5d11b..b651871bfc8 100644 --- a/backend.native/tests/interop/objc/smoke.h +++ b/backend.native/tests/interop/objc/smoke.h @@ -76,24 +76,6 @@ id createObjectWithFactory(id factory) { return [factory create]; } -@protocol BlockProvider -@required --(int (^)(int)) block; -@end; - -int callProvidedBlock(id blockProvider, int argument) { - return [blockProvider block](argument); -} - -@protocol BlockConsumer -@required --(int)callBlock:(int (^)(int))block argument:(int)argument; -@end; - -int callPlusOneBlock(id blockConsumer, int argument) { - return [blockConsumer callBlock:^int(int p) { return p + 1; } argument:argument]; -} - @protocol CustomRetainMethods @required -(id)returnRetained:(id)obj __attribute__((ns_returns_retained)); @@ -126,86 +108,6 @@ void useCustomRetainMethods(id p) { [p returnRetainedBlock:retainedBlock](); } -NSObject* createNSObject() { - return [NSObject new]; -} - -@protocol ExceptionThrower --(void)throwException; -@end; - -@interface ExceptionThrowerManager : NSObject -+(void)throwExceptionWith:(id)thrower; -@end; - -@interface Blocks : NSObject -+(BOOL)blockIsNull:(void (^)(void))block; -+(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block; - -@property (class) void (^nullBlock)(void); -@property (class) void (^notNullBlock)(void); -@end; - -@interface TestVarargs : NSObject --(instancetype _Nonnull)initWithFormat:(NSString*)format, ...; -+(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ...; -@property NSString* formatted; - -+(NSString* _Nonnull)stringWithFormat:(NSString*)format, ...; -+(NSObject* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args; -@end; - -@interface TestVarargs (TestVarargsExtension) --(instancetype _Nonnull)initWithFormat:(NSString*)format, ...; -@end; - -@interface TestVarargsSubclass : TestVarargs -// Test clashes: --(instancetype _Nonnull)initWithFormat:(NSString*)format args:(void*)args; -+(NSString* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args; -@end; - -@interface TestOverrideInit : NSObject --(instancetype)initWithValue:(int)value NS_DESIGNATED_INITIALIZER; -+(instancetype)createWithValue:(int)value; -@end; - -@interface MultipleInheritanceClashBase : NSObject -@property (nonnull) MultipleInheritanceClashBase* delegate; -@end; - -@protocol MultipleInheritanceClash -@optional -@property (nullable) id delegate; -@end; - -@interface MultipleInheritanceClash1 : MultipleInheritanceClashBase -@end; - -@interface MultipleInheritanceClash2 : MultipleInheritanceClashBase -@property MultipleInheritanceClashBase* delegate; -@end; - -@interface TestClashingWithAny1 : NSObject --(NSString*)toString; --(NSString*)toString_; --(int)hashCode; --(BOOL)equals:(id _Nullable)other; -@end; - -@interface TestClashingWithAny2 : NSObject --(void)toString; --(void)hashCode; --(void)equals:(int)p; // May clash. -@end; - -@interface TestClashingWithAny3 : NSObject -// Not clashing actually. --(NSString*)toString:(int)p; --(int)hashCode:(int)p; --(BOOL)equals; -@end; - id getPrinterProtocolRaw() { return @protocol(Printer); } @@ -213,76 +115,3 @@ id getPrinterProtocolRaw() { Protocol* getPrinterProtocol() { return @protocol(Printer); } - -@interface TestInitWithCustomSelector : NSObject --(instancetype)initCustom; -@property BOOL custom; - -+(instancetype _Nonnull)createCustom; -@end; - -@interface TestAllocNoRetain : NSObject -@property BOOL ok; -@end; - -@interface CustomString : NSString -- (instancetype)initWithValue:(int)value; -@property int value; -@end; - -CustomString* _Nonnull createCustomString(int value) { - return [[CustomString alloc] initWithValue:value]; -} - -int getCustomStringValue(CustomString* str) { - return str.value; -} - -extern BOOL customStringDeallocated; - -@interface TestConstructorReturnsNull : NSObject -- (instancetype)init; -@end; - -@interface TestCallableReferences : NSObject -@property int value; -- (int)instanceMethod; -+ (int)classMethod:(int)first :(int)second; -@end; - -// [KT-36067] cinterop tool fails when there is a structure member named Companion - -struct EnumFieldMangleStruct { - enum {Companion, Any} smth; -}; -struct EnumFieldMangleStruct enumMangledStruct = { Any }; - -struct MyStruct { - int Companion; // simple clash - int _Companion; - int $_Companion; - int super; -}; - -struct MyStruct myStruct = {11, 12, 13, 14}; - -@protocol Proto -@property int Companion; // clash on implementing -@end; - -@interface FooMangled : NSObject -//- (void) CompanionS; // mangleSimple does not support this: it may clash after mangling -@end; - - -@class DeallocListener; - -@interface DeallocExecutor : NSObject -@property DeallocListener* deallocListener; -@end; - -@interface DeallocListener : NSObject -@property (weak) DeallocExecutor* deallocExecutor; -@property BOOL deallocated; --(BOOL)deallocExecutorIsNil; -@end; diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 6110415a85e..c65be68f0bd 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -16,27 +16,11 @@ fun main(args: Array) { } fun run() { - testConversions() + // TODO: migrate remaining tests to interop/objc/tests/ testTypeOps() - testWeakRefs() - testExceptions() - testBlocks() testCustomRetain() - testVarargs() - testOverrideInit() - testMultipleInheritanceClash() - testClashingWithAny() - testInitWithCustomSelector() - testAllocNoRetain() - testNSOutputStreamToMemoryConstructor() testExportObjCClass() - testCustomString() testLocalizedStrings() - testConstructorReturnsNull() - testCallableReferences() - testMangling() - testSharing() - testObjCWeakRef() assertEquals(2, ForwardDeclaredEnum.TWO.value) @@ -197,80 +181,6 @@ fun testTypeOps() { assertFails { MutablePairImpl(1, 2).asAny() as Foo } } -fun testConversions() { - testMethodsOfAny(emptyList(), NSArray()) - testMethodsOfAny(listOf(1, "foo"), nsArrayOf(1, "foo")) - testMethodsOfAny(42, NSNumber.numberWithInt(42), 17) - testMethodsOfAny(true, NSNumber.numberWithBool(true), false) -} - -fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: Any = Any()) { - assertEquals(kotlinObject.hashCode(), equalNsObject.hashCode()) - assertEquals(kotlinObject.toString(), equalNsObject.toString()) - assertEquals(kotlinObject, equalNsObject) - assertEquals(equalNsObject, kotlinObject) - assertNotEquals(equalNsObject, otherObject) -} - -fun testWeakRefs() { - testWeakReference({ createNSObject()!! }) - - createAndAbandonWeakRef(NSObject()) - - testWeakReference({ NSArray.arrayWithArray(listOf(42)) as NSArray }) -} - -fun testWeakReference(block: () -> NSObject) { - val ref = autoreleasepool { - createAndTestWeakReference(block) - } - - kotlin.native.internal.GC.collect() - - assertNull(ref.get()) -} - -fun createAndTestWeakReference(block: () -> NSObject): WeakReference { - val ref = createWeakReference(block) - assertNotNull(ref.get()) - assertEquals(ref.get()!!.hash(), ref.get()!!.hash()) - return ref -} - -fun createWeakReference(block: () -> NSObject) = WeakReference(block()) - -fun createAndAbandonWeakRef(obj: NSObject) { - WeakReference(obj) -} - -fun testExceptions() { - assertFailsWith { - ExceptionThrowerManager.throwExceptionWith(object : NSObject(), ExceptionThrowerProtocol { - override fun throwException() { - throw MyException() - } - }) - } -} - -fun testBlocks() { - assertTrue(Blocks.blockIsNull(null)) - assertFalse(Blocks.blockIsNull({})) - - assertEquals(null, Blocks.nullBlock) - assertNotEquals(null, Blocks.notNullBlock) - - assertEquals(10, Blocks.same({ a, b, c, d -> a + b + c + d })!!(1, 2, 3, 4)) - - assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol { - override fun block(): (Int) -> Int = { it * 2 } - }, 111)) - - assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol { - override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument) - }, 321)) -} - private lateinit var retainedMustNotBeDeallocated: MustNotBeDeallocated fun testCustomRetain() { @@ -299,156 +209,6 @@ fun testCustomRetain() { assertFalse(unexpectedDeallocation) } -fun testVarargs() { - assertEquals( - "a b -1", - TestVarargs.testVarargsWithFormat( - "%@ %s %d", - "a" as NSString, "b".cstr, (-1).toByte() - ).formatted - ) - - assertEquals( - "2 3 9223372036854775807", - TestVarargs( - "%d %d %lld", - 2.toShort(), 3, Long.MAX_VALUE - ).formatted - ) - - assertEquals( - "0.1 0.2 1 0", - TestVarargs.create( - "%.1f %.1lf %d %d", - 0.1.toFloat(), 0.2, true, false - ).formatted - ) - - assertEquals( - "1 2 3", - TestVarargs( - format = "%d %d %d", - args = *arrayOf(1, 2, 3) - ).formatted - ) - - assertEquals( - "4 5 6", - TestVarargs( - args = *arrayOf(4, *arrayOf(5, 6)), - format = "%d %d %d" - ).formatted - ) - - assertEquals( - "7", - TestVarargsSubclass.stringWithFormat( - "%d", - 7 - ) - ) -} - -fun testOverrideInit() { - assertEquals(42, (TestOverrideInitImpl.createWithValue(42) as TestOverrideInitImpl).value) -} - -class TestOverrideInitImpl @OverrideInit constructor(val value: Int) : TestOverrideInit(value) { - companion object : TestOverrideInitMeta() -} - -private class MyException : Throwable() - -fun testMultipleInheritanceClash() { - val clash1 = MultipleInheritanceClash1() - val clash2 = MultipleInheritanceClash2() - - clash1.delegate = clash1 - assertEquals(clash1, clash1.delegate) - clash1.setDelegate(clash2) - assertEquals(clash2, clash1.delegate()) - - clash2.delegate = clash1 - assertEquals(clash1, clash2.delegate) - clash2.setDelegate(clash2) - assertEquals(clash2, clash2.delegate()) -} - -fun testClashingWithAny() { - assertEquals("description", TestClashingWithAny1().toString()) - assertEquals("toString", TestClashingWithAny1().toString_()) - assertEquals("toString_", TestClashingWithAny1().toString__()) - assertEquals(1, TestClashingWithAny1().hashCode()) - assertEquals(31, TestClashingWithAny1().hashCode_()) - assertFalse(TestClashingWithAny1().equals(TestClashingWithAny1())) - assertTrue(TestClashingWithAny1().equals_(TestClashingWithAny1())) - - assertEquals("description", TestClashingWithAny2().toString()) - assertEquals(Unit, TestClashingWithAny2().toString_()) - assertEquals(2, TestClashingWithAny2().hashCode()) - assertEquals(Unit, TestClashingWithAny2().hashCode_()) - assertFalse(TestClashingWithAny2().equals(TestClashingWithAny2())) - assertEquals(Unit, TestClashingWithAny2().equals_(42)) - - assertEquals("description", TestClashingWithAny3().toString()) - assertEquals("toString:11", TestClashingWithAny3().toString(11)) - assertEquals(3, TestClashingWithAny3().hashCode()) - assertEquals(4, TestClashingWithAny3().hashCode(3)) - assertFalse(TestClashingWithAny3().equals(TestClashingWithAny3())) - assertTrue(TestClashingWithAny3().equals()) -} - -fun testInitWithCustomSelector() { - assertFalse(TestInitWithCustomSelector().custom) - assertTrue(TestInitWithCustomSelector(custom = Unit).custom) - - val customSubclass: TestInitWithCustomSelector = TestInitWithCustomSelectorSubclass.createCustom() - assertTrue(customSubclass is TestInitWithCustomSelectorSubclass) - assertTrue(customSubclass.custom) - - // Test side effect: - var ok = false - assertTrue(TestInitWithCustomSelector(run { ok = true }).custom) - assertTrue(ok) -} - -private class TestInitWithCustomSelectorSubclass : TestInitWithCustomSelector { - @OverrideInit constructor(custom: Unit) : super(custom) { - assertSame(Unit, custom) - } - - companion object : TestInitWithCustomSelectorMeta() -} - -fun testAllocNoRetain() { - // Ensure that calling Kotlin constructor generated for Objective-C initializer doesn't result in - // redundant retain-release sequence for `alloc` result, since it may provoke specific bugs to reproduce, e.g. - // the one found in [[NSOutputStream alloc] initToMemory] sequence where initToMemory deallocates its receiver - // forcibly when replacing it with other object: (to be compiled with ARC enabled) - /* - #import - - void* mem; - NSOutputStream* allocated = nil; - - int main() { - allocated = [NSOutputStream alloc]; - NSOutputStream* initialized = [allocated initToMemory]; - mem = calloc(1, 0x10); // To corrupt the 'allocated' object header. - allocated = nil; // Crashes here in objc_release. - - return 0; - } - */ - - assertTrue(TestAllocNoRetain().ok) -} - -fun testNSOutputStreamToMemoryConstructor() { - val stream: Any = NSOutputStream(toMemory = Unit) - assertTrue(stream is NSOutputStream) -} - private const val TestExportObjCClass1Name = "TestExportObjCClass" @ExportObjCClass(TestExportObjCClass1Name) class TestExportObjCClass1 : NSObject() @@ -466,21 +226,6 @@ fun testExportObjCClass() { xor (TestExportObjCClass4().objCClassName == TestExportObjCClass34Name)) } -fun testCustomString() { - assertFalse(customStringDeallocated) - - fun test() = autoreleasepool { - val str: String = createCustomString(321) - assertEquals("321", str) - assertEquals("CustomString", str.objCClassName) - assertEquals(321, getCustomStringValue(str)) - } - - test() - kotlin.native.internal.GC.collect() - assertTrue(customStringDeallocated) -} - fun testLocalizedStrings() { val key = "screen_main_plural_string" val localizedString = NSBundle.mainBundle.localizedStringForKey(key, value = "", table = "Localizable") @@ -488,140 +233,7 @@ fun testLocalizedStrings() { assertEquals("Plural: 5 apples", string) } -fun testConstructorReturnsNull() { - assertFailsWith() { - TestConstructorReturnsNull() - } -} - -fun testCallableReferences() { - val createTestCallableReferences = ::TestCallableReferences - assertEquals("", createTestCallableReferences.name) - val testCallableReferences: Any = createTestCallableReferences() - assertTrue(testCallableReferences is TestCallableReferences) - - val valueRef: kotlin.reflect.KMutableProperty0 = testCallableReferences::value - assertEquals("value", valueRef.name) - assertEquals(0, valueRef()) - valueRef.set(42) - assertEquals(42, valueRef()) - - val classMethodRef = (TestCallableReferences)::classMethod - assertEquals("classMethod", classMethodRef.name) - assertEquals(3, classMethodRef(1, 2)) - - val instanceMethodRef = TestCallableReferences::instanceMethod - assertEquals("instanceMethod", instanceMethodRef.name) - assertEquals(42, instanceMethodRef(testCallableReferences)) - - val boundInstanceMethodRef = testCallableReferences::instanceMethod - assertEquals("instanceMethod", boundInstanceMethodRef.name) - assertEquals(42, boundInstanceMethodRef()) -} - -fun testMangling() { - assertEquals(11, myStruct.`Companion$`) - assertEquals(12, myStruct._Companion) - assertEquals(13, myStruct.`$_Companion`) - assertEquals(14, myStruct.`super`) - - val objc = FooMangled() - objc.`Companion$` = 99 - assertEquals(99, objc.Companion()) - assertEquals(99, objc.`Companion$`) - - enumMangledStruct.smth = Companion - assertEquals(Companion, enumMangledStruct.smth) -} - -private class NSObjectImpl : NSObject() { - var x = 111 -} - -fun testSharing() = withWorker { - val obj = NSObjectImpl() - val array = nsArrayOf(obj) - - assertFalse(obj.isFrozen) - - runInWorker { - assertFailsWith { - array.objectAtIndex(0) - } - } - - obj.x = 222 - obj.freeze() - assertTrue(obj.isFrozen) - - runInWorker { - val obj1 = array.objectAtIndex(0) as NSObjectImpl - assertFailsWith { - obj1.x = 333 - } - } - - assertEquals(222, obj.x) - - // TODO: test [obj release] etc. -} - -fun testObjCWeakRef() { - val deallocListener = DeallocListener() - assertFalse(deallocListener.deallocated) - - testObjCWeakRef0(deallocListener) - - kotlin.native.internal.GC.collect() - assertTrue(deallocListener.deallocated) - assertTrue(deallocListener.deallocExecutorIsNil()) -} - -fun testObjCWeakRef0(deallocListener: DeallocListener) = withWorker { - assertTrue(deallocListener.deallocExecutorIsNil()) - - val obj = object : DeallocExecutor() {} - deallocListener.deallocExecutor = obj - obj.deallocListener = deallocListener - - assertFalse(deallocListener.deallocExecutorIsNil()) - -// TODO: can't actually test, Obj-C runtime doesn't expect _tryRetain throwing an exception. -// runInWorker { -// assertFailsWith { -// deallocListener.deallocExecutorIsNil() -// } -// } - - obj.freeze() - - runInWorker { - // [deallocListener.deallocExecutorIsNil()] calls deallocExecutor getter, which retains [obj] and either - // puts it to autoreleasepool or releases it immediately (Obj-C ARC optimization). - // Wrap the call to autoreleasepool to ensure [obj] will be released: - autoreleasepool { - assertFalse(deallocListener.deallocExecutorIsNil()) - } - // Process release of Kotlin reference to [obj] in any case: - kotlin.native.internal.GC.collect() - } -} - -private fun Worker.runInWorker(block: () -> Unit) { - block.freeze() - val future = this.execute(TransferMode.SAFE, { block }) { - it() - } - future.result // Throws on failure. -} - private val Any.objCClassName: String get() = object_getClassName(this)!!.toKString() -fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { - elements.forEach { - this.addObject(it as ObjCObject) - } -} - fun Any?.asAny(): Any? = this diff --git a/backend.native/tests/interop/objc/smoke.m b/backend.native/tests/interop/objc/smoke.m index a245ab12019..08504da42a0 100644 --- a/backend.native/tests/interop/objc/smoke.m +++ b/backend.native/tests/interop/objc/smoke.m @@ -95,263 +95,3 @@ static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl; return block; } @end; - -@implementation ExceptionThrowerManager -+(void)throwExceptionWith:(id)thrower { - [thrower throwException]; -} -@end; - -@implementation Blocks -+(BOOL)blockIsNull:(void (^)(void))block { - return block == nil; -} - -+(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block { - return block; -} - -+(void (^)(void)) nullBlock { - return nil; -} - -+(void (^)(void)) notNullBlock { - return ^{}; -} - -@end; - -@implementation TestVarargs --(instancetype _Nonnull)initWithFormat:(NSString*)format, ... { - self = [super init]; - - va_list args; - va_start(args, format); - self.formatted = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - return self; -} - -+(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ... { - TestVarargs* result = [[self alloc] init]; - - va_list args; - va_start(args, format); - result.formatted = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - return result; -} - -+(NSString* _Nonnull)stringWithFormat:(NSString*)format, ... { - va_list args; - va_start(args, format); - NSString* result = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - return result; -} - -+(NSObject* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args { - abort(); -} - -@end; - -@implementation TestVarargsSubclass --(instancetype _Nonnull)initWithFormat:(NSString*)format args:(void*)args { - abort(); -} - -+(NSString* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args { - abort(); -} -@end; - -@implementation TestOverrideInit --(instancetype)initWithValue:(int)value { - return self = [super init]; -} - -+(instancetype)createWithValue:(int)value { - return [[self alloc] initWithValue:value]; -} -@end; - -@implementation MultipleInheritanceClashBase -@end; - -@implementation MultipleInheritanceClash1 -@end; - -@implementation MultipleInheritanceClash2 -@end; - -@implementation TestClashingWithAny1 --(NSString*)description { - return @"description"; -} - --(NSString*)toString { - return @"toString"; -} - --(NSString*)toString_ { - return @"toString_"; -} - --(NSUInteger)hash { - return 1; -} - --(int)hashCode { - return 31; -} - --(BOOL)equals:(id _Nullable)other { - return YES; -} -@end; - -@implementation TestClashingWithAny2 --(NSString*)description { - return @"description"; -} - --(void)toString { -} - --(NSUInteger)hash { - return 2; -} - --(void)hashCode { -} - --(void)equals:(int)p { -} -@end; - -@implementation TestClashingWithAny3 --(NSString*)description { - return @"description"; -} - --(NSString*)toString:(int)p { - return [NSString stringWithFormat:@"%s:%d", "toString", p]; -} - --(NSUInteger)hash { - return 3; -} - --(int)hashCode:(int)p { - return p + 1; -} - --(BOOL)equals { - return YES; -} -@end; - -@implementation TestInitWithCustomSelector - --(instancetype)initCustom { - if (self = [super init]) { - self.custom = YES; - } - return self; -} - --(instancetype)init { - if (self = [super init]) { - self.custom = NO; - } - return self; -} - -+(instancetype)createCustom { - return [[self alloc] initCustom]; -} - -@end; - -@implementation TestAllocNoRetain --(instancetype)init { - __weak id weakSelf = self; - self = [TestAllocNoRetain alloc]; - if (self = [super init]) { - // Ensure that original self value was deallocated: - self.ok = (weakSelf == nil); - // So it's RC was 1, which means there wasn't redundant retain applied to it. - } - return self; -} -@end; - -BOOL customStringDeallocated = NO; - -@implementation CustomString { - NSString* delegate; -} - -- (instancetype)initWithValue:(int)value { - if (self = [super init]) { - self->delegate = @(value).description; - self.value = value; - } - return self; -} - -- (unichar)characterAtIndex:(NSUInteger)index { - return [self->delegate characterAtIndex:index]; -} - -- (NSUInteger)length { - return self->delegate.length; -} - -- (id)copyWithZone:(NSZone *)zone { - return self; -} - -- (void)dealloc { - customStringDeallocated = YES; -} -@end; - -@implementation TestConstructorReturnsNull -- (instancetype)init { - return nil; -} -@end; - -@implementation TestCallableReferences -- (int)instanceMethod { - return self.value; -} - -+ (int)classMethod:(int)first :(int)second { - return first + second; -} -@end; - - -// [KT-36067] mangling -@implementation FooMangled : NSObject -@synthesize Companion; -@end; - - -@implementation DeallocExecutor --(void)dealloc { - self.deallocListener.deallocated = YES; -} -@end; - -@implementation DeallocListener --(BOOL)deallocExecutorIsNil { - return self.deallocExecutor == nil; -} -@end; diff --git a/backend.native/tests/interop/objc/tests/allocNoRetain.h b/backend.native/tests/interop/objc/tests/allocNoRetain.h new file mode 100644 index 00000000000..313ef9b0ada --- /dev/null +++ b/backend.native/tests/interop/objc/tests/allocNoRetain.h @@ -0,0 +1,5 @@ +#import + +@interface TestAllocNoRetain : NSObject +@property BOOL ok; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/allocNoRetain.kt b/backend.native/tests/interop/objc/tests/allocNoRetain.kt new file mode 100644 index 00000000000..5cba873ea32 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/allocNoRetain.kt @@ -0,0 +1,27 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testAllocNoRetain() { + // Ensure that calling Kotlin constructor generated for Objective-C initializer doesn't result in + // redundant retain-release sequence for `alloc` result, since it may provoke specific bugs to reproduce, e.g. + // the one found in [[NSOutputStream alloc] initToMemory] sequence where initToMemory deallocates its receiver + // forcibly when replacing it with other object: (to be compiled with ARC enabled) + /* + #import + + void* mem; + NSOutputStream* allocated = nil; + + int main() { + allocated = [NSOutputStream alloc]; + NSOutputStream* initialized = [allocated initToMemory]; + mem = calloc(1, 0x10); // To corrupt the 'allocated' object header. + allocated = nil; // Crashes here in objc_release. + + return 0; + } + */ + + assertTrue(TestAllocNoRetain().ok) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/allocNoRetain.m b/backend.native/tests/interop/objc/tests/allocNoRetain.m new file mode 100644 index 00000000000..122ccb9be0e --- /dev/null +++ b/backend.native/tests/interop/objc/tests/allocNoRetain.m @@ -0,0 +1,14 @@ +#import "allocNoRetain.h" + +@implementation TestAllocNoRetain +-(instancetype)init { + __weak id weakSelf = self; + self = [TestAllocNoRetain alloc]; + if (self = [super init]) { + // Ensure that original self value was deallocated: + self.ok = (weakSelf == nil); + // So it's RC was 1, which means there wasn't redundant retain applied to it. + } + return self; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/blocks.h b/backend.native/tests/interop/objc/tests/blocks.h new file mode 100644 index 00000000000..585827fb2f3 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/blocks.h @@ -0,0 +1,27 @@ +#import + +@protocol BlockProvider +@required +-(int (^)(int)) block; +@end; + +int callProvidedBlock(id blockProvider, int argument) { + return [blockProvider block](argument); +} + +@protocol BlockConsumer +@required +-(int)callBlock:(int (^)(int))block argument:(int)argument; +@end; + +int callPlusOneBlock(id blockConsumer, int argument) { + return [blockConsumer callBlock:^int(int p) { return p + 1; } argument:argument]; +} + +@interface Blocks : NSObject ++(BOOL)blockIsNull:(void (^)(void))block; ++(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block; + +@property (class) void (^nullBlock)(void); +@property (class) void (^notNullBlock)(void); +@end; diff --git a/backend.native/tests/interop/objc/tests/blocks.kt b/backend.native/tests/interop/objc/tests/blocks.kt new file mode 100644 index 00000000000..1cee38f95e7 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/blocks.kt @@ -0,0 +1,21 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testBlocks() { + assertTrue(Blocks.blockIsNull(null)) + assertFalse(Blocks.blockIsNull({})) + + assertEquals(null, Blocks.nullBlock) + assertNotEquals(null, Blocks.notNullBlock) + + assertEquals(10, Blocks.same({ a, b, c, d -> a + b + c + d })!!(1, 2, 3, 4)) + + assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol { + override fun block(): (Int) -> Int = { it * 2 } + }, 111)) + + assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol { + override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument) + }, 321)) +} diff --git a/backend.native/tests/interop/objc/tests/blocks.m b/backend.native/tests/interop/objc/tests/blocks.m new file mode 100644 index 00000000000..bb32316a1a3 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/blocks.m @@ -0,0 +1,20 @@ +#import "blocks.h" + +@implementation Blocks ++(BOOL)blockIsNull:(void (^)(void))block { + return block == nil; +} + ++(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block { + return block; +} + ++(void (^)(void)) nullBlock { + return nil; +} + ++(void (^)(void)) notNullBlock { + return ^{}; +} + +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/callableReferences.h b/backend.native/tests/interop/objc/tests/callableReferences.h new file mode 100644 index 00000000000..90af865e4e4 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/callableReferences.h @@ -0,0 +1,7 @@ +#import + +@interface TestCallableReferences : NSObject +@property int value; +- (int)instanceMethod; ++ (int)classMethod:(int)first :(int)second; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/callableReferences.kt b/backend.native/tests/interop/objc/tests/callableReferences.kt new file mode 100644 index 00000000000..eb7176af39b --- /dev/null +++ b/backend.native/tests/interop/objc/tests/callableReferences.kt @@ -0,0 +1,28 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testCallableReferences() { + val createTestCallableReferences = ::TestCallableReferences + assertEquals("", createTestCallableReferences.name) + val testCallableReferences: Any = createTestCallableReferences() + assertTrue(testCallableReferences is TestCallableReferences) + + val valueRef: kotlin.reflect.KMutableProperty0 = testCallableReferences::value + assertEquals("value", valueRef.name) + assertEquals(0, valueRef()) + valueRef.set(42) + assertEquals(42, valueRef()) + + val classMethodRef = (TestCallableReferences)::classMethod + assertEquals("classMethod", classMethodRef.name) + assertEquals(3, classMethodRef(1, 2)) + + val instanceMethodRef = TestCallableReferences::instanceMethod + assertEquals("instanceMethod", instanceMethodRef.name) + assertEquals(42, instanceMethodRef(testCallableReferences)) + + val boundInstanceMethodRef = testCallableReferences::instanceMethod + assertEquals("instanceMethod", boundInstanceMethodRef.name) + assertEquals(42, boundInstanceMethodRef()) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/callableReferences.m b/backend.native/tests/interop/objc/tests/callableReferences.m new file mode 100644 index 00000000000..17a7b232559 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/callableReferences.m @@ -0,0 +1,11 @@ +#import "callableReferences.h" + +@implementation TestCallableReferences +- (int)instanceMethod { + return self.value; +} + ++ (int)classMethod:(int)first :(int)second { + return first + second; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/clashingWithAny.h b/backend.native/tests/interop/objc/tests/clashingWithAny.h new file mode 100644 index 00000000000..897e408528a --- /dev/null +++ b/backend.native/tests/interop/objc/tests/clashingWithAny.h @@ -0,0 +1,22 @@ +#import +#import + +@interface TestClashingWithAny1 : NSObject +-(NSString*)toString; +-(NSString*)toString_; +-(int)hashCode; +-(BOOL)equals:(id _Nullable)other; +@end; + +@interface TestClashingWithAny2 : NSObject +-(void)toString; +-(void)hashCode; +-(void)equals:(int)p; // May clash. +@end; + +@interface TestClashingWithAny3 : NSObject +// Not clashing actually. +-(NSString*)toString:(int)p; +-(int)hashCode:(int)p; +-(BOOL)equals; +@end; diff --git a/backend.native/tests/interop/objc/tests/clashingWithAny.kt b/backend.native/tests/interop/objc/tests/clashingWithAny.kt new file mode 100644 index 00000000000..cacf89ecd1c --- /dev/null +++ b/backend.native/tests/interop/objc/tests/clashingWithAny.kt @@ -0,0 +1,27 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testClashingWithAny() { + assertEquals("description", TestClashingWithAny1().toString()) + assertEquals("toString", TestClashingWithAny1().toString_()) + assertEquals("toString_", TestClashingWithAny1().toString__()) + assertEquals(1, TestClashingWithAny1().hashCode()) + assertEquals(31, TestClashingWithAny1().hashCode_()) + assertFalse(TestClashingWithAny1().equals(TestClashingWithAny1())) + assertTrue(TestClashingWithAny1().equals_(TestClashingWithAny1())) + + assertEquals("description", TestClashingWithAny2().toString()) + assertEquals(Unit, TestClashingWithAny2().toString_()) + assertEquals(2, TestClashingWithAny2().hashCode()) + assertEquals(Unit, TestClashingWithAny2().hashCode_()) + assertFalse(TestClashingWithAny2().equals(TestClashingWithAny2())) + assertEquals(Unit, TestClashingWithAny2().equals_(42)) + + assertEquals("description", TestClashingWithAny3().toString()) + assertEquals("toString:11", TestClashingWithAny3().toString(11)) + assertEquals(3, TestClashingWithAny3().hashCode()) + assertEquals(4, TestClashingWithAny3().hashCode(3)) + assertFalse(TestClashingWithAny3().equals(TestClashingWithAny3())) + assertTrue(TestClashingWithAny3().equals()) +} diff --git a/backend.native/tests/interop/objc/tests/clashingWithAny.m b/backend.native/tests/interop/objc/tests/clashingWithAny.m new file mode 100644 index 00000000000..789bd5baa8e --- /dev/null +++ b/backend.native/tests/interop/objc/tests/clashingWithAny.m @@ -0,0 +1,68 @@ +#import "clashingWithAny.h" + +@implementation TestClashingWithAny1 +-(NSString*)description { + return @"description"; +} + +-(NSString*)toString { + return @"toString"; +} + +-(NSString*)toString_ { + return @"toString_"; +} + +-(NSUInteger)hash { + return 1; +} + +-(int)hashCode { + return 31; +} + +-(BOOL)equals:(id _Nullable)other { + return YES; +} +@end; + +@implementation TestClashingWithAny2 +-(NSString*)description { + return @"description"; +} + +-(void)toString { +} + +-(NSUInteger)hash { + return 2; +} + +-(void)hashCode { +} + +-(void)equals:(int)p { +} +@end; + +@implementation TestClashingWithAny3 +-(NSString*)description { + return @"description"; +} + +-(NSString*)toString:(int)p { + return [NSString stringWithFormat:@"%s:%d", "toString", p]; +} + +-(NSUInteger)hash { + return 3; +} + +-(int)hashCode:(int)p { + return p + 1; +} + +-(BOOL)equals { + return YES; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/constructorReturnsNull.h b/backend.native/tests/interop/objc/tests/constructorReturnsNull.h new file mode 100644 index 00000000000..25b079f5a95 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/constructorReturnsNull.h @@ -0,0 +1,5 @@ +#import + +@interface TestConstructorReturnsNull : NSObject +- (instancetype)init; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/constructorReturnsNull.kt b/backend.native/tests/interop/objc/tests/constructorReturnsNull.kt new file mode 100644 index 00000000000..782ca591cbe --- /dev/null +++ b/backend.native/tests/interop/objc/tests/constructorReturnsNull.kt @@ -0,0 +1,9 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testConstructorReturnsNull() { + assertFailsWith() { + TestConstructorReturnsNull() + } +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/constructorReturnsNull.m b/backend.native/tests/interop/objc/tests/constructorReturnsNull.m new file mode 100644 index 00000000000..07a6116f0dd --- /dev/null +++ b/backend.native/tests/interop/objc/tests/constructorReturnsNull.m @@ -0,0 +1,7 @@ +#import "constructorReturnsNull.h" + +@implementation TestConstructorReturnsNull +- (instancetype)init { + return nil; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/conversions.kt b/backend.native/tests/interop/objc/tests/conversions.kt new file mode 100644 index 00000000000..a413dca0c11 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/conversions.kt @@ -0,0 +1,18 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testConversions() { + testMethodsOfAny(emptyList(), NSArray()) + testMethodsOfAny(listOf(1, "foo"), nsArrayOf(1, "foo")) + testMethodsOfAny(42, NSNumber.numberWithInt(42), 17) + testMethodsOfAny(true, NSNumber.numberWithBool(true), false) +} + +private fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: Any = Any()) { + assertEquals(kotlinObject.hashCode(), equalNsObject.hashCode()) + assertEquals(kotlinObject.toString(), equalNsObject.toString()) + assertEquals(kotlinObject, equalNsObject) + assertEquals(equalNsObject, kotlinObject) + assertNotEquals(equalNsObject, otherObject) +} diff --git a/backend.native/tests/interop/objc/tests/customString.h b/backend.native/tests/interop/objc/tests/customString.h new file mode 100644 index 00000000000..1e3078b0ee7 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/customString.h @@ -0,0 +1,16 @@ +#import + +@interface CustomString : NSString +- (instancetype)initWithValue:(int)value; +@property int value; +@end; + +CustomString* _Nonnull createCustomString(int value) { + return [[CustomString alloc] initWithValue:value]; +} + +int getCustomStringValue(CustomString* str) { + return str.value; +} + +extern BOOL customStringDeallocated; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/customString.kt b/backend.native/tests/interop/objc/tests/customString.kt new file mode 100644 index 00000000000..6b90633a4dd --- /dev/null +++ b/backend.native/tests/interop/objc/tests/customString.kt @@ -0,0 +1,21 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testCustomString() { + assertFalse(customStringDeallocated) + + fun test() = autoreleasepool { + val str: String = createCustomString(321) + assertEquals("321", str) + assertEquals("CustomString", str.objCClassName) + assertEquals(321, getCustomStringValue(str)) + } + + test() + kotlin.native.internal.GC.collect() + assertTrue(customStringDeallocated) +} + +private val Any.objCClassName: String + get() = object_getClassName(this)!!.toKString() \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/customString.m b/backend.native/tests/interop/objc/tests/customString.m new file mode 100644 index 00000000000..1a03f684ca9 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/customString.m @@ -0,0 +1,32 @@ +#import "customString.h" + +BOOL customStringDeallocated = NO; + +@implementation CustomString { + NSString* delegate; +} + +- (instancetype)initWithValue:(int)value { + if (self = [super init]) { + self->delegate = @(value).description; + self.value = value; + } + return self; +} + +- (unichar)characterAtIndex:(NSUInteger)index { + return [self->delegate characterAtIndex:index]; +} + +- (NSUInteger)length { + return self->delegate.length; +} + +- (id)copyWithZone:(NSZone *)zone { + return self; +} + +- (void)dealloc { + customStringDeallocated = YES; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/exceptions.h b/backend.native/tests/interop/objc/tests/exceptions.h new file mode 100644 index 00000000000..8350db2d591 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/exceptions.h @@ -0,0 +1,9 @@ +#import + +@protocol ExceptionThrower +-(void)throwException; +@end; + +@interface ExceptionThrowerManager : NSObject ++(void)throwExceptionWith:(id)thrower; +@end; diff --git a/backend.native/tests/interop/objc/tests/exceptions.kt b/backend.native/tests/interop/objc/tests/exceptions.kt new file mode 100644 index 00000000000..f01f8314dd5 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/exceptions.kt @@ -0,0 +1,15 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testExceptions() { + assertFailsWith { + ExceptionThrowerManager.throwExceptionWith(object : NSObject(), ExceptionThrowerProtocol { + override fun throwException() { + throw MyException() + } + }) + } +} + +private class MyException : Throwable() \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/exceptions.m b/backend.native/tests/interop/objc/tests/exceptions.m new file mode 100644 index 00000000000..1154f23d56f --- /dev/null +++ b/backend.native/tests/interop/objc/tests/exceptions.m @@ -0,0 +1,7 @@ +#import "exceptions.h" + +@implementation ExceptionThrowerManager ++(void)throwExceptionWith:(id)thrower { + [thrower throwException]; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/initWithCustomSelector.h b/backend.native/tests/interop/objc/tests/initWithCustomSelector.h new file mode 100644 index 00000000000..47d026e2259 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/initWithCustomSelector.h @@ -0,0 +1,8 @@ +#import + +@interface TestInitWithCustomSelector : NSObject +-(instancetype)initCustom; +@property BOOL custom; + ++(instancetype _Nonnull)createCustom; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/initWithCustomSelector.kt b/backend.native/tests/interop/objc/tests/initWithCustomSelector.kt new file mode 100644 index 00000000000..df924753777 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/initWithCustomSelector.kt @@ -0,0 +1,25 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testInitWithCustomSelector() { + assertFalse(TestInitWithCustomSelector().custom) + assertTrue(TestInitWithCustomSelector(custom = Unit).custom) + + val customSubclass: TestInitWithCustomSelector = TestInitWithCustomSelectorSubclass.createCustom() + assertTrue(customSubclass is TestInitWithCustomSelectorSubclass) + assertTrue(customSubclass.custom) + + // Test side effect: + var ok = false + assertTrue(TestInitWithCustomSelector(run { ok = true }).custom) + assertTrue(ok) +} + +private class TestInitWithCustomSelectorSubclass : TestInitWithCustomSelector { + @OverrideInit constructor(custom: Unit) : super(custom) { + assertSame(Unit, custom) + } + + companion object : TestInitWithCustomSelectorMeta() +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/initWithCustomSelector.m b/backend.native/tests/interop/objc/tests/initWithCustomSelector.m new file mode 100644 index 00000000000..0e5edee5823 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/initWithCustomSelector.m @@ -0,0 +1,23 @@ +#import "initWithCustomSelector.h" + +@implementation TestInitWithCustomSelector + +-(instancetype)initCustom { + if (self = [super init]) { + self.custom = YES; + } + return self; +} + +-(instancetype)init { + if (self = [super init]) { + self.custom = NO; + } + return self; +} + ++(instancetype)createCustom { + return [[self alloc] initCustom]; +} + +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/main.kt b/backend.native/tests/interop/objc/tests/main.kt new file mode 100644 index 00000000000..178c49725de --- /dev/null +++ b/backend.native/tests/interop/objc/tests/main.kt @@ -0,0 +1,11 @@ +import kotlin.system.exitProcess +import kotlinx.cinterop.autoreleasepool +import kotlin.native.internal.test.testLauncherEntryPoint + +fun main(args: Array) { + autoreleasepool { + val exitCode = testLauncherEntryPoint(args) + // Note: this test runner checks for memory leaks after successful execution, unlike standard one. + if (exitCode != 0) exitProcess(exitCode) + } +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/mangling.h b/backend.native/tests/interop/objc/tests/mangling.h new file mode 100644 index 00000000000..a4a7868b52b --- /dev/null +++ b/backend.native/tests/interop/objc/tests/mangling.h @@ -0,0 +1,25 @@ +#import + +// [KT-36067] cinterop tool fails when there is a structure member named Companion + +struct EnumFieldMangleStruct { + enum {Companion, Any} smth; +}; +struct EnumFieldMangleStruct enumMangledStruct = { Any }; + +struct MyStruct { + int Companion; // simple clash + int _Companion; + int $_Companion; + int super; +}; + +struct MyStruct myStruct = {11, 12, 13, 14}; + +@protocol Proto +@property int Companion; // clash on implementing +@end; + +@interface FooMangled : NSObject +//- (void) CompanionS; // mangleSimple does not support this: it may clash after mangling +@end; diff --git a/backend.native/tests/interop/objc/tests/mangling.kt b/backend.native/tests/interop/objc/tests/mangling.kt new file mode 100644 index 00000000000..1682357618d --- /dev/null +++ b/backend.native/tests/interop/objc/tests/mangling.kt @@ -0,0 +1,18 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testMangling() { + assertEquals(11, myStruct.`Companion$`) + assertEquals(12, myStruct._Companion) + assertEquals(13, myStruct.`$_Companion`) + assertEquals(14, myStruct.`super`) + + val objc = FooMangled() + objc.`Companion$` = 99 + assertEquals(99, objc.Companion()) + assertEquals(99, objc.`Companion$`) + + enumMangledStruct.smth = Companion + assertEquals(Companion, enumMangledStruct.smth) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/mangling.m b/backend.native/tests/interop/objc/tests/mangling.m new file mode 100644 index 00000000000..90f6b717b97 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/mangling.m @@ -0,0 +1,6 @@ +#import "mangling.h" + +// [KT-36067] mangling +@implementation FooMangled : NSObject +@synthesize Companion; +@end; diff --git a/backend.native/tests/interop/objc/tests/multipleInheritanceClash.h b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.h new file mode 100644 index 00000000000..a1f25e029f0 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.h @@ -0,0 +1,17 @@ +#import + +@interface MultipleInheritanceClashBase : NSObject +@property (nonnull) MultipleInheritanceClashBase* delegate; +@end; + +@protocol MultipleInheritanceClash +@optional +@property (nullable) id delegate; +@end; + +@interface MultipleInheritanceClash1 : MultipleInheritanceClashBase +@end; + +@interface MultipleInheritanceClash2 : MultipleInheritanceClashBase +@property MultipleInheritanceClashBase* delegate; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/multipleInheritanceClash.kt b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.kt new file mode 100644 index 00000000000..078b42458fa --- /dev/null +++ b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.kt @@ -0,0 +1,18 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testMultipleInheritanceClash() { + val clash1 = MultipleInheritanceClash1() + val clash2 = MultipleInheritanceClash2() + + clash1.delegate = clash1 + assertEquals(clash1, clash1.delegate) + clash1.setDelegate(clash2) + assertEquals(clash2, clash1.delegate()) + + clash2.delegate = clash1 + assertEquals(clash1, clash2.delegate) + clash2.setDelegate(clash2) + assertEquals(clash2, clash2.delegate()) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/multipleInheritanceClash.m b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.m new file mode 100644 index 00000000000..5586cfbf12c --- /dev/null +++ b/backend.native/tests/interop/objc/tests/multipleInheritanceClash.m @@ -0,0 +1,10 @@ +#import "multipleInheritanceClash.h" + +@implementation MultipleInheritanceClashBase +@end; + +@implementation MultipleInheritanceClash1 +@end; + +@implementation MultipleInheritanceClash2 +@end; diff --git a/backend.native/tests/interop/objc/tests/nsOutputStream.kt b/backend.native/tests/interop/objc/tests/nsOutputStream.kt new file mode 100644 index 00000000000..2bd82ab34a7 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/nsOutputStream.kt @@ -0,0 +1,8 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testNSOutputStreamToMemoryConstructor() { + val stream: Any = NSOutputStream(toMemory = Unit) + assertTrue(stream is NSOutputStream) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/objcWeakRefs.h b/backend.native/tests/interop/objc/tests/objcWeakRefs.h new file mode 100644 index 00000000000..15ff66dd4cf --- /dev/null +++ b/backend.native/tests/interop/objc/tests/objcWeakRefs.h @@ -0,0 +1,13 @@ +#import + +@class DeallocListener; + +@interface DeallocExecutor : NSObject +@property DeallocListener* deallocListener; +@end; + +@interface DeallocListener : NSObject +@property (weak) DeallocExecutor* deallocExecutor; +@property BOOL deallocated; +-(BOOL)deallocExecutorIsNil; +@end; diff --git a/backend.native/tests/interop/objc/tests/objcWeakRefs.kt b/backend.native/tests/interop/objc/tests/objcWeakRefs.kt new file mode 100644 index 00000000000..26f3beafffe --- /dev/null +++ b/backend.native/tests/interop/objc/tests/objcWeakRefs.kt @@ -0,0 +1,45 @@ +import kotlinx.cinterop.* +import kotlin.native.concurrent.* +import kotlin.test.* +import objcTests.* + +@Test fun testObjCWeakRef() { + val deallocListener = DeallocListener() + assertFalse(deallocListener.deallocated) + + testObjCWeakRef0(deallocListener) + + kotlin.native.internal.GC.collect() + assertTrue(deallocListener.deallocated) + assertTrue(deallocListener.deallocExecutorIsNil()) +} + +private fun testObjCWeakRef0(deallocListener: DeallocListener) = withWorker { + assertTrue(deallocListener.deallocExecutorIsNil()) + + val obj = object : DeallocExecutor() {} + deallocListener.deallocExecutor = obj + obj.deallocListener = deallocListener + + assertFalse(deallocListener.deallocExecutorIsNil()) + +// TODO: can't actually test, Obj-C runtime doesn't expect _tryRetain throwing an exception. +// runInWorker { +// assertFailsWith { +// deallocListener.deallocExecutorIsNil() +// } +// } + + obj.freeze() + + runInWorker { + // [deallocListener.deallocExecutorIsNil()] calls deallocExecutor getter, which retains [obj] and either + // puts it to autoreleasepool or releases it immediately (Obj-C ARC optimization). + // Wrap the call to autoreleasepool to ensure [obj] will be released: + autoreleasepool { + assertFalse(deallocListener.deallocExecutorIsNil()) + } + // Process release of Kotlin reference to [obj] in any case: + kotlin.native.internal.GC.collect() + } +} diff --git a/backend.native/tests/interop/objc/tests/objcWeakRefs.m b/backend.native/tests/interop/objc/tests/objcWeakRefs.m new file mode 100644 index 00000000000..42c2ee9fa16 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/objcWeakRefs.m @@ -0,0 +1,13 @@ +#import "objcWeakRefs.h" + +@implementation DeallocExecutor +-(void)dealloc { + self.deallocListener.deallocated = YES; +} +@end; + +@implementation DeallocListener +-(BOOL)deallocExecutorIsNil { + return self.deallocExecutor == nil; +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/overrideInit.h b/backend.native/tests/interop/objc/tests/overrideInit.h new file mode 100644 index 00000000000..85a3624822e --- /dev/null +++ b/backend.native/tests/interop/objc/tests/overrideInit.h @@ -0,0 +1,6 @@ +#import + +@interface TestOverrideInit : NSObject +-(instancetype)initWithValue:(int)value NS_DESIGNATED_INITIALIZER; ++(instancetype)createWithValue:(int)value; +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/overrideInit.kt b/backend.native/tests/interop/objc/tests/overrideInit.kt new file mode 100644 index 00000000000..2631e6659ca --- /dev/null +++ b/backend.native/tests/interop/objc/tests/overrideInit.kt @@ -0,0 +1,11 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testOverrideInit() { + assertEquals(42, (TestOverrideInitImpl.createWithValue(42) as TestOverrideInitImpl).value) +} + +private class TestOverrideInitImpl @OverrideInit constructor(val value: Int) : TestOverrideInit(value) { + companion object : TestOverrideInitMeta() +} diff --git a/backend.native/tests/interop/objc/tests/overrideInit.m b/backend.native/tests/interop/objc/tests/overrideInit.m new file mode 100644 index 00000000000..0ab7225e500 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/overrideInit.m @@ -0,0 +1,11 @@ +#import "OverrideInit.h" + +@implementation TestOverrideInit +-(instancetype)initWithValue:(int)value { + return self = [super init]; +} + ++(instancetype)createWithValue:(int)value { + return [[self alloc] initWithValue:value]; +} +@end; diff --git a/backend.native/tests/interop/objc/tests/sharing.kt b/backend.native/tests/interop/objc/tests/sharing.kt new file mode 100644 index 00000000000..3af3c312f14 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/sharing.kt @@ -0,0 +1,36 @@ +import kotlinx.cinterop.* +import kotlin.native.concurrent.* +import kotlin.test.* +import objcTests.* + +private class NSObjectImpl : NSObject() { + var x = 111 +} + +@Test fun testSharing() = withWorker { + val obj = NSObjectImpl() + val array = nsArrayOf(obj) + + assertFalse(obj.isFrozen) + + runInWorker { + assertFailsWith { + array.objectAtIndex(0) + } + } + + obj.x = 222 + obj.freeze() + assertTrue(obj.isFrozen) + + runInWorker { + val obj1 = array.objectAtIndex(0) as NSObjectImpl + assertFailsWith { + obj1.x = 333 + } + } + + assertEquals(222, obj.x) + + // TODO: test [obj release] etc. +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/utils.kt b/backend.native/tests/interop/objc/tests/utils.kt new file mode 100644 index 00000000000..4dc98a58f0f --- /dev/null +++ b/backend.native/tests/interop/objc/tests/utils.kt @@ -0,0 +1,17 @@ +import kotlinx.cinterop.* +import kotlin.native.concurrent.* +import objcTests.* + +fun Worker.runInWorker(block: () -> Unit) { + block.freeze() + val future = this.execute(TransferMode.SAFE, { block }) { + it() + } + future.result // Throws on failure. +} + +fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply { + elements.forEach { + this.addObject(it as ObjCObject) + } +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/varargs.h b/backend.native/tests/interop/objc/tests/varargs.h new file mode 100644 index 00000000000..113844b27ed --- /dev/null +++ b/backend.native/tests/interop/objc/tests/varargs.h @@ -0,0 +1,20 @@ +#import + +@interface TestVarargs : NSObject +-(instancetype _Nonnull)initWithFormat:(NSString*)format, ...; ++(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ...; +@property NSString* formatted; + ++(NSString* _Nonnull)stringWithFormat:(NSString*)format, ...; ++(NSObject* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args; +@end; + +@interface TestVarargs (TestVarargsExtension) +-(instancetype _Nonnull)initWithFormat:(NSString*)format, ...; +@end; + +@interface TestVarargsSubclass : TestVarargs +// Test clashes: +-(instancetype _Nonnull)initWithFormat:(NSString*)format args:(void*)args; ++(NSString* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args; +@end; diff --git a/backend.native/tests/interop/objc/tests/varargs.kt b/backend.native/tests/interop/objc/tests/varargs.kt new file mode 100644 index 00000000000..c1808fb9e09 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/varargs.kt @@ -0,0 +1,53 @@ +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testVarargs() { + assertEquals( + "a b -1", + TestVarargs.testVarargsWithFormat( + "%@ %s %d", + "a" as NSString, "b".cstr, (-1).toByte() + ).formatted + ) + + assertEquals( + "2 3 9223372036854775807", + TestVarargs( + "%d %d %lld", + 2.toShort(), 3, Long.MAX_VALUE + ).formatted + ) + + assertEquals( + "0.1 0.2 1 0", + TestVarargs.create( + "%.1f %.1lf %d %d", + 0.1.toFloat(), 0.2, true, false + ).formatted + ) + + assertEquals( + "1 2 3", + TestVarargs( + format = "%d %d %d", + args = *arrayOf(1, 2, 3) + ).formatted + ) + + assertEquals( + "4 5 6", + TestVarargs( + args = *arrayOf(4, *arrayOf(5, 6)), + format = "%d %d %d" + ).formatted + ) + + assertEquals( + "7", + TestVarargsSubclass.stringWithFormat( + "%d", + 7 + ) + ) +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/varargs.m b/backend.native/tests/interop/objc/tests/varargs.m new file mode 100644 index 00000000000..4b9e0b41cb0 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/varargs.m @@ -0,0 +1,51 @@ +#import +#import +#import "varargs.h" + +@implementation TestVarargs +-(instancetype _Nonnull)initWithFormat:(NSString*)format, ... { + self = [super init]; + + va_list args; + va_start(args, format); + self.formatted = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + return self; +} + ++(instancetype _Nonnull)testVarargsWithFormat:(NSString*)format, ... { + TestVarargs* result = [[self alloc] init]; + + va_list args; + va_start(args, format); + result.formatted = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + return result; +} + ++(NSString* _Nonnull)stringWithFormat:(NSString*)format, ... { + va_list args; + va_start(args, format); + NSString* result = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + return result; +} + ++(NSObject* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args { + abort(); +} + +@end; + +@implementation TestVarargsSubclass +-(instancetype _Nonnull)initWithFormat:(NSString*)format args:(void*)args { + abort(); +} + ++(NSString* _Nonnull)stringWithFormat:(NSString*)format args:(void*)args { + abort(); +} +@end; \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/weakRefs.h b/backend.native/tests/interop/objc/tests/weakRefs.h new file mode 100644 index 00000000000..826629c564b --- /dev/null +++ b/backend.native/tests/interop/objc/tests/weakRefs.h @@ -0,0 +1,5 @@ +#import + +NSObject* createNSObject() { + return [NSObject new]; +} \ No newline at end of file diff --git a/backend.native/tests/interop/objc/tests/weakRefs.kt b/backend.native/tests/interop/objc/tests/weakRefs.kt new file mode 100644 index 00000000000..fa714be5601 --- /dev/null +++ b/backend.native/tests/interop/objc/tests/weakRefs.kt @@ -0,0 +1,35 @@ +import kotlin.native.ref.* +import kotlinx.cinterop.* +import kotlin.test.* +import objcTests.* + +@Test fun testWeakRefs() { + testWeakReference({ createNSObject()!! }) + + createAndAbandonWeakRef(NSObject()) + + testWeakReference({ NSArray.arrayWithArray(listOf(42)) as NSArray }) +} + +private fun testWeakReference(block: () -> NSObject) { + val ref = autoreleasepool { + createAndTestWeakReference(block) + } + + kotlin.native.internal.GC.collect() + + assertNull(ref.get()) +} + +private fun createAndTestWeakReference(block: () -> NSObject): WeakReference { + val ref = createWeakReference(block) + assertNotNull(ref.get()) + assertEquals(ref.get()!!.hash(), ref.get()!!.hash()) + return ref +} + +private fun createWeakReference(block: () -> NSObject) = WeakReference(block()) + +private fun createAndAbandonWeakRef(obj: NSObject) { + WeakReference(obj) +} \ No newline at end of file