Skip to content

Commit

Permalink
Send TestResult to Debuggee Listener
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamil Podsiadlo committed Nov 23, 2021
1 parent 5047569 commit dd50699
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 49 deletions.
14 changes: 7 additions & 7 deletions frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import bloop.engine.caches.ExpressionCompilerCache
import bloop.engine.tasks.{RunMode, Tasks}
import bloop.engine.{Dag, State}
import bloop.logging.Logger
import bloop.testing.{LoggingEventHandler, TestInternals}
import bloop.testing.{LoggingEventHandler, DebugLoggingEventHandler, TestInternals}
import ch.epfl.scala.bsp.ScalaMainClass
import ch.epfl.scala.debugadapter._
import monix.eval.Task
Expand All @@ -23,14 +23,14 @@ abstract class BloopDebuggeeRunner(initialState: State, ioScheduler: Scheduler)
override def run(listener: DebuggeeListener): CancelableFuture[Unit] = {
val debugSessionLogger = new DebuggeeLogger(listener, initialState.logger)

val task = start(initialState.copy(logger = debugSessionLogger))
val task = start(initialState.copy(logger = debugSessionLogger), listener)
.map { status =>
if (!status.isOk) throw new Exception(s"debugee failed with ${status.name}")
}
DapCancellableFuture.runAsync(task, ioScheduler)
}

protected def start(state: State): Task[ExitStatus]
protected def start(state: State, listener: DebuggeeListener): Task[ExitStatus]
}

