[K/N][Tests] Migrate dylib-interop-exe tests
^KT-61259
This commit is contained in:
committed by
Space Team
parent
83d851dd61
commit
2b2c685827
+3
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objc_wrap.h
|
||||
linkerOpts = -lforeignException
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Test different behavior depending on foreignExceptionMode option
|
||||
*/
|
||||
|
||||
import kotlin.test.*
|
||||
import objcExceptionMode.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.objc.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
|
||||
@Test fun testKT35056() {
|
||||
val name = "Some native exception"
|
||||
val reason = "Illegal value"
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "FAILED"
|
||||
try {
|
||||
raiseExc(name, reason)
|
||||
assertNotEquals("FAILED", catchBlockTest) // shall not get here anyway
|
||||
} catch (e: ForeignException) {
|
||||
val ret = logExc(e.nativeException) // return NSException name
|
||||
assertEquals(name, ret)
|
||||
assertEquals("$name:: $reason", e.message)
|
||||
println("OK: ForeignException")
|
||||
catchBlockTest = "PASSED"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("PASSED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun abnormal_handler(x: Any?) : Unit {
|
||||
println("OK: Ends with uncaught exception handler")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// Depending on the `foreignxceptionMode` option (def file or cinterop cli) this test should ends
|
||||
// normally with `ForeignException` handled or abnormally with `abnormal_handler`.
|
||||
// Test shall validate output (golden value) from `abnormal_handler`.
|
||||
|
||||
objc_setUncaughtExceptionHandler(staticCFunction(::abnormal_handler))
|
||||
|
||||
testKT35056()
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Test different behavior depending on foreignExceptionMode option
|
||||
*/
|
||||
|
||||
import kotlin.test.*
|
||||
import objcExceptionMode.*
|
||||
import kotlinx.cinterop.*
|
||||
import platform.objc.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Suppress("VARIABLE_WITH_REDUNDANT_INITIALIZER")
|
||||
@Test fun testKT35056() {
|
||||
val name = "Some native exception"
|
||||
val reason = "Illegal value"
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "FAILED"
|
||||
try {
|
||||
raiseExc(name, reason)
|
||||
assertNotEquals("FAILED", catchBlockTest) // shall not get here anyway
|
||||
} catch (e: ForeignException) {
|
||||
val ret = logExc(e.nativeException) // return NSException name
|
||||
assertEquals(name, ret)
|
||||
assertEquals("$name:: $reason", e.message)
|
||||
println("OK: ForeignException")
|
||||
catchBlockTest = "PASSED"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("PASSED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun abnormal_handler(x: Any?) : Unit {
|
||||
println("OK: Ends with uncaught exception handler")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// Depending on the `foreignxceptionMode` option (def file or cinterop cli) this test should ends
|
||||
// normally with `ForeignException` handled or abnormally with `abnormal_handler`.
|
||||
// Test shall validate output (golden value) from `abnormal_handler`.
|
||||
|
||||
objc_setUncaughtExceptionHandler(staticCFunction(::abnormal_handler))
|
||||
|
||||
testKT35056()
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objc_wrap.h
|
||||
linkerOpts = -lforeignException
|
||||
foreignExceptionMode = objc-wrap
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void raiseExc(id name, id reason);
|
||||
id logExc(id exception);
|
||||
|
||||
@interface Foo : NSObject
|
||||
- (void)instanceMethodThrow:(id)name reason:(id)reason;
|
||||
+ (void)classMethodThrow:(id)name reason:(id)reason;
|
||||
@end
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Test different types of callable with foreignExceptionMode=objc-wrap
|
||||
*/
|
||||
|
||||
import kotlin.test.*
|
||||
//import objcTests.*
|
||||
import objc_wrap.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun testInner(name: String, reason: String) {
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "NOT EXPECTED"
|
||||
try {
|
||||
raiseExc(name, reason)
|
||||
} catch (e: RuntimeException) {
|
||||
catchBlockTest = "This shouldn't happen"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("NOT EXPECTED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
typealias CallMe = (String, String) -> Unit
|
||||
|
||||
@Test fun testExceptionWrap(raise: CallMe) {
|
||||
val name = "Some native exception"
|
||||
val reason = "Illegal value"
|
||||
var finallyBlockTest = "FAILED"
|
||||
var catchBlockTest = "FAILED"
|
||||
try {
|
||||
raise(name, reason)
|
||||
} catch (e: ForeignException) {
|
||||
val ret = logExc(e.nativeException) // return NSException name
|
||||
assertEquals(name, ret)
|
||||
assertEquals("$name:: $reason", e.message)
|
||||
catchBlockTest = "PASSED"
|
||||
} finally {
|
||||
finallyBlockTest = "PASSED"
|
||||
}
|
||||
assertEquals("PASSED", catchBlockTest)
|
||||
assertEquals("PASSED", finallyBlockTest)
|
||||
}
|
||||
|
||||
class Bar() : Foo()
|
||||
|
||||
fun main() {
|
||||
testExceptionWrap(::raiseExc) // simple
|
||||
testExceptionWrap(::testInner) // nested try block
|
||||
testExceptionWrap(Foo::classMethodThrow) // class method
|
||||
testExceptionWrap(Foo()::instanceMethodThrow) // instance method
|
||||
testExceptionWrap(Bar()::instanceMethodThrow) // fake override
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "objc_wrap.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void raiseExc(id name, id reason) {
|
||||
[NSException raise:name format:@"%@", reason];
|
||||
}
|
||||
|
||||
id logExc(id exc) {
|
||||
assert([exc isKindOfClass:[NSException class]]);
|
||||
return ((NSException*)exc).name;
|
||||
}
|
||||
|
||||
@implementation Foo : NSObject
|
||||
- (void) instanceMethodThrow:(id)name reason:(id)reason {
|
||||
raiseExc(name, reason);
|
||||
}
|
||||
+ (void) classMethodThrow:(id)name reason:(id)reason {
|
||||
raiseExc(name, reason);
|
||||
}
|
||||
@end
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/friendly_dealloc.h
|
||||
linkerOpts = -lfriendly_dealloc
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#include <inttypes.h>
|
||||
#include <objc/NSObject.h>
|
||||
|
||||
@interface OnDestroyHook: NSObject
|
||||
-(instancetype)init:(void(^)(uintptr_t))onDestroy;
|
||||
-(uintptr_t)identity;
|
||||
@end
|
||||
|
||||
void retain(uintptr_t);
|
||||
void release(uintptr_t);
|
||||
void autorelease(uintptr_t);
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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)
|
||||
|
||||
import friendly_dealloc.*
|
||||
import platform.objc.*
|
||||
import platform.Foundation.*
|
||||
|
||||
import kotlin.concurrent.*
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.native.internal.test.testLauncherEntryPoint
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.test.*
|
||||
import kotlin.time.*
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
val timeout = 10.seconds
|
||||
|
||||
fun <T> allocCollectable(ctor: () -> T): T = autoreleasepool {
|
||||
ctor()
|
||||
}
|
||||
|
||||
class Event() : NSObject() {
|
||||
@Volatile
|
||||
private var triggered = false
|
||||
|
||||
fun isTriggered() = triggered
|
||||
|
||||
@ObjCAction
|
||||
fun trigger() {
|
||||
assertFalse(isTriggered())
|
||||
triggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
val trigerSelector = sel_registerName("trigger")
|
||||
|
||||
fun waitTriggered(event: Event) {
|
||||
val timeoutMark = TimeSource.Monotonic.markNow() + timeout
|
||||
|
||||
kotlin.native.internal.GC.collect()
|
||||
while (true) {
|
||||
if (event.isTriggered()) {
|
||||
return
|
||||
}
|
||||
assertFalse(timeoutMark.hasPassedNow(), "Timed out")
|
||||
}
|
||||
}
|
||||
|
||||
// All the tests are run on a secondary (non main) thread in order to prevent `objcDisposeOnMain` hacks from messing things up
|
||||
@Test
|
||||
fun testAutorelease() {
|
||||
val event = withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
val event = Event()
|
||||
assertFalse(event.isTriggered())
|
||||
|
||||
val victimId = allocCollectable {
|
||||
val v = OnDestroyHook {
|
||||
event.trigger()
|
||||
}
|
||||
retain(v.identity())
|
||||
v.identity()
|
||||
}
|
||||
|
||||
allocCollectable {
|
||||
OnDestroyHook {
|
||||
autorelease(victimId)
|
||||
}.identity()
|
||||
}
|
||||
|
||||
event
|
||||
}.result
|
||||
}
|
||||
waitTriggered(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTimer() {
|
||||
val event = Event()
|
||||
|
||||
withWorker {
|
||||
execute(TransferMode.SAFE, { event }) { event ->
|
||||
allocCollectable {
|
||||
OnDestroyHook {
|
||||
assertFalse(event.isTriggered())
|
||||
NSTimer.scheduledTimerWithTimeInterval(0.0, target=event, selector=trigerSelector, userInfo=null, repeats=false)
|
||||
}.identity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitTriggered(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSelector() {
|
||||
val event = Event()
|
||||
|
||||
withWorker {
|
||||
execute(TransferMode.SAFE, { event }) { event ->
|
||||
allocCollectable {
|
||||
OnDestroyHook {
|
||||
assertFalse(event.isTriggered())
|
||||
NSRunLoop.currentRunLoop().performSelector(trigerSelector, target=event, argument=null, order=0, modes=listOf(NSDefaultRunLoopMode))
|
||||
}.identity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitTriggered(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPerformBlock() {
|
||||
val event = Event()
|
||||
|
||||
withWorker {
|
||||
execute(TransferMode.SAFE, { event }) { event ->
|
||||
allocCollectable {
|
||||
OnDestroyHook {
|
||||
NSRunLoop.currentRunLoop().performBlock({
|
||||
assertFalse(event.isTriggered())
|
||||
event.trigger()
|
||||
});
|
||||
}.identity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
waitTriggered(event)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#include "friendly_dealloc.h"
|
||||
|
||||
@implementation OnDestroyHook {
|
||||
void (^_onDestroy)(uintptr_t);
|
||||
}
|
||||
|
||||
-(uintptr_t)identity {
|
||||
return (uintptr_t)self;
|
||||
}
|
||||
|
||||
-(instancetype)init:(void (^)(uintptr_t))onDestroy {
|
||||
if (self = [super init]) {
|
||||
_onDestroy = [onDestroy retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)dealloc {
|
||||
_onDestroy([self identity]);
|
||||
[_onDestroy release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void retain(uintptr_t obj) {
|
||||
[((id) obj) retain];
|
||||
}
|
||||
|
||||
void release(uintptr_t obj) {
|
||||
[((id) obj) release];
|
||||
}
|
||||
|
||||
void autorelease(uintptr_t obj) {
|
||||
[((id) obj) autorelease];
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/include_categories.h
|
||||
linkerOpts = -linclude_categories
|
||||
objcClassesIncludingCategories = BaseClass DerivedClass
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
@interface BaseClass : NSObject
|
||||
- (id) init;
|
||||
@property float floatProperty;
|
||||
@end;
|
||||
|
||||
@interface BaseClass(IncludeCategory)
|
||||
- (id)initWithFloat:(float)number;
|
||||
- (float) multiplyBy:(int)value;
|
||||
@end;
|
||||
|
||||
@interface DerivedClass : BaseClass
|
||||
- (id) init;
|
||||
@property int intProperty;
|
||||
@end;
|
||||
|
||||
@interface DerivedClass(IncludeCategory)
|
||||
- (id)initWithInt:(int)number;
|
||||
@end;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import include_categories.*
|
||||
|
||||
class KotlinDerived : BaseClass() {
|
||||
override fun multiplyBy(value: Int): Float {
|
||||
return this.floatProperty * value * 100
|
||||
}
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val base = BaseClass()
|
||||
println(base.floatProperty)
|
||||
val base2 = BaseClass(float = 3.14f)
|
||||
println(base2.floatProperty)
|
||||
println(base.multiplyBy(2))
|
||||
println()
|
||||
val derived = DerivedClass()
|
||||
println(derived.intProperty)
|
||||
val derived2 = DerivedClass(int = 6)
|
||||
println(derived2.intProperty)
|
||||
println(derived.multiplyBy(2))
|
||||
println()
|
||||
val kotlinDerived = KotlinDerived()
|
||||
println(kotlinDerived.floatProperty)
|
||||
println(kotlinDerived.multiplyBy(2))
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#include "include_categories.h"
|
||||
|
||||
@implementation BaseClass
|
||||
-(id) init {
|
||||
self.floatProperty = 3.0f;
|
||||
return self;
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation BaseClass(IncludeCategory)
|
||||
- (id)initWithFloat:(float)number {
|
||||
self.floatProperty = number;
|
||||
return self;
|
||||
}
|
||||
- (float) multiplyBy:(int)number {
|
||||
return self.floatProperty * 2;
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation DerivedClass
|
||||
-(id) init {
|
||||
self = [super init];
|
||||
self.intProperty = 3;
|
||||
return self;
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation DerivedClass(IncludeCategory)
|
||||
- (id)initWithInt:(int)number {
|
||||
self = [self init];
|
||||
self.intProperty = number;
|
||||
return self;
|
||||
}
|
||||
@end;
|
||||
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/kt42172.h
|
||||
linkerOpts = -lkt42172
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
typedef void (*Finalizer)(void*);
|
||||
|
||||
@interface WithFinalizer : NSObject
|
||||
|
||||
- (void)setFinalizer:(Finalizer)finalizer arg:(void*)arg;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class, kotlin.native.runtime.NativeRuntimeApi::class, ObsoleteWorkersApi::class)
|
||||
|
||||
import kt42172.*
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun main() {
|
||||
val worker = Worker.start()
|
||||
worker.execute(TransferMode.SAFE, {}) {
|
||||
val withFinalizer = WithFinalizer()
|
||||
val finalizer: Finalizer = staticCFunction { ptr: COpaquePointer? ->
|
||||
ptr?.asStableRef<Any>()?.dispose()
|
||||
println("Executed finalizer")
|
||||
}
|
||||
val arg = StableRef.create(Any()).asCPointer()
|
||||
withFinalizer.setFinalizer(finalizer, arg)
|
||||
}.result
|
||||
worker.requestTermination().result
|
||||
waitWorkerTermination(worker)
|
||||
|
||||
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||
// Experimental MM by default doesn't run GC neither on worker termination nor on program exit.
|
||||
// Enforce GC on program exit:
|
||||
kotlin.native.runtime.Debugging.forceCheckedShutdown = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "kt42172.h"
|
||||
|
||||
@implementation WithFinalizer {
|
||||
Finalizer finalizer_;
|
||||
void* arg_;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (finalizer_) {
|
||||
finalizer_(arg_);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFinalizer:(Finalizer)finalizer arg:(void*)arg {
|
||||
finalizer_ = finalizer;
|
||||
arg_ = arg;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/kt56402.h
|
||||
linkerOpts = -lkt56402
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <inttypes.h>
|
||||
#include <objc/NSObject.h>
|
||||
|
||||
@interface OnDestroyHook: NSObject
|
||||
-(instancetype)init:(void(^)(uintptr_t))onDestroy;
|
||||
-(uintptr_t)identity;
|
||||
@end
|
||||
|
||||
@protocol OnDestroyHook
|
||||
-(uintptr_t)identity;
|
||||
@end
|
||||
|
||||
void startApp(void(^task)());
|
||||
uint64_t currentThreadId();
|
||||
BOOL isMainThread();
|
||||
void spin();
|
||||
|
||||
OnDestroyHook* newGlobal(void(^onDestroy)(uintptr_t));
|
||||
void clearGlobal();
|
||||
|
||||
id<OnDestroyHook> newOnDestroyHook(void(^onDestroy)(uintptr_t));
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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)
|
||||
|
||||
import kt56402.*
|
||||
|
||||
import kotlin.concurrent.AtomicInt
|
||||
import kotlin.native.concurrent.*
|
||||
import kotlin.native.internal.test.testLauncherEntryPoint
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.test.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
class LockedSet<T> {
|
||||
private val lock = AtomicInt(0)
|
||||
private val impl = mutableSetOf<T>()
|
||||
|
||||
private inline fun <R> locked(f: () -> R): R {
|
||||
while (!lock.compareAndSet(0, 1)) {}
|
||||
try {
|
||||
return f()
|
||||
} finally {
|
||||
lock.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
fun add(id: T) = locked {
|
||||
assertFalse(id in impl)
|
||||
impl.add(id)
|
||||
}
|
||||
|
||||
fun remove(id: T) = locked {
|
||||
assertTrue(id in impl)
|
||||
impl.remove(id)
|
||||
}
|
||||
|
||||
operator fun contains(id: T) = locked {
|
||||
id in impl
|
||||
}
|
||||
}
|
||||
|
||||
class OnDestroyHookSub(onDestroy: (ULong) -> Unit) : OnDestroyHook(onDestroy)
|
||||
|
||||
val aliveObjectIds = LockedSet<ULong>()
|
||||
|
||||
fun alloc(ctor: ((ULong) -> Unit) -> ULong): ULong = autoreleasepool {
|
||||
val id = ctor {
|
||||
aliveObjectIds.remove(it)
|
||||
}
|
||||
aliveObjectIds.add(id)
|
||||
id
|
||||
}
|
||||
|
||||
fun waitDestruction(id: ULong) {
|
||||
assertTrue(isMainThread())
|
||||
// Make sure the finalizers are not run on the main thread even for STMS.
|
||||
withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
kotlin.native.internal.GC.collect()
|
||||
}.result
|
||||
}
|
||||
while (true) {
|
||||
spin()
|
||||
if (!(id in aliveObjectIds)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnMainThread() {
|
||||
assertTrue(isMainThread())
|
||||
val id = alloc { onDestroy ->
|
||||
OnDestroyHook {
|
||||
assertTrue(isMainThread())
|
||||
onDestroy(it)
|
||||
}.identity()
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnSecondaryThread() {
|
||||
val id = withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
assertFalse(isMainThread())
|
||||
alloc { onDestroy ->
|
||||
OnDestroyHook {
|
||||
assertFalse(isMainThread())
|
||||
onDestroy(it)
|
||||
}.identity()
|
||||
}
|
||||
}.result
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubOnMainThread() {
|
||||
assertTrue(isMainThread())
|
||||
val id = alloc { onDestroy ->
|
||||
OnDestroyHookSub {
|
||||
assertTrue(isMainThread())
|
||||
onDestroy(it)
|
||||
}.identity()
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSubOnSecondaryThread() {
|
||||
val id = withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
assertFalse(isMainThread())
|
||||
alloc { onDestroy ->
|
||||
OnDestroyHookSub {
|
||||
assertFalse(isMainThread())
|
||||
onDestroy(it)
|
||||
}.identity()
|
||||
}
|
||||
}.result
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGlobalOnMainThread() {
|
||||
assertTrue(isMainThread())
|
||||
val id = alloc { onDestroy ->
|
||||
val obj = newGlobal {
|
||||
assertTrue(isMainThread())
|
||||
onDestroy(it)
|
||||
}!!
|
||||
clearGlobal()
|
||||
obj.identity()
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGlobalOnSecondaryThread() {
|
||||
val id = withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
assertFalse(isMainThread())
|
||||
alloc { onDestroy ->
|
||||
val obj = newGlobal {
|
||||
assertFalse(isMainThread())
|
||||
onDestroy(it)
|
||||
}!!
|
||||
clearGlobal()
|
||||
obj.identity()
|
||||
}
|
||||
}.result
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProtocolOnMainThread() {
|
||||
assertTrue(isMainThread())
|
||||
val id = alloc { onDestroy ->
|
||||
newOnDestroyHook {
|
||||
assertTrue(isMainThread())
|
||||
onDestroy(it)
|
||||
}!!.identity()
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProtocolOnSecondaryThread() {
|
||||
val id = withWorker {
|
||||
execute(TransferMode.SAFE, {}) {
|
||||
assertFalse(isMainThread())
|
||||
alloc { onDestroy ->
|
||||
newOnDestroyHook {
|
||||
assertFalse(isMainThread())
|
||||
onDestroy(it)
|
||||
}!!.identity()
|
||||
}
|
||||
}.result
|
||||
}
|
||||
waitDestruction(id)
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) = startApp {
|
||||
val exitCode = testLauncherEntryPoint(args)
|
||||
if (exitCode != 0) {
|
||||
exitProcess(exitCode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "kt56402.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <dispatch/dispatch.h>
|
||||
#include <pthread.h>
|
||||
#import <AppKit/NSApplication.h>
|
||||
#import <Foundation/NSRunLoop.h>
|
||||
#import <Foundation/NSThread.h>
|
||||
|
||||
@implementation OnDestroyHook {
|
||||
void (^onDestroy_)(uintptr_t);
|
||||
}
|
||||
|
||||
- (uintptr_t)identity {
|
||||
return (uintptr_t)self;
|
||||
}
|
||||
|
||||
- (instancetype)init:(void (^)(uintptr_t))onDestroy {
|
||||
if (self = [super init]) {
|
||||
onDestroy_ = onDestroy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
onDestroy_([self identity]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface OnDestroyHookPrivate : NSObject<OnDestroyHook>
|
||||
@end
|
||||
|
||||
@implementation OnDestroyHookPrivate {
|
||||
void (^onDestroy_)(uintptr_t);
|
||||
}
|
||||
|
||||
- (uintptr_t)identity {
|
||||
return (uintptr_t)self;
|
||||
}
|
||||
|
||||
- (instancetype)init:(void (^)(uintptr_t))onDestroy {
|
||||
if (self = [super init]) {
|
||||
onDestroy_ = onDestroy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
onDestroy_([self identity]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void startApp(void (^task)()) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// At this point all other scheduled main queue tasks were already executed.
|
||||
// Executing via performBlock to allow a recursive run loop in `spin()`.
|
||||
[[NSRunLoop currentRunLoop] performBlock:^{
|
||||
task();
|
||||
[NSApp terminate:NULL];
|
||||
}];
|
||||
});
|
||||
[[NSApplication sharedApplication] run];
|
||||
}
|
||||
|
||||
uint64_t currentThreadId() {
|
||||
uint64_t result;
|
||||
int ret = pthread_threadid_np(NULL, &result);
|
||||
assert(ret == 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
BOOL isMainThread() {
|
||||
return [NSThread isMainThread];
|
||||
}
|
||||
|
||||
void spin() {
|
||||
if ([NSRunLoop currentRunLoop] != [NSRunLoop mainRunLoop]) {
|
||||
fprintf(stderr, "Must spin main run loop\n");
|
||||
exit(1);
|
||||
}
|
||||
[[NSRunLoop currentRunLoop]
|
||||
runMode:NSDefaultRunLoopMode
|
||||
beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
}
|
||||
|
||||
OnDestroyHook* global = NULL;
|
||||
|
||||
OnDestroyHook* newGlobal(void(^onDestroy)(uintptr_t)) {
|
||||
global = [[OnDestroyHook alloc] init:onDestroy];
|
||||
return global;
|
||||
}
|
||||
|
||||
void clearGlobal() {
|
||||
global = NULL;
|
||||
}
|
||||
|
||||
id<OnDestroyHook> newOnDestroyHook(void(^onDestroy)(uintptr_t)) {
|
||||
return [[OnDestroyHookPrivate alloc] init:onDestroy];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/messaging.h
|
||||
linkerOpts = -lmessaging
|
||||
@@ -0,0 +1,68 @@
|
||||
#import <objc/NSObject.h>
|
||||
#include <simd/simd.h>
|
||||
|
||||
@interface PrimitiveTestSubject : NSObject
|
||||
|
||||
+ (int)intFn;
|
||||
+ (float)floatFn;
|
||||
+ (double)doubleFn;
|
||||
+ (simd_float4)simdFn;
|
||||
|
||||
@end
|
||||
|
||||
typedef struct {
|
||||
float f;
|
||||
} SingleFloat;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char f1;
|
||||
short f2;
|
||||
char f3;
|
||||
char f4;
|
||||
} SimplePacked;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
char x;
|
||||
short y;
|
||||
char z;
|
||||
} EvenSmallerPacked;
|
||||
|
||||
typedef struct {
|
||||
float f1;
|
||||
float f2;
|
||||
float f3;
|
||||
float f4;
|
||||
} HomogeneousSmall;
|
||||
|
||||
typedef struct {
|
||||
float f1;
|
||||
float f2;
|
||||
float f3;
|
||||
float f4;
|
||||
float f5;
|
||||
float f6;
|
||||
float f7;
|
||||
float f8;
|
||||
} HomogeneousBig;
|
||||
|
||||
// TODO: Add more cases later: SIMD, bitfields.
|
||||
|
||||
typedef struct {
|
||||
short s1;
|
||||
simd_float4 v2;
|
||||
float f3;
|
||||
int i4;
|
||||
} GeterogeneousSmall;
|
||||
|
||||
@interface AggregateTestSubject : NSObject
|
||||
|
||||
+ (SingleFloat)singleFloatFn;
|
||||
+ (SimplePacked)simplePackedFn;
|
||||
+ (EvenSmallerPacked)evenSmallerPackedFn;
|
||||
+ (HomogeneousSmall)homogeneousSmallFn;
|
||||
+ (HomogeneousBig)homogeneousBigFn;
|
||||
+ (simd_quatf)simd_quatfFn;
|
||||
+ (GeterogeneousSmall)geterogeneousSmallFn;
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,58 @@
|
||||
import messaging.*
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.test.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
autoreleasepool {
|
||||
primitives()
|
||||
aggregates()
|
||||
}
|
||||
}
|
||||
|
||||
private fun primitives() {
|
||||
assertEquals(3.14f, PrimitiveTestSubject.floatFn())
|
||||
assertEquals(3.14, PrimitiveTestSubject.doubleFn())
|
||||
assertEquals(42, PrimitiveTestSubject.intFn())
|
||||
assertEquals(vectorOf(2f, 4f, 5f, 8f), PrimitiveTestSubject.simdFn())
|
||||
}
|
||||
|
||||
private fun aggregates() {
|
||||
AggregateTestSubject.singleFloatFn().useContents {
|
||||
assertEquals(3.14f, f)
|
||||
}
|
||||
AggregateTestSubject.simplePackedFn().useContents {
|
||||
assertEquals('0'.toByte(), f1)
|
||||
assertEquals(111, f2)
|
||||
}
|
||||
AggregateTestSubject.evenSmallerPackedFn().useContents {
|
||||
assertEquals('x'.toByte(), x)
|
||||
assertEquals(169, y)
|
||||
assertEquals('z'.toByte(), z)
|
||||
}
|
||||
AggregateTestSubject.homogeneousBigFn().useContents {
|
||||
assertEquals(1.0f, f1)
|
||||
assertEquals(2.0f, f2)
|
||||
assertEquals(3.0f, f3)
|
||||
assertEquals(4.0f, f4)
|
||||
assertEquals(5.0f, f5)
|
||||
assertEquals(6.0f, f6)
|
||||
assertEquals(7.0f, f7)
|
||||
assertEquals(8.0f, f8)
|
||||
}
|
||||
AggregateTestSubject.homogeneousSmallFn().useContents {
|
||||
assertEquals(1.0f, f1)
|
||||
assertEquals(2.0f, f2)
|
||||
assertEquals(3.0f, f3)
|
||||
assertEquals(4.0f, f4)
|
||||
}
|
||||
AggregateTestSubject.simd_quatfFn().useContents {
|
||||
assertEquals(vectorOf(1f, 4f, 9f, 25f), vector)
|
||||
}
|
||||
AggregateTestSubject.geterogeneousSmallFn().useContents {
|
||||
assertEquals(1, s1)
|
||||
assertEquals(vectorOf(1f, 4f, 9f, 25f), v2)
|
||||
assertEquals(3f, f3)
|
||||
assertEquals(4, i4)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#import "messaging.h"
|
||||
|
||||
@implementation PrimitiveTestSubject
|
||||
|
||||
+ (int)intFn {
|
||||
return 42;
|
||||
}
|
||||
|
||||
+ (float)floatFn {
|
||||
return 3.14f;
|
||||
}
|
||||
|
||||
+ (double)doubleFn {
|
||||
return 3.14;
|
||||
}
|
||||
|
||||
+ (simd_float4)simdFn {
|
||||
simd_float4 v;
|
||||
v.x = 2;
|
||||
v.y = 4;
|
||||
v.z = 5;
|
||||
v.w = 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation AggregateTestSubject
|
||||
|
||||
+ (SingleFloat)singleFloatFn {
|
||||
SingleFloat s;
|
||||
s.f = 3.14f;
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (SimplePacked)simplePackedFn {
|
||||
SimplePacked s;
|
||||
s.f1 = '0';
|
||||
s.f2 = 111;
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (EvenSmallerPacked)evenSmallerPackedFn {
|
||||
EvenSmallerPacked s;
|
||||
s.x = 'x';
|
||||
s.y = 169;
|
||||
s.z = 'z';
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (HomogeneousSmall)homogeneousSmallFn {
|
||||
HomogeneousSmall s;
|
||||
s.f1 = 1.0f;
|
||||
s.f2 = 2.0f;
|
||||
s.f3 = 3.0f;
|
||||
s.f4 = 4.0f;
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (HomogeneousBig)homogeneousBigFn {
|
||||
HomogeneousBig s;
|
||||
s.f1 = 1.0f;
|
||||
s.f2 = 2.0f;
|
||||
s.f3 = 3.0f;
|
||||
s.f4 = 4.0f;
|
||||
s.f5 = 5.0f;
|
||||
s.f6 = 6.0f;
|
||||
s.f7 = 7.0f;
|
||||
s.f8 = 8.0f;
|
||||
return s;
|
||||
}
|
||||
|
||||
+ (GeterogeneousSmall)geterogeneousSmallFn {
|
||||
return (GeterogeneousSmall){1, {1, 4, 9, 25}, 3, 4};
|
||||
}
|
||||
|
||||
+ (simd_quatf)simd_quatfFn {
|
||||
return (simd_quatf){ {1, 4, 9, 25} };
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objCAction.h
|
||||
linkerOpts = -lobjCAction
|
||||
@@ -0,0 +1,9 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void performSelector0(id target, NSString* selectorName);
|
||||
void performSelectorInNewThread0(id target, NSString* selectorName);
|
||||
void performSelector1(id target, NSString* selectorName, id arg1);
|
||||
void performSelectorInNewThread1(id target, NSString* selectorName, id arg1);
|
||||
void performSelector2(id target, NSString* selectorName, id arg1, id arg2);
|
||||
void performSelectorInNewThread2(id target, NSString* selectorName, id arg1, id arg2);
|
||||
void setProperty(id target, NSString* propertyName, id value);
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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.ExperimentalStdlibApi::class)
|
||||
|
||||
import objCAction.*
|
||||
|
||||
import platform.Foundation.*
|
||||
import kotlin.test.*
|
||||
import kotlinx.cinterop.*
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
class CondVar {
|
||||
@Volatile
|
||||
@PublishedApi
|
||||
internal var steps = 0
|
||||
|
||||
fun notify() {
|
||||
steps += 1
|
||||
}
|
||||
|
||||
inline fun <R> runAndWait(block: () -> R) {
|
||||
val prev = steps
|
||||
val result = block()
|
||||
// During block() execution someone somewhere does notify().
|
||||
// In this loop we wait to read the result of that notify(), which acts as a synchronization point.
|
||||
while (steps <= prev) {}
|
||||
}
|
||||
}
|
||||
|
||||
class Incrementor : NSObject() {
|
||||
@ObjCOutlet
|
||||
var counter: NSNumber = NSNumber.numberWithInt(0)
|
||||
|
||||
val condVar = CondVar()
|
||||
|
||||
@ObjCAction
|
||||
fun increment() { // Only Unit is allowed for return value
|
||||
println("I'm here to make sure this function generates a frame. Here's an object: ${Any()}")
|
||||
counter = NSNumber.numberWithInt(counter.intValue + 1)
|
||||
condVar.notify()
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun incrementBy(amount: NSNumber) { // Only Unit is allowed for return value
|
||||
println("I'm here to make sure this function generates a frame. Here's an object: ${Any()}")
|
||||
counter = NSNumber.numberWithInt(counter.intValue + amount.intValue)
|
||||
condVar.notify()
|
||||
}
|
||||
|
||||
@ObjCAction
|
||||
fun incrementOtherBy(other: Incrementor, amount: NSNumber) { // Only Unit is allowed for return value
|
||||
println("I'm here to make sure this function generates a frame. Here's an object: ${Any()}")
|
||||
other.incrementBy(amount)
|
||||
condVar.notify()
|
||||
}
|
||||
}
|
||||
|
||||
class IncrementorViaObjC {
|
||||
val impl = Incrementor()
|
||||
|
||||
var counter: NSNumber
|
||||
get() = impl.counter // ObjCOutlet only works for setters - not getters.
|
||||
set(value) = setProperty(impl, "counter", value)
|
||||
|
||||
fun increment() = performSelector0(impl, "increment")
|
||||
|
||||
fun incrementBy(amount: NSNumber) = performSelector1(impl, "incrementBy:", amount)
|
||||
|
||||
fun incrementOtherBy(other: Incrementor, amount: NSNumber) = performSelector2(impl, "incrementOtherBy:amount:", other, amount)
|
||||
}
|
||||
|
||||
class IncrementorViaObjCInNewThread {
|
||||
val impl = Incrementor()
|
||||
|
||||
var counter: NSNumber
|
||||
get() = impl.counter // ObjCOutlet only works for setters - not getters.
|
||||
set(value) = setProperty(impl, "counter", value)
|
||||
|
||||
fun increment() = impl.condVar.runAndWait { performSelectorInNewThread0(impl, "increment") }
|
||||
|
||||
fun incrementBy(amount: NSNumber) = impl.condVar.runAndWait { performSelectorInNewThread1(impl, "incrementBy:", amount) }
|
||||
|
||||
fun incrementOtherBy(other: Incrementor, amount: NSNumber) = impl.condVar.runAndWait { performSelectorInNewThread2(impl, "incrementOtherBy:amount:", other, amount) }
|
||||
}
|
||||
|
||||
// Sanity checking that using functions and properties as regular kotlin properties works.
|
||||
@Test
|
||||
fun testIncrementorKt() {
|
||||
val incrementor = Incrementor()
|
||||
assertEquals(0, incrementor.counter.intValue)
|
||||
|
||||
incrementor.increment()
|
||||
assertEquals(1, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(3))
|
||||
assertEquals(4, incrementor.counter.intValue)
|
||||
|
||||
incrementor.counter = NSNumber.numberWithInt(7)
|
||||
assertEquals(7, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(2))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
|
||||
val otherIncrementor = Incrementor()
|
||||
incrementor.incrementOtherBy(otherIncrementor, NSNumber.numberWithInt(5))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
assertEquals(5, otherIncrementor.counter.intValue)
|
||||
}
|
||||
|
||||
// Doing everything testIncrementorKt does, but via ObjC dynamic dispatch
|
||||
@Test
|
||||
fun testIncrementorObjC() {
|
||||
val incrementor = IncrementorViaObjC()
|
||||
assertEquals(0, incrementor.counter.intValue)
|
||||
|
||||
incrementor.increment()
|
||||
assertEquals(1, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(3))
|
||||
assertEquals(4, incrementor.counter.intValue)
|
||||
|
||||
incrementor.counter = NSNumber.numberWithInt(7)
|
||||
assertEquals(7, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(2))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
|
||||
val otherIncrementor = Incrementor()
|
||||
incrementor.incrementOtherBy(otherIncrementor, NSNumber.numberWithInt(5))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
assertEquals(5, otherIncrementor.counter.intValue)
|
||||
}
|
||||
|
||||
// Doing everything testIncrementorKt does, but via ObjC dynamic dispatch and in a new NSThread
|
||||
@Test
|
||||
fun testIncrementorObjCInNewThread() {
|
||||
if (!isExperimentalMM()) // Cross-thread stuff doesn't work with the legacy MM
|
||||
return
|
||||
|
||||
val incrementor = IncrementorViaObjCInNewThread()
|
||||
assertEquals(0, incrementor.counter.intValue)
|
||||
|
||||
incrementor.increment()
|
||||
assertEquals(1, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(3))
|
||||
assertEquals(4, incrementor.counter.intValue)
|
||||
|
||||
incrementor.counter = NSNumber.numberWithInt(7)
|
||||
assertEquals(7, incrementor.counter.intValue)
|
||||
|
||||
incrementor.incrementBy(NSNumber.numberWithInt(2))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
|
||||
val otherIncrementor = Incrementor()
|
||||
incrementor.incrementOtherBy(otherIncrementor, NSNumber.numberWithInt(5))
|
||||
assertEquals(9, incrementor.counter.intValue)
|
||||
assertEquals(5, otherIncrementor.counter.intValue)
|
||||
}
|
||||
|
||||
// Mixing Kt and ObjC accesses
|
||||
@Test
|
||||
fun testIncrementorMix() {
|
||||
val objc = IncrementorViaObjC()
|
||||
val kt = objc.impl
|
||||
assertEquals(0, kt.counter.intValue)
|
||||
|
||||
kt.increment()
|
||||
assertEquals(1, kt.counter.intValue)
|
||||
|
||||
objc.increment()
|
||||
assertEquals(2, kt.counter.intValue)
|
||||
|
||||
kt.counter = NSNumber.numberWithInt(7)
|
||||
assertEquals(7, kt.counter.intValue)
|
||||
|
||||
objc.increment()
|
||||
assertEquals(8, kt.counter.intValue)
|
||||
|
||||
objc.counter = NSNumber.numberWithInt(11)
|
||||
assertEquals(11, kt.counter.intValue)
|
||||
|
||||
kt.increment()
|
||||
assertEquals(12, kt.counter.intValue)
|
||||
}
|
||||
|
||||
// Mixing Kt and ObjC accesses when ObjC happens in a different thread
|
||||
@Test
|
||||
fun testIncrementorMixInNewThread() {
|
||||
if (!isExperimentalMM()) // Cross-thread stuff doesn't work with the legacy MM
|
||||
return
|
||||
|
||||
val objc = IncrementorViaObjCInNewThread()
|
||||
val kt = objc.impl
|
||||
assertEquals(0, kt.counter.intValue)
|
||||
|
||||
kt.increment()
|
||||
assertEquals(1, kt.counter.intValue)
|
||||
|
||||
objc.increment()
|
||||
assertEquals(2, kt.counter.intValue)
|
||||
|
||||
kt.counter = NSNumber.numberWithInt(7)
|
||||
assertEquals(7, kt.counter.intValue)
|
||||
|
||||
objc.increment()
|
||||
assertEquals(8, kt.counter.intValue)
|
||||
|
||||
objc.counter = NSNumber.numberWithInt(11)
|
||||
assertEquals(11, kt.counter.intValue)
|
||||
|
||||
kt.increment()
|
||||
assertEquals(12, kt.counter.intValue)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "objCAction.h"
|
||||
|
||||
void performSelector0(id target, NSString* selectorName) {
|
||||
NSLog(@"performSelector0(%@, %@)", target, selectorName);
|
||||
// Ignoring return value, because ObjCAction does not allow one.
|
||||
[target performSelector:NSSelectorFromString(selectorName)];
|
||||
}
|
||||
|
||||
void performSelectorInNewThread0(id target, NSString* selectorName) {
|
||||
NSLog(@"performSelectorInNewThread0(%@, %@)", target, selectorName);
|
||||
NSThread* thread = [[NSThread alloc] initWithBlock:^{
|
||||
performSelector0(target, selectorName);
|
||||
}];
|
||||
[thread start];
|
||||
}
|
||||
|
||||
void performSelector1(id target, NSString* selectorName, id arg1) {
|
||||
NSLog(@"performSelector1(%@, %@, %@)", target, selectorName, arg1);
|
||||
// Ignoring return value, because ObjCAction does not allow one.
|
||||
[target performSelector:NSSelectorFromString(selectorName) withObject:arg1];
|
||||
}
|
||||
|
||||
void performSelectorInNewThread1(id target, NSString* selectorName, id arg1) {
|
||||
NSLog(@"performSelectorInNewThread1(%@, %@, %@)", target, selectorName, arg1);
|
||||
// Use NSThread method directly just for variety.
|
||||
NSThread* thread = [[NSThread alloc] initWithTarget:target selector:NSSelectorFromString(selectorName) object:arg1];
|
||||
[thread start];
|
||||
}
|
||||
|
||||
void performSelector2(id target, NSString* selectorName, id arg1, id arg2) {
|
||||
NSLog(@"performSelector2(%@, %@, %@, %@)", target, selectorName, arg1, arg2);
|
||||
// Ignoring return value, because ObjCAction does not allow one.
|
||||
[target performSelector:NSSelectorFromString(selectorName) withObject:arg1 withObject:arg2];
|
||||
}
|
||||
|
||||
void performSelectorInNewThread2(id target, NSString* selectorName, id arg1, id arg2) {
|
||||
NSLog(@"performSelectorInNewThread2(%@, %@, %@, %@)", target, selectorName, arg1, arg2);
|
||||
NSThread* thread = [[NSThread alloc] initWithBlock:^{
|
||||
performSelector2(target, selectorName, arg1, arg2);
|
||||
}];
|
||||
[thread start];
|
||||
}
|
||||
|
||||
void setProperty(id target, NSString* propertyName, id value) {
|
||||
NSLog(@"setProperty(%@, %@, %@)", target, propertyName, value);
|
||||
[target setValue:value forKey:propertyName];
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
language = Objective-C
|
||||
headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h
|
||||
headerFilter = **/with_initializer.h Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h
|
||||
linkerOpts = -lwith_initializer
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#include <Foundation/NSObject.h>
|
||||
@interface A:NSObject
|
||||
|
||||
@end
|
||||
|
||||
@interface B:A
|
||||
+(A*)giveC;
|
||||
@end
|
||||
|
||||
@interface C:A
|
||||
@end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class)
|
||||
|
||||
import with_initializer.*
|
||||
import kotlin.native.Platform
|
||||
|
||||
val a = B.giveC()!! as C
|
||||
|
||||
fun main() {
|
||||
Platform.isMemoryLeakCheckerActive = true
|
||||
println("OK")
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#include "with_initializer.h"
|
||||
@implementation A
|
||||
|
||||
@end
|
||||
|
||||
@implementation B:A
|
||||
+(A*)giveC{
|
||||
return [[C alloc] init];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation C:A
|
||||
@end
|
||||
|
||||
#if 0
|
||||
int main() {
|
||||
[B giveC];
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user