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

sbt.TestResultLogger.scala Maven / Gradle / Ivy

There is a newer version: 1.10.2
Show newest version
/*
 * sbt
 * Copyright 2023, Scala center
 * Copyright 2011 - 2022, Lightbend, Inc.
 * Copyright 2008 - 2010, Mark Harrah
 * Licensed under Apache License 2.0 (see LICENSE)
 */

package sbt

import sbt.Tests.{ Output, Summary }
import sbt.protocol.testing.TestResult
import sbt.util.{ Level, Logger }

/**
 * Logs information about tests after they finish.
 *
 * Log output can be customised by providing a specialised instance of this
 * trait via the `testResultLogger` setting.
 *
 * @since 0.13.5
 */
trait TestResultLogger {

  /**
   * Perform logging.
   *
   * @param log The target logger to write output to.
   * @param results The test results about which to log.
   * @param taskName The task about which we are logging. Eg. "my-module-b/test:test"
   */
  def run(log: Logger, results: Output, taskName: String): Unit

  /** Only allow invocation if certain criteria is met, else use another `TestResultLogger` (defaulting to nothing) . */
  final def onlyIf(
      f: (Output, String) => Boolean,
      otherwise: TestResultLogger = TestResultLogger.Null
  ) =
    TestResultLogger.choose(f, this, otherwise)

  /** Allow invocation unless a certain predicate passes, in which case use another `TestResultLogger` (defaulting to nothing) . */
  final def unless(
      f: (Output, String) => Boolean,
      otherwise: TestResultLogger = TestResultLogger.Null
  ) =
    TestResultLogger.choose(f, otherwise, this)
}

object TestResultLogger {

  /** A `TestResultLogger` that does nothing. */
  val Null = const(_ => ())

  /** sbt's default `TestResultLogger`. Use `copy()` to change selective portions. */
  val Default = Defaults.Main()

  /** Twist on the default which is completely silent when the subject module doesn't contain any tests. */
  def SilentWhenNoTests = silenceWhenNoTests(Default)

  /** Creates a `TestResultLogger` using a given function. */
  def apply(f: (Logger, Output, String) => Unit): TestResultLogger =
    (log, results, taskName) => f(log, results, taskName)

  /** Creates a `TestResultLogger` that ignores its input and always performs the same logging. */
  def const(f: Logger => Unit) = apply((l, _, _) => f(l))

  /**
   * Selects a `TestResultLogger` based on a given predicate.
   *
   * @param t The `TestResultLogger` to choose if the predicate passes.
   * @param f The `TestResultLogger` to choose if the predicate fails.
   */
  def choose(cond: (Output, String) => Boolean, t: TestResultLogger, f: TestResultLogger) =
    TestResultLogger(
      (log, results, taskName) =>
        (if (cond(results, taskName)) t else f).run(log, results, taskName)
    )

  /** Transforms the input to be completely silent when the subject module doesn't contain any tests. */
  def silenceWhenNoTests(d: Defaults.Main) =
    d.copy(
      printStandard = d.printStandard.unless((results, _) => results.events.isEmpty),
      printNoTests = Null
    )

  object Defaults {

    /** sbt's default `TestResultLogger`. Use `copy()` to change selective portions. */
    case class Main(
        printStandard_? : Output => Boolean = Defaults.printStandard_?,
        printSummary: TestResultLogger = Defaults.printSummary,
        printStandard: TestResultLogger = Defaults.printStandard,
        printFailures: TestResultLogger = Defaults.printFailures,
        printNoTests: TestResultLogger = Defaults.printNoTests
    ) extends TestResultLogger {

      override def run(log: Logger, results: Output, taskName: String): Unit = {
        def run(r: TestResultLogger): Unit = r.run(log, results, taskName)

        run(printSummary)

        if (printStandard_?(results))
          run(printStandard)

        if (results.events.isEmpty)
          run(printNoTests)
        else
          run(printFailures)

        results.overall match {
          case TestResult.Error | TestResult.Failed => throw new TestsFailedException
          case TestResult.Passed                    =>
        }
      }
    }

    val printSummary = TestResultLogger((log, results, _) => {
      val multipleFrameworks = results.summaries.size > 1
      for (Summary(name, message) <- results.summaries)
        if (message.isEmpty)
          log.debug("Summary for " + name + " not available.")
        else {
          if (multipleFrameworks) log.info(name)
          log.info(message)
        }
    })

    val printStandard_? : Output => Boolean =
      results =>
        // Print the standard one-liner statistic if no framework summary is defined, or when > 1 framework is in used.
        results.summaries.size > 1 || results.summaries.headOption.forall(_.summaryText.isEmpty)

    val printStandard = TestResultLogger((log, results, _) => {
      val (
        skippedCount,
        errorsCount,
        passedCount,
        failuresCount,
        ignoredCount,
        canceledCount,
        pendingCount,
      ) =
        results.events.foldLeft((0, 0, 0, 0, 0, 0, 0)) {
          case (acc, (_, testEvent)) =>
            val (skippedAcc, errorAcc, passedAcc, failureAcc, ignoredAcc, canceledAcc, pendingAcc) =
              acc
            (
              skippedAcc + testEvent.skippedCount,
              errorAcc + testEvent.errorCount,
              passedAcc + testEvent.passedCount,
              failureAcc + testEvent.failureCount,
              ignoredAcc + testEvent.ignoredCount,
              canceledAcc + testEvent.canceledCount,
              pendingAcc + testEvent.pendingCount,
            )
        }
      val totalCount = failuresCount + errorsCount + skippedCount + passedCount
      val base =
        s"Total $totalCount, Failed $failuresCount, Errors $errorsCount, Passed $passedCount"

      val otherCounts = Seq(
        "Skipped" -> skippedCount,
        "Ignored" -> ignoredCount,
        "Canceled" -> canceledCount,
        "Pending" -> pendingCount
      )
      val extra = otherCounts.withFilter(_._2 > 0).map { case (label, count) => s", $label $count" }

      val postfix = base + extra.mkString
      results.overall match {
        case TestResult.Error  => log.error("Error: " + postfix)
        case TestResult.Passed => log.info("Passed: " + postfix)
        case TestResult.Failed => log.error("Failed: " + postfix)
      }
    })

    val printFailures = TestResultLogger((log, results, _) => {
      def select(resultTpe: TestResult) = results.events collect {
        case (name, tpe) if tpe.result == resultTpe =>
          scala.reflect.NameTransformer.decode(name)
      }

      def show(label: String, level: Level.Value, tests: Iterable[String]): Unit =
        if (tests.nonEmpty) {
          log.log(level, label)
          log.log(level, tests.mkString("\t", "\n\t", ""))
        }

      show("Passed tests:", Level.Debug, select(TestResult.Passed))
      show("Failed tests:", Level.Error, select(TestResult.Failed))
      show("Error during tests:", Level.Error, select(TestResult.Error))
    })

    val printNoTests = TestResultLogger(
      (log, results, taskName) => log.info("No tests to run for " + taskName)
    )
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy