ExportedAnnotationTest.kt
src/jvmMain/kotlin/afield/examples/ExportedAnnotationTest.kt · 229 lines
package afield.examples
import afield.document.graph.Box
import afield.runtime.kotlin.KotlinInterface
import afield.document.graph.preamble
FIELD_TEST id: examples.exported-annotation.smoke runner: jvm-main entrypoint: afield.examples.ExportedAnnotationTest tags: [smoke, examples, kotlin, exports] timeoutSec: 20 expect: exitCode: 0 stdoutContains: - "@exported annotation tests passed"
object ExportedAnnotationTest
Test for @exported annotation functionality
This follows the repo's lightweight "runnable object" style instead of JUnit.
object ExportedAnnotationTest {
fun runTests(): Boolean
fun runTests(): Boolean {
return try {
println("Running @exported annotation tests...")
testBasicExport()
testMultipleExports()
testNoDuplicates()
testTypeSimplification()
testExportedValAndVar()
testReplaceExistingDeclarationOnTypeChange()
println("✅ @exported annotation tests passed")
true
} catch (t: Throwable) {
println("❌ @exported annotation tests failed: ${t.message}")
t.printStackTrace()
false
}
}
fun main(args: Array<String>)
body — 185 lines, from line 44
@JvmStatic
fun main(args: Array<String>) {
check(runTests()) { "@exported annotation tests failed" }
}
private fun testBasicExport() {
println(" Testing basic @exported function...")
// Create a box and KI
val box = Box("test_export_1")
val ki = KotlinInterface(filePrefix = "test1")
ki.associatedBox = box
// Execute code with an @exported function
val code = """
@exported
fun add(x: Int, y: Int): Int {
return x + y
}
""".trimIndent()
ki.execAndWait(code)
// Check that the preamble was updated
val preamble = box.preamble
println(" Preamble: $preamble")
require(preamble.isNotEmpty()) { "Preamble should not be empty" }
require(preamble.contains("val Box._add by InheritanceFromContext<")) { "Preamble should contain exported _add declaration" }
require(preamble.contains("kotlin.Int")) { "Preamble should contain parameter types" }
require(preamble.contains("/**/")) { "Preamble should contain inherit marker /**/" }
val exported = box.getPropertyRaw("_add")
require(exported != null) { "Box property '_add' should be set to an exported function reference" }
@Suppress("UNCHECKED_CAST")
val add = exported as (Int, Int) -> Int
require(add(2, 3) == 5) { "Exported add function should be callable via box property" }
}
private fun testMultipleExports() {
println(" Testing multiple @exported functions...")
val box = Box("test_export_2")
val ki = KotlinInterface(filePrefix = "test2")
ki.associatedBox = box
// Execute code with multiple @exported functions
val code = """
@exported
fun multiply(x: Int, y: Int): Int = x * y
@exported
fun greet(name: String): String = "Hello, ${'$'}name"
""".trimIndent()
ki.execAndWait(code)
val preamble = box.preamble
println(" Preamble: $preamble")
require(preamble.contains("val Box._multiply by InheritanceFromContext<")) { "Preamble should contain _multiply" }
require(preamble.contains("val Box._greet by InheritanceFromContext<")) { "Preamble should contain _greet" }
}
private fun testNoDuplicates() {
println(" Testing no duplicate exports...")
val box = Box("test_export_3")
val ki = KotlinInterface(filePrefix = "test3")
ki.associatedBox = box
// Execute code with @exported function twice
val code = """
@exported
fun square(x: Int): Int = x * x
""".trimIndent()
ki.execAndWait(code)
val preambleAfterFirst = box.preamble
val countAfterFirst = preambleAfterFirst.split("val Box._square by InheritanceFromContext<").size - 1
ki.execAndWait(code)
val preambleAfterSecond = box.preamble
val countAfterSecond = preambleAfterSecond.split("val Box._square by InheritanceFromContext<").size - 1
println(" After first exec: $countAfterFirst occurrences")
println(" After second exec: $countAfterSecond occurrences")
require(countAfterFirst == 1) { "Should have 1 occurrence after first execution" }
require(countAfterSecond == 1) { "Should still have 1 occurrence after second execution (no duplicates)" }
}
private fun testTypeSimplification() {
println(" Testing type simplification for non-whitelisted types...")
val box = Box("test_export_4")
val ki = KotlinInterface(filePrefix = "test4")
ki.associatedBox = box
// Execute code with a function that uses a custom type
// Since we can't easily create custom types in the test, we'll just verify
// that the basic whitelisted types work correctly
val code = """
@exported
fun processData(items: List<String>): Int {
return items.size
}
""".trimIndent()
ki.execAndWait(code)
val preamble = box.preamble
println(" Preamble: $preamble")
require(preamble.contains("val Box._processData by InheritanceFromContext<")) { "Preamble should contain processData" }
require(preamble.contains("/**/")) { "Preamble should contain inherit marker" }
}
private fun testExportedValAndVar() {
println(" Testing @exported val/var...")
val box = Box("test_export_5")
val ki = KotlinInterface(filePrefix = "test5")
ki.associatedBox = box
val code = """
@exported
val answer: Int = 42
@exported
var counter: Int = 1
counter += 4
""".trimIndent()
ki.execAndWait(code)
val preamble = box.preamble
println(" Preamble: $preamble")
require(preamble.contains("val Box._answer by InheritanceFromContext<")) { "Preamble should contain exported val alias _answer" }
require(preamble.contains("var Box._counter by InheritanceFromContext<")) { "Preamble should contain exported var alias _counter" }
val answer = box.getPropertyRaw("_answer")
val counter = box.getPropertyRaw("_counter")
require(answer == 42) { "Expected _answer to be 42, got $answer" }
require(counter == 5) { "Expected _counter to be 5, got $counter" }
}
private fun testReplaceExistingDeclarationOnTypeChange() {
println(" Testing replacement of existing exported declarations when type changes...")
val box = Box("test_export_6")
val ki = KotlinInterface(filePrefix = "test6")
ki.associatedBox = box
// Seed preamble with legacy declaration forms that should be replaced.
box.preamble = """
val _convert by Delegation<(kotlin.Int) -> kotlin.Int>() /**/
val _answer: kotlin.Int /**/
""".trimIndent()
val code = """
@exported
fun convert(x: String): String = x.reversed()
@exported
val answer: String = "ok"
""".trimIndent()
ki.execAndWait(code)
val preamble = box.preamble
println(" Preamble after replacement: $preamble")
require(!preamble.contains("val _convert by Delegation<(kotlin.Int) -> kotlin.Int>() /**/")) {
"Old function declaration should be removed"
}
require(!preamble.contains("val _answer: kotlin.Int /**/")) {
"Old val declaration should be removed"
}
require(preamble.contains("val Box._convert by InheritanceFromContext<(kotlin.String) -> kotlin.String>() /*from:self*/ /**/")) {
"Updated function declaration should be present"
}
require(preamble.contains("val Box._answer by InheritanceFromContext<kotlin.String>() /*from:self*/ /**/")) {
"Updated val declaration should be present"
}
}
}