I’m attempting to read a file in a Kotlin multipla...
# multiplatform
j
I’m attempting to read a file in a Kotlin multiplatform unit test. I don’t have the absolute path to the file, but I do know it relative to my project directory. When I run the test on JVM or macosArm64, the process' current working directory (CWD) is the project directory so everything is easy. But when I run the test on a simulator, the CWD is
~/Library/Developer/CoreSimulator/Devices/C641A553-AA3D-4FAD-4AE-8BDD0E346C5D/data
. Anyone know of a way to recover the project directory when running a test in a simulator?
l
Keep in mind that Android does not allow you to access the computer’s file structure anyways. I would recommend hosting the file on some http endpoint (even a GitHub/Google Drive), and having the test download it if it’s not present on the device. This should only have to run when you make a new simulator.
If you can’t host it remotely for some reason, you can add a task to gradle
Copy code
val iosTestTasks = arrayOf("iosX64Test", "iosArm64Test").mapNotNull {
    tasks.findByName(it)
}

iosTestTasks.forEach {
    it.doFirst {
        copy {
            from(File(projectDir, "src/nativeTest/resources"))
            into(File(buildDir, "bin/iosX64/debugTest/resources"))
        }
    }
}
and use an expect/actual that points to the proper directory on iOS.
m
JS is still workaroundable because cwd is in the root project. Not sure about simulators...
l
Since Okio has a separate testing module, it would be nice to have some sort of getTestResourcesDirectory() defined there. I’ve done this before manually with expect/actuals, but it would be nice to not have to.
j
Ooooh nice. I voted on that issue @mbonnin!
I attempted to read the
GITHUB_WORKSPACE
directory to find my project in CI, but environment variables also don’t come across into the simulator
l
That makes sense. I would not code under the assumption that you can access the computer’s filesystem from a simulator. It’s better to get the file copied into the simulator. This will be critical on Android simulators.
j
I ran into a similar problem, where I need to access the commonTest/resources from both Android and iOS when running common tests on both platforms. Since Android does need the files in the test .apk, I've been using the analogous Android
Context.assets.open()
and iOS
NSBundle.mainBundle.pathForResource()
APIs. I created this youtrack to have the resources automatically copied for both test targets.
p
You can create a task to copy the resources to your framework
For example like this:
Copy code
tasks.withType<KotlinNativeTest> {
  val frameworkDir = executable.parentFile
  val copyResources = tasks.register<Sync>("copyResourcesFor${name.capitalize()}") {
    from("src/appleTest/resources/BundleLocalizerTest.bundle")
    into(File(frameworkDir, "BundleLocalizerTest.bundle"))
  }
  dependsOn(copyResources)
}
Within the test we access it like this:
Copy code
val bundlePath = NSBundle.mainBundle.resourcePath!!
      val sharedResourceBundlePath = "$bundlePath/BundleLocalizerTest.bundle"
      NSBundle(path = sharedResourceBundlePath)
Just reading the youtrack, that’s pretty much the same approach
j
Yeah, it's worked well with this approach. Would be nice to eliminate some of the boilerplate necessary to get it working though.
j
I ran into this same problem again for another project and I found a fix I like! In the build script, set an environment variable prefixed with
SIMCTL_CHILD_
. I also set it without that prefix for non-simulator builds:
Copy code
tasks.withType<KotlinNativeTest>().configureEach {
  // <https://stackoverflow.com/a/53604237/40013>
  environment("SIMCTL_CHILD_OKHTTP_ICU_ROOT_DIR", rootDir)
  environment("OKHTTP_ICU_ROOT_DIR", rootDir)
}
Lookup the environment variable during test execution:
Copy code
val okhttpIcuRootDir = platform.posix.getenv("OKHTTP_ICU_ROOT_DIR")?.toKString()
And voila, we can pass the project directory to the simulator test process.
PR that includes this, plus a bunch of other stuff.
p
Ha, now that is nice!
p
Nice, thanks for sharing. I know it's convenient but I would not give people code snippets that breaks the upcoming project isolation. I'd leave the: "how to apply it to multiple modules?" question to the reader to figure out and instead focus on the solution itself.
j
I don’t follow? What’s the upcoming project isolation?
p
https://gradle.github.io/configuration-cache/#project_isolation Basically: no more all projects, subprojects. Instead only convention plugins so that modules can be configured in parallel
j
Oh, gotcha. Yeah getting from here to there should be very easy
754 Views