org.scalatest.tools.HtmlReporter.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalatest_2.9.0 Show documentation
Show all versions of scalatest_2.9.0 Show documentation
ScalaTest is a free, open-source testing toolkit for Scala and Java
programmers.
/*
* Copyright 2001-2008 Artima, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.scalatest.tools
import org.scalatest._
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.util.Iterator
import java.util.Set
import java.io.StringWriter
import org.scalatest.events._
import org.scalatest.exceptions.TestFailedException
import PrintReporter.{BufferSize, makeDurationString}
import HtmlReporter._
import org.pegdown.PegDownProcessor
import scala.collection.mutable.ListBuffer
import scala.xml.NodeSeq
import scala.xml.XML
import java.util.UUID
import scala.xml.Node
import scala.annotation.tailrec
import java.net.URL
import scala.io.Source
import java.nio.channels.Channels
import java.text.DecimalFormat
/**
* A Reporter
that prints test status information in HTML format to a file.
*/
private[scalatest] class HtmlReporter(directoryPath: String, presentAllDurations: Boolean,
presentInColor: Boolean, presentStackTraces: Boolean, presentFullStackTraces: Boolean,
cssUrl: Option[URL], resultHolder: Option[SuiteResultHolder]) extends ResourcefulReporter {
private val specIndent = 15
private val targetDir = new File(directoryPath)
private val imagesDir = new File(targetDir, "images")
private val jsDir = new File(targetDir, "js")
private val cssDir = new File(targetDir, "css")
if (!targetDir.exists)
targetDir.mkdirs()
if (!imagesDir.exists)
imagesDir.mkdirs()
if (!jsDir.exists)
jsDir.mkdirs()
if (!cssDir.exists)
cssDir.mkdirs()
private def copyResource(url: URL, toDir: File, targetFileName: String) {
val inputStream = url.openStream
try {
val outputStream = new FileOutputStream(new File(toDir, targetFileName))
try {
outputStream getChannel() transferFrom(Channels.newChannel(inputStream), 0, Long.MaxValue)
}
finally {
outputStream.flush()
outputStream.close()
}
}
finally {
inputStream.close()
}
}
private def getResource(resourceName: String): URL =
classOf[Suite].getClassLoader.getResource(resourceName)
cssUrl match {
case Some(cssUrl) => copyResource(cssUrl, cssDir, "custom.css")
case None => // Do nothing.
}
copyResource(getResource("org/scalatest/HtmlReporter.css"), cssDir, "styles.css")
copyResource(getResource("org/scalatest/sorttable.js"), jsDir, "sorttable.js")
copyResource(getResource("org/scalatest/d3.v2.min.js"), jsDir, "d3.v2.min.js")
copyResource(getResource("images/greenbullet.gif"), imagesDir, "testsucceeded.gif")
copyResource(getResource("images/redbullet.gif"), imagesDir, "testfailed.gif")
copyResource(getResource("images/yellowbullet.gif"), imagesDir, "testignored.gif")
copyResource(getResource("images/yellowbullet.gif"), imagesDir, "testcanceled.gif")
copyResource(getResource("images/yellowbullet.gif"), imagesDir, "testpending.gif")
copyResource(getResource("images/graybullet.gif"), imagesDir, "infoprovided.gif")
private val results = resultHolder match {
case Some(holder) => holder
case None => new SuiteResultHolder()
}
private val pegDown = new PegDownProcessor
private def withPossibleLineNumber(stringToPrint: String, throwable: Option[Throwable]): String = {
throwable match {
case Some(testFailedException: TestFailedException) =>
testFailedException.failedCodeFileNameAndLineNumberString match {
case Some(lineNumberString) =>
Resources("printedReportPlusLineNumber", stringToPrint, lineNumberString)
case None => stringToPrint
}
case _ => stringToPrint
}
}
private def stringsToPrintOnError(noteResourceName: String, errorResourceName: String, message: String, throwable: Option[Throwable],
formatter: Option[Formatter], suiteName: Option[String], testName: Option[String], duration: Option[Long]): String = {
formatter match {
case Some(IndentedText(_, rawText, _)) =>
Resources("specTextAndNote", rawText, Resources(noteResourceName))
case _ =>
// Deny MotionToSuppress directives in error events, because error info needs to be seen by users
suiteName match {
case Some(sn) =>
testName match {
case Some(tn) => Resources(errorResourceName, sn + ": " + tn)
case None => Resources(errorResourceName, sn)
}
// Should not get here with built-in ScalaTest stuff, but custom stuff could get here.
case None => Resources(errorResourceName, Resources("noNameSpecified"))
}
}
}
private def stringToPrintWhenNoError(resourceName: String, formatter: Option[Formatter], suiteName: String, testName: Option[String]): Option[String] =
stringToPrintWhenNoError(resourceName, formatter, suiteName, testName, None)
private def stringToPrintWhenNoError(resourceName: String, formatter: Option[Formatter], suiteName: String, testName: Option[String], duration: Option[Long]): Option[String] = {
formatter match {
case Some(IndentedText(_, rawText, _)) =>
duration match {
case Some(milliseconds) =>
if (presentAllDurations)
Some(Resources("withDuration", rawText, makeDurationString(milliseconds)))
else
Some(rawText)
case None => Some(rawText)
}
case Some(MotionToSuppress) => None
case _ =>
val arg =
testName match {
case Some(tn) => suiteName + ": " + tn
case None => suiteName
}
val unformattedText = Resources(resourceName, arg)
duration match {
case Some(milliseconds) =>
if (presentAllDurations)
Some(Resources("withDuration", unformattedText, makeDurationString(milliseconds)))
else
Some(unformattedText)
case None => Some(unformattedText)
}
}
}
private def getIndentLevel(formatter: Option[Formatter]) =
formatter match {
case Some(IndentedText(formattedText, rawText, indentationLevel)) => indentationLevel
case _ => 0
}
private def getSuiteFileName(suiteResult: SuiteResult) =
suiteResult.suiteClassName match {
case Some(suiteClassName) => suiteClassName
case None => suiteResult.suiteName
}
private def makeSuiteFile(suiteResult: SuiteResult) {
val name = getSuiteFileName(suiteResult)
val pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(new File(targetDir, name + ".html")), BufferSize))
try {
pw.println {
"" + "\n" +
"" + "\n" +
getSuiteHtml(name, suiteResult)
}
}
finally {
pw.flush()
pw.close()
}
}
private def appendCombinedStatus(name: String, r: SuiteResult) =
if (r.testsFailedCount > 0)
name + "_with_failed"
else if (r.testsIgnoredCount > 0 || r.testsPendingCount > 0 || r.testsCanceledCount > 0)
name + "_passed"
else
name + "_passed_all"
private def transformStringForResult(s: String, suiteResult: SuiteResult): String =
s + (if (suiteResult.testsFailedCount > 0) "_failed" else "_passed")
private def getSuiteHtml(name: String, suiteResult: SuiteResult) =
ScalaTest Suite { name } Results
{
cssUrl match {
case Some(cssUrl) =>
case None => NodeSeq.Empty
}
}
{ suiteResult.suiteName }
{ "Tests: total " + (suiteResult.testsSucceededCount + suiteResult.testsFailedCount + suiteResult.testsCanceledCount + suiteResult.testsIgnoredCount + suiteResult.testsPendingCount) + ", succeeded " +
suiteResult.testsSucceededCount + ", failed " + suiteResult.testsFailedCount + ", canceled " + suiteResult.testsCanceledCount + ", ignored " + suiteResult.testsIgnoredCount + ", pending " +
suiteResult.testsPendingCount }
{
val scopeStack = new collection.mutable.Stack[String]()
suiteResult.eventList.map { e =>
e match {
case ScopeOpened(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) =>
val testNameInfo = nameInfo.testName
val stringToPrint = stringToPrintWhenNoError("scopeOpened", formatter, nameInfo.suiteName, nameInfo.testName)
stringToPrint match {
case Some(string) =>
val elementId = generateElementId
scopeStack.push(elementId)
scope(elementId, string, getIndentLevel(formatter) + 1)
case None =>
NodeSeq.Empty
}
case ScopeClosed(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) =>
scopeStack.pop
NodeSeq.Empty
case TestSucceeded(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, rerunnable, payload, threadName, timeStamp) =>
val stringToPrint = stringToPrintWhenNoError("testSucceeded", formatter, suiteName, Some(testName), duration)
val nodeSeq =
stringToPrint match {
case Some(string) =>
val elementId = generateElementId
test(elementId, List(string), getIndentLevel(formatter) + 1, "test_passed")
case None =>
NodeSeq.Empty
}
nodeSeq :: recordedEvents.map(processInfoMarkupProvided(_)).toList
case TestFailed(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, rerunnable, payload, threadName, timeStamp) =>
val stringToPrint = stringsToPrintOnError("failedNote", "testFailed", message, throwable, formatter, Some(suiteName), Some(testName), duration)
val elementId = generateElementId
val nodeSeq = testWithDetails(elementId, List(stringToPrint), message, throwable, getIndentLevel(formatter) + 1, "test_failed")
nodeSeq :: recordedEvents.map(processInfoMarkupProvided(_)).toList
case TestIgnored(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, payload, threadName, timeStamp) =>
val stringToPrint =
formatter match {
case Some(IndentedText(_, rawText, _)) => Some(Resources("specTextAndNote", rawText, Resources("ignoredNote")))
case Some(MotionToSuppress) => None
case _ => Some(Resources("testIgnored", suiteName + ": " + testName))
}
stringToPrint match {
case Some(string) =>
val elementId = generateElementId
test(elementId, List(string), getIndentLevel(formatter) + 1, "test_ignored")
case None =>
NodeSeq.Empty
}
case TestPending(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, payload, threadName, timeStamp) =>
val stringToPrint =
formatter match {
case Some(IndentedText(_, rawText, _)) => Some(Resources("specTextAndNote", rawText, Resources("pendingNote")))
case Some(MotionToSuppress) => None
case _ => Some(Resources("testPending", suiteName + ": " + testName))
}
val nodeSeq =
stringToPrint match {
case Some(string) =>
val elementId = generateElementId
test(elementId, List(string), getIndentLevel(formatter) + 1, "test_pending")
case None =>
NodeSeq.Empty
}
nodeSeq :: recordedEvents.map(processInfoMarkupProvided(_)).toList
case TestCanceled(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, payload, threadName, timeStamp) =>
val stringToPrint = stringsToPrintOnError("canceledNote", "testCanceled", message, throwable, formatter, Some(suiteName), Some(testName), duration)
val elementId = generateElementId
val nodeSeq = testWithDetails(elementId, List(stringToPrint), message, throwable, getIndentLevel(formatter) + 1, "test_canceled")
nodeSeq :: recordedEvents.map(processInfoMarkupProvided(_)).toList
case infoProvided: InfoProvided =>
processInfoMarkupProvided(infoProvided)
case markupProvided: MarkupProvided =>
processInfoMarkupProvided(markupProvided)
// TO CONTINUE: XML element must be last
case _ => NodeSeq.Empty
}
}
}
Suite ID
{ suiteResult.suiteId }
Class name
{ suiteResult.suiteClassName.getOrElse("-") }
Total duration
{
suiteResult.duration match {
case Some(duration) => makeDurationString(duration)
case None => "-"
}
}
private def processInfoMarkupProvided(event: Event) = {
event match {
case InfoProvided(ordinal, message, nameInfo, throwable, formatter, location, payload, threadName, timeStamp) =>
val (suiteName, testName) =
nameInfo match {
case Some(NameInfo(suiteName, _, _, testName)) => (Some(suiteName), testName)
case None => (None, None)
}
val infoContent = stringsToPrintOnError("infoProvidedNote", "infoProvided", message, throwable, formatter, suiteName, testName, None)
val elementId = generateElementId
test(elementId, List(infoContent), getIndentLevel(formatter) + 1, "info")
case MarkupProvided(ordinal, text, nameInfo, formatter, location, payload, threadName, timeStamp) =>
val (suiteName, testName) =
nameInfo match {
case Some(NameInfo(suiteName, _, _, testName)) => (Some(suiteName), testName)
case None => (None, None)
}
val elementId = generateElementId
markup(elementId, text, getIndentLevel(formatter) + 1, "markup")
case _ => NodeSeq.Empty
}
}
private def makeIndexFile(resourceName: String, duration: Option[Long]) {
val pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(new File(targetDir, "index.html")), BufferSize))
try {
pw.println {
"\n" +
"\n" +
getIndexHtml(resourceName, duration)
}
}
finally {
pw.flush()
pw.close()
}
}
private def getHeaderStatusColor(summary: Summary) =
if (summary.testsFailedCount == 0) "scalatest-header-passed" else "scalatest-header-failed"
private def getPieChartScript(summary: Summary) = {
import summary._
"/* modified from http://www.permadi.com/tutorial/cssGettingBackgroundColor/index.html - */" + "\n" +
"function getBgColor(elementId)" + "\n" +
"{" + "\n" +
" var element = document.getElementById(elementId);" + "\n" +
" if (element.currentStyle)" + "\n" +
" return element.currentStyle.backgroundColor;" + "\n" +
" if (window.getComputedStyle)" + "\n" +
" {" + "\n" +
" var elementStyle=window.getComputedStyle(element,\"\");" + "\n" +
" if (elementStyle)" + "\n" +
" return elementStyle.getPropertyValue(\"background-color\");" + "\n" +
" }" + "\n" +
" // Return 0 if both methods failed." + "\n" +
" return 0;" + "\n" +
"}" + "\n" +
"var data = [" + testsSucceededCount + ", " + testsFailedCount + ", " + testsIgnoredCount + ", " + testsPendingCount + ", " + testsCanceledCount + "];" + "\n" +
"var color = [getBgColor('summary_view_row_1_legend_succeeded_label'), " + "\n" +
" getBgColor('summary_view_row_1_legend_failed_label'), " + "\n" +
" getBgColor('summary_view_row_1_legend_ignored_label'), " + "\n" +
" getBgColor('summary_view_row_1_legend_pending_label'), " + "\n" +
" getBgColor('summary_view_row_1_legend_canceled_label')" + "\n" +
" ];" + "\n" +
"var width = document.getElementById('chart_div').offsetWidth," + "\n" +
" height = document.getElementById('chart_div').offsetHeight," + "\n" +
" outerRadius = Math.min(width, height) / 2," + "\n" +
" innerRadius = 0," + "\n" +
" donut = d3.layout.pie()," + "\n" +
" arc = d3.svg.arc().innerRadius(innerRadius).outerRadius(outerRadius);" + "\n" +
"var vis = d3.select(\"#chart_div\")" + "\n" +
" .append(\"svg\")" + "\n" +
" .data([data])" + "\n" +
" .attr(\"width\", width)" + "\n" +
" .attr(\"height\", height);" + "\n" +
"var arcs = vis.selectAll(\"g.arc\")" + "\n" +
" .data(donut)" + "\n" +
" .enter().append(\"g\")" + "\n" +
" .attr(\"class\", \"arc\")" + "\n" +
" .attr(\"transform\", \"translate(\" + outerRadius + \",\" + outerRadius + \")\");" + "\n" +
"arcs.append(\"path\")" + "\n" +
" .attr(\"fill\", function(d, i) { return color[i]; })" + "\n" +
" .attr(\"d\", arc);\n"
}
private def getIndexHtml(resourceName: String, duration: Option[Long]) = {
val summary = results.summary
import summary._
val decimalFormat = new DecimalFormat("#.##")
ScalaTest Results
{
cssUrl match {
case Some(cssUrl) =>
case None => NodeSeq.Empty
}
}
{ header(resourceName, duration, summary) }
Succeeded
{ testsSucceededCount }
({ decimalFormat.format(testsSucceededCount * 100.0 / totalTestsCount) }%)
Failed
{ testsFailedCount }
({ decimalFormat.format(testsFailedCount * 100.0 / totalTestsCount) }%)
Canceled
{ testsCanceledCount }
({ decimalFormat.format(testsCanceledCount * 100.0 / totalTestsCount) }%)
Ignored
{ testsIgnoredCount }
({ decimalFormat.format(testsIgnoredCount * 100.0 / totalTestsCount) }%)
Pending
{ testsPendingCount }
({ decimalFormat.format(testsPendingCount * 100.0 / totalTestsCount) }%)
{ getStatistic(summary) }
{ suiteResults }
Click on suite name to view details.
Click on column name to sort.
}
// TODO: This needs to be internationalized
private def getStatistic(summary: Summary) =
private def header(resourceName: String, duration: Option[Long], summary: Summary) =
ScalaTest Results
{ getDuration(resourceName, duration) }
{ getTotalTests(summary) }
{ getSuiteSummary(summary) }
{ getTestSummary(summary) }
private def generateElementId = UUID.randomUUID.toString
private def setBit(stack: collection.mutable.Stack[String], tagMap: collection.mutable.HashMap[String, Int], bit: Int) {
stack.foreach { scopeElementId =>
val currentBits = tagMap(scopeElementId)
tagMap.put(scopeElementId, currentBits | bit)
}
}
val tagMap = collection.mutable.HashMap[String, Int]()
private def suiteResults =
Suite
Duration (ms.)
Succeeded
Failed
Canceled
Ignored
Pending
Total
{
val sortedSuiteList = results.suiteList.sortWith { (a, b) =>
if (a.testsFailedCount == b.testsFailedCount) {
if (a.testsCanceledCount == b.testsCanceledCount) {
if (a.testsIgnoredCount == b.testsIgnoredCount) {
if (a.testsPendingCount == b.testsPendingCount)
a.startEvent.suiteName < b.startEvent.suiteName
else
a.testsPendingCount > b.testsPendingCount
}
else
a.testsIgnoredCount > b.testsIgnoredCount
}
else
a.testsCanceledCount > b.testsCanceledCount
}
else
a.testsFailedCount > b.testsFailedCount
}.toArray
sortedSuiteList map { r =>
val elementId = generateElementId
import r._
val bits =
(if (testsSucceededCount > 0) SUCCEEDED_BIT else 0) +
(if (testsFailedCount > 0) FAILED_BIT else 0) +
(if (testsIgnoredCount > 0) IGNORED_BIT else 0) +
(if (testsPendingCount > 0) PENDING_BIT else 0) +
(if (testsCanceledCount > 0) CANCELED_BIT else 0)
tagMap.put(elementId, bits)
suiteSummary(elementId, getSuiteFileName(r), r)
}
}
private def countStyle(prefix: String, count: Int) =
if (count == 0)
prefix + "_zero"
else
prefix
private def durationDisplay(duration: Option[Long]) =
duration match {
case Some(duration) => duration
case None => "-"
}
private def suiteSummary(elementId: String, suiteFileName: String, suiteResult: SuiteResult) = {
import suiteResult._
{ suiteName }
{ durationDisplay(duration) }
{ testsSucceededCount }
{ testsFailedCount }
{ testsCanceledCount }
{ testsIgnoredCount }
{ testsPendingCount }
{ testsSucceededCount + testsFailedCount + testsIgnoredCount + testsPendingCount + testsCanceledCount }
}
private def twoLess(indentLevel: Int): Int =
indentLevel - 2 match {
case lev if lev < 0 => 0
case lev => lev
}
private def oneLess(indentLevel: Int): Int =
indentLevel - 1 match {
case lev if lev < 0 => 0
case lev => lev
}
private def scope(elementId: String, message: String, indentLevel: Int) =
{ message }
private def test(elementId: String, lines: List[String], indentLevel: Int, styleName: String) =
{
lines.map { line =>
- { line }
}
}
private def testWithDetails(elementId: String, lines: List[String], message: String, throwable: Option[Throwable], indentLevel: Int, styleName: String) = {
def getHTMLForStackTrace(stackTraceList: List[StackTraceElement]) =
stackTraceList.map((ste: StackTraceElement) => { ste.toString })
def getHTMLForCause(throwable: Throwable): scala.xml.NodeBuffer = {
val cause = throwable.getCause
if (cause != null) {
{ Resources("DetailsCause") + ":" }
{ cause.getClass.getName }
{ Resources("DetailsMessage") + ":" }
{ if (cause.getMessage != null) cause.getMessage else Resources("None") }
{ getHTMLForStackTrace(cause.getStackTrace.toList) }
&+ getHTMLForCause(cause)
}
else new scala.xml.NodeBuffer
}
val (grayStackTraceElements, blackStackTraceElements) =
throwable match {
case Some(throwable) =>
val stackTraceElements = throwable.getStackTrace.toList
throwable match {
case sde: exceptions.StackDepthException =>
(stackTraceElements.take(sde.failedCodeStackDepth), stackTraceElements.drop(sde.failedCodeStackDepth))
case _ => (List(), stackTraceElements)
}
case None => (List(), List())
}
val throwableTitle =
throwable match {
case Some(throwable) => Some(throwable.getClass.getName)
case None => None
}
val fileAndLineOption: Option[String] =
throwable match {
case Some(throwable) =>
throwable match {
case stackDepth: StackDepth =>
stackDepth.failedCodeFileNameAndLineNumberString
case _ => None
}
case None => None
}
val linkId = UUID.randomUUID.toString
val contentId = UUID.randomUUID.toString
{
lines.map { line =>
- { line }
}
}
}
private def markup(elementId: String, text: String, indentLevel: Int, styleName: String) =
{ XML.loadString(pegDown.markdownToHtml(text)) }
private def tagMapScript =
"tagMap = { \n" +
tagMap.map { case (elementId, bitSet) => "\"" + elementId + "\": " + bitSet }.mkString(", \n") +
"};\n" +
"applyFilter();"
private var eventList = new ListBuffer[Event]()
private var runEndEvent: Option[Event] = None
def apply(event: Event) {
event match {
case RunStarting(ordinal, testCount, configMap, formatter, location, payload, threadName, timeStamp) =>
case RunCompleted(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) =>
runEndEvent = Some(event)
case RunStopped(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) =>
runEndEvent = Some(event)
case RunAborted(ordinal, message, throwable, duration, summary, formatter, location, payload, threadName, timeStamp) =>
runEndEvent = Some(event)
case SuiteCompleted(ordinal, suiteName, suiteId, suiteClassName, duration, formatter, location, rerunner, payload, threadName, timeStamp) =>
val (suiteEvents, otherEvents) = extractSuiteEvents(suiteId)
eventList = otherEvents
val sortedSuiteEvents = suiteEvents.sorted
if (sortedSuiteEvents.length == 0)
throw new IllegalStateException("Expected SuiteStarting for completion event: " + event + " in the head of suite events, but we got no suite event at all")
sortedSuiteEvents.head match {
case suiteStarting: SuiteStarting =>
val suiteResult = sortedSuiteEvents.foldLeft(SuiteResult(suiteId, suiteName, suiteClassName, duration, suiteStarting, event, Vector.empty ++ sortedSuiteEvents.tail, 0, 0, 0, 0, 0, true)) { case (r, e) =>
e match {
case testSucceeded: TestSucceeded => r.copy(testsSucceededCount = r.testsSucceededCount + 1)
case testFailed: TestFailed => r.copy(testsFailedCount = r.testsFailedCount + 1)
case testIgnored: TestIgnored => r.copy(testsIgnoredCount = r.testsIgnoredCount + 1)
case testPending: TestPending => r.copy(testsPendingCount = r.testsPendingCount + 1)
case testCanceled: TestCanceled => r.copy(testsCanceledCount = r.testsCanceledCount + 1)
case _ => r
}
}
results += suiteResult
makeSuiteFile(suiteResult)
case other =>
throw new IllegalStateException("Expected SuiteStarting for completion event: " + event + " in the head of suite events, but we got: " + other)
}
case SuiteAborted(ordinal, message, suiteName, suiteId, suiteClassName, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) =>
val (suiteEvents, otherEvents) = extractSuiteEvents(suiteId)
eventList = otherEvents
val sortedSuiteEvents = suiteEvents.sorted
if (sortedSuiteEvents.length == 0)
throw new IllegalStateException("Expected SuiteStarting for completion event: " + event + " in the head of suite events, but we got no suite event at all")
sortedSuiteEvents.head match {
case suiteStarting: SuiteStarting =>
val suiteResult = sortedSuiteEvents.foldLeft(SuiteResult(suiteId, suiteName, suiteClassName, duration, suiteStarting, event, Vector.empty ++ sortedSuiteEvents.tail, 0, 0, 0, 0, 0, false)) { case (r, e) =>
e match {
case testSucceeded: TestSucceeded => r.copy(testsSucceededCount = r.testsSucceededCount + 1)
case testFailed: TestFailed => r.copy(testsFailedCount = r.testsFailedCount + 1)
case testIgnored: TestIgnored => r.copy(testsIgnoredCount = r.testsIgnoredCount + 1)
case testPending: TestPending => r.copy(testsPendingCount = r.testsPendingCount + 1)
case testCanceled: TestCanceled => r.copy(testsCanceledCount = r.testsCanceledCount + 1)
case _ => r
}
}
results += suiteResult
makeSuiteFile(suiteResult)
case other =>
throw new IllegalStateException("Expected SuiteStarting for completion event: " + event + " in the head of suite events, but we got: " + other)
}
case _ => eventList += event
}
}
def extractSuiteEvents(suiteId: String) = eventList partition { e =>
e match {
case e: TestStarting => e.suiteId == suiteId
case e: TestSucceeded => e.suiteId == suiteId
case e: TestIgnored => e.suiteId == suiteId
case e: TestFailed => e.suiteId == suiteId
case e: TestPending => e.suiteId == suiteId
case e: TestCanceled => e.suiteId == suiteId
case e: InfoProvided =>
e.nameInfo match {
case Some(nameInfo) =>
nameInfo.suiteId == suiteId
case None => false
}
case e: MarkupProvided =>
e.nameInfo match {
case Some(nameInfo) =>
nameInfo.suiteId == suiteId
case None => false
}
case e: ScopeOpened => e.nameInfo.suiteId == suiteId
case e: ScopeClosed => e.nameInfo.suiteId == suiteId
case e: SuiteStarting => e.suiteId == suiteId
case _ => false
}
}
def dispose() {
runEndEvent match {
case Some(event) =>
event match {
case RunCompleted(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) =>
makeIndexFile("runCompleted", duration)
case RunStopped(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) =>
makeIndexFile("runStopped", duration)
case RunAborted(ordinal, message, throwable, duration, summary, formatter, location, payload, threadName, timeStamp) =>
makeIndexFile("runAborted", duration)
case other =>
throw new IllegalStateException("Expected run ending event only, but got: " + other.getClass.getName)
}
case None => // If no run end event (e.g. when run in sbt), just use runCompleted with sum of suites' duration.
makeIndexFile("runCompleted", Some(results.totalDuration))
}
}
private def getDuration(resourceName: String, duration: Option[Long]) = {
duration match {
case Some(msSinceEpoch) =>
Resources(resourceName + "In", makeDurationString(msSinceEpoch))
case None =>
Resources(resourceName)
}
}
private def getTotalTests(summary: Summary) =
Resources("totalNumberOfTestsRun", summary.testsCompletedCount.toString)
// Suites: completed {0}, aborted {1}
private def getSuiteSummary(summary: Summary) =
Resources("suiteSummary", summary.suitesCompletedCount.toString, summary.suitesAbortedCount.toString)
// Tests: succeeded {0}, failed {1}, canceled {4}, ignored {2}, pending {3}
private def getTestSummary(summary: Summary) =
Resources("testSummary", summary.testsSucceededCount.toString, summary.testsFailedCount.toString, summary.testsCanceledCount.toString, summary.testsIgnoredCount.toString,
summary.testsPendingCount.toString)
// We subtract one from test reports because we add "- " in front, so if one is actually zero, it will come here as -1
// private def indent(s: String, times: Int) = if (times <= 0) s else (" " * times) + s
// Stupid properties file won't let me put spaces at the beginning of a property
// " {0}" comes out as "{0}", so I can't do indenting in a localizable way. For now
// just indent two space to the left. // if (times <= 0) s
// else Resources("indentOnce", indent(s, times - 1))
}
private[tools] object HtmlReporter {
final val SUCCEEDED_BIT = 1
final val FAILED_BIT = 2
final val IGNORED_BIT = 4
final val PENDING_BIT = 8
final val CANCELED_BIT = 16
}
private[tools] object PCDATA {
def apply(in: String): Node = scala.xml.Unparsed(in)
}