From 108611ca253cd7b199e4503b4a9c8e8c03bbeb92 Mon Sep 17 00:00:00 2001 From: Margus Sipria Date: Mon, 30 Sep 2019 17:47:15 +0300 Subject: [PATCH] Fix for JUnitXmlTestsListener that is removing part of test name when it contains dot (#5139) Fix for JUnitXmlTestsListener removing part of test when it contains dot Fixes https://github.com/sbt/sbt/issues/5114 Fixes https://github.com/sbt/sbt/issues/2949 --- .../sbt-test/tests/junit-xml-report/build.sbt | 34 +++++++++++++-- .../src/test/scala/scala-tests.scala | 5 +++ .../scala/sbt/JUnitXmlTestsListener.scala | 42 ++++++++++++------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/sbt/src/sbt-test/tests/junit-xml-report/build.sbt b/sbt/src/sbt-test/tests/junit-xml-report/build.sbt index 661acf38d..1d864c81b 100644 --- a/sbt/src/sbt-test/tests/junit-xml-report/build.sbt +++ b/sbt/src/sbt-test/tests/junit-xml-report/build.sbt @@ -26,7 +26,16 @@ lazy val root = (project in file(".")). if( oneSecondReport.label != "testsuite" ) sys.error("Report should have a root element.") // somehow the 'success' event doesn't go through... TODO investigate // if( (oneSecondReport \ "@time").text.toFloat < 1f ) sys.error("expected test to take at least 1 sec") + if( (oneSecondReport \ "@tests").text != "1" ) sys.error("expected 1 tests") + if( (oneSecondReport \ "@failures").text != "0" ) sys.error("expected 0 failures") if( (oneSecondReport \ "@name").text != "a.pkg.OneSecondTest" ) sys.error("wrong fixture name: " + (oneSecondReport \ "@name").text) + oneSecondReport foreach { testsuite => + val className = testsuite \ "testcase" \@ "classname" + if( className != "a.pkg.OneSecondTest" ) sys.error(s"wrong class name: $className") + + val actualTestName = testsuite \ "testcase" \@ "name" + if( actualTestName != "oneSecond") sys.error(s"wrong test names: $actualTestName") + } // TODO more checks val failingReport = XML.loadFile(failingReportFile) @@ -37,17 +46,36 @@ lazy val root = (project in file(".")). val scalaTestFlatReport = XML.loadFile(flatSuiteReportFile) if( scalaTestFlatReport.label != "testsuite" ) sys.error("Report should have a root element.") - if( (scalaTestFlatReport \ "@tests").text != "2" ) sys.error("expected 2 tests") + if( (scalaTestFlatReport \ "@tests").text != "3" ) sys.error("expected 3 tests") if( (scalaTestFlatReport \ "@failures").text != "1" ) sys.error("expected 1 failures") if( (scalaTestFlatReport \ "@name").text != "my.scalatest.MyFlatSuite" ) sys.error("wrong fixture name: " + (scalaTestFlatReport \ "@name").text) + scalaTestFlatReport foreach { testsuite => + val classNames = (testsuite \ "testcase").map(_ \@ "classname") + if( classNames.length != 3 ) sys.error(s"expected 3 classname's, actual result: " + classNames.length) + if( classNames.toSet != Set("my.scalatest.MyFlatSuite") ) sys.error(s"wrong class names: ${classNames.mkString(", ")}") + + val expectedTestNames = Set( + "Passing test should pass", + "Passing test should also pass with file.extension", + "Failing test should fail" + ) + val actualTestName = (testsuite \ "testcase").map(_ \@ "name") + if( actualTestName.toSet != expectedTestNames) sys.error(s"wrong test names: ${actualTestName.mkString(", ")}") + } val nestedSuitesReport = XML.loadFile(nestedSuitesReportFile) if( nestedSuitesReport.label != "testsuite" ) sys.error("Report should have a root element.") if( (nestedSuitesReport \ "@tests").text != "2" ) sys.error("expected 2 tests") if( (nestedSuitesReport \ "@failures").text != "1" ) sys.error("expected 1 failures") if( (nestedSuitesReport \ "@name").text != "my.scalatest.MyNestedSuites" ) sys.error("wrong fixture name: " + (nestedSuitesReport \ "@name").text) - val actualTestName = (nestedSuitesReport \ "testcase").map(t => (t \ "@name").text) - if( actualTestName.toSet != Set("MyInnerSuite.Inner passing test should pass", "MyInnerSuite.Inner failing test should fail")) sys.error(s"wrong test names: ${actualTestName.mkString(", ")}") + nestedSuitesReport foreach { testsuite => + val classNames = (testsuite \ "testcase").map(_ \@ "classname") + if( classNames.length != 2 ) sys.error(s"expected 2 classname's, actual result: " + classNames.length) + if( classNames.toSet != Set("my.scalatest.MyInnerSuite") ) sys.error(s"wrong class names: ${classNames.mkString(", ")}") + + val actualTestName = (testsuite \ "testcase").map(_ \@ "name") + if( actualTestName.toSet != Set("Inner passing test should pass", "Inner failing test should fail")) sys.error(s"wrong test names: ${actualTestName.mkString(", ")}") + } // TODO check console output is in the report }, diff --git a/sbt/src/sbt-test/tests/junit-xml-report/src/test/scala/scala-tests.scala b/sbt/src/sbt-test/tests/junit-xml-report/src/test/scala/scala-tests.scala index affe50e3f..8e9e707e1 100644 --- a/sbt/src/sbt-test/tests/junit-xml-report/src/test/scala/scala-tests.scala +++ b/sbt/src/sbt-test/tests/junit-xml-report/src/test/scala/scala-tests.scala @@ -6,11 +6,16 @@ package my.scalatest { } + it should "also pass with file.extension" in { + + } + "Failing test" should "fail" in { sys.error("wah wah") } } + @DoNotDiscover class MyInnerSuite(arg: String) extends FlatSpec { "Inner passing test" should "pass" in { diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index e95d87923..cb8db7338 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -38,7 +38,7 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests def this(outputDir: String) = this(outputDir, null) /**Current hostname so we know which machine executed the tests*/ - val hostname = { + val hostname: String = { val start = System.nanoTime val name = try InetAddress.getLocalHost.getHostName catch { @@ -59,7 +59,7 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests val targetDir = new File(outputDir + "/test-reports/") /**all system properties as XML*/ - val properties = + val properties: Elem = { // create a clone, defending against [[ConcurrentModificationException]] @@ -84,7 +84,7 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests val events: ListBuffer[TEvent] = new ListBuffer() /**Adds one test result to this suite.*/ - def addEvent(e: TEvent) = events += e + def addEvent(e: TEvent): ListBuffer[TEvent] = events += e /** Returns the number of tests of each state for the specified. */ def count(status: TStatus) = events.count(_.status == status) @@ -103,6 +103,9 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests TStatus.Pending ) + // for sbt/junit-interface version 0.11 (in future versions this should be done there) + val classnameRegex = s"^($name|${name.split('.').last})\\.?".r + val result = selector.testName.split('.').last - case nested: NestedTestSelector => - nested.suiteId().split('.').last + "." + nested.testName() - case other => s"(It is not a test it is a ${other.getClass.getCanonicalName})" + case nested: NestedTestSelector => nested.suiteId() + case _ => name + } + } name={ + e.selector match { + case selector: TestSelector => + val matchEnd = + classnameRegex.findFirstMatchIn(selector.testName).map(_.end).getOrElse(0) + selector.testName.substring(matchEnd) + case nested: NestedTestSelector => nested.testName() + case other => s"(It is not a test it is a ${other.getClass.getCanonicalName})" } } time={(e.duration() / 1000.0).toString}> { @@ -161,11 +171,11 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests override def initialValue(): Option[TestSuite] = None } - private def withTestSuite[T](f: TestSuite => T) = + private def withTestSuite[T](f: TestSuite => T): T = testSuite.get().map(f).getOrElse(sys.error("no test suite")) /**Creates the output Dir*/ - override def doInit() = { + override def doInit(): Unit = { val _ = targetDir.mkdirs() } @@ -203,9 +213,9 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests * * */ - override def endGroup(name: String, t: Throwable) = { + override def endGroup(name: String, t: Throwable): Unit = { // create our own event to record the error - val event = new TEvent { + val event: TEvent = new TEvent { def fullyQualifiedName = name //def description = //"Throwable escaped the test run of '%s'".format(name) @@ -223,7 +233,7 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests * Ends the current suite, wraps up the result and writes it to an XML file * in the output folder that is named after the suite. */ - override def endGroup(name: String, result: TestResult) = { + override def endGroup(name: String, result: TestResult): Unit = { writeSuite() } @@ -237,7 +247,7 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests private[this] def formatISO8601DateTime(d: LocalDateTime): String = d.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - private def writeSuite() = { + private def writeSuite(): Unit = { val legacyFile = new File(targetDir, s"${normalizeName(withTestSuite(_.name))}.xml").getAbsolutePath val file = @@ -245,8 +255,8 @@ class JUnitXmlTestsListener(val outputDir: String, logger: Logger) extends Tests // TODO would be nice to have a logger and log this with level debug // System.err.println("Writing JUnit XML test report: " + file) val testSuiteResult = withTestSuite(_.stop()) - XML.save(legacyFile, testSuiteResult, "UTF-8", true, null) - XML.save(file, testSuiteResult, "UTF-8", true, null) + XML.save(legacyFile, testSuiteResult, "UTF-8", xmlDecl = true, null) + XML.save(file, testSuiteResult, "UTF-8", xmlDecl = true, null) testSuite.remove() }