private final class MainClassDebugAdapter(
Expand All @@ -45,7 +45,7 @@ private final class MainClassDebugAdapter(
) extends BloopDebuggeeRunner(initialState, ioScheduler) {
val javaRuntime: Option[JavaRuntime] = JavaRuntime(env.javaHome.underlying)
def name: String = s"${getClass.getSimpleName}(${project.name}, ${mainClass.`class`})"
def start(state: State): Task[ExitStatus] = {
def start(state: State, listener: DebuggeeListener): Task[ExitStatus] = {
val workingDir = state.commonOptions.workingPath
// TODO: https://github.com/scalacenter/bloop/issues/1456
// Metals used to add the `-J` prefix but it is not needed anymore
Expand Down Expand Up @@ -81,9 +81,9 @@ private final class TestSuiteDebugAdapter(
val filtersStr = filters.mkString("[", ", ", "]")
s"${getClass.getSimpleName}($projectsStr, $filtersStr)"
}
override def start(state: State): Task[ExitStatus] = {
override def start(state: State, listener: DebuggeeListener): Task[ExitStatus] = {
val filter = TestInternals.parseFilters(filters)
val handler = new LoggingEventHandler(state.logger)
val handler = new DebugLoggingEventHandler(state.logger, listener)

val task = Tasks.test(
state,
Expand All @@ -107,7 +107,7 @@ private final class AttachRemoteDebugAdapter(
override val classPath: Seq[Path]
) extends BloopDebuggeeRunner(initialState, ioScheduler) {
override def name: String = s"${getClass.getSimpleName}(${initialState.build.origin})"
override def start(state: State): Task[ExitStatus] = Task(ExitStatus.Ok)
override def start(state: State, listener: DebuggeeListener): Task[ExitStatus] = Task(ExitStatus.Ok)
}

object BloopDebuggeeRunner {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/main/scala/bloop/engine/tasks/Tasks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import bloop.engine.{Dag, State}
import bloop.exec.{Forker, JvmProcessForker}
import bloop.io.AbsolutePath
import bloop.util.JavaCompat.EnrichOptional
import bloop.testing.{LoggingEventHandler, TestSuiteEvent, TestSuiteEventHandler}
import bloop.testing.{LoggingEventHandler, TestSuiteEventHandler}
import ch.epfl.scala.debugadapter.util.TestSuiteEvent
import monix.eval.Task
import sbt.internal.inc.{Analysis, AnalyzingCompiler, ConcreteAnalysisContents, FileAnalysisStore}
import sbt.internal.inc.classpath.ClasspathUtilities
Expand Down
1 change: 1 addition & 0 deletions frontend/src/main/scala/bloop/testing/TestInternals.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import bloop.engine.ExecutionContext
import bloop.exec.{Forker, JvmProcessForker}
import bloop.io.AbsolutePath
import bloop.logging.{DebugFilter, Logger}
import ch.epfl.scala.debugadapter.util.TestSuiteEvent
import monix.eval.Task
import monix.execution.atomic.AtomicBoolean
import sbt.testing.{
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/main/scala/bloop/testing/TestServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import java.net.ServerSocket

import bloop.cli.CommonOptions
import bloop.config.Config

import scala.util.Try
import bloop.logging.{DebugFilter, Logger}
import ch.epfl.scala.debugadapter.util.TestSuiteEvent
import monix.eval.Task
import monix.execution.misc.NonFatal
import sbt.{ForkConfiguration, ForkTags}
import sbt.testing.{Event, Framework, TaskDef}

import scala.concurrent.Promise
import scala.util.Try

/**
* Implements the protocol that the forked remote JVM talks with the host process.
Expand Down
96 changes: 58 additions & 38 deletions frontend/src/main/scala/bloop/testing/TestSuiteEvent.scala
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
package bloop.testing

import bloop.logging.{DebugFilter, Logger}
import bloop.logging.DebugFilter
import bloop.logging.Logger
import bloop.util.TimeFormat
import ch.epfl.scala.bsp
import ch.epfl.scala.bsp.BuildTargetIdentifier
import ch.epfl.scala.bsp.endpoints.{Build, BuildTarget}
import sbt.testing.{Event, Selector, Status}
import ch.epfl.scala.bsp.endpoints.Build
import ch.epfl.scala.bsp.endpoints.BuildTarget
import ch.epfl.scala.debugadapter.DebuggeeListener
import ch.epfl.scala.debugadapter.SingleTestResult
import ch.epfl.scala.debugadapter.TestSuiteResult
import ch.epfl.scala.debugadapter.TestResultEvent
import ch.epfl.scala.debugadapter.util.TestSuiteEvent
import ch.epfl.scala.debugadapter.{util => ch}
import sbt.testing.Event
import sbt.testing.Selector
import sbt.testing.Status
import sbt.testing.TestSelector

import scala.collection.mutable
import scala.meta.jsonrpc.JsonRpcClient
import scala.util.Try

sealed trait TestSuiteEvent
object TestSuiteEvent {
case object Done extends TestSuiteEvent
case class Error(message: String) extends TestSuiteEvent
case class Warn(message: String) extends TestSuiteEvent
case class Info(message: String) extends TestSuiteEvent
case class Debug(message: String) extends TestSuiteEvent
case class Trace(throwable: Throwable) extends TestSuiteEvent

/** @param testSuite Class name of test suite */
case class Results(testSuite: String, events: List[Event]) extends TestSuiteEvent
}
import collection.JavaConverters._

trait TestSuiteEventHandler {
def handle(testSuiteEvent: TestSuiteEvent): Unit
trait TestSuiteEventHandler extends ch.TestSuiteEventHandler {
def report(): Unit
}

class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
private type SuiteName = String
private type TestName = String
private type FailureMessage = String
import LoggingEventHandler._

type SuiteName = String
type TestName = String
type FailureMessage = String

protected var suitesDuration = 0L
protected var suitesPassed = 0
protected var suitesAborted = 0
Expand All @@ -56,7 +58,7 @@ class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
suitesAborted += 1
suitesTotal += 1

case TestSuiteEvent.Results(testSuite, events) =>
case TestSuiteEvent.Results(testSuite, events: List[Event]) =>
val testsTotal = events.length

val duration = events.map(_.duration()).sum
Expand All @@ -83,19 +85,8 @@ class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
val testMetrics = formatMetrics(regularMetrics ++ failureMetrics)
if (!testMetrics.isEmpty) logger.info(testMetrics)

val failedStatuses = Set(Status.Error, Status.Canceled, Status.Failure)
if (failureCount > 0) {
val currentFailedTests = {
events
.filter(e => failedStatuses.contains(e.status()))
.map { event =>
val key = TestUtils.printSelector(event.selector, logger).getOrElse("")
val value = TestUtils.printThrowable(event.throwable()).getOrElse("")
key -> value
}
.toMap
}

val currentFailedTests = extractErrors(events, logger)
val previousFailedTests = testsFailedBySuite.getOrElse(testSuite, Map.empty)
testsFailedBySuite += testSuite -> (previousFailedTests ++ currentFailedTests)
} else if (testsTotal <= 0) logger.info("No test suite was run")
Expand All @@ -111,6 +102,22 @@ class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
case TestSuiteEvent.Done => ()
}

private def extractErrors(events: List[Event], logger: Logger) =
events
.filter(e => failedStatuses.contains(e.status()))
.map { event =>
val selectorOpt = ch.TestUtils.printSelector(event.selector)
if (selectorOpt.isEmpty) {
logger.debug(s"Unexpected test selector ${event.selector} won't be pretty printed!")(
DebugFilter.Test
)
}
val key = selectorOpt.getOrElse("")
val value = ch.TestUtils.printThrowable(event.throwable()).getOrElse("")
key -> value
}
.toMap

override def report(): Unit = {
// TODO: Shall we think of a better way to format this delimiter based on screen length?
logger.info("===============================================")
Expand All @@ -134,12 +141,11 @@ class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
testsFailedBySuite.foreach {
case (suiteName, failedTests) =>
logger.info(s"- $suiteName:")
failedTests.foreach {
case ("", failureMessage) => logger.info(s" * $failureMessage")
case (testSuiteName, "") => logger.info(s" * $testSuiteName")
case (testSuiteName, failureMessage) =>
logger.info(s" * $testSuiteName - $failureMessage")
val summary = failedTests.map {
case (suiteName, failureMsg) =>
ch.TestSuiteEventHandler.formatError(suiteName, failureMsg, indentSize = 2)
}
summary.foreach(s => logger.info(s))
}
}
}
Expand All @@ -148,6 +154,20 @@ class LoggingEventHandler(logger: Logger) extends TestSuiteEventHandler {
}
}

object LoggingEventHandler {
val failedStatuses = Set(Status.Error, Status.Canceled, Status.Failure)
}

final class DebugLoggingEventHandler(logger: Logger, listener: DebuggeeListener)
extends LoggingEventHandler(logger) {

val testResultsHandler = new ch.TestSuiteResultsEventHandler(listener)
override def handle(event: TestSuiteEvent): Unit = {
testResultsHandler.handle(event)
super.handle(event)
}
}

final class BspLoggingEventHandler(id: BuildTargetIdentifier, logger: Logger, client: JsonRpcClient)
extends LoggingEventHandler(logger) {
implicit val client0: JsonRpcClient = client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bloop.testing

import bloop.logging.{DebugFilter, Logger, RecordingLogger}
import ch.epfl.scala.debugadapter.util.TestSuiteEvent
import sbt.testing.{Event, Fingerprint, OptionalThrowable, Selector, Status, TestSelector}

object LoggingEventHandlerSpec extends BaseSuite {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/test/scala/bloop/testing/TestPrinterSpec.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package bloop.testing
import bloop.util.TestUtil
import ch.epfl.scala.debugadapter.{util => ch}

object TestPrinterSpec extends BaseSuite {
test("stripping parts of message created by test framework") {
Expand All @@ -13,7 +14,7 @@ object TestPrinterSpec extends BaseSuite {
testCases.foreach {
case (testFrameworkMessage, expectedTruncatedMessage) =>
assertNoDiff(
TestUtils.stripTestFrameworkSpecificInformation(testFrameworkMessage),
ch.TestUtils.stripTestFrameworkSpecificInformation(testFrameworkMessage),
expectedTruncatedMessage
)
}
Expand Down

0 comments on commit dd50699

Please sign in to comment.