kotlin kotest/kotlintest BehaviorSpec afterSpec/finalizeSpec called too often or not at all - kotlin

I have a kotlin kotest (formerly known as kotlintest) BehaviorSpec
with one Given("...") and many When("...") Then("...") under it
I want to execute a cleanup after the whole Spec (respectively every Given clause) has finished.
#MicronautTest
class StructurePersistSpec(
private val iC : InstancesC
) : BehaviorSpec({
// afterSpec {
finalizeSpec {
cleanup()
}
Given("...") {
When("...") {
Then("...") {
...
}
Then("...") {
...
}
}
When("...") {
Then("...") {
...
}
Then("...") {
...
}
}
}
...
}
on using afterSpec { } I get multiple calls (amount of Whens??) to the afterSpec { } clause and NOT just one after the Spec finished (or finishing of the/each Given Clause)
on using finalizeSpec { } it does NOT get called at all (breakpoint inside it is never hit)
what am I doing wrong?
or did I miss some fancy characteristics of BehaviorSpecs ?

The reason you are getting multiple calls is that probably you have set a different IsolationMode for your test.
That would mean your Spec will be recreated (and then cleaned) for every test. In order to have a single afterSpec call from the framework, your IsolationMode must be set to SingleInstance.
Bare in mind that might affect the way your tests are being executed hence their validity or ability to pass.
Documentation: https://kotest.io/isolation_mode/

Related

Kotlin: retain coroutine context in scenario with nested runBlocking

I'm fairly new to kotlin coroutines, and I have what I think is a somewhat esoteric use case related to how runBlocking and coroutine contexts interact.
To start with, a simple example. Let's say I've got a dead simple context element. Nothing fancy.
class ExampleContext(val s: String) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<ExampleContext>
}
When I run these examples, they behave exactly the way I'd expect them to:
runBlocking(ExampleContext("foo")) {
println(coroutineContext[ExampleContext.Key]?.s) // prints "foo
}
runBlocking(ExampleContext("foo")) {
launch {
println(coroutineContext[ExampleContext.Key]?.s) // prints "foo"
}
}
runBlocking(ExampleContext("foo")) {
launch(ExampleContext("bar")) {
println(coroutineContext[ExampleContext.Key]?.s) // prints "bar"
}
}
When I do this it prints null (as I would expect it to, because it runBlocking defaults to having EmptyContext in its constructor):
runBlocking(ExampleContext("foo")) {
runBlocking {
println(coroutineContext[ExampleContext.Key]?.s) // prints null
}
}
So here's my conundrum. The docs (and all the guidance I've found on the web) basically say don't do this: runBlocking is supposed to be run at the outermost layer of the coroutine logic and that's it. No nesting. What I'm working on is a library that needs to populate some context for access inside code that I don't own that gets called later (basically, you can think of it like an interceptor). The rough pseudocode looks a little like this:
class MyLibrary(otherPeoplesLogic: OtherPeoplesBusinessLogic) {
fun <IN, OUT> execute(input: IN): OUT {
... do my library's thing, including adding in a custom context element ...
try {
return otherPeoplesLogic.execute(input)
} finally {
... do my library's cleanup ...
}
}
}
To support coroutines in OtherPeoplesBusinessLogic, all I'd really have to do is add runBlocking like this:
class MyLibrary(otherPeoplesLogic: OtherPeoplesBusinessLogic) {
fun <IN, OUT> execute(input: IN): OUT {
... do my library's thing ...
runBlocking(myCustomContext) {
try {
return otherPeoplesLogic.execute(input)
} finally {
... do my library's cleanup ...
}
}
}
}
So long as all OtherPeoplesBusinessLogic::execute does is launch/async/etc, everything is fine: myCustomContext will be accessible. What I'm worried about is what happens if OtherPeoplesBusinessLogic::execute (which I'm not in control of) misbehaves and does its own runBlocking call with no context argument passed at all: what I think will happen is that myCustomContext will just silently get dropped like the example above. Not good, because it needs to be accessible.
Phew. A lot of explanation. Thanks for bearing with me. :)
So my ultimate question here is this: is there anything I can do (outside of scolding the users of my library to not call runBlocking) to prevent an accidental nested runBlocking call from dropping my context? Or am I just out of luck here and should scrap the whole idea?

Is this the correct way to process results through the Spring Integration Flow?

