mirror of https://github.com/sbt/sbt.git
Port testing
This commit is contained in:
parent
f2edcff583
commit
4b928f16c2
|
|
@ -191,6 +191,7 @@ lazy val sbtRoot: Project = (project in file("."))
|
|||
utilCache,
|
||||
taskProj,
|
||||
stdTaskProj,
|
||||
testingProj,
|
||||
)
|
||||
.settings(
|
||||
minimalSettings,
|
||||
|
|
@ -472,7 +473,8 @@ lazy val testingProj = (project in file("testing"))
|
|||
scalaXml.value,
|
||||
testInterface,
|
||||
launcherInterface,
|
||||
sjsonNewScalaJson.value
|
||||
sjsonNewScalaJson.value,
|
||||
sjsonNewCore.value,
|
||||
),
|
||||
Compile / scalacOptions += "-Ywarn-unused:-locals,-explicits,-privates",
|
||||
Compile / managedSourceDirectories +=
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@
|
|||
// DO NOT EDIT MANUALLY
|
||||
package sbt.protocol.testing.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait TestItemEventFormats { self: sbt.protocol.testing.codec.TestResultFormats with sbt.protocol.testing.codec.TestItemDetailFormats with sjsonnew.BasicJsonProtocol =>
|
||||
trait TestItemEventFormats { self: sbt.protocol.testing.codec.TestResultFormats with
|
||||
sbt.protocol.testing.codec.TestItemDetailFormats with
|
||||
sbt.internal.testing.StatusFormats with
|
||||
sbt.protocol.testing.codec.TestCompleteEventFormats with
|
||||
sbt.protocol.testing.codec.StartTestGroupEventFormats with
|
||||
sbt.protocol.testing.codec.EndTestGroupErrorEventFormats with
|
||||
sbt.protocol.testing.codec.TestInitEventFormats with
|
||||
sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TestItemEventFormat: JsonFormat[sbt.protocol.testing.TestItemEvent] = new JsonFormat[sbt.protocol.testing.TestItemEvent] {
|
||||
override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestItemEvent = {
|
||||
__jsOpt match {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,16 @@
|
|||
package sbt.protocol.testing.codec
|
||||
|
||||
import _root_.sjsonnew.JsonFormat
|
||||
trait TestMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.testing.codec.TestStringEventFormats with sbt.protocol.testing.codec.TestInitEventFormats with sbt.protocol.testing.codec.TestResultFormats with sbt.protocol.testing.codec.TestCompleteEventFormats with sbt.protocol.testing.codec.StartTestGroupEventFormats with sbt.protocol.testing.codec.EndTestGroupEventFormats with sbt.protocol.testing.codec.EndTestGroupErrorEventFormats with sbt.protocol.testing.codec.TestItemDetailFormats with sbt.protocol.testing.codec.TestItemEventFormats =>
|
||||
trait TestMessageFormats { self: sjsonnew.BasicJsonProtocol with
|
||||
sbt.internal.testing.StatusFormats with
|
||||
sbt.protocol.testing.codec.TestStringEventFormats with
|
||||
sbt.protocol.testing.codec.TestInitEventFormats with
|
||||
sbt.protocol.testing.codec.TestResultFormats with
|
||||
sbt.protocol.testing.codec.TestCompleteEventFormats with
|
||||
sbt.protocol.testing.codec.StartTestGroupEventFormats with
|
||||
sbt.protocol.testing.codec.EndTestGroupEventFormats with
|
||||
sbt.protocol.testing.codec.EndTestGroupErrorEventFormats with
|
||||
sbt.protocol.testing.codec.TestItemDetailFormats with
|
||||
sbt.protocol.testing.codec.TestItemEventFormats =>
|
||||
implicit lazy val TestMessageFormat: JsonFormat[sbt.protocol.testing.TestMessage] = flatUnionFormat7[sbt.protocol.testing.TestMessage, sbt.protocol.testing.TestStringEvent, sbt.protocol.testing.TestInitEvent, sbt.protocol.testing.TestCompleteEvent, sbt.protocol.testing.StartTestGroupEvent, sbt.protocol.testing.EndTestGroupEvent, sbt.protocol.testing.EndTestGroupErrorEvent, sbt.protocol.testing.TestItemEvent]("type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ import util.Logger
|
|||
import sbt.protocol.testing.TestResult
|
||||
|
||||
/**
|
||||
* A tests listener that outputs the results it receives in junit xml
|
||||
* report format.
|
||||
* @param targetDir directory in which test reports are generated
|
||||
* A tests listener that outputs the results it receives in junit xml report format.
|
||||
* @param targetDir
|
||||
* directory in which test reports are generated
|
||||
*/
|
||||
class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logger: Logger)
|
||||
extends TestsListener {
|
||||
|
|
@ -45,13 +45,14 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
@deprecated("Provided for binary compatibility: please use `targetDir` instead", "1.6.0")
|
||||
def outputDir: String = targetDir.getParent
|
||||
|
||||
/**Current hostname so we know which machine executed the tests*/
|
||||
/** Current hostname so we know which machine executed the tests */
|
||||
val hostname: String = {
|
||||
val start = System.nanoTime
|
||||
val name = try InetAddress.getLocalHost.getHostName
|
||||
catch {
|
||||
case _: IOException => "localhost"
|
||||
}
|
||||
val name =
|
||||
try InetAddress.getLocalHost.getHostName
|
||||
catch {
|
||||
case _: IOException => "localhost"
|
||||
}
|
||||
val elapsed = System.nanoTime - start
|
||||
if ((NANOSECONDS.toSeconds(elapsed) >= 4) && Properties.isMac && logger != null) {
|
||||
logger.warn(
|
||||
|
|
@ -63,7 +64,7 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
name
|
||||
}
|
||||
|
||||
/**all system properties as XML*/
|
||||
/** all system properties as XML */
|
||||
val properties: Elem =
|
||||
<properties>
|
||||
{
|
||||
|
|
@ -80,23 +81,22 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
</properties>
|
||||
|
||||
/**
|
||||
* Gathers data for one Test Suite. We map test groups to TestSuites.
|
||||
* Each TestSuite gets its own output file.
|
||||
* Gathers data for one Test Suite. We map test groups to TestSuites. Each TestSuite gets its own
|
||||
* output file.
|
||||
*/
|
||||
class TestSuite(val name: String, timestamp: LocalDateTime) {
|
||||
def this(name: String) = this(name, LocalDateTime.now())
|
||||
|
||||
val events: ListBuffer[TEvent] = new ListBuffer()
|
||||
|
||||
/**Adds one test result to this suite.*/
|
||||
/** Adds one test result to this suite. */
|
||||
def addEvent(e: TEvent): ListBuffer[TEvent] = events += e
|
||||
|
||||
/** Returns the number of tests of each state for the specified. */
|
||||
def count(status: TStatus) = events.count(_.status == status)
|
||||
|
||||
/**
|
||||
* Stops the time measuring and emits the XML for
|
||||
* All tests collected so far.
|
||||
* Stops the time measuring and emits the XML for All tests collected so far.
|
||||
*/
|
||||
def stop(): Elem = {
|
||||
val duration = events.map(_.duration()).sum
|
||||
|
|
@ -112,7 +112,9 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
val classnameRegex = s"^($name|${name.split('.').last})\\.?".r
|
||||
|
||||
val result =
|
||||
<testsuite hostname={hostname} name={name} tests={tests.toString} errors={errors.toString} failures={
|
||||
<testsuite hostname={hostname} name={name} tests={tests.toString} errors={
|
||||
errors.toString
|
||||
} failures={
|
||||
failures + ""
|
||||
} skipped={ignoredSkippedPending.toString} time={(duration / 1000.0).toString} timestamp={
|
||||
formatISO8601DateTime(timestamp)
|
||||
|
|
@ -132,7 +134,7 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
classnameRegex.findFirstMatchIn(selector.testName).map(_.end).getOrElse(0)
|
||||
selector.testName.substring(matchEnd)
|
||||
case nested: NestedTestSelector => nested.testName()
|
||||
case other => s"(It is not a test it is a ${other.getClass.getCanonicalName})"
|
||||
case other => s"(It is not a test it is a ${other.getClass.getCanonicalName})"
|
||||
}
|
||||
} time={(e.duration() / 1000.0).toString}>
|
||||
{
|
||||
|
|
@ -155,7 +157,7 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
<failure message={e.throwable.get.getMessage} type={
|
||||
e.throwable.get.getClass.getName
|
||||
}>{trace}</failure>
|
||||
case TStatus.Failure => <failure message={"No Exception or message provided"}/>
|
||||
case TStatus.Failure => <failure message={"No Exception or message provided"}/>
|
||||
case TStatus.Ignored | TStatus.Skipped | TStatus.Pending => <skipped/>
|
||||
case _ => {}
|
||||
}
|
||||
|
|
@ -171,7 +173,7 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
}
|
||||
}
|
||||
|
||||
/**The currently running test suite*/
|
||||
/** The currently running test suite */
|
||||
private val testSuite = new InheritableThreadLocal[Option[TestSuite]] {
|
||||
override def initialValue(): Option[TestSuite] = None
|
||||
}
|
||||
|
|
@ -179,7 +181,7 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
private def withTestSuite[T](f: TestSuite => T): T =
|
||||
testSuite.get().map(f).getOrElse(sys.error("no test suite"))
|
||||
|
||||
/**Creates the output Dir*/
|
||||
/** Creates the output Dir */
|
||||
override def doInit(): Unit = {
|
||||
val _ = targetDir.mkdirs()
|
||||
}
|
||||
|
|
@ -197,34 +199,29 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
}
|
||||
|
||||
/**
|
||||
* called for each class or equivalent grouping
|
||||
* We map one group to one Testsuite, so for each Group
|
||||
* we create [[https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd JUnit XML file]], and looks like this:
|
||||
* called for each class or equivalent grouping We map one group to one Testsuite, so for each
|
||||
* Group we create
|
||||
* [[https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd JUnit XML file]], and looks
|
||||
* like this:
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8" ?>
|
||||
* <testsuite skipped="w" errors="x" failures="y" tests="z" hostname="example.com" name="eu.henkelmann.bla.SomeTest" time="0.23" timestamp="2018-01-01T10:00:00">
|
||||
* <properties>
|
||||
* <property name="os.name" value="Linux" />
|
||||
* ...
|
||||
* </properties>
|
||||
* <testcase classname="eu.henkelmann.bla.SomeTest" name="testFooWorks" time="0.0" >
|
||||
* <error message="the foo did not work" type="java.lang.NullPointerException">... stack ...</error>
|
||||
* </testcase>
|
||||
* <testcase classname="eu.henkelmann.bla.SomeTest" name="testBarThrowsException" time="0.0" />
|
||||
* <testcase classname="eu.henkelmann.bla.SomeTest" name="testBaz" time="0.0">
|
||||
* <failure message="the baz was no bar" type="junit.framework.AssertionFailedError">...stack...</failure>
|
||||
* </testcase>
|
||||
* <system-out><![CDATA[]]></system-out>
|
||||
* <system-err><![CDATA[]]></system-err>
|
||||
* </testsuite>
|
||||
* <?xml version="1.0" encoding="UTF-8" ?> <testsuite skipped="w" errors="x" failures="y"
|
||||
* tests="z" hostname="example.com" name="eu.henkelmann.bla.SomeTest" time="0.23"
|
||||
* timestamp="2018-01-01T10:00:00"> <properties> <property name="os.name" value="Linux" /> ...
|
||||
* </properties> <testcase classname="eu.henkelmann.bla.SomeTest" name="testFooWorks" time="0.0" >
|
||||
* <error message="the foo did not work" type="java.lang.NullPointerException">... stack
|
||||
* ...</error> </testcase> <testcase classname="eu.henkelmann.bla.SomeTest"
|
||||
* name="testBarThrowsException" time="0.0" /> <testcase classname="eu.henkelmann.bla.SomeTest"
|
||||
* name="testBaz" time="0.0"> <failure message="the baz was no bar"
|
||||
* type="junit.framework.AssertionFailedError">...stack...</failure> </testcase>
|
||||
* <system-out><![CDATA[]]></system-out> <system-err><![CDATA[]]></system-err> </testsuite>
|
||||
*/
|
||||
override def endGroup(name: String, t: Throwable): Unit = {
|
||||
// create our own event to record the error
|
||||
val event: TEvent = new TEvent {
|
||||
def fullyQualifiedName = name
|
||||
//def description =
|
||||
//"Throwable escaped the test run of '%s'".format(name)
|
||||
def duration = -1
|
||||
// def description =
|
||||
// "Throwable escaped the test run of '%s'".format(name)
|
||||
def duration() = -1
|
||||
def status = TStatus.Error
|
||||
def fingerprint = null
|
||||
def selector = null
|
||||
|
|
@ -235,8 +232,8 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
}
|
||||
|
||||
/**
|
||||
* Ends the current suite, wraps up the result and writes it to an XML file
|
||||
* in the output folder that is named after the suite.
|
||||
* Ends the current suite, wraps up the result and writes it to an XML file in the output folder
|
||||
* that is named after the suite.
|
||||
*/
|
||||
override def endGroup(name: String, result: TestResult): Unit = {
|
||||
writeSuite()
|
||||
|
|
@ -265,9 +262,9 @@ class JUnitXmlTestsListener(val targetDir: File, legacyTestReport: Boolean, logg
|
|||
testSuite.remove()
|
||||
}
|
||||
|
||||
/**Does nothing, as we write each file after a suite is done.*/
|
||||
/** Does nothing, as we write each file after a suite is done. */
|
||||
override def doComplete(finalResult: TestResult): Unit = {}
|
||||
|
||||
/**Returns None*/
|
||||
/** Returns None */
|
||||
override def contentLogger(test: TestDefinition): Option[ContentLogger] = None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,14 +179,13 @@ object TestFramework {
|
|||
def getFingerprints(framework: Framework): Seq[Fingerprint] =
|
||||
framework.getClass.getMethod("fingerprints").invoke(framework) match {
|
||||
case fingerprints: Array[Fingerprint] => fingerprints.toList
|
||||
case _ => sys.error("Could not call 'fingerprints' on framework " + framework)
|
||||
case _ => sys.error("Could not call 'fingerprints' on framework " + framework)
|
||||
}
|
||||
|
||||
private[sbt] def safeForeach[T](it: Iterable[T], log: ManagedLogger)(f: T => Unit): Unit =
|
||||
it.foreach(
|
||||
i =>
|
||||
try f(i)
|
||||
catch { case NonFatal(e) => log.trace(e); log.error(e.toString) }
|
||||
it.foreach(i =>
|
||||
try f(i)
|
||||
catch { case NonFatal(e) => log.trace(e); log.error(e.toString) }
|
||||
)
|
||||
|
||||
private[sbt] def hashCode(f: Fingerprint): Int = f match {
|
||||
|
|
@ -221,9 +220,16 @@ object TestFramework {
|
|||
if (mappedTests.isEmpty)
|
||||
(() => (), Vector(), _ => () => ())
|
||||
else
|
||||
createTestTasks(testLoader, runners.map {
|
||||
case (tf, r) => (frameworks(tf), new TestRunner(r, listeners, log))
|
||||
}, mappedTests, tests, log, listeners)
|
||||
createTestTasks(
|
||||
testLoader,
|
||||
runners.map { case (tf, r) =>
|
||||
(frameworks(tf), new TestRunner(r, listeners, log))
|
||||
},
|
||||
mappedTests,
|
||||
tests,
|
||||
log,
|
||||
listeners
|
||||
)
|
||||
}
|
||||
|
||||
private[this] def order(
|
||||
|
|
@ -270,14 +276,13 @@ object TestFramework {
|
|||
|
||||
val startTask = foreachListenerSafe(_.doInit())
|
||||
val testTasks =
|
||||
Map(tests.toSeq.flatMap {
|
||||
case (framework, testDefinitions) =>
|
||||
val runner = runners(framework)
|
||||
val testTasks = withContextLoader(loader) { runner.tasks(testDefinitions) }
|
||||
for (testTask <- testTasks) yield {
|
||||
val taskDef = testTask.taskDef
|
||||
(taskDef.fullyQualifiedName, createTestFunction(loader, taskDef, runner, testTask))
|
||||
}
|
||||
Map(tests.toSeq.flatMap { case (framework, testDefinitions) =>
|
||||
val runner = runners(framework)
|
||||
val testTasks = withContextLoader(loader) { runner.tasks(testDefinitions) }
|
||||
for (testTask <- testTasks) yield {
|
||||
val taskDef = testTask.taskDef
|
||||
(taskDef.fullyQualifiedName, createTestFunction(loader, taskDef, runner, testTask))
|
||||
}
|
||||
}: _*)
|
||||
|
||||
val endTask = (result: TestResult) => foreachListenerSafe(_.doComplete(result))
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ trait TestsListener extends TestReportListener {
|
|||
|
||||
}
|
||||
|
||||
/** Provides the overall `result` of a group of tests (a suite) and test counts for each result type. */
|
||||
/**
|
||||
* Provides the overall `result` of a group of tests (a suite) and test counts for each result type.
|
||||
*/
|
||||
final class SuiteResult(
|
||||
val result: TestResult,
|
||||
val passedCount: Int,
|
||||
|
|
@ -98,7 +100,9 @@ final class SuiteResult(
|
|||
|
||||
object SuiteResult {
|
||||
|
||||
/** Computes the overall result and counts for a suite with individual test results in `events`. */
|
||||
/**
|
||||
* Computes the overall result and counts for a suite with individual test results in `events`.
|
||||
*/
|
||||
def apply(events: Seq[TEvent]): SuiteResult = {
|
||||
def count(status: TStatus) = events.count(_.status == status)
|
||||
new SuiteResult(
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ object TestLogger {
|
|||
import sbt.protocol.testing.codec.JsonProtocol._
|
||||
|
||||
implicit val testStringEventShowLines: ShowLines[TestStringEvent] =
|
||||
ShowLines[TestStringEvent]({
|
||||
case a: TestStringEvent => List(a.value)
|
||||
ShowLines[TestStringEvent]({ case a: TestStringEvent =>
|
||||
List(a.value)
|
||||
})
|
||||
|
||||
private def generateName: String = "test-" + generateId.incrementAndGet
|
||||
|
|
|
|||
Loading…
Reference in New Issue