All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.kotest.extensions.allure.AllureWriter.kt Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
package io.kotest.extensions.allure

import io.kotest.core.spec.Spec
import io.kotest.core.test.Description
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestPath
import io.kotest.core.test.TestResult
import io.kotest.core.test.TestStatus
import io.qameta.allure.Allure
import io.qameta.allure.AllureLifecycle
import io.qameta.allure.Epic
import io.qameta.allure.Feature
import io.qameta.allure.Issue
import io.qameta.allure.Owner
import io.qameta.allure.Severity
import io.qameta.allure.Story
import io.qameta.allure.SeverityLevel
import io.qameta.allure.Link
import io.qameta.allure.Links
import io.qameta.allure.model.Label
import io.qameta.allure.model.Status
import io.qameta.allure.model.StatusDetails
import io.qameta.allure.model.StepResult
import io.qameta.allure.util.ResultsUtils
import java.lang.reflect.InvocationTargetException
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation

class AllureWriter {

   companion object {
      const val LanguageLabel = "kotlin"
      const val FrameworkLabel = "kotest"
   }

   /**
    * Loads the [AllureLifecycle] object which is used to report test lifecycle events.
    */
   val allure = try {
      Allure.getLifecycle() ?: throw IllegalStateException("Allure lifecycle was null")
   } catch (t: Throwable) {
      t.printStackTrace()
      throw t
   }

   private val uuids = mutableMapOf()

   fun id(testCase: TestCase) = uuids[testCase.description.testPath()]

   fun startTestCase(testCase: TestCase) {
      val labels = listOfNotNull(
         testCase.epic(),
         testCase.feature(),
         ResultsUtils.createFrameworkLabel(FrameworkLabel),
         ResultsUtils.createHostLabel(),
         ResultsUtils.createLanguageLabel(LanguageLabel),
         testCase.owner(),
         ResultsUtils.createPackageLabel(testCase.spec::class.java.`package`.name),
         ResultsUtils.createSuiteLabel(testCase.description.spec().displayName()),
         testCase.config.severity?.let { ResultsUtils.createSeverityLabel(it.name.convertToSeverity()) } ?: testCase.severity(),
         testCase.story(),
         ResultsUtils.createThreadLabel()
      )

      val links = mutableListOf()
      testCase.issue()?.let {
         links.add(testCase.issue())
      }
      testCase.link()?.let {
         links.add(testCase.link())
      }
      testCase.links()?.forEach{
         links.add(ResultsUtils.createLink(it))
      }
      val uuid = UUID.randomUUID().toString()
      uuids[testCase.description.testPath()] = uuid

      val result = io.qameta.allure.model.TestResult()
         .setFullName(testCase.description.testDisplayPath().value)
         .setName(testCase.description.testDisplayPath().value)
         .setUuid(uuid)
         .setTestCaseId(safeId(testCase.description))
         .setHistoryId(safeId(testCase.description))
         .setLabels(labels)
         .setLinks(links)
         .setDescription(testCase.description())

      allure.scheduleTestCase(result)
      allure.startTestCase(uuid)
   }

   fun finishTestCase(testCase: TestCase, result: TestResult) {
      val status = when (result.status) {
         // what we call an error, allure calls broken
         TestStatus.Error -> Status.BROKEN
         TestStatus.Failure -> Status.FAILED
         TestStatus.Ignored -> Status.SKIPPED
         TestStatus.Success -> Status.PASSED
      }

      val uuid = uuids[testCase.description.testPath()]
      val details = ResultsUtils.getStatusDetails(result.error)

      allure.updateTestCase(uuid) {
         it.status = status
         it.statusDetails = details.orElseGet { null }
         testCase.description.parents().forEach { d ->
            it.steps.add(StepResult()
               .setName(d.displayName())
               .setStatus(Status.PASSED)
               .setStart(0L)
               .setStop(0L)
            )
         }
      }
      allure.stopTestCase(uuid)
      allure.writeTestCase(uuid)
   }

   private fun links(kclass: KClass): List {
      val links = mutableListOf()
      kclass.issue()?.let {
         links.add(kclass.issue())
      }
      kclass.link()?.let {
         links.add(kclass.link())
      }
      kclass.links()?.forEach{
         links.add(ResultsUtils.createLink(it))
      }
      return links.toList()
   }

