AllureLifecycle error in a multi-threaded Kotlin test

27 Views Asked by At

I have a @Step-annotated Kotlin method in UserManager class that adds an entry to the SQL database:

@Step("Insert record")
fun insertAll(
    profileId: String,
) {
    userDAO.insertAll(
        profileId = profileId
    )
}

If I call it from my @Test-annotated method in single threaded mode then everything works well.

for (i in 0..usersCount) {
    userManager.insertAll(
        profileId
    )
}

But if I use parallelStream(), then a strange thing happens.

(0..usersCount)
    .toList()
    .parallelStream()
    .forEach {
        userManager.insertAll(
            profileId
        )
    }

On the one hand, the code continues to work and I get an increase in productivity, but on the other hand, the logs are filled with the same type of error messages:

15:36:27.762 ERROR [AllureLifecycle] : Could not start step: no test case running
15:36:27.764 ERROR [AllureLifecycle] : Could not update step: no step running
15:36:27.764 ERROR [AllureLifecycle] : Could not stop step: no step running

Dependencies:

implementation("io.qameta.allure:allure-java-commons:2.21.0")

What am I doing wrong?

1

There are 1 best solutions below

0
Dmitry Baev On

The issue with parallelStream is that it uses a shared thread pool, which may or may not have a context of the current executed test or step.

To deal with that, an API method Allure.getLifecycle().setCurrentTestCase(String) is available. You need to get the current test case UUID from the main thread and set it within the worker thread (before any other Allure API use):

val currentTestCase = Allure.getLifecycle().currentTestCase.orElseThrow()
(0..usersCount)
    .toList()
    .parallelStream()
    .forEach {
        Allure.getLifecycle().setCurrentTestCase(currentTestCase);
        userManager.insertAll(
            profileId
        )
    } 

However, if you need to create a wrapping step around that code, it will be ignored (since we only set test context into worker thread). Unfortunately, there are no methods to set the current step context available yet. So to workaround it, you need to use low-level (lifecycle) API to create steps:

Allure.step("child steps run in parallel", ThrowableRunnableVoid {
    // get the current step context
    val parentUuid = Allure.getLifecycle()
        .currentTestCaseOrStep.orElseThrow()

    (0..usersCount)
        .toList()
        .parallelStream()
        .forEach {
            val uuid = UUID.randomUUID().toString()
            // parentUuid is essential. Instead of using value from ThreadLocal,
            // provide parent context directly
            Allure.getLifecycle().startStep(
                parentUuid, uuid, StepResult()
                    .setName("userManager.insertAll(${profileId})")
            )
            try {
                // run your code here
                userManager.insertAll(
                   profiled
                )

                // if no exception, update the step
                Allure.getLifecycle()
                    .updateStep(uuid) { step: StepResult ->
                        step.setStatus(
                            Status.PASSED
                        )
                    }
            } catch (e: Exception) {
                // process the exception
                Allure.getLifecycle()
                    .updateStep(uuid) { s: StepResult ->
                        s
                            .setStatus(
                                ResultsUtils.getStatus(e).orElse(Status.BROKEN)
                            )
                            .setStatusDetails(ResultsUtils.getStatusDetails(e).orElse(null))
                    }
                ExceptionUtils.sneakyThrow<RuntimeException>(e)
            } finally {
                // finally stop the step
                Allure.getLifecycle().stopStep(uuid)
            }
        }