Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specs2 filtering of JUnit descriptions #766

Merged
merged 3 commits into from
Jun 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions src/java/io/bazel/rulesscala/specs2/Specs2RunnerBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand All @@ -75,20 +86,27 @@ 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.
*
* 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)
Expand All @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/java/io/bazel/rulesscala/specs2/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
2 changes: 1 addition & 1 deletion test_expect_failure/scala_junit_test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
Original file line number Diff line number Diff line change
@@ -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
}
}
35 changes: 35 additions & 0 deletions test_rules_scala.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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
Expand Down