[K/N][Tests] Migrate dylib-interop-exe tests

^KT-61259
This commit is contained in:
Vladimir Sukharev
2024-02-15 13:57:30 +01:00
committed by Space Team
parent 83d851dd61
commit 2b2c685827
56 changed files with 278 additions and 397 deletions
@@ -0,0 +1,40 @@
// TARGET_BACKEND: NATIVE
// MODULE: cinterop
// FILE: kt55938.def
language = Objective-C
headers = kt55938.h
// FILE: kt55938.h
#import <Foundation/Foundation.h>
@interface ObjCClass : NSObject
+ (int)foo;
@end
// FILE: kt55938.m
#include "kt55938.h"
@implementation ObjCClass {
}
+ (int)foo {
return 42;
}
@end
// MODULE: lib(cinterop)
// FILE: lib.kt
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
package kt55938lib
import kt55938.*
inline fun foo() = ObjCClass.foo()
// MODULE: main(lib, cinterop)
// FILE: main.kt
import kt55938lib.*
fun box(): String {
val foo = foo()
if (foo != 42) return "FAIL $foo"
return "OK"
}
@@ -0,0 +1,3 @@
language = Objective-C
headerFilter = **/objc_wrap.h
linkerOpts = -lforeignException
@@ -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,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
@@ -0,0 +1,3 @@
language = Objective-C
headerFilter = **/friendly_dealloc.h
linkerOpts = -lfriendly_dealloc
@@ -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);
@@ -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)
}
@@ -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];
}
@@ -0,0 +1,4 @@
language = Objective-C
headerFilter = **/include_categories.h
linkerOpts = -linclude_categories
objcClassesIncludingCategories = BaseClass DerivedClass
@@ -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;
@@ -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))
}
@@ -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];
}
@@ -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
@@ -0,0 +1,11 @@
#include <Foundation/NSObject.h>
@interface A:NSObject
@end
@interface B:A
+(A*)giveC;
@end
@interface C:A
@end
@@ -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")
}
@@ -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