when are kotlin objects intantiated? in `object My...
# announcements
c
when are kotlin objects intantiated? in
object MyObj { val blah=getBlah }
, when is getBlah called? when the containing file is imported? or when a method references
MyObj.blah
?
m
Depends on the platform. On JVM the first time you use
MyObj
. On JS upon script loading. I don’t know on Native.
r
Call order being platform dependent could introduce some really nasty to diagnose bugs.
m
Yeah, it's a known issue.
r
@christophsturm in the spirit of “teach a man to fish” - it would be pretty easy to check by experimentation:
Copy code
fun getBlah(): String {
  println("getBlah called")
  return "blah"
}

object MyObj { val blah = getBlah() }

fun main() {
  println("start")
  val myObj = MyObj
  println("MyObj referenced")
  val blah = myObj.blah
  println("MyObj.blah referenced; was $blah")
}
Prints:
Copy code
start
getBlah called
MyObj referenced
MyObj.blah referenced; was blah
on the JVM, which shows it isn’t loaded immediately, but is loaded when
MyObj
is first referenced rather than when
MyObj.blah
is referenced.
m
Yup, JVM implementation is the most correct one (though not perfect).
c
is there a way to instantiate all the objects in a package without classpath scanning?
m
not really 🤔
c
i guess there isnt because of the way classloaders work.
probably the fastest way is to find the classes directory and load all files in the directory that corresponds to that package.
m
Can’t you just manually load them in code?
Copy code
fun preload() {
   Obj1
   Obj2
   Obj3
   // …
}
r
If your app is dependent on something loading at a certain point in the lifecycle I’d suggest doing it explicitly rather than depending on an object being referenced.
☝️ 4
c
ok my usecase is this: I’m writing a small fast test runner from scratch:
Copy code
```package nanotest

import strikt.api.expectThat
import strikt.api.expectThrows
import strikt.assertions.all
import strikt.assertions.containsExactlyInAnyOrder
import strikt.assertions.hasSize
import strikt.assertions.isA
import strikt.assertions.isEqualTo
import strikt.assertions.isFalse
import strikt.assertions.isLessThan
import strikt.assertions.isTrue
import strikt.assertions.map
import java.lang.management.ManagementFactory
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit

fun main() {
    val testFinished = CompletableFuture<Unit>()
    val failingTestFinished = CompletableFuture<Throwable>()
    val results = Suite {
        test("firstTest") {
            expectThat(true).isTrue()
            testFinished.complete(Unit)
        }
        test("failing test") {
            try {
                expectThat(true).isFalse()
            } catch (e: AssertionError) {
                failingTestFinished.complete(e)
                throw e
            }
        }
        context("child context") {
            context("grandchild context") {
                test("failing test") {
                    expectThat(true).isFalse()
                }
            }
        }
    }.run()
    expectThat(results) {
        get(SuiteResult::allOk).isFalse()
        get(SuiteResult::failedTests).hasSize(2).all {
            get(TestFailure::name).isEqualTo("failing test")
            get(TestFailure::throwable).isA<AssertionError>()
        }
        get(SuiteResult::contexts).map { it.name }
            .containsExactlyInAnyOrder("root", "child context", "grandchild context")
    }
    expectThrows<RuntimeException> { results.check() }
    testFinished.get(1, TimeUnit.SECONDS)

    Suite(listOf(TestContextTest.context, SuiteTest.context, ContextTest.context)).run().check()
    val uptime = ManagementFactory.getRuntimeMXBean().uptime
    println("finished after: ${uptime}ms")
    expectThat(uptime).isLessThan(1000) // lets see how far we can get with one second
}
and right now i have a list of test classes in my bootstrap test. (i run the bootstrap test in main and after checking that it runs all the tests and fails on failure i run the rest via the normal test runner) right now i have a list of all the test objects there and i wonder if i can automate it while staying in my 1000ms budget
m
I have no idea how that’s related 🙂
c
currently i specify all the classes that contain test contexts:
Suite(listOf(TestContextTest.context, SuiteTest.context, ContextTest.context)).run().check()
and trying to avoid that was the motivation for the question. but i guess there is no simple trick thats faster than just walking the classes directory
r
Classpath scanning is what nearly all test frameworks do, but personally I prefer your current solution - explicit registering of the tests in code. I hate reflection; slow and prone to surprise you (delete that .kt class and the test still runs because the .class file is still there)
In combination with a linting rule that requires all code to be reachable from a main method
m
I don’t know what test contexts are about. But it sounds weird that you need to have all of them upfront. Can’t you register them lazily as each test class is used?
c
thats just how i tell the test runner that i want it to run those tests.
@Rob Elliot I have seen that problem with tests that still run after the kt file is removed too. but thats also a bug in the build system then. it should remove classes where the source was removed.
m
Ah okay. In that case you’ll need either that or a compiler plugin (e.g. ksp).
c
@Rob Elliot what linter would i use for such a rule?
r
Sorry, that was a misleading comment - it was a general “how I would like to approach the problem” rather than “this is how I actually do it”. I haven’t yet looked hard to see if any linters can do that.
c
ah ok. its an interesting idea. I was just asking because i have not tried kotlin linters myself
a
On K/N as well, its set up at the first access.