[K/N][Tests] Migrate objcSmoke & objcTests tests

^KT-61259
This commit is contained in:
Vladimir Sukharev
2024-01-24 18:04:17 +01:00
committed by Space Team
parent 58f7dd1c83
commit 524f0d335e
98 changed files with 227 additions and 8 deletions
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- AUTO-GENERATED FILE. DO NOT MODIFY. -->
<!-- This file was automatically generated by the LocoLaser tool. -->
<!-- It should not be modified by hand. -->
<plist version="1.0">
<dict>
<key>screen_main_plural_string</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@value@</string>
<key>value</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>Plural: %d apples</string>
<key>one</key>
<string>Plural: one apple</string>
<key>zero</key>
<string>Plural: no apples</string>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,5 @@
language = Objective-C
headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h Foundation/NSBundle.h
headerFilter = smoke.h Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h \
objc/objc.h Foundation/NSBundle.h
linkerOpts = -lobjcsmoke
@@ -0,0 +1,8 @@
language = Objective-C
headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h Foundation/NSBundle.h \
objc/runtime.h
headerFilter = tests/**.h \
Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h Foundation/NSStream.h \
objc/objc.h Foundation/NSBundle.h \
objc/runtime.h
linkerOpts = -lobjctests
+117
View File
@@ -0,0 +1,117 @@
#import <objc/NSObject.h>
#import <CoreFoundation/CoreFoundation.h>
@class Foo;
@protocol Printer;
@protocol Printer;
@protocol Empty
@end
@protocol Forward;
@class Forward;
void useForward1(Forward * p) {}
void useForward2(id<Forward> p) {}
typedef NSString NSStringTypedef;
@interface Foo : NSObject <Empty>
@property NSStringTypedef* name;
-(void)helloWithPrinter:(id <Printer>)printer;
@end
@interface Foo (FooExtensions)
-(void)hello;
@end
@protocol Printer
@required
-(void)print:(const char*)string;
@end
@protocol MutablePair
@required
@property (readonly) int first;
@property (readonly) int second;
-(void)update:(int)index add:(int)delta;
-(void)update:(int)index sub:(int)delta;
@end
void replacePairElements(id <MutablePair> pair, int first, int second);
int invoke1(int arg, int (^block)(int)) {
return block(arg);
}
void invoke2(void (^block)(void)) {
block();
}
int (^getSupplier(int x))(void);
Class (^ _Nonnull getClassGetter(NSObject* obj))(void);
extern NSString* globalString;
extern NSObject* globalObject;
int formatStringLength(NSString* format, ...);
#define STRING_MACRO @"String macro"
#define CFSTRING_MACRO CFSTR("CFString macro")
typedef NS_ENUM(int32_t, ForwardDeclaredEnum);
typedef NS_ENUM(int32_t, ForwardDeclaredEnum) {
ZERO, ONE, TWO,
};
@protocol ObjectFactory
@required
-(id)create;
@end
id createObjectWithFactory(id<ObjectFactory> factory) {
return [factory create];
}
@protocol CustomRetainMethods
@required
-(id)returnRetained:(id)obj __attribute__((ns_returns_retained));
-(void)consume:(id) __attribute__((ns_consumed)) obj;
-(void)consumeSelf __attribute__((ns_consumes_self));
-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained));
@end
extern BOOL unexpectedDeallocation;
@interface MustNotBeDeallocated : NSObject
@end
@interface CustomRetainMethodsImpl : MustNotBeDeallocated <CustomRetainMethods>
@end
static MustNotBeDeallocated* retainedObj;
static void (^retainedBlock)(void);
void useCustomRetainMethods(id<CustomRetainMethods> p) {
MustNotBeDeallocated* obj = [MustNotBeDeallocated new];
retainedObj = obj; // Retain to detect possible over-release.
[p returnRetained:obj];
[p consume:p];
[p consumeSelf];
MustNotBeDeallocated* capturedObj = [MustNotBeDeallocated new];
retainedBlock = ^{ [capturedObj description]; }; // Retain to detect possible over-release.
[p returnRetainedBlock:retainedBlock]();
}
id getPrinterProtocolRaw() {
return @protocol(Printer);
}
Protocol* getPrinterProtocol() {
return @protocol(Printer);
}
+241
View File
@@ -0,0 +1,241 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
import kotlinx.cinterop.*
import objcSmoke.*
import kotlin.native.concurrent.*
import kotlin.native.ref.*
import kotlin.test.*
fun main(args: Array<String>) {
// Test relies on full deinitialization at shutdown.
kotlin.native.runtime.Debugging.forceCheckedShutdown = true
autoreleasepool {
run()
}
}
fun run() {
// TODO: migrate remaining tests to interop/objc/tests/
testTypeOps()
testCustomRetain()
testExportObjCClass()
testLocalizedStrings()
assertEquals(2, ForwardDeclaredEnum.TWO.value)
println(
getSupplier(
invoke1(42) { it * 2 }
)!!()
)
val foo = Foo()
val classGetter = getClassGetter(foo)
invoke2 { println(classGetter()) }
foo.hello()
foo.name = "everybody"
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
override fun print(string: CPointer<ByteVar>?) {
println("Kotlin says: " + string?.toKString())
}
})
Bar().hello()
val pair = MutablePairImpl(42, 17)
replacePairElements(pair, 1, 2)
pair.swap()
println("${pair.first}, ${pair.second}")
val defaultPair = MutablePairImpl()
assertEquals(defaultPair.first(), 123)
assertEquals(defaultPair.second(), 321)
// equals and hashCode (virtually):
val map = mapOf(foo to pair, pair to foo)
// equals (directly):
if (!foo.equals(pair)) {
// toString (directly):
println(map[pair].toString() + map[foo].toString() == foo.description() + pair.description())
}
// hashCode (directly):
// hash() returns value of NSUInteger type.
val hash = if (sizeOf<NSUIntegerVar>() == 4L) {
foo.hash().toInt()
} else {
foo.hash().let { it.toInt() xor (it shr 32).toInt() }
}
if (foo.hashCode() == hash) {
// toString (virtually):
if (Platform.memoryModel == MemoryModel.STRICT)
println(map.keys.map { it.toString() }.minOrNull() == foo.description())
else
// TODO: hack until proper cycle collection in maps.
println(true)
}
println(globalString)
autoreleasepool {
globalString = "Another global string"
}
println(globalString)
println(globalObject)
globalObject = object : NSObject() {
override fun description() = "global object"
}
println(globalObject)
globalObject = null // Prevent Kotlin object above from leaking.
println(formatStringLength("%d %d", 42, 17))
println(STRING_MACRO)
println(CFSTRING_MACRO)
// Ensure that overriding method bridge has retain-autorelease sequence:
createObjectWithFactory(object : NSObject(), ObjectFactoryProtocol {
override fun create() = autoreleasepool { NSObject() }
})
}
fun MutablePairProtocol.swap() {
update(0, add = second)
update(1, sub = first)
update(0, add = second)
update(1, sub = second*2)
}
class Bar : Foo() {
override fun helloWithPrinter(printer: PrinterProtocol?) = memScoped {
printer!!.print("Hello from Kotlin".cstr.getPointer(memScope))
}
}
@Suppress("CONFLICTING_OVERLOADS")
class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol {
private var elements = intArrayOf(first, second)
override fun first() = elements.first()
override fun second() = elements.last()
override fun update(index: Int, add: Int) {
elements[index] += add
}
override fun update(index: Int, sub: Int) {
elements[index] -= sub
}
constructor() : this(123, 321)
}
interface Zzz
fun testTypeOps() {
assertTrue(99.asAny() is NSNumber)
assertTrue(null.asAny() is NSNumber?)
assertFalse(null.asAny() is NSNumber)
assertFalse("".asAny() is NSNumber)
assertTrue("bar".asAny() is NSString)
assertTrue(Foo.asAny() is FooMeta)
assertFalse(Foo.asAny() is Zzz)
assertTrue(Foo.asAny() is NSObjectMeta)
assertTrue(Foo.asAny() is NSObject)
assertFalse(Foo.asAny() is Foo)
assertTrue(NSString.asAny() is NSCopyingProtocolMeta)
assertFalse(NSString.asAny() is NSCopyingProtocol)
assertTrue(NSValue.asAny() is NSObjectProtocolMeta)
assertFalse(NSValue.asAny() is NSObjectProtocol) // Must be true, but not implemented properly yet.
assertFalse(Any() is ObjCClass)
assertFalse(Any() is ObjCClassOf<*>)
assertFalse(NSObject().asAny() is ObjCClass)
assertFalse(NSObject().asAny() is ObjCClassOf<*>)
assertTrue(NSObject.asAny() is ObjCClass)
assertTrue(NSObject.asAny() is ObjCClassOf<*>)
assertFalse(Any() is ObjCProtocol)
assertTrue(getPrinterProtocolRaw() is ObjCProtocol)
val printerProtocol = getPrinterProtocol()!!
assertTrue(printerProtocol.asAny() is ObjCProtocol)
assertEquals(3u, ("foo" as NSString).length())
assertEquals(4u, ((1..4).joinToString("") as NSString).length())
assertEquals(2u, (listOf(0, 1) as NSArray).count())
assertEquals(42L, (42 as NSNumber).longLongValue())
assertFails { "bar" as NSNumber }
assertFails { 42 as NSArray }
assertFails { listOf(1) as NSString }
assertFails { NSObject() as Bar }
assertFails { NSObject() as NSValue }
MutablePairImpl(1, 2).asAny() as MutablePairProtocol
assertFails { MutablePairImpl(1, 2).asAny() as Foo }
}
private lateinit var retainedMustNotBeDeallocated: MustNotBeDeallocated
fun testCustomRetain() {
fun test() {
useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol {
override fun returnRetained(obj: Any?) = obj
override fun consume(obj: Any?) {}
override fun consumeSelf() {}
override fun returnRetainedBlock(block: (() -> Unit)?) = block
})
CustomRetainMethodsImpl().let {
it.returnRetained(Any())
retainedMustNotBeDeallocated = MustNotBeDeallocated() // Retain to detect possible over-release.
it.consume(retainedMustNotBeDeallocated)
it.consumeSelf()
it.returnRetainedBlock({})!!()
}
}
autoreleasepool {
test()
kotlin.native.runtime.GC.collect()
}
assertFalse(unexpectedDeallocation)
}
private const val TestExportObjCClass1Name = "TestExportObjCClass"
@ExportObjCClass(TestExportObjCClass1Name) class TestExportObjCClass1 : NSObject()
@ExportObjCClass class TestExportObjCClass2 : NSObject()
const val TestExportObjCClass34Name = "TestExportObjCClass34"
@ExportObjCClass(TestExportObjCClass34Name) class TestExportObjCClass3 : NSObject()
@ExportObjCClass(TestExportObjCClass34Name) class TestExportObjCClass4 : NSObject()
fun testExportObjCClass() {
assertEquals(TestExportObjCClass1Name, TestExportObjCClass1().objCClassName)
assertEquals("TestExportObjCClass2", TestExportObjCClass2().objCClassName)
assertTrue((TestExportObjCClass3().objCClassName == TestExportObjCClass34Name)
xor (TestExportObjCClass4().objCClassName == TestExportObjCClass34Name))
}
fun testLocalizedStrings() {
val key = "screen_main_plural_string"
val localizedString = NSBundle.mainBundle.localizedStringForKey(key, value = "", table = "Localizable")
val string = NSString.localizedStringWithFormat(localizedString, 5)
assertEquals("Plural: 5 apples", string)
}
private val Any.objCClassName: String
get() = object_getClassName(this)!!.toKString()
fun Any?.asAny(): Any? = this
+97
View File
@@ -0,0 +1,97 @@
#import <stdio.h>
#import <Foundation/NSString.h>
#import "smoke.h"
@interface CPrinter : NSObject <Printer>
-(void)print:(const char*)string;
@end
@implementation CPrinter
-(void)print:(const char*)string {
printf("%s\n", string);
fflush(stdout);
}
@end
@implementation Foo
@synthesize name;
-(instancetype)init {
if (self = [super init]) {
self.name = @"World";
}
return self;
}
-(void)helloWithPrinter:(id <Printer>)printer {
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
[printer print:message.UTF8String];
}
-(void)dealloc {
printf("Deallocated\n");
}
@end
@implementation Foo (FooExtensions)
-(void)hello {
CPrinter* printer = [[CPrinter alloc] init];
[self helloWithPrinter:printer];
}
@end
void replacePairElements(id <MutablePair> pair, int first, int second) {
[pair update:0 add:(first - pair.first)];
[pair update:1 sub:(pair.second - second)];
}
int (^getSupplier(int x))(void) {
return ^{
return x;
};
}
Class (^ _Nonnull getClassGetter(NSObject* obj))() {
return ^{ return obj.class; };
}
NSString* globalString = @"Global string";
NSObject* globalObject = nil;
int formatStringLength(NSString* format, ...) {
va_list args;
va_start(args, format);
NSString* result = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
return result.length;
}
BOOL unexpectedDeallocation = NO;
@implementation MustNotBeDeallocated
-(void)dealloc {
unexpectedDeallocation = YES;
}
@end
static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl;
@implementation CustomRetainMethodsImpl
-(id)returnRetained:(id)obj __attribute__((ns_returns_retained)) {
return obj;
}
-(void)consume:(id) __attribute__((ns_consumed)) obj {
}
-(void)consumeSelf __attribute__((ns_consumes_self)) {
retainedCustomRetainMethodsImpl = self; // Retain to detect possible over-release.
}
-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained)) {
return block;
}
@end
+19
View File
@@ -0,0 +1,19 @@
84
Foo
Deallocated
Hello, World!
Kotlin says: Hello, everybody!
Hello from Kotlin
2, 1
true
true
Global string
Another global string
null
global object
5
String macro
CFString macro
Deallocated
Deallocated
Class TestExportObjCClass34 has multiple implementations. Which one will be used is undefined.
@@ -0,0 +1,241 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
import kotlinx.cinterop.*
import objcSmoke.*
import kotlin.native.concurrent.*
import kotlin.native.ref.*
import kotlin.test.*
fun main(args: Array<String>) {
// Test relies on full deinitialization at shutdown.
kotlin.native.runtime.Debugging.forceCheckedShutdown = true
autoreleasepool {
run()
}
}
fun run() {
// TODO: migrate remaining tests to interop/objc/tests/
testTypeOps()
testCustomRetain()
testExportObjCClass()
testLocalizedStrings()
assertEquals(2, ForwardDeclaredEnum.TWO.value)
println(
getSupplier(
invoke1(42) { it * 2 }
)!!()
)
val foo = Foo()
val classGetter = getClassGetter(foo)
invoke2 { println(classGetter()) }
foo.hello()
foo.name = "everybody"
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
override fun print(string: CPointer<ByteVar>?) {
println("Kotlin says: " + string?.toKString())
}
})
Bar().hello()
val pair = MutablePairImpl(42, 17)
replacePairElements(pair, 1, 2)
pair.swap()
println("${pair.first}, ${pair.second}")
val defaultPair = MutablePairImpl()
assertEquals(defaultPair.first(), 123)
assertEquals(defaultPair.second(), 321)
// equals and hashCode (virtually):
val map = mapOf(foo to pair, pair to foo)
// equals (directly):
if (!foo.equals(pair)) {
// toString (directly):
println(map[pair].toString() + map[foo].toString() == foo.description() + pair.description())
}
// hashCode (directly):
// hash() returns value of NSUInteger type.
val hash = if (sizeOf<NSUIntegerVar>() == 4L) {
foo.hash().toInt()
} else {
foo.hash().let { it.toInt() xor (it shr 32).toInt() }
}
if (foo.hashCode() == hash) {
// toString (virtually):
if (Platform.memoryModel == MemoryModel.STRICT)
println(map.keys.map { it.toString() }.minOrNull() == foo.description())
else
// TODO: hack until proper cycle collection in maps.
println(true)
}
println(globalString)
autoreleasepool {
globalString = "Another global string"
}
println(globalString)
println(globalObject)
globalObject = object : NSObject() {
override fun description() = "global object"
}
println(globalObject)
globalObject = null // Prevent Kotlin object above from leaking.
println(formatStringLength("%d %d", 42, 17))
println(STRING_MACRO)
println(CFSTRING_MACRO)
// Ensure that overriding method bridge has retain-autorelease sequence:
createObjectWithFactory(object : NSObject(), ObjectFactoryProtocol {
override fun create() = autoreleasepool { NSObject() }
})
}
fun MutablePairProtocol.swap() {
update(0, add = second)
update(1, sub = first)
update(0, add = second)
update(1, sub = second*2)
}
class Bar : Foo() {
override fun helloWithPrinter(printer: PrinterProtocol?) = memScoped {
printer!!.print("Hello from Kotlin".cstr.getPointer(memScope))
}
}
@Suppress("CONFLICTING_OVERLOADS")
class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol {
private var elements = intArrayOf(first, second)
override fun first() = elements.first()
override fun second() = elements.last()
override fun update(index: Int, add: Int) {
elements[index] += add
}
override fun update(index: Int, sub: Int) {
elements[index] -= sub
}
constructor() : this(123, 321)
}
interface Zzz
fun testTypeOps() {
assertTrue(99.asAny() is NSNumber)
assertTrue(null.asAny() is NSNumber?)
assertFalse(null.asAny() is NSNumber)
assertFalse("".asAny() is NSNumber)
assertTrue("bar".asAny() is NSString)
assertTrue(Foo.asAny() is FooMeta)
assertFalse(Foo.asAny() is Zzz)
assertTrue(Foo.asAny() is NSObjectMeta)
assertTrue(Foo.asAny() is NSObject)
assertFalse(Foo.asAny() is Foo)
assertTrue(NSString.asAny() is NSCopyingProtocolMeta)
assertFalse(NSString.asAny() is NSCopyingProtocol)
assertTrue(NSValue.asAny() is NSObjectProtocolMeta)
assertFalse(NSValue.asAny() is NSObjectProtocol) // Must be true, but not implemented properly yet.
assertFalse(Any() is ObjCClass)
assertFalse(Any() is ObjCClassOf<*>)
assertFalse(NSObject().asAny() is ObjCClass)
assertFalse(NSObject().asAny() is ObjCClassOf<*>)
assertTrue(NSObject.asAny() is ObjCClass)
assertTrue(NSObject.asAny() is ObjCClassOf<*>)
assertFalse(Any() is ObjCProtocol)
assertTrue(getPrinterProtocolRaw() is ObjCProtocol)
val printerProtocol = getPrinterProtocol()!!
assertTrue(printerProtocol.asAny() is ObjCProtocol)
assertEquals(3u, ("foo" as NSString).length())
assertEquals(4u, ((1..4).joinToString("") as NSString).length())
assertEquals(2u, (listOf(0, 1) as NSArray).count())
assertEquals(42L, (42 as NSNumber).longLongValue())
assertFails { "bar" as NSNumber }
assertFails { 42 as NSArray }
assertFails { listOf(1) as NSString }
assertFails { NSObject() as Bar }
assertFails { NSObject() as NSValue }
MutablePairImpl(1, 2).asAny() as MutablePairProtocol
assertFails { MutablePairImpl(1, 2).asAny() as Foo }
}
private lateinit var retainedMustNotBeDeallocated: MustNotBeDeallocated
fun testCustomRetain() {
fun test() {
useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol {
override fun returnRetained(obj: Any?) = obj
override fun consume(obj: Any?) {}
override fun consumeSelf() {}
override fun returnRetainedBlock(block: (() -> Unit)?) = block
})
CustomRetainMethodsImpl().let {
it.returnRetained(Any())
retainedMustNotBeDeallocated = MustNotBeDeallocated() // Retain to detect possible over-release.
it.consume(retainedMustNotBeDeallocated)
it.consumeSelf()
it.returnRetainedBlock({})!!()
}
}
autoreleasepool {
test()
kotlin.native.runtime.GC.collect()
}
assertFalse(unexpectedDeallocation)
}
private const val TestExportObjCClass1Name = "TestExportObjCClass"
@ExportObjCClass(TestExportObjCClass1Name) class TestExportObjCClass1 : NSObject()
@ExportObjCClass class TestExportObjCClass2 : NSObject()
const val TestExportObjCClass34Name = "TestExportObjCClass34"
@ExportObjCClass(TestExportObjCClass34Name) class TestExportObjCClass3 : NSObject()
@ExportObjCClass(TestExportObjCClass34Name) class TestExportObjCClass4 : NSObject()
fun testExportObjCClass() {
assertEquals(TestExportObjCClass1Name, TestExportObjCClass1().objCClassName)
assertEquals("TestExportObjCClass2", TestExportObjCClass2().objCClassName)
assertTrue((TestExportObjCClass3().objCClassName == TestExportObjCClass34Name)
xor (TestExportObjCClass4().objCClassName == TestExportObjCClass34Name))
}
fun testLocalizedStrings() {
val key = "screen_main_plural_string"
val localizedString = NSBundle.mainBundle.localizedStringForKey(key, value = "", table = "Localizable")
val string = NSString.localizedStringWithFormat(localizedString, 5)
assertEquals("Plural: 5 apples", string)
}
private val Any.objCClassName: String
get() = object_getClassName(this)!!.toKString()
fun Any?.asAny(): Any? = this
@@ -0,0 +1,16 @@
84
Foo
Hello, World!
Kotlin says: Hello, everybody!
Hello from Kotlin
2, 1
true
true
Global string
Another global string
null
global object
5
String macro
CFString macro
Class TestExportObjCClass34 has multiple implementations. Which one will be used is undefined.
@@ -0,0 +1,12 @@
#import <objc/NSObject.h>
@interface KT37067_Impl : NSObject
@property (nonatomic, readonly) NSObject *newValue;
@property (nonatomic, readonly) NSObject *allocValue;
@property (nonatomic, readonly) NSObject *copyValue;
@property (nonatomic, readonly) NSObject *mutableCopyValue;
@end
@@ -0,0 +1,10 @@
import kotlin.test.*
import objcTests.*
/// KT-37067: attribute prefixed with "new" causes cinterop failure
// No matter what is in the test: I only need to get it compilable with cinterop
@Test fun testKT37067() {
val impl = KT37067_Impl()
assertEquals(null, impl.newValue)
}
@@ -0,0 +1,20 @@
#include "KT37067_prefix.h"
@implementation KT37067_Impl : NSObject
/// KT-37067 is a cinterop issue, so .m implementaion is not relevant at all,
// but I still need to make objc compilable with `-fobjc-arc` option
- (NSObject *)newValue {
return nil;
}
- (NSObject *)allocValue {
return nil;
}
- (NSObject *)copyValue {
return nil;
}
- (NSObject *)mutableCopyValue {
return nil;
}
@end
@@ -0,0 +1,9 @@
#import <Foundation/NSObject.h>
@protocol KT38234_P1
-(int)foo;
@end
@interface KT38234_Base : NSObject <KT38234_P1>
-(int)callFoo;
@end
@@ -0,0 +1,10 @@
import kotlin.test.*
import objcTests.*
class KT38234_Impl : KT38234_P1Protocol, KT38234_Base() {
override fun foo(): Int = 566
}
@Test fun testKT38234() {
assertEquals(566, KT38234_Impl().callFoo())
}
@@ -0,0 +1,10 @@
#include "KT38234_override.h"
@implementation KT38234_Base
-(int)foo {
return 1;
}
-(int)callFoo {
return [self foo];
}
@end
@@ -0,0 +1,5 @@
#import <Foundation/NSObject.h>
@interface TestAllocNoRetain : NSObject
@property BOOL ok;
@end
@@ -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 <Foundation/Foundation.h>
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)
}
@@ -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
@@ -0,0 +1,27 @@
#import <Foundation/NSObject.h>
@protocol BlockProvider
@required
-(int (^)(int)) block;
@end
int callProvidedBlock(id<BlockProvider> blockProvider, int argument) {
return [blockProvider block](argument);
}
@protocol BlockConsumer
@required
-(int)callBlock:(int (^)(int))block argument:(int)argument;
@end
int callPlusOneBlock(id<BlockConsumer> 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
@@ -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))
}
@@ -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
@@ -0,0 +1,7 @@
#import <Foundation/NSObject.h>
@interface TestCallableReferences : NSObject
@property int value;
- (int)instanceMethod;
+ (int)classMethod:(int)first :(int)second;
@end
@@ -0,0 +1,28 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testCallableReferences() {
val createTestCallableReferences = ::TestCallableReferences
assertEquals("<init>", createTestCallableReferences.name)
val testCallableReferences: Any = createTestCallableReferences()
assertTrue(testCallableReferences is TestCallableReferences)
val valueRef: kotlin.reflect.KMutableProperty0<Int> = 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())
}
@@ -0,0 +1,11 @@
#import "callableReferences.h"
@implementation TestCallableReferences
- (int)instanceMethod {
return self.value;
}
+ (int)classMethod:(int)first :(int)second {
return first + second;
}
@end
@@ -0,0 +1,22 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h>
@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
@@ -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())
}
@@ -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
@@ -0,0 +1,5 @@
#import <Foundation/NSObject.h>
@interface TestConstructorReturnsNull : NSObject
- (instancetype)init;
@end
@@ -0,0 +1,9 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testConstructorReturnsNull() {
assertFailsWith<NullPointerException>() {
TestConstructorReturnsNull()
}
}
@@ -0,0 +1,7 @@
#import "constructorReturnsNull.h"
@implementation TestConstructorReturnsNull
- (instancetype)init {
return nil;
}
@end
@@ -0,0 +1,18 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testConversions() {
testMethodsOfAny(emptyList<Nothing>(), 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)
}
@@ -0,0 +1,16 @@
#import <Foundation/NSString.h>
@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;
@@ -0,0 +1,23 @@
@file:OptIn(kotlin.native.runtime.NativeRuntimeApi::class)
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.runtime.GC.collect()
assertTrue(customStringDeallocated)
}
private val Any.objCClassName: String
get() = object_getClassName(this)!!.toKString()
@@ -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
@@ -0,0 +1,10 @@
#import <Foundation/NSObject.h>
@interface DeallocFlagHolder : NSObject
@property BOOL deallocated;
@end
@interface ObjectWithDeallocFlag : NSObject
@property (nonnull) DeallocFlagHolder* deallocFlagHolder;
- (instancetype _Nonnull)sameObject;
@end
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
import kotlin.test.*
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.*
import kotlinx.cinterop.internal.detachObjCObject
import objcTests.*
class detachObjCObjectTests {
@AfterTest
private fun gc() {
// An attempt to make sure the GC is fine after detachObjCObject.
kotlin.native.runtime.GC.collect()
kotlin.native.runtime.GC.collect()
}
private fun checkThroughWeakRef(checkWeakRefBeforeReset: Boolean) {
val obj = NSObject()
val ref = WeakReference(obj)
if (checkWeakRefBeforeReset) {
repeat(2) {
val refValue = ref.value
assertNotNull(refValue)
// refValue is actually a new wrapper, so we need to reset it as well:
detachObjCObject(refValue)
// The next iteration will check that the object is not yet removed.
}
}
detachObjCObject(obj)
val refValue = ref.value
assertNull(refValue)
}
@Test
fun checkThroughWeakRef() {
checkThroughWeakRef(checkWeakRefBeforeReset = false)
}
@Test
fun checkThroughWeakRefWithMultipleWrappers() {
checkThroughWeakRef(checkWeakRefBeforeReset = true)
}
@Test
fun checkThroughDeallocFlag() {
val obj = ObjectWithDeallocFlag()
val deallocFlagHolder = obj.deallocFlagHolder
assertFalse(deallocFlagHolder.deallocated)
detachObjCObject(obj)
assertTrue(deallocFlagHolder.deallocated)
}
@Test
fun checkThroughDeallocFlagWithMultipleWrappers() {
val obj = ObjectWithDeallocFlag()
val deallocFlagHolder = obj.deallocFlagHolder
assertFalse(deallocFlagHolder.deallocated)
val sameObj = obj.sameObject() // Same object, different wrapper
detachObjCObject(obj)
assertFalse(deallocFlagHolder.deallocated)
detachObjCObject(obj)
assertFalse(deallocFlagHolder.deallocated)
detachObjCObject(sameObj)
assertTrue(deallocFlagHolder.deallocated)
}
}
@@ -0,0 +1,21 @@
#import "detachObjCObject.h"
@implementation DeallocFlagHolder
@end
@implementation ObjectWithDeallocFlag
- (instancetype)init {
if (self = [super init]) {
self.deallocFlagHolder = [DeallocFlagHolder new];
self.deallocFlagHolder.deallocated = NO;
}
return self;
}
- (void)dealloc {
self.deallocFlagHolder.deallocated = YES;
}
- (instancetype _Nonnull)sameObject; {
return self;
}
@end
@@ -0,0 +1,9 @@
#import <Foundation/NSObject.h>
@protocol ExceptionThrower
-(void)throwException;
@end
@interface ExceptionThrowerManager : NSObject
+(void)throwExceptionWith:(id<ExceptionThrower>)thrower;
@end
@@ -0,0 +1,15 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testExceptions() {
assertFailsWith<MyException> {
ExceptionThrowerManager.throwExceptionWith(object : NSObject(), ExceptionThrowerProtocol {
override fun throwException() {
throw MyException()
}
})
}
}
private class MyException : Throwable()
@@ -0,0 +1,7 @@
#import "exceptions.h"
@implementation ExceptionThrowerManager
+(void)throwExceptionWith:(id<ExceptionThrower>)thrower {
[thrower throwException];
}
@end
@@ -0,0 +1,8 @@
#import <Foundation/NSObject.h>
@interface TestInitWithCustomSelector : NSObject
-(instancetype)initCustom;
@property BOOL custom;
+(instancetype _Nonnull)createCustom;
@end
@@ -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()
}
@@ -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
@@ -0,0 +1,21 @@
import kotlin.test.*
import objcTests.*
class KT26478
@Test
fun testKT26478compiletime() {
val exception = assertFailsWith<ClassCastException> {
NSBundle() as KT26478
}
assertEquals("class NSBundle cannot be cast to class KT26478", exception.message)
}
val bundle26478:Any = NSBundle()
@Test
fun testKT26478runtime() {
val exception = assertFailsWith<ClassCastException> {
bundle26478 as KT26478
}
assertEquals("class NSBundle cannot be cast to class KT26478", exception.message)
}
@@ -0,0 +1,18 @@
import kotlin.test.*
import objcTests.*
@Test
fun testKT35544() {
val exception = assertFailsWith<ClassCastException> {
123 as NSString
}
assertEquals("class kotlin.Int cannot be cast to class objcTests.NSString", exception.message)
}
val bundle35544: Any = NSBundle()
@Test
fun testKT35544runtime() {
val exception = assertFailsWith<ClassCastException> {
bundle35544 as NSString
}
assertEquals("class NSBundle cannot be cast to class objcTests.NSString", exception.message)
}
@@ -0,0 +1 @@
void kt36766(void (^block)()) {}
@@ -0,0 +1,6 @@
import kotlin.test.*
import objcTests.*
@Test fun testKt36766() {
kt36766(null)
}
@@ -0,0 +1,10 @@
#import <Foundation/NSObject.h>
NS_ASSUME_NONNULL_BEGIN
@interface KT38850Color : NSObject
+(instancetype)blackColor;
@property NSString* name;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,12 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
enum class ColorRef(val color: KT38850Color){
Black(KT38850Color.blackColor()),
}
@Test
fun testKT38850() {
assertEquals("black", ColorRef.Black.color.name)
}
@@ -0,0 +1,9 @@
#import "kt38850.h"
@implementation KT38850Color
+(instancetype)blackColor {
KT38850Color* result = [KT38850Color new];
result.name = @"black";
return result;
}
@end
@@ -0,0 +1,30 @@
#import <Foundation/NSObject.h>
extern BOOL deallocRetainReleaseDeallocated;
@interface DeallocRetainRelease : NSObject
@end
extern DeallocRetainRelease* globalDeallocRetainRelease;
@protocol WeakReference
@required
@property (weak) id referent;
@end
@interface ObjCWeakReference : NSObject <WeakReference>
@property (weak) id referent;
@end
extern id <WeakReference> weakDeallocLoadWeak;
extern BOOL deallocLoadWeakDeallocated;
@interface DeallocLoadWeak : NSObject
-(void)checkWeak;
@end
extern BOOL deallocRetainAndAccessDeallocated;
@interface DeallocRetainAndAccess : NSObject
@property void (^onDealloc)(id);
@end
@@ -0,0 +1,161 @@
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
// Note: these tests rely on GC assertions: without the fix and the assertions it won't actually crash.
// GC should fire an assertion if it obtains a reference to Kotlin object that is being (or has been) deallocated.
@Test
fun testKT41811() {
// Attempt to make the state predictable:
kotlin.native.runtime.GC.collect()
deallocRetainReleaseDeallocated = false
assertFalse(deallocRetainReleaseDeallocated)
createGarbageDeallocRetainRelease()
// Runs [DeallocRetainRelease dealloc]:
kotlin.native.runtime.GC.collect()
assertTrue(deallocRetainReleaseDeallocated)
// Might crash due to double-dispose if the dealloc applied addRef/releaseRef to reclaimed Kotlin object:
kotlin.native.runtime.GC.collect()
}
private fun createGarbageDeallocRetainRelease() {
autoreleasepool {
object : DeallocRetainRelease() {}
}
}
@Test
fun testKT41811LoadWeak() {
testKT41811LoadWeak(ObjCWeakReference())
}
@Test
fun testKT41811LoadKotlinWeak() {
val kotlinWeak = object : NSObject(), WeakReferenceProtocol {
lateinit var weakReference: WeakReference<Any>
override fun referent(): Any? {
return weakReference.value
}
override fun setReferent(referent: Any?) {
weakReference = WeakReference(referent!!)
}
}
testKT41811LoadWeak(kotlinWeak)
}
private fun testKT41811LoadWeak(weakRef: WeakReferenceProtocol) {
// Attempt to make the state predictable:
kotlin.native.runtime.GC.collect()
deallocLoadWeakDeallocated = false
assertFalse(deallocLoadWeakDeallocated)
createGarbageDeallocLoadWeak(weakRef)
// Runs [DeallocLoadWeak dealloc]:
kotlin.native.runtime.GC.collect()
assertTrue(deallocLoadWeakDeallocated)
// Might crash due to double-dispose if the dealloc applied addRef/releaseRef to reclaimed Kotlin object:
kotlin.native.runtime.GC.collect()
weakDeallocLoadWeak = null
}
private fun createGarbageDeallocLoadWeak(weakRef: WeakReferenceProtocol) {
autoreleasepool {
val obj = object : DeallocLoadWeak() {}
weakDeallocLoadWeak = weakRef.apply { referent = obj }
obj.checkWeak()
assertSame(obj, weakDeallocLoadWeak!!.referent)
}
}
@Test
fun testKT41811WithGlobal() {
// Attempt to make the state predictable:
kotlin.native.runtime.GC.collect()
deallocRetainReleaseDeallocated = false
assertFalse(deallocRetainReleaseDeallocated)
autoreleasepool {
{
globalDeallocRetainRelease = object: DeallocRetainRelease() {}
}()
}
assertFalse(deallocRetainReleaseDeallocated)
// Clean up local DeallocRetainRelease on Kotlin side
kotlin.native.runtime.GC.collect()
assertFalse(deallocRetainReleaseDeallocated)
// And drop the last reference to DeallocRetainRelease from ObjC global scope.
globalDeallocRetainRelease = null
assertFalse(deallocRetainReleaseDeallocated)
// This will dispose `DeallocRetainRelease` on Kotlin side, which will cause `dealloc`
// on ObjC side, which triggers `retain` and `release` of `self`. If these messages
// were to reach Kotlin side, the `release` would have immediately scheduled the
// second disposal of Kotlin object.
kotlin.native.runtime.GC.collect()
assertTrue(deallocRetainReleaseDeallocated)
}
var localDeallocRetainAndAccessDeallocated = false
@Test
fun testKT41811WithAccess() {
// Legacy MM crashes with an assertion failure.
@OptIn(kotlin.ExperimentalStdlibApi::class)
if (!isExperimentalMM())
return
// Attempt to make the state predictable:
kotlin.native.internal.GC.collect()
deallocRetainAndAccessDeallocated = false
localDeallocRetainAndAccessDeallocated = false
createGarbageDeallocRetainAndAccess()
// Runs [DeallocRetainAndAccess dealloc]:
kotlin.native.internal.GC.collect()
assertTrue(deallocRetainAndAccessDeallocated)
assertTrue(localDeallocRetainAndAccessDeallocated)
// Might crash due to double-dispose if the dealloc applied addRef/releaseRef to reclaimed Kotlin object:
kotlin.native.internal.GC.collect()
}
private fun createGarbageDeallocRetainAndAccess() {
autoreleasepool {
object : DeallocRetainAndAccess() {
init {
onDealloc = {
assertNull(it)
assertFalse(localDeallocRetainAndAccessDeallocated)
localDeallocRetainAndAccessDeallocated = true
}
}
}
}
}
@@ -0,0 +1,56 @@
#import "kt41811.h"
#import "assert.h"
id retainObject = nil;
BOOL deallocRetainReleaseDeallocated = NO;
@implementation DeallocRetainRelease
-(void)dealloc {
retainObject = self;
assert(retainObject == self);
retainObject = nil;
assert(!deallocRetainReleaseDeallocated);
deallocRetainReleaseDeallocated = YES;
}
@end
DeallocRetainRelease* globalDeallocRetainRelease = nil;
@implementation ObjCWeakReference
@end
id <WeakReference> weakDeallocLoadWeak = nil;
BOOL deallocLoadWeakDeallocated = NO;
@implementation DeallocLoadWeak
-(void)checkWeak {
assert(weakDeallocLoadWeak != nil);
assert(weakDeallocLoadWeak.referent == self);
}
-(void)dealloc {
assert(weakDeallocLoadWeak != nil);
assert(weakDeallocLoadWeak.referent == nil);
assert(!deallocLoadWeakDeallocated);
deallocLoadWeakDeallocated = YES;
}
@end
id retainObject2 = nil;
BOOL deallocRetainAndAccessDeallocated = NO;
@implementation DeallocRetainAndAccess
- (void)dealloc {
retainObject2 = self;
assert(_onDealloc != nil);
_onDealloc(retainObject2);
retainObject2 = nil;
assert(!deallocRetainAndAccessDeallocated);
deallocRetainAndAccessDeallocated = YES;
}
@end
@@ -0,0 +1,10 @@
#import <Foundation/NSObject.h>
extern BOOL kt42482Deallocated;
extern id kt42482Global;
@interface KT42482 : NSObject
-(int)fortyTwo;
@end
void kt42482Swizzle(id obj);
@@ -0,0 +1,37 @@
@file:OptIn(kotlin.native.runtime.NativeRuntimeApi::class)
import kotlin.native.ref.WeakReference
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test
fun testKT42482() {
// Attempt to make the state predictable:
kotlin.native.runtime.GC.collect()
kt42482Deallocated = false
assertFalse(kt42482Deallocated);
{
assertEquals(41, KT42482().fortyTwo())
val obj: KT42482 = KT42482Impl()
assertEquals(42, obj.fortyTwo())
kt42482Swizzle(obj)
assertEquals(43, obj.fortyTwo())
// Test retain and release on swizzled object:
kt42482Global = obj
kt42482Global = null
}()
kotlin.native.runtime.GC.collect()
assertTrue(kt42482Deallocated)
}
class KT42482Impl : KT42482() {
override fun fortyTwo() = 42
}
@@ -0,0 +1,36 @@
#import "kt42482.h"
#import "assert.h"
#import <objc/runtime.h>
BOOL kt42482Deallocated = NO;
id kt42482Global = nil;
@implementation KT42482
-(int)fortyTwo {
return 41;
}
-(void)dealloc {
kt42482Deallocated = YES;
}
@end
int fortyTwoSwizzledImp(id self, SEL _cmd) {
return 43;
}
void kt42482Swizzle(id obj) {
Class oldClass = object_getClass(obj);
SEL selector = @selector(fortyTwo);
Class newClass = objc_allocateClassPair(oldClass, "KT42482Swizzled", 0);
assert(newClass != nil);
objc_registerClassPair(newClass);
Method method = class_getInstanceMethod([KT42482 class], selector);
assert(method != nil);
class_addMethod(newClass, selector, (IMP)&fortyTwoSwizzledImp, method_getTypeEncoding(method));
object_setClass(obj, newClass);
}
@@ -0,0 +1,5 @@
@class KT48491_Foo;
__attribute__((external_source_symbol(language="Swift", defined_in="KT48491_Bar",generated_declaration)))
@interface KT48491_Foo
@end
@@ -0,0 +1,17 @@
#import <Foundation/NSObject.h>
// https://youtrack.jetbrains.com/issue/KT-49455
@interface KT49455 : NSObject
@end
__attribute__((external_source_symbol(language="Swift", defined_in="sample",generated_declaration)))
@interface KT49455 (KT49455Ext)
- (int)extensionFunction;
@end
// Just to ensure that unavailable categories don't break anything.
__attribute__((unavailable("unavailableExtensionFunction is unavailable")))
@interface KT49455 (KT49455UnavailableExt)
- (int)unavailableExtensionFunction;
@end
@@ -0,0 +1,7 @@
import kotlin.test.*
import objcTests.*
@Test
fun testKT49455() {
assertEquals(111, KT49455().extensionFunction())
}
@@ -0,0 +1,10 @@
#import "kt49455.h"
@implementation KT49455
@end
@implementation KT49455 (KT49455Ext)
- (int)extensionFunction {
return 111;
}
@end
@@ -0,0 +1,34 @@
package kt50786
import kotlinx.cinterop.*
import kotlin.coroutines.*
open class EmptyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<Any?> {
companion object : EmptyContinuation()
override fun resumeWith(result: Result<Any?>) { result.getOrThrow() }
}
lateinit var continuation: Continuation<Unit>
suspend fun suspendHere(): Unit = suspendCoroutine { cont ->
continuation = cont
}
fun startCoroutine(block: suspend () -> Unit) {
block.startCoroutine(EmptyContinuation)
}
@kotlin.test.Test
fun testSafeSuspensionIsAllowedInAutoreleasepool() {
// This test checks that the compiler doesn't prohibit calling suspend functions from `autoreleasepool {}`
// if this call is not actually in the block, but in the local declaration inside it.
// See https://youtrack.jetbrains.com/issue/KT-50786 for more details.
autoreleasepool {
startCoroutine {
suspendHere()
}
}
continuation.resume(Unit)
}
@@ -0,0 +1,5 @@
import objcTests.*
import kotlinx.cinterop.*
@ExportObjCClass
class KT53373 : NSObject()
@@ -0,0 +1,10 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test
fun testKT53373() {
val kt53373Class = objc_lookUpClass("KT53373")
assertNotNull(kt53373Class)
assertEquals("KT53373", class_getName(kt53373Class)?.toKString())
}
+13
View File
@@ -0,0 +1,13 @@
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class)
import kotlin.system.exitProcess
import kotlinx.cinterop.autoreleasepool
import kotlin.native.internal.test.testLauncherEntryPoint
fun main(args: Array<String>) {
autoreleasepool {
val exitCode = testLauncherEntryPoint(args)
// Note: this test runner checks for memory leaks after successful execution, unlike standard one.
if (exitCode != 0) exitProcess(exitCode)
}
}
@@ -0,0 +1,25 @@
#import <Foundation/NSObject.h>
// [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<Proto>
//- (void) CompanionS; // mangleSimple does not support this: it may clash after mangling
@end
@@ -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)
}
@@ -0,0 +1,6 @@
#import "mangling.h"
// [KT-36067] mangling
@implementation FooMangled : NSObject
@synthesize Companion;
@end
@@ -0,0 +1,17 @@
#import <Foundation/NSObject.h>
@interface MultipleInheritanceClashBase : NSObject
@property (nonnull) MultipleInheritanceClashBase* delegate;
@end
@protocol MultipleInheritanceClash
@optional
@property (nullable) id<MultipleInheritanceClash> delegate;
@end
@interface MultipleInheritanceClash1 : MultipleInheritanceClashBase <MultipleInheritanceClash>
@end
@interface MultipleInheritanceClash2 : MultipleInheritanceClashBase <MultipleInheritanceClash>
@property MultipleInheritanceClashBase* delegate;
@end
@@ -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())
}
@@ -0,0 +1,10 @@
#import "multipleInheritanceClash.h"
@implementation MultipleInheritanceClashBase
@end
@implementation MultipleInheritanceClash1
@end
@implementation MultipleInheritanceClash2
@end
@@ -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)
}
@@ -0,0 +1,13 @@
#import <Foundation/NSObject.h>
@class DeallocListener;
@interface DeallocExecutor : NSObject
@property DeallocListener* deallocListener;
@end
@interface DeallocListener : NSObject
@property (weak) DeallocExecutor* deallocExecutor;
@property BOOL deallocated;
-(BOOL)deallocExecutorIsNil;
@end
@@ -0,0 +1,52 @@
@file:OptIn(FreezingIsDeprecated::class, kotlin.native.runtime.NativeRuntimeApi::class, ObsoleteWorkersApi::class)
import kotlinx.cinterop.*
import kotlin.native.concurrent.*
import kotlin.test.*
import objcTests.*
@Test fun testObjCWeakRef() {
val deallocListener = DeallocListener()
assertFalse(deallocListener.deallocated)
// [deallocListener.deallocExecutorIsNil()] calls deallocExecutor getter, which retains the result and either
// puts it to autoreleasepool or releases it immediately (Obj-C ARC optimization). It seems to depend on the platform.
// Wrap the call to autoreleasepool to ensure the object will be released:
autoreleasepool {
testObjCWeakRef0(deallocListener)
}
kotlin.native.runtime.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<IncorrectDereferenceException> {
// 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.runtime.GC.collect()
}
}
@@ -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
@@ -0,0 +1,6 @@
#import <Foundation/NSObject.h>
@interface TestOverrideInit : NSObject
-(instancetype)initWithValue:(int)value NS_DESIGNATED_INITIALIZER;
+(instancetype)createWithValue:(int)value;
@end
@@ -0,0 +1,22 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testOverrideInit1() {
assertEquals(42, (TestOverrideInitImpl1.createWithValue(42) as TestOverrideInitImpl1).value)
}
private class TestOverrideInitImpl1 @OverrideInit constructor(val value: Int) : TestOverrideInit(value) {
companion object : TestOverrideInitMeta()
}
// See https://youtrack.jetbrains.com/issue/KT-41910
@Test fun testOverrideInitWithDefaultArguments() {
assertEquals(42, (TestOverrideInitImpl2.createWithValue(42) as TestOverrideInitImpl2).value)
assertEquals(123, TestOverrideInitImpl2(123).value)
assertEquals(17, TestOverrideInitImpl2().value)
}
private class TestOverrideInitImpl2 @OverrideInit constructor(val value: Int = 17) : TestOverrideInit(value) {
companion object : TestOverrideInitMeta()
}
@@ -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
@@ -0,0 +1,34 @@
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, FreezingIsDeprecated::class, ObsoleteWorkersApi::class)
import kotlinx.cinterop.*
import kotlin.native.concurrent.*
import kotlin.test.*
import objcTests.*
private class NSObjectImpl : NSObject() {
var x = 111
}
// Also see counterpart interop/objc/illegal_sharing.kt
@Test fun testSharing() = withWorker {
val obj = NSObjectImpl()
val array = nsArrayOf(obj)
assertFalse(obj.isFrozen)
obj.x = 222
if (Platform.isFreezingEnabled) {
obj.freeze()
assertTrue(obj.isFrozen)
runInWorker {
val obj1 = array.objectAtIndex(0) as NSObjectImpl
assertFailsWith<InvalidMutabilityException> {
obj1.x = 333
}
}
assertEquals(222, obj.x)
}
// TODO: test [obj release] etc.
}
@@ -0,0 +1,17 @@
#import <Foundation/NSString.h>
struct CStructWithNSObjects {
id any;
NSString* nsString;
NSString* _Nonnull nonNullString;
NSObject* object;
NSArray* array;
NSMutableArray* mutableArray;
NSSet* set;
NSDictionary* dictionary;
};
@@ -0,0 +1,65 @@
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
private class NSObjectSubClass : NSObject() {
val x = 111
}
@Test
fun testStructWithNSObject() {
memScoped {
val struct = alloc<CStructWithNSObjects>()
struct.any = 5
assertEquals(5, struct.any)
struct.any = null
assertEquals(null, struct.any)
struct.nsString = "hello"
assertEquals("hello", struct.nsString)
struct.nsString = null
assertEquals(null, struct.nsString)
struct.nonNullString = "world"
assertEquals("world", struct.nonNullString)
struct.`object` = NSObjectSubClass()
assertEquals(111, (struct.`object` as NSObjectSubClass).x)
struct.`object` = null
assertEquals(null, struct.`object`)
struct.array = null
assertEquals(null, struct.array)
struct.array = listOf(1, 2, 3)
assertEquals(listOf(1, 2, 3), struct.array)
struct.set = null
assertEquals(null, struct.set)
struct.set = setOf("hello", "world")
assertEquals(setOf("hello", "world"), struct.set)
struct.dictionary = null
assertEquals(null, struct.dictionary)
struct.dictionary = mapOf("k1" to "v1", "k2" to "v2")
assertEquals(mapOf<Any?, String>("k1" to "v1", "k2" to "v2"), struct.dictionary)
struct.mutableArray = null
assertEquals(null, struct.mutableArray)
struct.mutableArray = mutableListOf(1, 2)
struct.mutableArray!! += 3
assertEquals(mutableListOf<Any?>(1, 2, 3), struct.mutableArray)
// Check that subtyping via Nothing-returning functions does not break compiler.
assertFailsWith<NotImplementedError> {
struct.any = TODO()
struct.nsString = TODO()
struct.nonNullString = TODO()
struct.`object` = TODO()
struct.array = TODO()
struct.set = TODO()
struct.dictionary = TODO()
struct.mutableArray = TODO()
}
}
}
@@ -0,0 +1,6 @@
#import <Foundation/NSObject.h>
@interface WeakRefHolder : NSObject
@property (weak) id obj;
-(void)loadManyTimes;
@end
@@ -0,0 +1,27 @@
@file:OptIn(kotlin.native.runtime.NativeRuntimeApi::class)
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test
fun testTryRetainGC() {
kotlin.native.runtime.GC.collect()
val weakRefHolder = WeakRefHolder()
createGarbageNSObjects(weakRefHolder)
weakRefHolder.obj = object : NSObject() {}
// Loading weak ref takes a lock. If K/N runtime runs GC while the lock is taken,
// then it releases garbage objects and thus Obj-C runtime might take a recursive lock
// and abort in _os_unfair_lock_recursive_abort.
weakRefHolder.loadManyTimes()
}
private fun createGarbageNSObjects(weakRefHolder: WeakRefHolder) {
autoreleasepool {
repeat(100) {
// Assigning the object to a weak reference so Obj-C would take a lock when deallocating it:
weakRefHolder.obj = NSObject()
}
weakRefHolder.obj = null
}
}
@@ -0,0 +1,11 @@
#import "tryRetainGC.h"
#import <Foundation/NSArray.h>
@implementation WeakRefHolder
-(void)loadManyTimes {
NSMutableArray* array = [NSMutableArray new];
for (int i = 0; i < 10000; ++i) {
[array addObject:self.obj];
}
}
@end
@@ -0,0 +1,19 @@
@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class)
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)
}
}
@@ -0,0 +1,20 @@
#import <Foundation/NSObject.h>
@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
@@ -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
)
)
}
@@ -0,0 +1,51 @@
#import <stdlib.h>
#import <Foundation/NSString.h>
#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
@@ -0,0 +1,5 @@
#import <Foundation/NSObject.h>
NSObject* createNSObject() {
return [NSObject new];
}
@@ -0,0 +1,38 @@
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class)
import kotlin.native.ref.*
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test fun testWeakRefs() {
testWeakReference({ createNSObject()!! })
createAndAbandonWeakRef(NSObject())
// Uncomment when KT-61418 fixed
//testWeakReference({ NSArray.arrayWithArray(listOf(42)) as NSArray })
}
private fun testWeakReference(block: () -> NSObject) {
val ref = autoreleasepool {
createAndTestWeakReference(block)
}
kotlin.native.runtime.GC.collect()
assertNull(ref.get())
}
private fun createAndTestWeakReference(block: () -> NSObject): WeakReference<NSObject> {
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)
}
@@ -0,0 +1,9 @@
#import <Foundation/NSObject.h>
@interface CreateAutoreleaseDeallocated : NSObject
@property BOOL value;
@end
@interface CreateAutorelease : NSObject
+(void)createAutorelease:(CreateAutoreleaseDeallocated*)deallocated;
@end
@@ -0,0 +1,201 @@
@file:OptIn(FreezingIsDeprecated::class, ObsoleteWorkersApi::class)
import kotlin.concurrent.AtomicReference
import kotlin.native.concurrent.*
import kotlinx.cinterop.*
import kotlin.test.*
import objcTests.*
@Test
fun testExecuteInMainPark() =
test(Execute, InMain, Park)
@Test
fun testExecuteInMainProcessQueue() =
test(Execute, InMain, ProcessQueue)
@Test
fun testExecuteInWorkerPark() =
test(Execute, InWorker, Park)
@Test
fun testExecuteInWorkerProcessQueue() =
test(Execute, InWorker, ProcessQueue)
@Test
fun testExecuteInMainToWorkerNoYield() =
test(Execute, InMainToWorker, NoYield)
@Test
fun testExecuteAfter0InMainPark() =
test(ExecuteAfter0, InMain, Park)
@Test
fun testExecuteAfter0InMainProcessQueue() =
test(ExecuteAfter0, InMain, ProcessQueue)
@Test
fun testExecuteAfter0InWorkerPark() =
test(ExecuteAfter0, InWorker, Park)
@Test
fun testExecuteAfter0InWorkerProcessQueue() =
test(ExecuteAfter0, InWorker, ProcessQueue)
@Test
fun testExecuteAfter0InMainToWorkerNoYield() =
test(ExecuteAfter0, InMainToWorker, NoYield)
@Test
fun testExecuteAfter10InMainPark() =
testExecuteAfter10(InMain, Park)
@Test
fun testExecuteAfter10InMainProcessQueue() =
testExecuteAfter10(InMain, ProcessQueue)
@Test
fun testExecuteAfter10InWorkerPark() =
testExecuteAfter10(InWorker, Park)
@Test
fun testExecuteAfter10InWorkerProcessQueue() =
testExecuteAfter10(InWorker, ProcessQueue)
@Test
fun testExecuteAfter10InMainToWorkerNoYield() =
testExecuteAfter10(InMainToWorker, NoYield)
private fun testExecuteAfter10(context: Context, yieldMethod: Yield) =
test(ExecuteAfter10(yieldMethod), context, yieldMethod)
private fun <F> test(method: ExecuteMethod<F>, context: Context, yieldMethod: Yield) {
context.withWorker { worker ->
fun execute(block: () -> Unit) {
val future = method.submit(worker, block)
yieldMethod.yield()
method.wait(future)
}
val deallocated = CreateAutoreleaseDeallocated()
execute {
CreateAutorelease.createAutorelease(deallocated)
// Object is still in autorelease pool:
assertFalse(deallocated.value)
}
// autorelease pool is processed after the job is finished, so the object should be deallocated;
// Checking in a job to make sure previous job is completely processed:
execute {
assertTrue(deallocated.value)
}
}
}
interface ExecuteMethod<F> {
fun submit(worker: Worker, block: () -> Unit): F
fun wait(future: F)
}
object Execute : ExecuteMethod<Future<Unit>> {
override fun submit(worker: Worker, block: () -> Unit) = worker.execute(TransferMode.SAFE, { block.freeze() }) {
it()
}
override fun wait(future: Future<Unit>) {
future.result // Throws on failure.
}
}
abstract class ExecuteAfter : ExecuteMethod<AtomicReference<Any?>> {
abstract val timeout: Long
abstract fun sleepAndYield()
override fun submit(worker: Worker, block: () -> Unit): AtomicReference<Any?> {
val result = AtomicReference<Any?>(null)
worker.executeAfter(timeout, {
try {
block()
result.value = true
} catch (e: Throwable) {
result.value = e.freeze()
}
}.freeze())
return result
}
override fun wait(future: AtomicReference<Any?>) {
while (true) {
sleepAndYield()
when (val it = future.value) {
null -> continue
true -> return
else -> throw it as Throwable
}
}
}
}
object ExecuteAfter0 : ExecuteAfter() {
override val timeout = 0L
override fun sleepAndYield() {
// No sleep or additional yield required.
}
}
class ExecuteAfter10(val yieldMethod: Yield) : ExecuteAfter() {
override val timeout = 10L
override fun sleepAndYield() {
Worker.current.park(timeout + 1L, process = false)
yieldMethod.yield()
}
}
interface Context {
fun withWorker(block: (worker: Worker) -> Unit)
}
object InMain : Context {
override fun withWorker(block: (worker: Worker) -> Unit) = block(Worker.current)
}
object InMainToWorker : Context {
override fun withWorker(block: (Worker) -> Unit) = kotlin.native.concurrent.withWorker {
block(this)
}
}
object InWorker : Context {
override fun withWorker(block: (Worker) -> Unit) = kotlin.native.concurrent.withWorker {
val method = Execute
method.wait(method.submit(this) {
block(this)
})
}
}
interface Yield {
fun yield()
}
object Park : Yield {
override fun yield() {
Worker.current.park(0L, process = true)
}
}
object ProcessQueue : Yield {
override fun yield() {
Worker.current.processQueue()
}
}
object NoYield : Yield {
override fun yield() {}
}
@@ -0,0 +1,20 @@
#import "workerAutoreleasePool.h"
@implementation CreateAutoreleaseDeallocated
@end
@implementation CreateAutorelease {
CreateAutoreleaseDeallocated* deallocated;
}
+(void)createAutorelease:(CreateAutoreleaseDeallocated*)deallocated {
// __autoreleasing attribute prevents from early deallocation
// and thus makes test behaviour identical on Intel and ARM CPUs.
__autoreleasing CreateAutorelease* result = [self new];
result->deallocated = deallocated;
}
-(void)dealloc {
deallocated.value = YES;
}
@end