But unlike the stock JUnit-based setup, makeRedisH...
# spek
m
But unlike the stock JUnit-based setup, makeRedisHandler in the Spek instance fails because it claims it cannot get the container IP or port until the container is started.
r
in the docs it mentions that spek will eagerly execute any logic within group scopes (
given
,
describe
, etc ..)
Copy code
beforeTest { init container here }
describe("a profile handler") {
    val userProfileRedis by memoized { ... }
}
m
@raniejade -- I thought I was already doing that -- the
userProfileRedisContainer.start()
is done in the
beforeGroup
-- that sets up the container. All I'm doing afterwards is to just take a couple of properties from the running container to initialize a local redis handler...
r
but you’re initializing
userProfileRedis
inside a group.
m
OK, I got the following to work:
Copy code
class ProfileHandlerSpec: Spek({
    val userProfileRedisContainer = KGenericContainer("redis:latest").withExposedPorts(6379)!!
    var ph : ProfileHandler? = null

    beforeGroup {
        userProfileRedisContainer.start()
        val userProfileRedis : RedisHandler = makeRedisHandler(userProfileRedisContainer.containerIpAddress, userProfileRedisContainer.getMappedPort(6379))
        ph = ProfileHandler(userProfileRedis)
    }

    describe("a profile handler") {
        val uuid = UUID.randomUUID().toString()
        val cTime = Date.from(Instant.now()).time
        on("new profile creation") {
            val profile = ph!!.createNewUserProfile(UserProfile(uuid, "Hoylu", "User", "<mailto:testuser@hoylu.com|testuser@hoylu.com>"))

            it("should create a profile with the proper uuid, creationTime, and default lastLoginTime") {
                assertEquals(uuid, profile.uuid)
                assertTrue(profile.creationTime >= cTime)
                assertEquals(0L, profile.lastLoginInfo.lastLoginTime)
            }

        }
    }

    afterGroup {
        userProfileRedisContainer.stop()
    }
})
But I have to say that I'm unclear on how I could have used
by memoized
to make this any more idiomatic, @raniejade
r
Problem is you have initialization logic inside groups, they will be executed eagerly during the discovery phase.
I would re-write it like this:
Copy code
class ProfileHandlerSpec: Spek({
    val userProfileRedisContainer by memoized { KGenericContainer("redis:latest").withExposedPorts(6379)!! }

    var ph by memoized {
        userProfileRedisContainer.start()
        val userProfileRedis : RedisHandler = makeRedisHandler(userProfileRedisContainer.containerIpAddress, userProfileRedisContainer.getMappedPort(6379))
        ProfileHandler(userProfileRedis)
    }

    beforeEachTest {
        userProfileRedisContainer.start()
    }

    describe("a profile handler") {
        on("new profile creation") {
            val uuid = UUID.randomUUID().toString()
            val cTime = Date.from(Instant.now()).time
            val profile = ph!!.createNewUserProfile(UserProfile(uuid, "Hoylu", "User", "<mailto:testuser@hoylu.com|testuser@hoylu.com>"))

            it("should create a profile with the proper uuid, creationTime, and default lastLoginTime") {
                assertEquals(uuid, profile.uuid)
                assertTrue(profile.creationTime >= cTime)
                assertEquals(0L, profile.lastLoginInfo.lastLoginTime)
            }

        }
    }

    afterEachTest {
        userProfileRedisContainer.stop()
    }
})
take note
on
is not a group, but considered a test that’s why it’s okay to have initialization login inside of it
using memoized you also avoid of using nullable types. so no more
!!
m
IntelliJ seems unhappy with your
var ph by memoized
block:
Copy code
Error:(15, 15) Kotlin: Missing 'setValue(Nothing?, KProperty<*>, ProfileHandler)' method on delegate of type 'LifecycleAware<ProfileHandler>'
r
use
val
m
OK, so now I've got the following, which works insofar as the tests pass, but the test run does not terminate:
Copy code
class ProfileHandlerSpec: Spek({
    val userProfileRedisContainer by memoized { KGenericContainer("redis:latest").withExposedPorts(6379) }

    val ph by memoized {
        val userProfileRedis= makeRedisHandler(userProfileRedisContainer.containerIpAddress, userProfileRedisContainer.getMappedPort(6379))
        ProfileHandler(userProfileRedis)
    }

    given("a profile handler") {
        beforeGroup {
            userProfileRedisContainer.start()
        }
        on("new profile creation") {
            val uuid = UUID.randomUUID().toString()
            val cTime = Date.from(Instant.now()).time
            val profile = ph.createNewUserProfile(UserProfile(uuid, "Hoylu", "User", "<mailto:testuser@hoylu.com|testuser@hoylu.com>"))

            it("should create a profile with the proper uuid, creationTime, and default lastLoginTime") {
                assertEquals(uuid, profile.uuid)
                assertTrue(profile.creationTime >= cTime)
                assertEquals(0L, profile.lastLoginInfo.lastLoginTime)
            }

            val gotProfile = ph.getUserProfile(profile.uuid)
            it("should return the profile for the uuid") {
                assertEquals(profile, gotProfile)
            }
        }
        afterGroup {
            userProfileRedisContainer.stop()
        }
    }
})
I can see in the debugger that the afterGroup block is being executed, which stops the container. I can step all the way through the test termination sequence, see it return a tests successful, and finally step out of
execute
in
DefaultLauncher.java
. And then it seems to still wait for something...
I have to hit "Stop Process" in the IDE, at which point it happily stops.
r
can you paste your code?
m
Copy code
package lu.hoy.eng.userprofile

