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

ai.starlake.tests.StarlakeTestResult.scala Maven / Gradle / Ivy

package ai.starlake.tests

import ai.starlake.config.Settings
import ai.starlake.utils.Utils

import java.io.File
import java.nio.file.Files
import java.time.format.DateTimeFormatter
import java.util
import scala.jdk.CollectionConverters._
import scala.reflect.io.Directory

case class JUnitTestSuite(
  name: String,
  tests: Int,
  failures: Int,
  errors: Int,
  time: Double,
  testCases: List[JunitTestCase]
) {
  def getName() = name
  def getTests(): Int = tests
  def getFailures(): Int = failures
  def getErrors(): Int = errors
  def getTime(): Double = time
  def getTestCases(): util.List[JunitTestCase] = testCases.asJava
}

case class JunitTestSuites(
  tests: Int,
  failures: Int,
  errors: Int,
  time: Double,
  loadSuite: JUnitTestSuite,
  transformSuite: JUnitTestSuite
) {
  def getTests(): Int = tests
  def getFailures(): Int = failures
  def getErrors(): Int = errors
  def getTime(): Double = time
  def getLoadSuite(): JUnitTestSuite = loadSuite
  def getTransformSuite(): JUnitTestSuite = transformSuite
  def getTestSuites(): util.List[JUnitTestSuite] = List(loadSuite, transformSuite).asJava
}

case class JunitTestCase(
  name: String,
  classname: String,
  time: Double,
  failure: Option[String] = None
) {
  def getName(): String = name
  def getClassname(): String = classname
  def getTime(): Double = time
  def getFailure(): String = failure.getOrElse("")
}

case class StarlakeTestResult(
  testFolder: String,
  domainName: String,
  tableName: String,
  taskName: String,
  testName: String,
  expectationName: String,
  missingColumns: List[String],
  notExpectedColumns: List[String],
  missingRecords: File,
  notExpectedRecords: File,
  success: Boolean,
  exception: Option[String],
  duration: Long
) {
  def junitErrMessage(): Option[String] = {
    if (!success) {
      Some(
        s"""Missing columns: ${getMissingColumnsCount()}, Not expected columns: ${getNotExpectedColumnsCount()}, Missing records: ${getMissingRecordsCount()}, Not expected records: ${getNotExpectedRecordsCount()}, Exception: ${getExceptionHead()}""".stripMargin
      )
    } else None
  }
  def tName = if (Option(tableName).isEmpty || tableName.isEmpty) taskName else tableName
  def toJunitTestCase(): JunitTestCase = {
    val name: String = s"$domainName.$tName.$testName"
    val classname: String = s"$domainName.$taskName"
    val time: Double = duration.toDouble / 1000
    val failure: Option[String] = junitErrMessage()
    JunitTestCase(name, classname, time, failure)
  }
  // getters for jinjava
  def getTestFolder(): String = testFolder
  def getDomainName(): String = domainName
  def getTableName(): String = tableName
  def getTaskName(): String = taskName
  def getTestName(): String = testName
  def getExpectationName(): String =
    if (expectationName.isEmpty) "default" else expectationName.substring(1)
  def getMissingColumns(): java.util.List[String] = missingColumns.asJava
  def getMissingColumnsCount(): Int = missingColumns.size
  def getNotExpectedColumns(): java.util.List[String] = notExpectedColumns.asJava
  def getNotExpectedColumnsCount(): Int = notExpectedColumns.size
  def getMissingRecords(): String =
    if (missingRecords.exists())
      Files.readAllLines(missingRecords.toPath).asScala.mkString("\n")
    else ""
  def getMissingRecordsCount() = {
    val nbLines = if (missingRecords.exists()) getMissingRecords().split("\n").length else 0
    if (nbLines >= 1) nbLines - 1 else 0
  }

  def getNotExpectedRecords(): String =
    if (notExpectedRecords.exists())
      Files.readAllLines(notExpectedRecords.toPath).asScala.mkString("\n")
    else ""

  def getNotExpectedRecordsCount() = {
    val nbLines = if (notExpectedRecords.exists()) getNotExpectedRecords().split("\n").length else 0
    if (nbLines >= 1) nbLines - 1 else 0

  }

  def getSuccess(): Boolean = success
  def getExceptionHead(): String =
    exception.getOrElse("None").split("\n").head
  def getException(): util.List[String] =
    exception.getOrElse("None").split("\n").toList.asJava
  def getDuration(): String = {
    val d: Double = duration.toDouble / 1000
    s"$d"
  }

}

