diff --git a/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala b/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala index d783956ee..c26c63a13 100644 --- a/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala +++ b/src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala @@ -3,21 +3,24 @@ package io.bazel.rulesscala.specs2 import java.util import java.util.regex.Pattern -import io.bazel.rulesscala.test_discovery._ import io.bazel.rulesscala.test_discovery.FilteredRunnerBuilder.FilteringRunnerBuilder +import io.bazel.rulesscala.test_discovery._ import org.junit.runner.notification.RunNotifier import org.junit.runner.{Description, RunWith, Runner} import org.junit.runners.Suite import org.junit.runners.model.RunnerBuilder import org.specs2.concurrent.ExecutionEnv import org.specs2.control.Action +import org.specs2.data.Trees._ +import org.specs2.fp.TreeLoc import org.specs2.main.{Arguments, CommandLine, Select} -import org.specs2.specification.core.Env +import org.specs2.specification.core.{Env, Fragment, SpecStructure} import org.specs2.specification.process.Stats -import scala.language.reflectiveCalls import scala.collection.JavaConverters._ +import scala.language.reflectiveCalls import scala.util.Try +import scala.util.control.NonFatal @RunWith(classOf[Specs2PrefixSuffixTestDiscoveringSuite]) class Specs2DiscoveredTestSuite @@ -54,12 +57,20 @@ class FilteredSpecs2ClassRunner(testClass: Class[_], testFilter: Pattern) extends org.specs2.runner.JUnitRunner(testClass) { override def getDescription(env: Env): Description = { - val root = super.getDescription(env) - val filtered = flatten(root).filter(matchingFilter) + try createFilteredDescription(specStructure, env.specs2ExecutionEnv) + catch { case NonFatal(t) => env.shutdown; throw t; } + } + + private def createFilteredDescription(specStructure: SpecStructure, ee: ExecutionEnv): Description = { + val descTree = createDescriptionTree(ee).map(_._2) + descTree.toTree.bottomUp { + (description: Description, children: Stream[Description]) => + children.filter(matchingFilter).foreach { + child => description.addChild(child) + } + description + }.rootLabel - val flattenedRoot = root.childlessCopy() - filtered.foreach(flattenedRoot.addChild) - flattenedRoot } def matchesFilter: Boolean = { @@ -75,6 +86,13 @@ class FilteredSpecs2ClassRunner(testClass: Class[_], testFilter: Pattern) else sanitized } + private def createDescriptionTree(implicit ee: ExecutionEnv): TreeLoc[(Fragment, Description)] = + Try(allDescriptions[specs2_v4].createDescriptionTree(specStructure)(ee)) + .getOrElse(allDescriptions[specs2_v3].createDescriptionTree(specStructure)) + + private def allFragmentDescriptions(implicit ee: ExecutionEnv): Map[Fragment, Description] = + createDescriptionTree(ee).toTree.flattenLeft.toMap + /** * Retrieves an original (un-sanitized) text of an example fragment, * used later as a regex string for specs2 matching. @@ -82,13 +100,13 @@ class FilteredSpecs2ClassRunner(testClass: Class[_], testFilter: Pattern) * This is done by matching the actual (sanitized) string with the sanitized version * of the original example text. */ - private def specs2Description(desc: String)(implicit ee: ExecutionEnv): String = - Try { allDescriptions[specs2_v4].fragmentDescriptions(specStructure)(ee) } - .getOrElse(allDescriptions[specs2_v3].fragmentDescriptions(specStructure)) + private def specs2Description(desc: String)(implicit ee: ExecutionEnv): String = { + allFragmentDescriptions .keys .map(fragment => fragment.description.show) .find(sanitize(_) == desc) .getOrElse(desc) + } private def toDisplayName(description: Description)(implicit ee: ExecutionEnv): Option[String] = for { name <- Option(description.getMethodName) @@ -115,20 +133,20 @@ class FilteredSpecs2ClassRunner(testClass: Class[_], testFilter: Pattern) * * This function returns a flat list of the descriptions and their children, starting with the root. */ - def flatten(root: Description): List[Description] = { - def flatten0(desc: Description, xs: List[Description]): List[Description] = - desc.childlessCopy() :: desc.getChildren.asScala.foldLeft(xs)((acc, x) => flatten0(x, acc)) - - flatten0(root, Nil) + def flattenChildren(root: Description): List[Description] = { + root.getChildren.asScala.toList.flatMap(d => d :: flattenChildren(d)) } private def matchingFilter(desc: Description): Boolean = { - val testCase = desc.getClassName + "#" + desc.getMethodName - testFilter.toString.r.findAllIn(testCase).nonEmpty + if (desc.isSuite) true + else { + val testCase = desc.getClassName + "#" + Option(desc.getMethodName).mkString + testFilter.toString.r.findFirstIn(testCase).nonEmpty + } } private def specs2Examples(implicit ee: ExecutionEnv): List[String] = - flatten(getDescription).flatMap(toDisplayName(_)) + flattenChildren(getDescription).flatMap(toDisplayName(_)) override def runWithEnv(n: RunNotifier, env: Env): Action[Stats] = { implicit val ee = env.executionEnv diff --git a/src/java/io/bazel/rulesscala/specs2/package.scala b/src/java/io/bazel/rulesscala/specs2/package.scala index 341cb1649..030842a3b 100644 --- a/src/java/io/bazel/rulesscala/specs2/package.scala +++ b/src/java/io/bazel/rulesscala/specs2/package.scala @@ -2,16 +2,19 @@ package io.bazel.rulesscala import org.junit.runner.Description import org.specs2.concurrent.ExecutionEnv +import org.specs2.fp.TreeLoc import org.specs2.reporter.JUnitDescriptions import org.specs2.specification.core.{Fragment, SpecStructure} package object specs2 { type specs2_v4 = { - def fragmentDescriptions(spec: SpecStructure)(ee: ExecutionEnv): Map[Fragment, Description] + //noinspection ScalaUnusedSymbol + def createDescriptionTree(spec: SpecStructure)(ee: ExecutionEnv): TreeLoc[(Fragment, Description)] } type specs2_v3 = { - def fragmentDescriptions(spec: SpecStructure): Map[Fragment, Description] + //noinspection ScalaUnusedSymbol + def createDescriptionTree(spec: SpecStructure): TreeLoc[(Fragment, Description)] } - def allDescriptions[T] = JUnitDescriptions.asInstanceOf[T] -} + def allDescriptions[T]: T = JUnitDescriptions.asInstanceOf[T] +} \ No newline at end of file diff --git a/test_expect_failure/scala_junit_test/BUILD b/test_expect_failure/scala_junit_test/BUILD index 2f91acad5..e6fb14702 100644 --- a/test_expect_failure/scala_junit_test/BUILD +++ b/test_expect_failure/scala_junit_test/BUILD @@ -23,6 +23,6 @@ scala_junit_test( scala_specs2_junit_test( name = "specs2_failing_test", size = "small", - srcs = ["specs2/FailingTest.scala"], + srcs = ["specs2/FailingTest.scala", "specs2/SuiteWithOneFailingTest.scala"], suffixes = ["Test"], ) diff --git a/test_expect_failure/scala_junit_test/specs2/SuiteWithOneFailingTest.scala b/test_expect_failure/scala_junit_test/specs2/SuiteWithOneFailingTest.scala new file mode 100644 index 000000000..bef083903 --- /dev/null +++ b/test_expect_failure/scala_junit_test/specs2/SuiteWithOneFailingTest.scala @@ -0,0 +1,15 @@ +package scalarules.test.junit.specs2 + +import org.specs2.mutable.SpecWithJUnit + +class SuiteWithOneFailingTest extends SpecWithJUnit { + + "specs2 tests" should { + "succeed" >> success + "fail" >> failure("boom") + } + + "some other suite" should { + "do stuff" >> success + } +} diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 038d5d220..b68d92083 100755 --- a/test_rules_scala.sh +++ b/test_rules_scala.sh @@ -528,6 +528,39 @@ scala_specs2_only_filtered_test_shows_in_the_xml(){ test -e } +scala_specs2_all_tests_show_in_the_xml(){ + bazel test \ + --nocache_test_results \ + --test_output=streamed \ + '--test_filter=scalarules.test.junit.specs2.JunitSpecs2Test#' \ + test:Specs2Tests + matches=$(grep -c -e "testcase name='specs2 tests should::run smoothly in bazel'" -e "testcase name='specs2 tests should::not run smoothly in bazel'" ./bazel-testlogs/test/Specs2Tests/test.xml) + if [ $matches -eq 2 ]; then + return 0 + else + echo "Expecting two results, found a different number ($matches). Please check 'bazel-testlogs/test/Specs2Tests/test.xml'" + return 1 + fi + test -e +} + +scala_specs2_only_failed_test_shows_in_the_xml(){ + set +e + bazel test \ + --nocache_test_results \ + --test_output=streamed \ + '--test_filter=scalarules.test.junit.specs2.SuiteWithOneFailingTest#specs2 tests should::fail$' \ + test_expect_failure/scala_junit_test:specs2_failing_test + echo "got results" + matches=$(grep -c -e "testcase name='specs2 tests should::fail'" -e "testcase name='specs2 tests should::succeed'" ./bazel-testlogs/test_expect_failure/scala_junit_test/specs2_failing_test/test.xml) + if [ $matches -eq 1 ]; then + return 0 + else + echo "Expecting only one result, found more than one. Please check './bazel-testlogs/test_expect_failure/scala_junit_test/specs2_failing_test/test.xml'" + return 1 + fi +} + scala_specs2_junit_test_test_filter_exact_match(){ local output=$(bazel test \ --nocache_test_results \ @@ -1002,7 +1035,9 @@ $runner scala_specs2_junit_test_test_filter_exact_match_escaped_and_sanitized $runner scala_specs2_junit_test_test_filter_match_multiple_methods $runner scala_specs2_exception_in_initializer_without_filter $runner scala_specs2_exception_in_initializer_terminates_without_timeout +$runner scala_specs2_all_tests_show_in_the_xml $runner scala_specs2_only_filtered_test_shows_in_the_xml +$runner scala_specs2_only_failed_test_shows_in_the_xml $runner scalac_jvm_flags_are_configured $runner javac_jvm_flags_are_configured $runner javac_jvm_flags_via_javacopts_are_configured