import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.given
import <http://org.jetbrains.spek.api.dsl.it|org.jetbrains.spek.api.dsl.it>
import org.jetbrains.spek.api.dsl.on
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class ProfileHandlerSpec: Spek({
    val userProfileRedisContainer by memoized { KGenericContainer("redis:latest").withExposedPorts(6379) }

    val ph by memoized {
        val userProfileRedis= makeRedisHandler(userProfileRedisContainer.containerIpAddress, userProfileRedisContainer.getMappedPort(6379))
        ProfileHandler(userProfileRedis)
    }

    given("a profile handler") {
        beforeGroup {
            userProfileRedisContainer.start()
        }
        on("new profile creation") {
            val uuid = UUID.randomUUID().toString()
            val cTime = Date.from(Instant.now()).time
            val profile = ph.createNewUserProfile(UserProfile(uuid, "Hoylu", "User", "<mailto:testuser@hoylu.com|testuser@hoylu.com>"))

            it("should create a profile with the proper uuid, creationTime, and default lastLoginTime") {
                assertEquals(uuid, profile.uuid)
                assertTrue(profile.creationTime >= cTime)
                assertEquals(0L, profile.lastLoginInfo.lastLoginTime)
            }

            val gotProfile = ph.getUserProfile(profile.uuid)
            it("should return the profile for the uuid") {
                assertEquals(profile, gotProfile)
            }
        }
        afterGroup {
            userProfileRedisContainer.stop()
        }
    }
})
r
can you try using
beforeEachTest
and
afterEachTest
?
m
Same behavior
r
hmm
I think the container is being instantiated twice, can you put a breakpoint on it?
m
On the
start()
?
BTW, gradle from the command line finishes the test run just fine
r
hmmm
yep on start
m
I had one start() and one stop()
r
can you try on
KGenericContainer(...)
m
Both tests show green, but the "Running tests..." top level tree control is still spinning yellow.
One single call on KGenericContainer
r
okay, probably a non-daemon thread is blocking it from shutting down.
m
When I dump the threads, I see a waiting Finalizer and a waiting Reference Handler
r
can you paste the dump here?
m
One sec...
The default copy seems too big for slack...
r
gist 😄
I think it's those three sleeping lettuce threads
r
Copy code
"pool-1-thread-1@3028" prio=5 tid=0xe nid=NA waiting
  java.lang.Thread.State: WAITING
	  at sun.misc.Unsafe.park(Unsafe.java:-1)
	  at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	  at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
	  at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
	  at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
	  at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
	  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
	  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	  at java.lang.Thread.run(Thread.java:748)

"dockerjava-netty-1-16@4317" prio=5 tid=0x21 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at org.testcontainers.shaded.io.netty.channel.kqueue.Native.keventWait(Native.java:-1)
	  at org.testcontainers.shaded.io.netty.channel.kqueue.Native.keventWait(Native.java:76)
	  at org.testcontainers.shaded.io.netty.channel.kqueue.KQueueEventLoop.kqueueWait(KQueueEventLoop.java:158)
	  at org.testcontainers.shaded.io.netty.channel.kqueue.KQueueEventLoop.kqueueWait(KQueueEventLoop.java:149)
	  at org.testcontainers.shaded.io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:219)
	  at org.testcontainers.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
	  at org.testcontainers.shaded.io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)
	  at java.lang.Thread.run(Thread.java:748)
I see several
dockerjava-netty
non daemon threads
plus
pool-1-thread-xxxx
netty not being shutdown?
m
But why would that be different under Spek?
Like I mentioned earlier, my regular JUnit4 tests do just fine with this:
Copy code
class ProfileTests {
    companion object {
        val userProfileRedisContainer = KGenericContainer("redis:latest").withExposedPorts(6379)!!

        @BeforeClass
        @JvmStatic
        fun classSetup() {
            userProfileRedisContainer.start()
        }

        @AfterClass
        @JvmStatic
        fun classShutdown() {
            userProfileRedisContainer.stop()
        }
    }

    private val userProfileRedis = makeRedisHandler(userProfileRedisContainer.containerIpAddress, userProfileRedisContainer.getMappedPort(6379))

<bunch of tests here>
r
Based on personal experience non daemon threads prevents tests from stopping when running from the ide.
spek is not doing that
m
It's not the pool thread... Just ran another dump (same test run instance), and the pool thread is gone. The sleeping lettuce threads remain
r
dockerjava-netty-1-16@4317
non daemon
it will prevent the JVM from shutting down
m
OK, I see... so according to that StackOverflow link, JUnit was masking this behavior...
r
yes
m
OK. Gives me something to chase down.
Thanks heaps for all the help with this, Raine.
You're a legend.
r
It’s Ranie XD
no worries, happy to help!
m
s/in/ni/
Apologies.
r
hahaha, not sure why people always misspell my name.