Skip to content

Commit

Permalink
Filter examples based on TestSelector (#1217)
Browse files Browse the repository at this point in the history
Previously, the Specs2 runner would ignore the selectors that are passed
via a `TaskDef`.

With this patch, when a `TaskDef`'s selectors contains only
`TestSelector`s, then only the examples whose name match the input
selectors will be executed.
  • Loading branch information
Duhemm committed Jan 22, 2024
1 parent 34d484c commit 07e0f2e
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 2 deletions.
96 changes: 96 additions & 0 deletions core/jvm/src/test/scala/org/specs2/runner/SbtSelectorSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.specs2
package runner

import sbt.testing.{Event, EventHandler, Logger, Selector, Status, SuiteSelector, TaskDef, TestSelector}

import scala.collection.mutable.ArrayBuffer

class SbtSelectorSpec extends Specification {
def is = s2"""

An sbt runner executes the examples passed in the `TestSelector`s
when there is a single `TestSelector` $singleTestSelector
when there are 2 `TestSelector`s $twoTestSelectors
run everything if there are other selectors $otherSelectors
run nothing if there are no matches $noMatches
regexes in test selectors are escaped $regexesAreEscaped
"""

private def singleTestSelector = {
val wholeSuiteEvents = runWithSelectors(new SuiteSelector :: Nil)
wholeSuiteEvents.length === 3
val failEvents = wholeSuiteEvents.filter(_.status == Status.Failure)
val singleExampleEvents = runWithSelectors(failEvents.map(_.selector()))

(failEvents must haveSize(1)) and
(testName(failEvents.head) === "has a failing test") and
(singleExampleEvents must haveSize(1)) and
(singleExampleEvents.head.status() === Status.Failure)
}

private def twoTestSelectors = {
val events = runWithSelectors(
List(new TestSelector("has a successful test"), new TestSelector("has a failing test"))
)
(events must haveSize(2)) and
(events.map(testName) must contain("has a successful test", "has a failing test"))
}

private def otherSelectors = {
val events = runWithSelectors(List(new SuiteSelector, new TestSelector("hello")))
(events must haveSize(3)) and
(events.map(testName) must contain("has a successful test", "has a failing test", ".*"))
}

private def noMatches = {
val events = runWithSelectors(List(new TestSelector("won't match anything")))
events must beEmpty
}

private def regexesAreEscaped = {
val events = runWithSelectors(new TestSelector(".*") :: Nil)
(events must haveSize(1)) and
(events.head.status() === Status.Success) and
(testName(events.head) === ".*")
}

private def runWithSelectors(selectors: List[Selector]): List[Event] = {
val loggers = Array(NoLogger: Logger)
val events = ArrayBuffer.empty[Event]
val handler: EventHandler = (e: Event) => events.append(e)
val framework = new Specs2Framework()
val runner = framework.runner(Array.empty, Array.empty, getClass.getClassLoader)
val fqcn = classOf[HelperSpec].getName

val taskDef = new TaskDef(fqcn, Fingerprints.fp1m, true, selectors.toArray)
val tasks = runner.tasks(Array(taskDef))
tasks.foreach(_.execute(handler, loggers))

events.toList
}

private def testName(event: Event): String = {
event.selector() match {
case ts: TestSelector => ts.testName()
}
}

}

private class HelperSpec extends Specification {
def is = s2"""
The helper spec
has a successful test $ok
has a failing test $ko
.* $ok
"""
}

private object NoLogger extends Logger {
override def ansiCodesSupported(): Boolean = false
override def error(msg: String): Unit = ()
override def warn(msg: String): Unit = ()
override def info(msg: String): Unit = ()
override def debug(msg: String): Unit = ()
override def trace(t: Throwable): Unit = ()
}
15 changes: 13 additions & 2 deletions core/shared/src/main/scala/org/specs2/runner/SbtRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import org.specs2.concurrent.awaitResult
import org.specs2.control.ExecuteActions._
import org.specs2.data.NamedTag

import java.util.regex.Pattern

import scala.concurrent.duration.Duration
import scala.concurrent.{Future}

Expand All @@ -35,8 +37,17 @@ abstract class BaseSbtRunner(args: Array[String], remoteArgs: Array[String], loa
taskDefs.toList.map(newTask).toArray

/** create a new test task */
def newTask(aTaskDef: TaskDef): Task =
SbtTask(aTaskDef, env, loader)
def newTask(aTaskDef: TaskDef): Task = {
val fullEnv =
if (env.arguments.select._ex.isDefined || aTaskDef.selectors().exists(!_.isInstanceOf[TestSelector])) env
else {
val names = aTaskDef.selectors().toList.collect { case ts: TestSelector => Pattern.quote(ts.testName()) }
val select = env.arguments.select.copy(_ex = Some(names.mkString("|")))
env.setArguments(env.arguments.copy(select = select))
}

SbtTask(aTaskDef, fullEnv, loader)
}

def done() = {
env.shutdown
Expand Down

0 comments on commit 07e0f2e

Please sign in to comment.