WIP tests
diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.h b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.h
index 610da07..eb55b2d 100644
--- a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.h
+++ b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.h
@@ -2,17 +2,26 @@
 #import <Foundation/NSObject.h>
 #import <Foundation/NSString.h>
 
+@interface ObjCLivenessTracker : NSObject
+-(void)add:(id)obj;
+-(Boolean)isEmpty;
+-(Boolean)objectsAreAlive;
+-(Boolean)objectsAreDead;
+@end;
+
 @interface NoAutoreleaseCustomObject : NSObject
 @end;
 
 @protocol NoAutoreleaseHelper
 @required
 
--(void)sendObject:(id)obj;
--(id)sameObject:(id)obj;
+@property id kotlinObject;
 
--(void)sendCustomObject:(NoAutoreleaseCustomObject*)customObject;
--(NoAutoreleaseCustomObject*)receiveCustomObject;
+-(void)sendKotlinObject:(id)kotlinObject;
+-(id)receiveKotlinObject;
+
+-(void)sendObjCObject:(NoAutoreleaseCustomObject*)objCObject;
+-(NoAutoreleaseCustomObject*)receiveObjCObject;
 
 -(void)sendArray:(NSArray*)array;
 -(NSArray*)receiveArray;
@@ -22,10 +31,76 @@
 
 -(void)sendBlock:(int (^)(void))block;
 -(int (^)(void))receiveBlock;
-
--(Boolean)weakIsNull;
 @end;
 
-id<NoAutoreleaseHelper> getNoAutoreleaseHelperImpl(void);
+id<NoAutoreleaseHelper> getNoAutoreleaseHelperImpl(ObjCLivenessTracker*);
 
-NSString* testSend
\ No newline at end of file
+void callSendKotlinObject(id<NoAutoreleaseHelper> helper, id obj, ObjCLivenessTracker* tracker) {
+    [helper sendKotlinObject:obj];
+    [helper sendKotlinObject:obj];
+    [tracker add:obj];
+}
+
+void callReceiveKotlinObject(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    [tracker add:[helper receiveKotlinObject]];
+    [tracker add:[helper receiveKotlinObject]];
+}
+
+void callSendObjCObject(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    NoAutoreleaseCustomObject* obj = [NoAutoreleaseCustomObject new];
+
+    [helper sendObjCObject:obj];
+    [helper sendObjCObject:obj];
+    [tracker add:obj];
+}
+
+void callReceiveObjCObject(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    [tracker add:[helper receiveObjCObject]];
+    [tracker add:[helper receiveObjCObject]];
+}
+
+void callSendArray(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    NSArray* array = @[[NSObject new]];
+
+    [helper sendArray:array];
+    [helper sendArray:array];
+    [tracker add:array];
+}
+
+void callReceiveArray(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    [tracker add:[helper receiveArray]];
+    [tracker add:[helper receiveArray]];
+}
+
+void callSendString(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    NSString* string = [[NSObject new] description]; // To make it dynamic.
+
+    [helper sendString:string];
+    [helper sendString:string];
+    [tracker add:string];
+}
+
+void callReceiveString(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    [tracker add:[helper receiveString]];
+    [tracker add:[helper receiveString]];
+}
+
+extern int blockResult;
+
+int (^createBlock(void))() {
+    int localBlockResult = blockResult;
+    return ^{ return localBlockResult; }; // Try to make it capturing thus dynamic.
+}
+
+void callSendBlock(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    int (^block)(void) = createBlock(); // Try to make it heap-allocated.
+
+    [helper sendBlock:block];
+    [helper sendBlock:block];
+    [tracker add:block];
+}
+
+void callReceiveBlock(id<NoAutoreleaseHelper> helper, ObjCLivenessTracker* tracker) {
+    [tracker add:[helper receiveBlock]];
+    [tracker add:[helper receiveBlock]];
+}
diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.kt b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.kt
index 101653a..9ec4ad1 100644
--- a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.kt
+++ b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.kt
@@ -1,119 +1,251 @@
 import kotlinx.cinterop.*
+import kotlin.native.internal.GC
 import kotlin.native.ref.*
 import kotlin.test.*
 import objcTests.*
 