I am currently working on a personal project - in which I need my Spring application to take queries from an EMQX (MQTT Server) and query its data for corresponding results, and then push the results to a topic with the query UUID.
This is working - after many hours understanding how the Spring Integration framework works. But I think the way in which the handler is using "block" is incorrect - and not in keeping with the manner in which the Integration Flow should operate. Whilst this works I do want to make sure it is being done properly - out of respect for the work - and to avoid future issues.
The code snippet below should be enough to understand what it is that I'm trying to achieve - and where the potential issue lies.
#Bean
fun mqttInFlow() : Publisher<Message<String>> {
return IntegrationFlows.from(inbound())
.handle<String> { payload, headers ->
val emotionalOutput: EmotionalOutput = gson.fromJson(payload, EmotionalOutput::class.java)
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map {
MessageBuilder.withPayload(gson.toJson(it))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, "query/" + it.query_uuid).build()
}.block()
}
.channel(outgoingChannel())
.toReactivePublisher()
}
EDIT - Thanks for the advice - here is what I understood to be the potential edit for the Kotlin DSL solution - this is now producing an error - complaining that an output-channel or replyChannel was not available - nothing outside of the this function has been changed.
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
wireTap {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid)
}
}
}
}
channel("outgoingChannel")
}
Exception is :
exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Although I have many years experience with Java - this approach is new - so thank you very much for your assistance. It's appreciated. If the whole class would be useful - I can post that.
EDIT
Here is the Configuration file - which might give a better insight into what might be causing this secondary error -
021-03-28 21:59:48.008 ERROR 84492 --- [T Call: divnrin] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: error occurred in message handler [bean 'mqttOutbound'; defined in: 'class path resource [io/divnr/appserver/configuration/MQTTConfiguration.class]'; from source: 'org.springframework.core.type.classreading.SimpleMethodMetadata#4a9419d7']; nested exception is java.lang.IllegalArgumentException: This default converter can only handle 'byte[]' or 'String' payloads; consider adding a transformer to your flow definition, or provide a BytesMessageMapper, or subclass this converter for reactor.core.publisher.MonoMapFuseable payloads, failedMessage=GenericMessage [payload=MonoMapFuseable, headers={mqtt_receivedRetained=false, mqtt_id=0, mqtt_duplicate=false, id=c5a75283-c0fe-ebac-4168-dabddd989da9, mqtt_receivedTopic=source/d9e50e8f-67e0-4505-7ca2-4d05b1242207, mqtt_receivedQos=0, timestamp=1616961588004}]
at org.springframework.integration.support.utils.IntegrationUtils.wrapInHandlingExceptionIfNecessary(IntegrationUtils.java:192)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:65)
at
The full class is provided here.
#Configuration
#EnableIntegration
#IntegrationComponentScan
class MQTTConfiguration(val emotionalPrintService: EmotionalPrintService,
val gson: Gson,
val applicationConfiguration: ApplicationConfiguration) {
#Bean
fun mqttServiceFactory() : MqttPahoClientFactory {
return DefaultMqttPahoClientFactory().apply {
connectionOptions = MqttConnectOptions().apply {
serverURIs = arrayOf<String>(applicationConfiguration.mqttServerAddress)
}
}
}
#Bean
fun newMqttInFlow() =
integrationFlow (inbound()) {
handle<String> { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}
channel(outgoingChannel())
}
#Bean
#ServiceActivator(requiresReply = "false", inputChannel = "outgoingChannel")
fun mqttOutbound(): MessageHandler {
val messageHandler = MqttPahoMessageHandler("divnrout", mqttServiceFactory())
messageHandler.setAsync(true)
return messageHandler
}
#Bean
fun outgoingChannel() : FluxMessageChannel {
return FluxMessageChannel()
}
#Bean
fun inbound(): MessageProducerSupport {
return MqttPahoMessageDrivenChannelAdapter("divnrin", mqttServiceFactory(),
"source/" + applicationConfiguration.sourceUuid).apply {
setConverter(DefaultPahoMessageConverter())
setQos(1)
}
}
}
You indeed don't need that block() in the end of your handle(). You just can return the Mono from that emotionalPrintService.populateEmotionalOutput() and the framework will take for you about the proper subscription and back-pressure handling.
What you would need yet is to make that outgoingChannel() as a FluxMessageChannel.
See more info in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/reactive-streams.html#reactive-streams
Plus consider to move your IntegrationFlow solution to the proper Kotlin DSL: https://docs.spring.io/spring-integration/docs/current/reference/html/kotlin-dsl.html#kotlin-dsl
Also: when it is a FluxMessageChannel in the end of flow, there is no reason to worry about a toReactivePublisher() - the FluxMessageChannel is a Publisher<Message<?>> by itself.
UPDATE
The problem is here:
handle<String>( { payload, headers ->
gson.fromJson<EmotionalOutput>(payload, EmotionalOutput::class.java).let { emotionalOutput ->
emotionalPrintService.populateEmotionalOutput(emotionalOutput).map { populatedEmotionalOutput ->
MessageBuilder.withPayload(gson.toJson(populatedEmotionalOutput))
.copyHeaders(headers)
.setHeader(MqttHeaders.TOPIC, populatedEmotionalOutput.query_uuid).build()
}
}
}) { async(true) }
See that async(true) option. Unfortunately in the current version we don't let it to process reactive reply in the reactive manner by default. You have to say that you'd like to be an async at this end-point. So, your Publisher reply and and FluxMessageChannel as an output is going to do the proper trick.

