Skip to content

Instantly share code, notes, and snippets.

@dellisd
Last active December 28, 2023 11:33
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dellisd/a1df42787d42b41cd3ce16f573984674 to your computer and use it in GitHub Desktop.
Save dellisd/a1df42787d42b41cd3ce16f573984674 to your computer and use it in GitHub Desktop.
Kotlin Multiplatform test resources
// Part of a multi-module project, hence the rootProject everywhere
task copyTestResourcesForJs(type: Copy) {
from "$projectDir/src/commonTest/resources"
into "${rootProject.buildDir}/js/packages/${rootProject.name}-${project.name}-test/src/commonTest/resources"
}
jsNodeTest.dependsOn copyTestResourcesForJs
// Common
const val RESOURCE_PATH = "./src/commonTest/resources"
expect class Resource(name: String) {
val name: String
fun exists(): Boolean
fun readText(): String
}
// JVM
import java.io.File
actual class Resource actual constructor(actual val name: String) {
private val file = File("$RESOURCE_PATH/$name")
actual fun exists(): Boolean = file.exists()
actual fun readText(): String = file.readText()
}
// Native
import kotlinx.cinterop.*
import platform.posix.*
actual class Resource actual constructor(actual val name: String) {
private val file: CPointer<FILE>? = fopen("$RESOURCE_PATH/$name", "r")
actual fun exists(): Boolean = file != null
actual fun readText(): String {
fseek(file, 0, SEEK_END)
val size = ftell(file)
rewind(file)
return memScoped {
val tmp = allocArray<ByteVar>(size)
fread(tmp, sizeOf<ByteVar>().convert(), size.convert(), file)
tmp.toKString()
}
}
}
// JS (Node)
private external fun require(module: String): dynamic
private val fs = require("fs")
actual class Resource actual constructor(actual val name: String) {
private val path = "$RESOURCE_PATH/$name"
actual fun exists(): Boolean = fs.existsSync(path) as Boolean
actual fun readText(): String = fs.readFileSync(path, "utf8") as String
}
@Test
fun testLength2() {
// Located at /src/commonTest/resources/test.json
val resource = Resource("test.json")
val geometry = resource.readText().toGeometry<LineString>()
assertEquals(42.560767589197006, length(geometry, Units.Kilometers))
}
@micHar
Copy link

micHar commented May 2, 2021

You helped me a lot with this script, thanks!

Do you maybe know how to read an InputStream (NSInputStream for ios) instead of string directly? Byte operations in Kotlin Native are a great mystery for me ;)

@dellisd
Copy link
Author

dellisd commented May 3, 2021

I haven't had a need to stream the input yet so I haven't really thought much about it, but you could probably use something like Okio and return an implementation of a Source (or whatever implementation of an InputStream you want to use). You'd just have to adjust the fread call to read the number of bytes that are requested.

@micHar
Copy link

micHar commented May 3, 2021

Yesterday I played with this script a bit and it didn't really work for iosX64 target. I guess it's more complicated than that, after all it's not run inside an ios filesystem (although on an m1 mac there should be a way).

@dellisd
Copy link
Author

dellisd commented May 3, 2021

That's true. This was built for use in JVM, Native (non-iOS/watchOS/tvOS), and Node environments.

Android Instrumentation tests, those other Apple environments, and Browser environments would require some more work, but I suspect they're not impossible.

@Tiagoperes
Copy link

It didn't work for me while trying to run tests on iOS. This modification did the job:

// Native
import kotlinx.cinterop.*
import platform.posix.*

actual class Resource actual constructor(actual val name: String) {
    private val path = NSBundle.mainBundle.pathForResource("resources/FILE_NAME", "FILE_EXTENSION") ?: ""
    private val file: CPointer<FILE>? = fopen(path, "r")

Then, in the build.gradle.kts file:

tasks.register<Copy>("copyiOSTestResources") {
  from("src/commonTest/resources")
  into("build/bin/iosX64/debugTest/resources")
}

tasks.findByName("iosX64Test")!!.dependsOn("copyiOSTestResources")

This article helped me out a lot: https://developer.squareup.com/blog/kotlin-multiplatform-shared-test-resources/

@YSakhno
Copy link

YSakhno commented Oct 9, 2023

Cannot you simply specify

private val path = "kotlin/$name"

in the JS implementation of the Resource class's constructor (i.e. without the need for the copyTestResourcesForJs Gradle task)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment