-
Notifications
You must be signed in to change notification settings - Fork 406
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
sbt ScalaCheckRunner: loss of test selection fidelity #1105
Comments
Hi @satorg!
Indeed they are connected! That PR closes a part of the gap that I need closed ;) To clarify: this is not about any issues with ScalaCheck itself; it is about driving it via the SBT Test Interface. Invented by the SBT developers, that interface allows plugging test frameworks into SBT without requiring SBT to know the details of the framework's API. Now that every test framework has an implementation of this interface, it can be used to integrate them with tools other than SBT (in my case - Gradle). The protocol is as follows:
When obtaining the To describe to the framework what needs to be run in a class/object, a
Coming from the code driving the framework via the SBT Test Interface,
The problem is that currently ScalaCheck's implementation of the SBT Test Interface completely ignores both Thank you! Here is a little test that demonstrates (a part of) this issue: package org.scalacheck
import sbt.testing.{Event, EventHandler, Framework, Runner, Selector, SuiteSelector, Task, TaskDef, TestSelector}
object SbtFixture extends Properties("SbtFixture") {
property("success") = Prop.passed
}
object SbtSpecification extends Properties("Sbt") {
property("runAll") = {
val ran: List[String] = SbtDriver.run(
fullyQualifiedName = "org.scalacheck.SbtFixture",
selectors = Array(new SuiteSelector)
)
ran.length == 1 && ran.head == "SbtFixture.success"
}
property("runNonExistent") = {
val ran: List[String] = SbtDriver.run(
fullyQualifiedName = "org.scalacheck.SbtFixture",
selectors = Array(new TestSelector("nonexistent"))
)
// Since we are asking to run a property that does not exist, result should be:
ran.isEmpty
// but because ScalaCheckFramework ignores taskDef.selectors, it runs everything instead:
// ran.length == 1 && ran.head == "SbtFixture.success"
}
}
object SbtDriver {
private class StoringEventHandler extends EventHandler {
private var ran: List[String] = List.empty
def getRan: List[String] = ran
override def handle(event: Event): Unit = ran = ran.appended(event.selector.asInstanceOf[TestSelector].testName)
}
def run(
fullyQualifiedName: String,
explicitlySpecified: Boolean = false,
selectors: Array[Selector]
): List[String] = {
val framework: Framework = new ScalaCheckFramework
val runner: Runner = framework.runner(Array.empty, Array.empty, getClass.getClassLoader)
val taskDef: TaskDef = new TaskDef(
fullyQualifiedName,
framework.fingerprints()(2), // object ... extends org.scalacheck.Properties
explicitlySpecified,
selectors
)
val eventHandler: StoringEventHandler = new StoringEventHandler
def execute(task: Task): Unit = task.execute(eventHandler, Array.empty).foreach(execute)
runner.tasks(Array(taskDef)).foreach(execute)
eventHandler.getRan
}
} |
…dcardSelector`s, filter properties to run by matching their names against the `Selector`s; - match a property by both its short and full name; - added a test that actually runs ScalaCheck via the `SBT Test Interface` and demonstrates the (now correct) treatment of the `Selector`s; - test also demonstrates two unfixable infidelities in the treatment of the nested properties; - thankfully, ScalaCheck's implementation of `sbt.testing.Framework` is, unlike in some other test frameworks, shared between the platforms (JVM, Scala.js, Scala Native), so the fixes do not have to be replicated for each platform, but: - the test needs to supply a `testClassLoader: ClassLoader` parameter when calling `sbt.testing.Framework.runner()`; on platforms other than JVM, `getClass.getClassLoader` is not available, so `Platform.getClassLoader: ClassLoader` method was added to every `Platform`; on platforms other than the JVM, it returns `null`, which is fine since on those platforms `sbt.testing.Framework.runner()` ignores the `testClassLoader` parameter anyway. fixes typelevel#1105
The only code that looks at the test selectors supplied via the
sbt.testing.TaskDef
isScalaCheckRunner.checkPropTask()
; it recognizessbt.testing.TestSelector
s and selects the properties with the exact names supplied; it should also recognizesbt.testing.TestWildcardSelector
s and select the properties with the names containing the wildcard supplied.Also, ScalaCheckRunner.deserializeTask() should probably count
sbt.testing.TestWildcardSelector
s towardscountTestSelectors
and thus dispatch toScalaCheckRunner.checkPropTask()
and not toScalaCheckRunner.rootTask()
if any are supplied.Finally, whatever test selection functionality
ScalaCheck
provides only kicks in in very specific use cases:ScalaCheckRunner.deserializeTask()
is called orsbt.testing.Fingerprint
used contains"ForkMain"
.When running
ScalaCheck
the "official"sbt.testing
way, by callingScalaCheckRunner.tasks()
with the fingerprints returned fromScalaCheckFramework.fingerprints()
(none of which contain"ForkMain"
in their class names),ScalaCheckRunner.checkPropTask()
is never called, and no test selection is applied.Context: Scala.js Gradle plugin integrates with
sbt.testing
-enabled test frameworks, andScalaCheck
is among them. Plugin does its ownTaskDef
serialization/deserialization in a way that is not specific to the test framework used, but works with Gradle. As a result of the above idiosyncrasies in theScalaCheck
's test selection functionality, plugin can not access any of it :(The text was updated successfully, but these errors were encountered: