Skip to content

Commit

Permalink
Filter examples based on TestSelector (#1216)
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 authored Jan 22, 2024
1 parent 34584de commit d0f255d
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 1 deletion.
84 changes: 84 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,84 @@
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 = ()
12 changes: 11 additions & 1 deletion core/shared/src/main/scala/org/specs2/runner/SbtRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import org.specs2.fp.*, syntax.*
import org.specs2.concurrent.ExecutionEnv
import org.specs2.data.NamedTag
import scala.util.*

import java.util.regex.Pattern

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future, ExecutionContext}

Expand All @@ -38,7 +41,14 @@ abstract class BaseSbtRunner(args: Array[String], remoteArgs: Array[String], loa

/** create a new test task */
def newTask(aTaskDef: TaskDef): Task =
SbtTask(aTaskDef, env, loader, this)
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, this)

def done =
val result = env.shutdown()
Expand Down

0 comments on commit d0f255d

Please sign in to comment.