From dd5069917ee65754d4bb081874d6c9f3164f5f6f Mon Sep 17 00:00:00 2001 From: Kamil Podsiadlo Date: Tue, 16 Nov 2021 12:02:07 +0100 Subject: [PATCH] Send TestResult to Debuggee Listener --- .../scala/bloop/dap/BloopDebuggeeRunner.scala | 14 +-- .../main/scala/bloop/engine/tasks/Tasks.scala | 3 +- .../scala/bloop/testing/TestInternals.scala | 1 + .../main/scala/bloop/testing/TestServer.scala | 4 +- .../scala/bloop/testing/TestSuiteEvent.scala | 96 +++++++++++-------- .../testing/LoggingEventHandlerSpec.scala | 1 + .../scala/bloop/testing/TestPrinterSpec.scala | 3 +- 7 files changed, 73 insertions(+), 49 deletions(-) diff --git a/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala b/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala index f99ab5da02..dce08da252 100644 --- a/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala +++ b/frontend/src/main/scala/bloop/dap/BloopDebuggeeRunner.scala @@ -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 @@ -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( @@ -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 @@ -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, @@ -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 { diff --git a/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala b/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala index 0046a52014..2b9ae9eae9 100644 --- a/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala +++ b/frontend/src/main/scala/bloop/engine/tasks/Tasks.scala @@ -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 diff --git a/frontend/src/main/scala/bloop/testing/TestInternals.scala b/frontend/src/main/scala/bloop/testing/TestInternals.scala index 2bf619e1fd..ec25c0ce68 100644 --- a/frontend/src/main/scala/bloop/testing/TestInternals.scala +++ b/frontend/src/main/scala/bloop/testing/TestInternals.scala @@ -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.{ diff --git a/frontend/src/main/scala/bloop/testing/TestServer.scala b/frontend/src/main/scala/bloop/testing/TestServer.scala index 64bebe04e0..b88406a3ee 100644 --- a/frontend/src/main/scala/bloop/testing/TestServer.scala +++ b/frontend/src/main/scala/bloop/testing/TestServer.scala @@ -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. diff --git a/frontend/src/main/scala/bloop/testing/TestSuiteEvent.scala b/frontend/src/main/scala/bloop/testing/TestSuiteEvent.scala index 5a6852a901..f0b63e5f1c 100644 --- a/frontend/src/main/scala/bloop/testing/TestSuiteEvent.scala +++ b/frontend/src/main/scala/bloop/testing/TestSuiteEvent.scala @@ -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 @@ -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 @@ -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") @@ -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("===============================================") @@ -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)) } } } @@ -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 diff --git a/frontend/src/test/scala/bloop/testing/LoggingEventHandlerSpec.scala b/frontend/src/test/scala/bloop/testing/LoggingEventHandlerSpec.scala index 211c5ef4f6..f7451c5ae9 100644 --- a/frontend/src/test/scala/bloop/testing/LoggingEventHandlerSpec.scala +++ b/frontend/src/test/scala/bloop/testing/LoggingEventHandlerSpec.scala @@ -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 { diff --git a/frontend/src/test/scala/bloop/testing/TestPrinterSpec.scala b/frontend/src/test/scala/bloop/testing/TestPrinterSpec.scala index 9d02632799..8eafc854c1 100644 --- a/frontend/src/test/scala/bloop/testing/TestPrinterSpec.scala +++ b/frontend/src/test/scala/bloop/testing/TestPrinterSpec.scala @@ -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") { @@ -13,7 +14,7 @@ object TestPrinterSpec extends BaseSuite { testCases.foreach { case (testFrameworkMessage, expectedTruncatedMessage) => assertNoDiff( - TestUtils.stripTestFrameworkSpecificInformation(testFrameworkMessage), + ch.TestUtils.stripTestFrameworkSpecificInformation(testFrameworkMessage), expectedTruncatedMessage ) }