Why can't use continue in let or run

Why it is not allowed to continue from let function?
This code:
fun foo(elements: List<String?>) {
for (element in elements) {
element?.let {
continue // error: 'break' or 'continue' jumps across a function or a class boundary
}
}
}
And even this code:
fun foo(elements: List<String?>) {
loop# for (element in elements) {
element?.let {
continue#loop // error: 'break' or 'continue' jumps across a function or a class boundary
}
}
}
Does not compile with error:
'break' or 'continue' jumps across a function or a class boundary
I know that in this particular case I can use filterNotNull or manual check with smart cast, but my question is why it is not allowed to use continue here?
Please vote for this feature here: https://youtrack.jetbrains.com/issue/KT-1436
These would be called "non-local" breaks and continues. According to the documentation:
break and continue are not yet available in inlined lambdas, but we are planning to support them too.
Using a bare (e.g. non-local) return inside a lambda is only supported if it is an inlined lambda (because otherwise it doesn't have awareness of the context it is called from). So break and continue should be able to be supported. I don't know the reason for the functionality to be delayed.
Note, there are work-arounds for both of them by run either inside or outside the loop, and taking advantage of the fact that at least non-local returns are supported for inline functions.
fun foo(elements: List<String?>) {
run {
for (element in elements) {
element?.let {
println("Non-null value found in list.")
return#run // breaks the loop
}
}
}
println("Finished checking list")
}
fun bar(elements: List<String?>) {
for (element in elements) {
run {
element?.let {
return#run // continues the loop
}
println("Element is a null value.")
}
}
}

Kotlin ConflatedBroadcastChannel.offer() doesn't work?

I am sending a value via MyRepository.myConflatedChannel.offer(myvalue).
I then expect to receive it in collect { } or onEach { } blocks in my ViewModel. However, neither function is invoked. It is as if nothing is passed down the ConflatedBroadcastChannel.
Has anybody seen a similar problem?
Make sure you properly work with receiving values.
If you use the ConflatedBroadcastChannel, you can use either OpenSubscription to get a ReceiveChannel or you can represent it as flow (with asFlow).
Note that consume and consumeEach are terminal, they perform an action and then cancel the channel after the execution of the block. See this.
First case:
val receivingChannel = MyRepository.myConflatedChannel.openSubscription()
// then you can consume values using for example a for loop, e.g.:
launch {
for (value in receivingChannel) {
// do something
}
}
Second case:
val receivingFlow = MyRepository.myConflatedChannel.asFlow()
launch {
receivingFlow.collect {
// do something
}
}

LessCss extend broken when using parent selector

I am using &:extend(.klass)
I have a method:
.method(#mixin) {
html[something] & { #mixin() }
}
.klass {
.method({});
}
So far so good. However, when I use this:
.anotherclass { &:extend(.klass) }
I get no complaints, it extends the klass properly.
Resulting css:
html[something] .klass { ... }
.klass, .anotherclass { ... }
However, it should ideally, also extend the html[something] .klass as well.
So the result should IDEALLY be:
html[something] .klass, html[something] .anotherclass { ... }
.klass, .anotherclass { ... }
Right?
Otherwise, you could end up using &:extend(.klass) get no complaint, expecting to get the right rules applied.
I am using less on java, and it might be using an old version.
Does this work "better" in newer versions?
Note, I am aware I can just turn klass into a method call instead.