[K/N] Add tests on ObjCAction and ObjCOutlet. ^KT-58441
This commit is contained in:
committed by
Space Team
parent
927db1bb56
commit
860d0e32f7
@@ -4447,6 +4447,10 @@ if (PlatformInfo.isAppleTarget(project)) {
|
||||
it.defFile 'interop/objc/kt56402/objclib.def'
|
||||
it.headers "$projectDir/interop/objc/kt56402/objclib.h"
|
||||
}
|
||||
createInterop("objCAction") {
|
||||
it.defFile 'interop/objc/objCAction/objclib.def'
|
||||
it.headers "$projectDir/interop/objc/objCAction/objclib.h"
|
||||
}
|
||||
}
|
||||
|
||||
createInterop("withSpaces") {
|
||||
@@ -5316,6 +5320,24 @@ if (PlatformInfo.isAppleTarget(project)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interopTest("interop_objCAction") {
|
||||
source = 'interop/objc/objCAction/main.kt'
|
||||
interop = "objCAction"
|
||||
flags = ["-tr"]
|
||||
doBeforeBuild {
|
||||
mkdir(buildDir)
|
||||
project.extensions.execClang.execClangForCompilerTests(project.target) {
|
||||
args "$projectDir/interop/objc/objCAction/objclib.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjCAction.dylib"
|
||||
}
|
||||
if (UtilsKt.isSimulatorTarget(project, project.target)) {
|
||||
UtilsKt.codesign(project, "$buildDir/libobjCAction.dylib")
|
||||
}
|
||||
}
|
||||
UtilsKt.dependsOnPlatformLibs(it)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("KT-50983", KonanDriverTest) {
|
||||
|
||||
@@ -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 objclib.*
|
||||
|
||||
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,3 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/objclib.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,47 @@
|
||||
#include "objclib.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];
|
||||
}
|
||||
Reference in New Issue
Block a user