   fun allureResultSpecInitFailure(kclass: KClass, t: Throwable) {
      val uuid = UUID.randomUUID()
      val labels = listOfNotNull(
         ResultsUtils.createSuiteLabel(kclass.qualifiedName),
         ResultsUtils.createThreadLabel(),
         ResultsUtils.createHostLabel(),
         ResultsUtils.createLanguageLabel("kotlin"),
         ResultsUtils.createFrameworkLabel("kotest"),
         ResultsUtils.createPackageLabel(kclass.java.`package`.name),
         kclass.severity(),
         kclass.owner(),
         kclass.epic(),
         kclass.feature(),
         kclass.story()
      )

      val links = links(kclass)

      val result = io.qameta.allure.model.TestResult()
         .setFullName(kclass.qualifiedName)
         .setName(kclass.simpleName)
         .setUuid(uuid.toString())
         .setLabels(labels)
         .setLinks(links)

      allure.scheduleTestCase(result)
      allure.startTestCase(uuid.toString())

      val instanceError = (t.cause as InvocationTargetException).targetException

      val details = StatusDetails()
      details.message = instanceError?.message ?: "Unknown error"
      var trace = ""
      instanceError.stackTrace?.forEach {
         trace += "$it\n"
      }
      details.trace = trace

      allure.updateTestCase(uuid.toString()) {
         it.status = Status.FAILED
         it.statusDetails = details
      }
      allure.stopTestCase(uuid.toString())
      allure.writeTestCase(uuid.toString())
   }

   // returns an id that's acceptable in format for allure
   private fun safeId(description: Description): String = description.id.value
}

fun TestCase.epic(): Label? = this.spec::class.findAnnotation()?.let { ResultsUtils.createEpicLabel(it.value) }
fun TestCase.feature(): Label? =
   this.spec::class.findAnnotation()?.let { ResultsUtils.createFeatureLabel(it.value) }

fun TestCase.severity(): Label? =
   this.spec::class.findAnnotation()?.let { ResultsUtils.createSeverityLabel(it.value) }

fun TestCase.story(): Label? = this.spec::class.findAnnotation()?.let { ResultsUtils.createStoryLabel(it.value) }
fun TestCase.owner(): Label? = this.spec::class.findAnnotation()?.let { ResultsUtils.createOwnerLabel(it.value) }
fun TestCase.issue() = spec::class.findAnnotation()?.let { ResultsUtils.createIssueLink(it.value) }
fun TestCase.description() = spec::class.findAnnotation()?.value
fun TestCase.link() = spec::class.findAnnotation()?.let { ResultsUtils.createLink(it) }
fun TestCase.links() = spec::class.findAnnotation()?.value

fun String.convertToSeverity(): SeverityLevel? = when (this) {
   "BLOCKER" -> SeverityLevel.BLOCKER
   "CRITICAL" -> SeverityLevel.CRITICAL
   "NORMAL" -> SeverityLevel.NORMAL
   "MINOR" -> SeverityLevel.MINOR
   "TRIVIAL" -> SeverityLevel.TRIVIAL
   else -> null
}

fun KClass.epic(): Label? = this.findAnnotation()?.let { ResultsUtils.createEpicLabel(it.value) }
fun KClass.feature(): Label? = this.findAnnotation()?.let { ResultsUtils.createFeatureLabel(it.value) }
fun KClass.severity(): Label? = this.findAnnotation()?.let { ResultsUtils.createSeverityLabel(it.value) }
fun KClass.story(): Label? = this.findAnnotation()?.let { ResultsUtils.createStoryLabel(it.value) }
fun KClass.owner(): Label? = this.findAnnotation()?.let { ResultsUtils.createOwnerLabel(it.value) }
fun KClass.issue() = this.findAnnotation()?.let { ResultsUtils.createIssueLink(it.value) }
fun KClass.link() = this.findAnnotation()?.let { ResultsUtils.createLink(it) }
fun KClass.links() = this.findAnnotation()?.value
fun KClass.description() = this.findAnnotation()?.value




© 2015 - 2025 Weber Informatics LLC | Privacy Policy