-private fun <T> testSend(
+// The tests below make best efforts to ensure that objects don't "leak" to autoreleasepool
+// when simply passing them between Kotlin and Objective-C.
+
+private class KotlinLivenessTracker {
+    val refs = mutableListOf<WeakReference<Any>>()
+
+    fun add(obj: Any) {
+        refs += WeakReference(obj)
+    }
+
+    fun isEmpty() = refs.isEmpty()
+    fun objectsAreAlive() = refs.all { it.value !== null }
+    fun objectsAreDead() = refs.all { it.value === null }
+}
+
+private fun test(
+        kotlinPeerRetainsObjC: Boolean = true,
+        block: (kotlinLivenessTracker: KotlinLivenessTracker, objCLivenessTracker: ObjCLivenessTracker) -> Unit
+) = repeat(2) {
+    val kotlinLivenessTracker = KotlinLivenessTracker()
+    val objCLivenessTracker = ObjCLivenessTracker()
+
+    GC.collect() // Make predictable
+
+    autoreleasepool { // FIXME: remove!!
+    block(kotlinLivenessTracker, objCLivenessTracker)
+    }
+
+    assertFalse(kotlinLivenessTracker.isEmpty())
+    assertTrue(kotlinLivenessTracker.objectsAreAlive())
+
+    assertFalse(objCLivenessTracker.isEmpty())
+    if (kotlinPeerRetainsObjC) {
+        assertTrue(objCLivenessTracker.objectsAreAlive())
+    } else {
+        assertTrue(objCLivenessTracker.objectsAreDead())
+    }
+
+    GC.collect()
+
+    assertTrue(kotlinLivenessTracker.objectsAreDead())
+    assertTrue(objCLivenessTracker.objectsAreDead())
+
+    // TODO: can we create an autoreleasepool and actually check that it is not used?
+}
+
+private fun <T> testSendToObjC(
         createObject: () -> T,
         kotlinPeerRetainsObjC: Boolean = true,
         sendObject: NoAutoreleaseHelperProtocol.(T) -> Unit
-) = repeat(2) {
-    val helper1 = getNoAutoreleaseHelperImpl()!!
-    val helper2 = getNoAutoreleaseHelperImpl()!!
+) = test(kotlinPeerRetainsObjC) { kotlinLivenessTracker, objCLivenessTracker ->
+    val helper = getNoAutoreleaseHelperImpl(objCLivenessTracker)!!
 
-    val weakRef = runNoInline {
-        val obj = createObject()
+    val obj = createObject()!!
+    kotlinLivenessTracker.add(obj)
 
-        helper1.sendObject(obj)
-        helper2.sendObject(obj)
-
-        WeakReference(obj)
-    }
-
-    runNoInline {
-        assertNotNull(weakRef.value)
-        assertEquals(!kotlinPeerRetainsObjC, helper1.weakIsNull())
-        assertEquals(!kotlinPeerRetainsObjC, helper2.weakIsNull())
-    }
-
-    kotlin.native.internal.GC.collect()
-
-    runNoInline {
-        assertNull(weakRef.value)
-        assertTrue(helper1.weakIsNull())
-        assertTrue(helper2.weakIsNull())
-    }
+    helper.sendObject(obj)
+    helper.sendObject(obj)
 }
 
-private fun <T> testReceive(
+private fun <T> testReceiveFromObjC(
         kotlinPeerRetainsObjC: Boolean = true, // FIXME: is it used?
         receiveObject: NoAutoreleaseHelperProtocol.() -> T
-) = repeat(2) {
-    val helper = getNoAutoreleaseHelperImpl()!!
+) = test(kotlinPeerRetainsObjC) { kotlinLivenessTracker, objCLivenessTracker ->
+    val helper = getNoAutoreleaseHelperImpl(objCLivenessTracker)!!
 
-    val (weakRef1, weakRef2) = runNoInline {
-        val obj1 = helper.receiveObject()
-        val obj2 = helper.receiveObject()
+    val obj1 = helper.receiveObject()!!
+    val obj2 = helper.receiveObject()!!
 
-        helper.clear()
+    kotlinLivenessTracker.add(obj1)
+    kotlinLivenessTracker.add(obj2)
+}
 
-        Pair(WeakReference(obj1), WeakReference(obj2))
+private fun testCallToKotlin(
+        kotlinPeerRetainsObjC: Boolean = true, // FIXME: is it used?
+        callObjC: NoAutoreleaseHelperProtocol.(ObjCLivenessTracker) -> Unit
+) = test { kotlinLivenessTracker, objCLivenessTracker ->
+
+    val helper = object : NSObject(), NoAutoreleaseHelperProtocol {
+        val myKotlinObject = KotlinObject4()
+        val objCObject = NoAutoreleaseCustomObject()
+        val array = listOf(Any())
+        val string = Any().toString()
+        val block = createLambda()
+
+        override fun kotlinObject(): Any? {
+            error("should not be used")
+        }
+
+        override fun setKotlinObject(value: Any?) {
+            error("should not be used")
+        }
+
+        override fun sendKotlinObject(kotlinObject: Any?) {
+            kotlinLivenessTracker.add(kotlinObject!!)
+        }
+
+        override fun receiveKotlinObject(): Any? {
+            val result = myKotlinObject
+            kotlinLivenessTracker.add(result)
+            return result
+        }
+
+        override fun sendObjCObject(objCObject: NoAutoreleaseCustomObject?) {
+            kotlinLivenessTracker.add(objCObject!!)
+        }
+
+        override fun receiveObjCObject(): NoAutoreleaseCustomObject? {
+            val result = objCObject
+            kotlinLivenessTracker.add(result)
+            return result
+        }
+
+        override fun sendArray(array: List<*>?) {
+            kotlinLivenessTracker.add(array!!)
+        }
+
+        override fun receiveArray(): List<*>? {
+            val result = array
+            kotlinLivenessTracker.add(result)
+            return result
+        }
+
+        override fun sendString(string: String?) {
+            kotlinLivenessTracker.add(string!!)
+        }
+
+        override fun receiveString(): String? {
+            val result = string
+            kotlinLivenessTracker.add(result)
+            return result
+        }
+
+        override fun sendBlock(block: (() -> Int)?) {
+            kotlinLivenessTracker.add(block!!)
+        }
+
+        override fun receiveBlock(): (() -> Int)? {
+            val result = block
+            kotlinLivenessTracker.add(result)
+            return result
+        }
     }
 
-    runNoInline {
-        assertNotNull(weakRef1.value)
-        assertNotNull(weakRef2.value)
-        assertEquals(!kotlinPeerRetainsObjC, helper.weakIsNull())
-    }
+    helper.callObjC(objCLivenessTracker)
+}
 
-    kotlin.native.internal.GC.collect()
+private class KotlinObject1
+private class KotlinObject2
+private class KotlinObject3
+private class KotlinObject4
 
-    runNoInline {
-        assertNull(weakRef1.value)
-        assertNull(weakRef2.value)
-        assertTrue(helper.weakIsNull())
+@Test fun testSendKotlinObjectToObjC() = testSendToObjC({ KotlinObject1() }) {
+    sendKotlinObject(it)
+}
+
+@Test fun testReceiveKotlinObjectFromObjC() {
+    val obj = KotlinObject2()
+    testReceiveFromObjC {
+        this.kotlinObject = obj
+        receiveKotlinObject()
     }
 }
 
-private class KotlinObject
-
-@Test fun testSendObject() = testSend({ KotlinObject() }) {
-    sendObject(it)
+@Test fun testSendObjCObjectToObjC() = testSendToObjC({ NoAutoreleaseCustomObject() }) {
+    sendObjCObject(it)
 }
 
-@Test
-fun testSameObject() {
-    val obj = KotlinObject()
-    testReceive {
-        sameObject(obj)
-    }
+@Test fun testReceiveObjCObjectFromObjC() = testReceiveFromObjC {
+    receiveObjCObject()
 }
 
-@Test fun testSendCustomObject() = testSend({ NoAutoreleaseCustomObject() }) {
-    sendCustomObject(it)
-}
-
-@Test fun testReceiveCustomObject() = testReceive {
-    receiveCustomObject()
-}
-
-@Test fun testSendArray() = testSend({ listOf(KotlinObject()) }, kotlinPeerRetainsObjC = false) {
+@Test fun testSendArrayToObjC() = testSendToObjC({ listOf(KotlinObject1()) }, kotlinPeerRetainsObjC = false) {
     sendArray(it)
 }
 
-@Test fun testReceiveArray() = testReceive {
+@Test fun testReceiveArrayFromObjC() = testReceiveFromObjC {
     receiveArray()
 }
 
-@Test fun testSendString() = testSend({ Any().toString() }) {
+@Test fun testSendStringToObjC() = testSendToObjC({ Any().toString() }) {
     sendString(it)
 }
 
-@Test fun testReceiveString() = testReceive {
+@Test fun testReceiveStringFromObjC() = testReceiveFromObjC {
     receiveString()
 }
 
-@Test fun testSendBlock() = testSend({
-    val lambdaResult = 123
-    { lambdaResult } // make it capturing
-}, kotlinPeerRetainsObjC = false) {
+@Test fun testSendBlockToObjC() = testSendToObjC({ createLambda() }, kotlinPeerRetainsObjC = false) {
     sendBlock(it)
 }
 
-@Test fun testReceiveBlock() = testReceive {
+@Test fun testReceiveBlockFromObjC() = testReceiveFromObjC {
     receiveBlock()
 }
 
+@Test fun testSendKotlinObjectToKotlin() = testCallToKotlin {
+    callSendKotlinObject(this, KotlinObject3(), it)
+}
+
+@Test fun testReceiveKotlinObjectFromKotlin() = testCallToKotlin {
+    callReceiveKotlinObject(this, it)
+}
+
+@Test fun testSendObjCObjectToKotlin() = testCallToKotlin {
+    callSendObjCObject(this, it)
+}
+
+@Test fun testReceiveObjCObjectFromKotlin() = testCallToKotlin {
+    callReceiveObjCObject(this, it)
+}
+
+@Test fun testSendArrayToKotlin() = testCallToKotlin {
+    callSendArray(this, it)
+}
+
+@Test fun testReceiveArrayFromKotlin() = testCallToKotlin {
+    callReceiveArray(this, it)
+}
+
+@Test fun testSendStringToKotlin() = testCallToKotlin {
+    callSendString(this, it)
+}
+
+@Test fun testReceiveStringFromKotlin() = testCallToKotlin {
+    callReceiveString(this, it)
+}
+
+@Test fun testSendBlockToKotlin() = testCallToKotlin {
+    callSendBlock(this, it)
+}
+
+@Test fun testReceiveBlockFromKotlin() = testCallToKotlin {
+    callReceiveBlock(this, it)
+}
+
+private fun createLambda(): () -> Int {
+    val lambdaResult = 123
+    return { lambdaResult } // make it capturing
+}
+
+// FIXME: remove
 // Note: this executes code with a separate stack frame,
 //   so no stack refs will remain after it, and GC will be able to collect the garbage.
 private fun <R> runNoInline(block: () -> R) = block()
diff --git a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.m b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.m
index b4ad6ae..f387c29 100644
--- a/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.m
+++ b/kotlin-native/backend.native/tests/interop/objc/tests/noAutorelease.m
@@ -4,68 +4,127 @@
 @end;
 
 @interface NoAutoreleaseHelperImpl : NSObject <NoAutoreleaseHelper>
-@property (weak) id weakRef;
+@property ObjCLivenessTracker* objCLivenessTracker;
+
+@property id kotlinObject;
+@property NoAutoreleaseCustomObject* objCObject;
+@property NSArray* array;
+@property NSString* string;
+@property int (^block)(void);
+
 @end;
 
-int globalBlockResult = 123;
-
 @implementation NoAutoreleaseHelperImpl
-
--(void)sendObject:(id)obj {
-    self.weakRef = obj;
+-(instancetype)init {
+    if (self = [super init]) {
+        self.objCObject = [NoAutoreleaseCustomObject new];
+        self.array = @[[NSObject new]];
+        self.string = [[NSObject new] description];
+        self.block = createBlock();
+    }
+    return self;
 }
 
--(id)sameObject:(id)obj {
-    self.weakRef = obj;
-    return obj;
+-(void)sendKotlinObject:(id)kotlinObject {
+    [self.objCLivenessTracker add:kotlinObject];
 }
 
--(void)sendCustomObject:(NoAutoreleaseCustomObject*)customObject {
-    self.weakRef = customObject;
+-(id)receiveKotlinObject {
+    id result = self.kotlinObject;
+    [self.objCLivenessTracker add:result];
+    return result;
 }
 
--(NoAutoreleaseCustomObject*)receiveCustomObject {
-    NoAutoreleaseCustomObject* result = [NoAutoreleaseCustomObject new];
-    self.weakRef = result;
+-(void)sendObjCObject:(NoAutoreleaseCustomObject*)objCObject {
+    [self.objCLivenessTracker add:objCObject];
+}
+
+-(NoAutoreleaseCustomObject*)receiveObjCObject {
+    NoAutoreleaseCustomObject* result = self.objCObject;
+    [self.objCLivenessTracker add:result];
     return result;
 }
 
 -(void)sendArray:(NSArray*)array {
-    self.weakRef = array;
+    [self.objCLivenessTracker add:array];
 }
 
 -(NSArray*)receiveArray {
-    NSArray* result = @[[NSObject new]];
-    self.weakRef = result;
+    NSArray* result = self.array;
+    [self.objCLivenessTracker add:result];
     return result;
 }
 
 -(void)sendString:(NSString*)string {
-    self.weakRef = string;
+    [self.objCLivenessTracker add:string];
 }
 
 -(NSString*)receiveString {
-    NSString* result = [NSObject new].description;
-    self.weakRef = result;
+    NSString* result = self.string;
+    [self.objCLivenessTracker add:result];
     return result;
 }
 
 -(void)sendBlock:(int (^)(void))block {
-    self.weakRef = block;
+    [self.objCLivenessTracker add:block];
 }
 
 -(int (^)(void))receiveBlock {
-    int blockResult = globalBlockResult; // To make block capturing.
-    int (^result)(void) = ^{ return blockResult; };
-    self.weakRef = result;
+    int (^result)(void) = self.block;
+    [self.objCLivenessTracker add:result];
     return result;
 }
 
--(Boolean)weakIsNull {
-    return self.weakRef == nil;
-}
 @end;
 
-id<NoAutoreleaseHelper> getNoAutoreleaseHelperImpl(void) {
-    return [NoAutoreleaseHelperImpl new];
+id<NoAutoreleaseHelper> getNoAutoreleaseHelperImpl(ObjCLivenessTracker* objCLivenessTracker) {
+    NoAutoreleaseHelperImpl* result = [NoAutoreleaseHelperImpl new];
+    result.objCLivenessTracker = objCLivenessTracker;
+    return result;
 }
+
+int blockResult = 42;
+
+@interface ObjCWeakRef : NSObject
+@property (weak) id value;
+@end;
+
+@implementation ObjCWeakRef;
+@end;
+
+@implementation ObjCLivenessTracker {
+    NSMutableArray<ObjCWeakRef*>* weakRefs;
+}
+
+-(instancetype)init {
+    if (self = [super init]) {
+        self->weakRefs = [NSMutableArray new];
+    }
+    return self;
+}
+
+-(void)add:(id)obj {
+    ObjCWeakRef* weakRef = [ObjCWeakRef new];
+    weakRef.value = obj;
+    [weakRefs addObject:weakRef];
+}
+
+-(Boolean)isEmpty {
+    return [weakRefs count] == 0;
+}
+
+-(Boolean)objectsAreAlive {
+    for (ObjCWeakRef* weakRef in weakRefs) {
+        if (weakRef.value == nil) return NO;
+    }
+    return YES;
+}
+
+-(Boolean)objectsAreDead {
+    for (ObjCWeakRef* weakRef in weakRefs) {
+        if (weakRef.value != nil) return NO;
+    }
+    return YES;
+}
+
+@end;