[K/N][Tests] Migrate objcSmoke & objcTests tests
^KT-61259
This commit is contained in:
committed by
Space Team
parent
58f7dd1c83
commit
524f0d335e
@@ -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
@@ -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
@@ -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,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
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user