object StarlakeTestResult {
  val loader = new StarlakeTestTemplateLoader()

  def copyCssAndJs(toFolder: Directory)(implicit settings: Settings): Unit = {
    val cssAndJs = Array("css/base-style.css", "css/style.css", "js/report.js")
    cssAndJs.foreach { cj =>
      val content = loader.loadTemplate(s"$cj.j2")
      val targetFile = new File(toFolder.path, cj)
      targetFile.getParentFile().mkdirs()
      Files.write(targetFile.toPath, content.getBytes())
    }
  }
  def toJunitTestSuites(
    loadResults: List[StarlakeTestResult],
    transformResults: List[StarlakeTestResult]
  ): JunitTestSuites = {
    val loadTestCases = loadResults.map(_.toJunitTestCase())
    val transformTestCases = transformResults.map(_.toJunitTestCase())
    val loadSuite = JUnitTestSuite(
      name = "Tests.Load",
      tests = loadTestCases.size,
      failures = loadTestCases.count(_.failure.isDefined),
      errors = 0,
      time = loadTestCases.map(_.time).sum,
      testCases = loadTestCases
    )
    val transformSuite = JUnitTestSuite(
      name = "Tests.Transform",
      tests = transformTestCases.size,
      failures = transformTestCases.count(_.failure.isDefined),
      errors = 0,
      time = transformTestCases.map(_.time).sum,
      testCases = transformTestCases
    )
    JunitTestSuites(
      tests = loadSuite.tests + transformSuite.tests,
      failures = loadSuite.failures + transformSuite.failures,
      errors = 0,
      time = loadSuite.time + transformSuite.time,
      loadSuite = loadSuite,
      transformSuite = transformSuite
    )
  }
  def junitXml(
    loadResults: List[StarlakeTestResult],
    transformResults: List[StarlakeTestResult],
    rootFolder: Directory
  )(implicit settings: Settings): Unit = {

    val junitTestSuites = toJunitTestSuites(loadResults, transformResults)
    val j2Params = Map(
      "junitTestSuites" -> junitTestSuites,
      "timestamp"       -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now())
    )
    val indexJ2 = loader.loadTemplate("junit.xml.j2")
    val indexContent = Utils.parseJinja(indexJ2, j2Params)
    Files.write(new File(rootFolder.path, "junit.xml").toPath, indexContent.getBytes())

  }

  def html(
    loadAndCoverageResults: (List[StarlakeTestResult], StarlakeTestCoverage),
    transformAndCoverageResults: (List[StarlakeTestResult], StarlakeTestCoverage),
    outputDir: Option[String]
  )(implicit settings: Settings): Unit = {
    val rootFolder =
      outputDir
        .flatMap { dir =>
          val file = new File(dir)
          if (file.exists() || file.mkdirs()) {
            Option(new Directory(file))
          } else {
            Console.err.println(s"Could not create output directory $dir")
            None
          }
        }
        .getOrElse(new Directory(new File(settings.appConfig.root, "test-reports")))
    copyCssAndJs(rootFolder)

    val (loadResults, loadCoverage) = loadAndCoverageResults
    val loadSummaries = StarlakeTestsDomainSummary.summaries(loadResults)
    val loadIndex = StarlakeTestsSummary.summaryIndex(loadSummaries)

    val (transformResults, transformCoverage) = transformAndCoverageResults
    val transformSummaries = StarlakeTestsDomainSummary.summaries(transformResults)
    val transformIndex = StarlakeTestsSummary.summaryIndex(transformSummaries)
    val j2Params = Map(
      "loadIndex"          -> loadIndex,
      "loadSummaries"      -> loadSummaries.asJava,
      "transformIndex"     -> transformIndex,
      "transformSummaries" -> transformSummaries.asJava,
      "timestamp"          -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now()),
      "loadCoverage"       -> loadCoverage,
      "transformCoverage"  -> transformCoverage
    )
    val indexJ2 = loader.loadTemplate("root.html.j2")
    val indexContent = Utils.parseJinja(indexJ2, j2Params)
    Files.write(new File(rootFolder.path, "index.html").toPath, indexContent.getBytes())

    val loadFolder = new Directory(new File(rootFolder.jfile, "load"))
    loadFolder.createDirectory()
    html(loadResults, loadFolder, "Load")
    val transformFolder = new Directory(new File(rootFolder.jfile, "transform"))
    transformFolder.createDirectory()
    html(transformResults, transformFolder, "Transform")
    junitXml(loadResults, transformResults, rootFolder)
  }

  def html(
    results: List[StarlakeTestResult],
    testsFolder: Directory,
    loadOrTransform: String
  )(implicit
    settings: Settings
  ): Unit = {
    val domainSummaries = StarlakeTestsDomainSummary.summaries(results)
    val summaryIndex = StarlakeTestsSummary.summaryIndex(domainSummaries)

    val j2Params = Map(
      "summaryIndex"    -> summaryIndex,
      "domainSummaries" -> domainSummaries.asJava,
      "timestamp"       -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now()),
      "loadOrTransform" -> loadOrTransform
    )
    val indexJ2 = loader.loadTemplate("index.html.j2")
    val indexContent = Utils.parseJinja(indexJ2, j2Params)
    Files.write(new File(testsFolder.path, "index.html").toPath, indexContent.getBytes())

    domainSummaries.foreach { domainSummary =>
      val tableSummaries = StarlakeTestsTableSummary.summaries(domainSummary.name, results)
      val indexJ2 = loader.loadTemplate("index.domain.html.j2")
      val j2Params = Map(
        "domainSummary"   -> domainSummary,
        "tableSummaries"  -> tableSummaries.asJava,
        "timestamp"       -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now()),
        "loadOrTransform" -> loadOrTransform
      )
      val domainFolder = new File(testsFolder.path, domainSummary.name)
      domainFolder.mkdir()
      val result = Utils.parseJinja(indexJ2, j2Params)
      Files.write(new File(domainFolder, "index.html").toPath, result.getBytes())

      tableSummaries.foreach { tableSummary =>
        val tableResults =
          results.filter(r => s"${r.domainName}.${r.taskName}" == tableSummary.name)
        val indexJ2 = loader.loadTemplate("index.table.html.j2")
        val j2Params = Map(
          "domainName"      -> domainSummary.name,
          "tableSummary"    -> tableSummary,
          "testResults"     -> tableResults.asJava,
          "timestamp"       -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now()),
          "loadOrTransform" -> loadOrTransform
        )
        val tableFolder = new File(domainFolder, tableSummary.getTableName())
        tableFolder.mkdir()
        val result = Utils.parseJinja(indexJ2, j2Params)
        Files.write(new File(tableFolder, "index.html").toPath, result.getBytes())
      }
    }
    results.foreach { result =>
      val indexJ2 = loader.loadTemplate("index.test.html.j2")
      val j2Params = Map(
        "domainName"      -> result.domainName,
        "tableName"       -> result.taskName,
        "testResult"      -> result,
        "timestamp"       -> DateTimeFormatter.ISO_INSTANT.format(java.time.Instant.now()),
        "loadOrTransform" -> loadOrTransform
      )
      val testFolder =
        new File(
          testsFolder.path,
          result.domainName + File.separator + result.taskName + File.separator + result.testName
        )
      testFolder.mkdir()
      val resultContent = Utils.parseJinja(indexJ2, j2Params)
      Files.write(
        new File(testFolder, s"${result.getExpectationName()}.html").toPath,
        resultContent.getBytes()
      )
    }

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy