question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Assertion calls on CapturedOutput with Kotlin are very slow

See original GitHub issue

I have a test that checks to see if a string is in the captured output. I expect this to work because CapturedOutput implements CharSequence. However, the test hangs indefinitely.

// this test hangs indefinitely
@Test
fun `check if output contains string`(output: CapturedOutput) {
    assertAll(
        { assertTrue("Started" in output) },
    )
}

I would expect that this works the same as if I had called output.all.

// this test works as expected
@Test
fun `check if output contains string`(output: CapturedOutput) {
    assertAll(
        { assertTrue("Started" in output.all) },
    )
}
  • Spring Boot 2.7.2
  • Kotlin 1.7.10
  • Gradle 7.5.1

ApplicationTest.kt

import org.junit.jupiter.api.Assertions.assertAll
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.api.parallel.Isolated
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension
import org.springframework.context.ApplicationContext
import org.springframework.test.annotation.DirtiesContext

@SpringBootTest(
    classes = [Application::class],
    webEnvironment = RANDOM_PORT,
)
@ExtendWith(OutputCaptureExtension::class)
internal class ApplicationTest {

    @Test
    fun `expect Spring Context loads`(context: ApplicationContext?) {
        assertNotNull(context)
    }

    @Test
    fun `check if output contains string`(output: CapturedOutput) {
        assertAll(
            { assertTrue("Started" in output) },
        )
    }
}

Application.kt

import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.TypeExcludeFilter
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.ComponentScan.Filter
import org.springframework.context.annotation.FilterType.CUSTOM
import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator

@SpringBootApplication
@ComponentScan(
    excludeFilters = [
        Filter(type = CUSTOM, classes = [TypeExcludeFilter::class]),
        Filter(type = CUSTOM, classes = [AutoConfigurationExcludeFilter::class])
    ],
    nameGenerator = FullyQualifiedAnnotationBeanNameGenerator::class
)
@EnableConfigurationProperties
class Application

fun main() {
    try {
        runApplication<Application>()
    } catch (e: Exception) {
        e.printStackTrace()
        throw e
    }
}

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
scottfrederickcommented, Aug 15, 2022

I can’t reproduce the hanging test with the provided sample, but the this test hangs test does take as long as 30 seconds to run. It is likely to take exponentially longer with more captured output (which is why you see a difference between log levels INFO and DEBUG).

The problem is specific to Kotlin. A statement like assertTrue("Started ApplicationTest" in output) treats output as a CharSequence and invokes Kotlin’s CharSequence.contains. This calls the length() and charAt() methods of Spring Boot’s CapturedOutput class many times. These methods are not very efficient, as they call toString() and assemble a String from the captured output with each call.

When calling assertTrue("Started ApplicationTest" in output.all), the result of output.all is a String so the CharSequence.contains method is not called and the test executes very quickly. This is essentially the same as calling assertTrue("Started ApplicationTest" in output.toString()). assertThat(output).contains("Started ApplicationTest") is also very fast.

Marking this for team attention so we can decide what, if anything, we want to do about this.

0reactions
philwebbcommented, Aug 19, 2022

I think we should add some caching to org.springframework.boot.test.system.OutputCapture so that repeated calls are faster.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to do a JUnit assert on a message in a logger
The call to setUseParentHandlers() is to silence the normal handlers, so that (for this junit-test run) no unnecessary logging happens.
Read more >
JUnit 5 User Guide
1. Kotlin Assertion Support. JUnit Jupiter also comes with a few assertion methods that lend themselves well to being used in Kotlin. All...
Read more >
[GitHub] [myfaces-tobago] dependabot[bot] opened a new pull ...
... <li>Assertion calls on CapturedOutput with Kotlin are very slow <a ... archiveFile property is annotated as an <code>@Input</code> but should be ...
Read more >
Testing Exceptions in Kotlin with assertFailsWith - Baeldung
A quick and practical guide to Kotlin's assertFailsWith. ... in JUnit 5 that we can use to assert exceptions in Kotlin is assertThrows()....
Read more >
Spring Boot Reference Documentation
The preceding instructions install a local instance of spring called the dev ... The first run of your application is slow, as dependencies...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found