https://kotlinlang.org logo
#kotest
Title
# kotest
w

Wesley Hartford

11/10/2023, 10:45 PM
I have a spec class that is hanging after the last test completes. All tests run and pass, but test execution does not finish. I have a logging listener registered. The
afterAny
gets called for the last test and the test container, the
afterContainer
gets called, but the
afterSpec
function does not get called. Does anyone know what happens between
afterContainer
and
afterSpec
that could cause the test execution to hang? Thanks.
l

LeoColman

11/10/2023, 11:01 PM
after project, perhaps?
w

Wesley Hartford

11/10/2023, 11:23 PM
I'm pretty sure
afterProject
happens after
afterSpec
, I've added an
afterProject
log to my test and it does not get called.
s

sam

11/11/2023, 12:31 AM
Do you have anything thready or coroutiny going on in the callbacks ?
w

Wesley Hartford

11/11/2023, 12:38 AM
In
beforeSpec
, I launch a coroutine in
GlobalScope
which continues in the background (the tests interact with services running there). That launched job is cancelled in an
afterSpec
. The test hangs before that
afterSpec
is called.
s

sam

11/11/2023, 12:39 AM
anything launched in global scope shouldn't matter
as different lifestyles
w

Wesley Hartford

11/11/2023, 12:40 AM
That's what I was thinking.
s

sam

11/11/2023, 12:40 AM
if you remove that call does the test complete
w

Wesley Hartford

11/11/2023, 12:41 AM
test cases fail, but the test completes.
s

sam

11/11/2023, 12:42 AM
doesn't hang anymore? So does sound like something in that coroutine is locking it up
w

Wesley Hartford

11/11/2023, 12:45 AM
Yeah, that seems odd though right? It's launched in global scope, so how can it interfere with the completion of the test? I can imagine a case where it might prevent the JVM from shutting down, but how would it prevent the call to
afterSpec
?
s

sam

11/11/2023, 12:46 AM
yeah it doesn't make much sense to me either
Does the launched co routine do anything funky ?
w

Wesley Hartford

11/11/2023, 12:48 AM
That's a good question... this is an integration test, so it starts a handful of other services that the service under test interacts with.
In other words, the launched coroutine does a lot of stuff, some of it probably funky.
s

sam

11/11/2023, 12:49 AM
It might be doing something that causes things to hang
like a long running thread
w

Wesley Hartford

11/11/2023, 12:56 AM
I've made the test complete by removing the
coroutineContext
passed to the
launch
function. I had been passing a modified version of the
currentCoroutineContext()
, but with no context argument, the test completes.
Yeah, it looks like that did it, do you know if something might have changed between 5.6.2 and 5.7.2 to change the behaviour there? The upgrade might have been when the breakage was introduced.
s

sam

11/11/2023, 1:11 AM
not off top of my head but we did do a bunch of work on coroutines in general. I know @Oliver.O knows this stuff pretty well he might be able to help (night time for him right now)
w

Wesley Hartford

11/11/2023, 1:12 AM
🤷 It's working now
I might poke around on Monday to see if I can produce a reasonably minimal reproduction.
s

sam

11/11/2023, 1:13 AM
that would be helpful
w

Wesley Hartford

11/11/2023, 1:13 AM
Thanks for your help.
s

sam

11/11/2023, 1:13 AM
np
o

Oliver.O

11/11/2023, 1:24 AM
Stuff hanging before afterSpec is called seems exotic. The common case for a hang on exit is a non-daemon thread.
You could also check for thread starvation. runBlocking should be avoided. Also any kind of dispatcher using a limited thread pool. See here for an explanation why to use Dispatchers.IO.limitedParallelism(…): https://blog.jetbrains.com/kotlin/2021/12/introducing-kotlinx-coroutines-1-6-0/#dispatchers.io-elasticity-for-limited-parallelism
w

Wesley Hartford

11/11/2023, 1:56 AM
So it turns out it's actually really easy to reproduce. Here is a self-contained test class that hangs:
Copy code
class KotestHangBeforeAfterSpec : FunSpec(
  {
    lateinit var job: Job
    beforeSpec {
      job = GlobalScope.launch(currentCoroutineContext()) { delay(Duration.INFINITE) }
    }

    afterSpec {
      job.cancel()
    }

    test("always pass") {
      assert(true)
    }
  }
)
Removing the
currentCoroutineContext()
argument to the
launch
function fixes the problem.
I've just confirmed that the above test class hangs when using kotest 5.7.2 but does not hang when using kotest 5.6.2.
s

sam

11/11/2023, 2:04 AM
thanks for the reproducer and the deep dive
we can look into that
w

Wesley Hartford

11/11/2023, 2:05 AM
Would you like me to make an issue on GitHub?
s

sam

11/11/2023, 2:05 AM
yes please
w

Wesley Hartford

11/11/2023, 2:05 AM
I'll do that in the next few hours. Right now I need to make dinner.
o

Oliver.O

11/11/2023, 9:29 AM
Cool, thanks! I’ll be looking into it.
OK, got to the root cause after all, so fixing this should not be too hard. But seems like we're already a bit in interesting waters (sort of Gradle-like lazy configuration experience):
because we have various dynamic ways of determining if a test is enabled
https://github.com/kotest/kotest/issues/3534#issuecomment-1656845546