mirror of
https://github.com/sbt/sbt.git
synced 2026-08-22 14:17:13 +02:00
Merge ee6137da2a into da1a94bea9
This commit is contained in:
@@ -28,6 +28,7 @@ import testing.{
|
|||||||
}
|
}
|
||||||
import util.Logger
|
import util.Logger
|
||||||
import sbt.protocol.testing.TestResult
|
import sbt.protocol.testing.TestResult
|
||||||
|
import sbt.internal.worker1.ForkTestMain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Companion object for JUnitXmlTestsListener that caches the hostname lazily.
|
* Companion object for JUnitXmlTestsListener that caches the hostname lazily.
|
||||||
@@ -159,25 +160,36 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||||||
} time={(e.duration() / 1000.0).toString}>
|
} time={(e.duration() / 1000.0).toString}>
|
||||||
{
|
{
|
||||||
val trace: String = if (e.throwable.isDefined) {
|
val trace: String = if (e.throwable.isDefined) {
|
||||||
val stringWriter = new StringWriter()
|
e.throwable.get match {
|
||||||
val writer = new PrintWriter(stringWriter)
|
case fe: ForkTestMain.ForkError =>
|
||||||
e.throwable.get.printStackTrace(writer)
|
formatForkErrorTrace(fe)
|
||||||
writer.flush()
|
case other =>
|
||||||
stringWriter.toString
|
val stringWriter = new StringWriter()
|
||||||
|
val writer = new PrintWriter(stringWriter)
|
||||||
|
other.printStackTrace(writer)
|
||||||
|
writer.flush()
|
||||||
|
stringWriter.toString
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
val (exType, exMessage) = e.throwable match {
|
||||||
|
case t if t.isDefined =>
|
||||||
|
t.get match {
|
||||||
|
case fe: ForkTestMain.ForkError =>
|
||||||
|
(fe.getOriginalName, fe.getOriginalMessage)
|
||||||
|
case other =>
|
||||||
|
(other.getClass.getName, other.getMessage)
|
||||||
|
}
|
||||||
|
case _ => ("", "")
|
||||||
|
}
|
||||||
e.status match {
|
e.status match {
|
||||||
case TStatus.Error if (e.throwable.isDefined) =>
|
case TStatus.Error if (e.throwable.isDefined) =>
|
||||||
<error message={e.throwable.get.getMessage} type={
|
<error message={exMessage} type={exType}>{trace}</error>
|
||||||
e.throwable.get.getClass.getName
|
|
||||||
}>{trace}</error>
|
|
||||||
case TStatus.Error =>
|
case TStatus.Error =>
|
||||||
<error message={"No Exception or message provided"}/>
|
<error message={"No Exception or message provided"}/>
|
||||||
case TStatus.Failure if (e.throwable.isDefined) =>
|
case TStatus.Failure if (e.throwable.isDefined) =>
|
||||||
<failure message={e.throwable.get.getMessage} type={
|
<failure message={exMessage} type={exType}>{trace}</failure>
|
||||||
e.throwable.get.getClass.getName
|
|
||||||
}>{trace}</failure>
|
|
||||||
case TStatus.Failure =>
|
case TStatus.Failure =>
|
||||||
<failure message={"No Exception or message provided"}/>
|
<failure message={"No Exception or message provided"}/>
|
||||||
case TStatus.Ignored | TStatus.Skipped | TStatus.Pending =>
|
case TStatus.Ignored | TStatus.Skipped | TStatus.Pending =>
|
||||||
@@ -262,6 +274,36 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||||||
writeSuite()
|
writeSuite()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a ForkError stacktrace using the original exception name instead of
|
||||||
|
* the ForkError wrapper class. This fixes the JUnit XML report showing
|
||||||
|
* `sbt.internal.worker1.ForkTestMain$ForkError` instead of the actual exception type.
|
||||||
|
* See https://github.com/sbt/sbt/issues/1469
|
||||||
|
*/
|
||||||
|
private def formatForkErrorTrace(fe: ForkTestMain.ForkError): String = {
|
||||||
|
val sb = new StringBuilder
|
||||||
|
sb.append(fe.getOriginalName)
|
||||||
|
val msg = fe.getOriginalMessage
|
||||||
|
if (msg != null) sb.append(": ").append(msg)
|
||||||
|
sb.append('\n')
|
||||||
|
for (elem <- fe.getStackTrace)
|
||||||
|
sb.append("\tat ").append(elem).append('\n')
|
||||||
|
val cause = fe.getCause
|
||||||
|
if (cause != null) {
|
||||||
|
cause match {
|
||||||
|
case feCause: ForkTestMain.ForkError =>
|
||||||
|
sb.append("Caused by: ").append(formatForkErrorTrace(feCause))
|
||||||
|
case other =>
|
||||||
|
val stringWriter = new StringWriter()
|
||||||
|
val writer = new PrintWriter(stringWriter)
|
||||||
|
other.printStackTrace(writer)
|
||||||
|
writer.flush()
|
||||||
|
sb.append("Caused by: ").append(stringWriter.toString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.toString
|
||||||
|
}
|
||||||
|
|
||||||
// Here we normalize the name to ensure that it's a nicer filename, rather than
|
// Here we normalize the name to ensure that it's a nicer filename, rather than
|
||||||
// contort the user into not using spaces.
|
// contort the user into not using spaces.
|
||||||
private def normalizeName(s: String) = s.replaceAll("""\s+""", "-")
|
private def normalizeName(s: String) = s.replaceAll("""\s+""", "-")
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import testing.{ Event as TEvent, OptionalThrowable, Status as TStatus, TestSele
|
|||||||
import util.{ AbstractLogger, Level, ControlEvent, LogEvent }
|
import util.{ AbstractLogger, Level, ControlEvent, LogEvent }
|
||||||
import sbt.io.IO
|
import sbt.io.IO
|
||||||
import sbt.protocol.testing.TestResult
|
import sbt.protocol.testing.TestResult
|
||||||
|
import sbt.internal.worker1.ForkTestMain
|
||||||
import verify.BasicTestSuite
|
import verify.BasicTestSuite
|
||||||
|
import scala.xml.XML
|
||||||
|
|
||||||
object JUnitXmlTestsListenerSpec extends BasicTestSuite:
|
object JUnitXmlTestsListenerSpec extends BasicTestSuite:
|
||||||
|
|
||||||
@@ -88,4 +90,54 @@ object JUnitXmlTestsListenerSpec extends BasicTestSuite:
|
|||||||
val xmlFile = new File(tempDir, "TEST-TestSuite.xml")
|
val xmlFile = new File(tempDir, "TEST-TestSuite.xml")
|
||||||
assert(xmlFile.exists(), "XML file should be created even when logger is null")
|
assert(xmlFile.exists(), "XML file should be created even when logger is null")
|
||||||
|
|
||||||
|
test("JUnit XML report should use original exception type for forked test failures"):
|
||||||
|
IO.withTemporaryDirectory: tempDir =>
|
||||||
|
val listener = new JUnitXmlTestsListener(tempDir, false, null)
|
||||||
|
listener.doInit()
|
||||||
|
listener.startGroup("TestSuite")
|
||||||
|
|
||||||
|
// Simulate a forked test failure: the original NullPointerException gets
|
||||||
|
// wrapped in ForkError during serialization across the forked JVM boundary.
|
||||||
|
val originalException = new NullPointerException("something was null")
|
||||||
|
originalException.setStackTrace(
|
||||||
|
Array(new StackTraceElement("com.example.MyTest", "testFoo", "MyTest.java", 42))
|
||||||
|
)
|
||||||
|
val forkError = new ForkTestMain.ForkError(originalException)
|
||||||
|
|
||||||
|
val testEvent = new TEvent:
|
||||||
|
def fullyQualifiedName = "TestSuite.testFoo"
|
||||||
|
def duration() = 100L
|
||||||
|
def status = TStatus.Failure
|
||||||
|
def fingerprint = null
|
||||||
|
def selector = new TestSelector("testFoo")
|
||||||
|
def throwable = new OptionalThrowable(forkError)
|
||||||
|
|
||||||
|
listener.testEvent(sbt.TestEvent(Seq(testEvent)))
|
||||||
|
listener.endGroup("TestSuite", TestResult.Failed)
|
||||||
|
|
||||||
|
val xmlFile = new File(tempDir, "TEST-TestSuite.xml")
|
||||||
|
assert(xmlFile.exists(), "XML file should be created")
|
||||||
|
val xml = XML.loadFile(xmlFile)
|
||||||
|
val failureNodes = xml \\ "failure"
|
||||||
|
assert(failureNodes.nonEmpty, "Should have a failure element")
|
||||||
|
val failureType = (failureNodes.head \ "@type").text
|
||||||
|
val failureMessage = (failureNodes.head \ "@message").text
|
||||||
|
assert(
|
||||||
|
failureType == "java.lang.NullPointerException",
|
||||||
|
s"Expected type 'java.lang.NullPointerException' but got '$failureType'"
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
failureMessage == "something was null",
|
||||||
|
s"Expected message 'something was null' but got '$failureMessage'"
|
||||||
|
)
|
||||||
|
val traceText = failureNodes.head.text
|
||||||
|
assert(
|
||||||
|
traceText.contains("java.lang.NullPointerException"),
|
||||||
|
s"Stacktrace should contain original exception name, got: $traceText"
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
!traceText.contains("ForkError"),
|
||||||
|
s"Stacktrace should not contain ForkError, got: $traceText"
|
||||||
|
)
|
||||||
|
|
||||||
end JUnitXmlTestsListenerSpec
|
end JUnitXmlTestsListenerSpec
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class ForkTestMain {
|
|||||||
private final String originalName;
|
private final String originalName;
|
||||||
private ForkError cause1;
|
private ForkError cause1;
|
||||||
|
|
||||||
ForkError(final Throwable t) {
|
public ForkError(final Throwable t) {
|
||||||
originalMessage = t.getMessage();
|
originalMessage = t.getMessage();
|
||||||
originalName = t.getClass().getName();
|
originalName = t.getClass().getName();
|
||||||
setStackTrace(t.getStackTrace());
|
setStackTrace(t.getStackTrace());
|
||||||
@@ -178,6 +178,16 @@ public class ForkTestMain {
|
|||||||
return originalName + ": " + originalMessage;
|
return originalName + ": " + originalMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the fully qualified class name of the original exception. */
|
||||||
|
public String getOriginalName() {
|
||||||
|
return originalName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the original exception message (without the class name prefix). */
|
||||||
|
public String getOriginalMessage() {
|
||||||
|
return originalMessage;
|
||||||
|
}
|
||||||
|
|
||||||
public Exception getCause() {
|
public Exception getCause() {
|
||||||
return cause1;
|
return cause1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user