Skip to content

Commit

Permalink
Implement Filterable
Browse files Browse the repository at this point in the history
  • Loading branch information
Olafur Pall Geirsson committed Jan 6, 2020
1 parent 04498f1 commit 485ac14
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 49 deletions.
6 changes: 4 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ inThisBuild(
List(
organization := "com.geirsson",
homepage := Some(url("https://github.com/olafurpg/funsuite")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
licenses := List(
"Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")
),
developers := List(
Developer(
"olafurpg",
Expand Down Expand Up @@ -39,7 +41,7 @@ lazy val funsuite = project
.settings(
libraryDependencies ++= List(
"junit" % "junit" % "4.13",
"com.geirsson" % "junit-interface" % "0.11.3",
"com.geirsson" % "junit-interface" % "0.11.4-SNAPSHOT",
"com.lihaoyi" %% "sourcecode" % "0.1.9",
"com.lihaoyi" %% "fansi" % fansiVersion.value,
"com.googlecode.java-diff-utils" % "diffutils" % "1.3.0",
Expand Down
8 changes: 8 additions & 0 deletions funsuite/src/main/java/funsuite/Ignore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package funsuite;

import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;

@Retention(RetentionPolicy.RUNTIME)
@interface Ignore {
}
6 changes: 5 additions & 1 deletion funsuite/src/main/scala/funsuite/FunSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ class FunSuite extends Assertions with TestOptionsConversions {

private[funsuite] val tests = mutable.ArrayBuffer.empty[Test]

def funsuiteName: String = this.getClass().getCanonicalName()
def funsuiteTests: Seq[Test] = {
val onlyTests = tests.filter(_.tags(Only))
if (onlyTests.nonEmpty) onlyTests.toSeq
else tests.toSeq
}

def isCI: Boolean = "true" == System.getenv("CI")
def isFlakyFailureOk: Boolean = "true" == System.getenv("FUNSUITE_FLAKY_OK")
Expand Down
94 changes: 63 additions & 31 deletions funsuite/src/main/scala/funsuite/FunSuiteRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,34 @@ import fansi.ErrorMode.Throw
import funsuite.internal.StackMarker
import org.junit.AssumptionViolatedException
import fansi.Color
import org.junit.runner.manipulation.Filterable
import org.junit.runner.manipulation.Filter
import org.junit.runner.manipulation.NoTestsRemainException

final class FunSuiteRunner(cls: Class[_ <: FunSuite])
extends org.junit.runner.Runner {
extends org.junit.runner.Runner
with Filterable {
require(
hasEligibleConstructor(),
s"Class '${cls.getCanonicalName()}' is missing a public empty argument constructor"
)
lazy val suite = cls.newInstance()
lazy val testsToRun = suite.funsuiteTests.toBuffer
private val suiteDescription = Description.createSuiteDescription(cls)
@volatile private var filter: Filter = Filter.ALL

def filter(filter: Filter): Unit = {
val oldTests = testsToRun
val newTests = oldTests.filter { test =>
filter.shouldRun(createTestDescription(test))
}
if (newTests.isEmpty) {
throw new NoTestsRemainException
} else if (newTests.length < oldTests.length) {
testsToRun.clear()
testsToRun.addAll(newTests)
}
}

def createTestDescription(test: Test): Description = {
Description.createTestDescription(cls, test.name, test.location)
Expand All @@ -41,6 +60,10 @@ final class FunSuiteRunner(cls: Class[_ <: FunSuite])
description
}

def isIgnored(): Boolean = {
cls.getAnnotationsByType(classOf[Ignore]).nonEmpty
}

override def run(notifier: RunNotifier): Unit = {
notifier.fireTestSuiteStarted(suiteDescription)
try {
Expand Down Expand Up @@ -102,40 +125,49 @@ final class FunSuiteRunner(cls: Class[_ <: FunSuite])
)
}

def runTest(notifier: RunNotifier, test: Test): Unit = {
val description = createTestDescription(test)
if (!filter.shouldRun(description)) return
var isContinue = runBeforeEach(notifier, test)
if (isContinue) {
notifier.fireTestStarted(description)
try {
StackMarker.dropOutside(test.body()) match {
case f: FlakyFailure =>
notifier.fireTestAssumptionFailed(new Failure(description, f))
case Ignore =>
notifier.fireTestIgnored(description)
case _ =>
}
} catch {
case ex: AssumptionViolatedException =>
StackMarker.trimStackTrace(ex)
case NonFatal(ex) =>
StackMarker.trimStackTrace(ex)
val failure = new Failure(description, ex)
ex match {
case _: AssumptionViolatedException =>
notifier.fireTestAssumptionFailed(failure)
case _ =>
notifier.fireTestFailure(failure)
}
} finally {
notifier.fireTestFinished(description)
runAfterEach(notifier, test)
}
}
}

def runAll(notifier: RunNotifier): Unit = {
if (isIgnored()) {
notifier.fireTestIgnored(suiteDescription)
return
}
try {
val isContinue = runBeforeAll(notifier)
if (isContinue) {
suite.tests.foreach { test =>
val description = createTestDescription(test)
var isContinue = runBeforeEach(notifier, test)
if (isContinue) {
notifier.fireTestStarted(description)
try {
StackMarker.dropOutside(test.body()) match {
case f: FlakyFailure =>
notifier.fireTestAssumptionFailed(new Failure(description, f))
case Ignore =>
notifier.fireTestIgnored(description)
case _ =>
}
} catch {
case ex: AssumptionViolatedException =>
StackMarker.trimStackTrace(ex)
case NonFatal(ex) =>
StackMarker.trimStackTrace(ex)
val failure = new Failure(description, ex)
ex match {
case _: AssumptionViolatedException =>
notifier.fireTestAssumptionFailed(failure)
case _ =>
notifier.fireTestFailure(failure)
}
} finally {
notifier.fireTestFinished(description)
runAfterEach(notifier, test)
}
}
testsToRun.foreach { test =>
runTest(notifier, test)
}
}
} finally {
Expand Down
19 changes: 13 additions & 6 deletions funsuite/src/main/scala/funsuite/Tag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ import java.nio.file.PathMatcher
import scala.util.control.NonFatal
import java.lang.annotation.Annotation
import scala.reflect.ClassTag
import scala.runtime.Statics

class Tag(val value: String) extends Annotation {
// Not a case class so that it possible to override these.
override def equals(obj: Any): Boolean = obj match {
case t: Tag => t.value == value
case _ => false
}
override def hashCode(): Int = {
var acc: Int = -881232
acc = Statics.mix(acc, Statics.anyHash("funsuite.Tag"))
acc = Statics.mix(acc, Statics.anyHash(value))
acc
}
def annotationType(): Class[_ <: Annotation] = this.getClass()
override def toString(): String = s"Tag($value)"
}

case object Ignore extends Tag("Ignore")

case object ExpectFailure extends Tag("ExpectFailure")

case object Flaky extends Tag("Flaky")
1 change: 1 addition & 0 deletions funsuite/src/main/scala/funsuite/TestOptions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ case class TestOptions(name: String, tags: Set[Tag], loc: Location) {
def fail: TestOptions = tag(ExpectFailure)
def flaky: TestOptions = tag(Flaky)
def ignore: TestOptions = tag(Ignore)
def only: TestOptions = tag(Only)
def tag(t: Tag): TestOptions = copy(tags = tags + t)
}

Expand Down
7 changes: 3 additions & 4 deletions funsuite/src/main/scala/funsuite/internal/Diffs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,9 @@ object Diffs {
if (!obtained.contains("\n")) pprint.tokenize(obtained).mkString
else {
val out = new StringBuilder
out.append(" \"\"\"|\n")
obtained.trim.linesIterator.foreach(line =>
out.append(" |").append(line).append("\n")
)
val lines = obtained.trim.linesIterator
out.append(" \"\"\"|" + lines.next() + "\n")
lines.foreach(line => out.append(" |").append(line).append("\n"))
out.append(" |\"\"\".stripMargin")
out.toString()
}
Expand Down
6 changes: 6 additions & 0 deletions funsuite/src/main/scala/funsuite/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package object funsuite {
val Ignore = new Tag("Ignore")
val Only = new Tag("Only")
val Flaky = new Tag("Flaky")
val ExpectFailure = new Tag("ExpectFailure")
}
6 changes: 4 additions & 2 deletions funsuite/src/test/scala/funsuite/BasicSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package funsuite
import org.junit.runner.RunWith
import scala.util.Properties

object BasicSuite extends FunSuite {
// @Ignore
class BasicSuite extends FunSuite {
override def isCI = true
override def isFlakyFailureOk = true
def assertNoDiff()(implicit loc: Location): Unit = {
Expand Down Expand Up @@ -36,7 +37,8 @@ object BasicSuite extends FunSuite {
assertEqual(42, 53)
assertEqual(john2, john)
}
test("assertSame") {

test("only") {
assertNoDiff(
"""|val x = 42
|val y = 42
Expand Down
61 changes: 58 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class MySuite extends funsuite.FunSuite {

### Source locations for assertion errors

Assertions errors show the source code location where the assertion failed. Use
Assertion errors show the source code location where the assertion failed. Use
cmd+click on the location "`/path/to/BasicSuite.scala:36`" to open the exact
line number in your editor (may not work in all terminals).

Expand Down Expand Up @@ -93,6 +93,47 @@ Use `.fail` to mark a test case that is expected to fail.
A failed test only succeeds if the test body fails. If the test body succeeds,
the test fails.

### Running individual tests

Use `.only` to run only a single test.

```scala
test("issue-457") {
// will not run
}
test("issue-456".only) {
// only test that runs
}
test("issue-455") {
// will not run
}
```

### Ignore tests

Use the `@Ignore` annotation to skip all tests in a test suite.

```scala
@funsuite.Ignore
class MySuite extends funsuite.FunSuite {
test("hello1") {
// will not run
}
test("hello2") {
// will not run
}
// ...
}
```

Use `.ignore` to skip an individual test case in a test suite.

```scala
test("issue-456".ignore) {
// will not run
}
```

## JVM-only

FunSuite is currently only published for the JVM. It's unlikely that FunSuite
Expand All @@ -115,5 +156,19 @@ FunSuite is inspired by several existing testing libraries:

## Changelog

- 0.1.1: support for Scala 2.11.
- 0.1.0: initial release with basic functionality. Expect breaking changes.
### 0.1.2 (Jan 6th, 2020)

- Add support for `@Ignore` annotation

### 0.1.1 (Jan 6th, 2020)

- Add support for Scala 2.11.

### 0.1.0 (Jan 6th, 2020)

- Initial release with basic functionality.
- Expect breaking changes.

```
```

0 comments on commit 485ac14

Please sign in to comment.