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

Add method-level test filtering support to JUnitRunner #34

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
45 changes: 37 additions & 8 deletions src/main/scala/org/scalatestplus/junit/JUnitRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
*/
package org.scalatestplus.junit

import org.scalatest._
import org.scalatest.{Args, ConfigMap, DynaTags, Stopper, Suite, Tracker, Filter}
import org.junit.runner.notification.RunNotifier
import org.junit.runner.notification.Failure
import org.junit.runner.Description
import org.junit.runner.manipulation.{Filter => TestFilter, Filterable, NoTestsRemainException}

import java.util.concurrent.ConcurrentHashMap
import scala.collection.JavaConverters._
import scala.collection.mutable

/*
I think that Stopper really should be a no-op, like it is, because the user has
no way to stop it. This is wierd, because it will call nested suites. So the tests
Expand Down Expand Up @@ -71,6 +75,8 @@ final class JUnitRunner(suiteClass: java.lang.Class[_ <: Suite]) extends org.jun
*/
val getDescription = createDescription(suiteToRun)

private val excludedTests: mutable.Set[String] = ConcurrentHashMap.newKeySet[String]().asScala

private def createDescription(suite: Suite): Description = {
val description = Description.createSuiteDescription(suite.getClass)
// If we don't add the testNames and nested suites in, we get
Expand All @@ -96,12 +102,29 @@ final class JUnitRunner(suiteClass: java.lang.Class[_ <: Suite]) extends org.jun
*/
def run(notifier: RunNotifier): Unit = {
try {
val includedTests: Set[String] = suiteToRun.testNames.diff(excludedTests)
val testTags: Map[String, Map[String, Set[String]]] = Map(
suiteToRun.suiteId ->
includedTests.map(test => test -> Set("org.scalatest.Selected")).toMap
)
val filter = Filter(
tagsToInclude = Some(Set("org.scalatest.Selected")),
dynaTags = DynaTags(suiteTags = Map.empty, testTags = testTags)
)
// TODO: What should this Tracker be?
suiteToRun.run(None, Args(new RunNotifierReporter(notifier),
Stopper.default, Filter(), ConfigMap.empty, None,
new Tracker))
}
catch {
suiteToRun.run(
None,
Args(
new RunNotifierReporter(notifier),
Stopper.default,
filter,
ConfigMap.empty,
None,
new Tracker,
Set.empty
)
)
} catch {
case e: Exception =>
notifier.fireTestFailure(new Failure(getDescription, e))
}
Expand All @@ -113,10 +136,16 @@ final class JUnitRunner(suiteClass: java.lang.Class[_ <: Suite]) extends org.jun
*
* @return the expected number of tests that will run when this suite is run
*/
override def testCount() = suiteToRun.expectedTestCount(Filter())
override def testCount(): Int = suiteToRun.expectedTestCount(Filter())

@throws(classOf[NoTestsRemainException])
override def filter(filter: TestFilter): Unit = {
if (!filter.shouldRun(getDescription)) throw new NoTestsRemainException
val children = getDescription.getChildren.asScala
excludedTests ++= children
.filterNot(filter.shouldRun)
.map(_.getMethodName)

if (children.isEmpty) throw new NoTestsRemainException
}

}
Expand Down
44 changes: 44 additions & 0 deletions src/test/scala/org/scalatestplus/junit/JUnitRunnerSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ package org.scalatestplus.junit {
assert(1 === 1)
}
}

class MethodExclusionFilter(methodNamePattern: String) extends TestFilter {
override def shouldRun(description: Description): Boolean = {
!description.getMethodName().contains(methodNamePattern)
}

override def describe(): String = s"Excludes tests with method names containing: '$methodNamePattern'"
}

@RunWith(classOf[JUnitRunner])
class MethodFilterTargetSuite extends funsuite.AnyFunSuite {

test("JUnit ran this OK!") {
assert(1 === 1)
}

test("should pass filter and execute") {
assert(2 === 2)
}

test("should be filtered out") {
assert(3 === 5)
}
}
}

import org.junit.runner.Description
Expand All @@ -112,6 +136,7 @@ package org.scalatestplus.junit {
import org.scalatestplus.junit.helpers.EasySuite
import org.scalatestplus.junit.helpers.KerblooeySuite
import org.scalatestplus.junit.helpers.{FilteredInSuite, FilteredOutSuite, NameFilter}
import org.scalatestplus.junit.helpers.{MethodExclusionFilter, MethodFilterTargetSuite}
import scala.util.Try

class JUnitRunnerSuite extends funsuite.AnyFunSuite {
Expand Down Expand Up @@ -190,5 +215,24 @@ package org.scalatestplus.junit {
assert(runNotifier.ran.head.getDisplayName ===
"JUnit ran this OK!(org.scalatestplus.junit.helpers.FilteredInSuite)")
}

test("Should execute only methods that don't match the filter pattern") {
class ExecutedTestsNotifier extends RunNotifier {
var executedTests: List[Description] = Nil

override def fireTestFinished(description: Description): Unit = {
executedTests = description :: executedTests
}
}
val runNotifier = new ExecutedTestsNotifier()
val runner = new JUnitRunner(classOf[MethodFilterTargetSuite])

val excludeMethodFilter = new MethodExclusionFilter("should be filtered out")

runner.filter(excludeMethodFilter)
runner.run(runNotifier)

assert(runNotifier.executedTests.size === 2) // Verifies that only 2 tests ran (one was filtered out)
}
}
}