org.scalatest.tools.JUnitXmlReporter.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 org.scalatest.events._
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.Enumeration
import java.util.Properties
import java.net.UnknownHostException
import java.net.InetAddress
import scala.collection.mutable.Set
import scala.collection.mutable.ListBuffer
import scala.xml
/**
* A Reporter
that writes test status information in XML format
* using the same format as is generated by the xml formatting option of the
* ant <junit> task.
*
* A separate file is written for each test suite, named TEST-[classname].xml,
* to the directory specified.
*
* @exception IOException if unable to open the file for writing
*
* @author George Berger
*/
private[scalatest] class JUnitXmlReporter(directory: String) extends Reporter {
private val events = Set.empty[Event]
private val propertiesXml = genPropertiesXml
//
// Records events in 'events' set. Generates xml from events upon receipt
// of SuiteCompleted or SuiteAborted events.
//
def apply(event: Event) {
events += event
event match {
case e: SuiteCompleted =>
writeSuiteFile(e, e.suiteId)
case e: SuiteAborted =>
writeSuiteFile(e, e.suiteId)
case _ =>
}
}
//
// Writes the xml file for a single test suite. Removes processed
// events from the events Set as they are used.
//
private def writeSuiteFile(endEvent: Event, suiteId: String) {
require(endEvent.isInstanceOf[SuiteCompleted] ||
endEvent.isInstanceOf[SuiteAborted])
val testsuite = getTestsuite(endEvent, suiteId)
val xmlStr = xmlify(testsuite)
val filespec = directory + "/TEST-" + testsuite.name + ".xml"
val out = new PrintWriter(filespec, "UTF-8")
out.print(xmlStr)
out.close()
}
//
// Constructs a Testsuite object corresponding to a specified
// SuiteCompleted or SuiteAborted event.
//
// Scans events reported so far and builds the Testsuite from events
// associated with the specified suite. Removes events from
// the class's events Set as they are consumed.
//
// Only looks at events that have the same ordinal prefix as the
// end event being processed (where an event's ordinal prefix is its
// ordinal list with last element removed). Events with the same
// prefix get processed sequentially, so filtering this way eliminates
// events from any nested suites being processed concurrently
// that have not yet completed when the parent's SuiteCompleted or
// SuiteAborted event is processed.
//
private def getTestsuite(endEvent: Event, suiteId: String): Testsuite = {
require(endEvent.isInstanceOf[SuiteCompleted] ||
endEvent.isInstanceOf[SuiteAborted])
val orderedEvents = events.toList.filter { 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 e: SuiteAborted => e.suiteId == suiteId
case e: SuiteCompleted => e.suiteId == suiteId
case _ => false
}
}.sortWith((a, b) => a < b).toArray
val (startIndex, endIndex) = locateSuite(orderedEvents, endEvent)
val startEvent = orderedEvents(startIndex).asInstanceOf[SuiteStarting]
events -= startEvent
val name =
startEvent.suiteClassName match {
case Some(className) => className
case None => startEvent.suiteName
}
val testsuite = Testsuite(name, startEvent.timeStamp)
var idx = startIndex + 1
while (idx <= endIndex) {
val event = orderedEvents(idx)
events -= event
event match {
case e: TestStarting =>
val (testEndIndex, testcase) = processTest(orderedEvents, e, idx)
testsuite.testcases += testcase
if (testcase.failure != None) testsuite.failures += 1
idx = testEndIndex + 1
case e: SuiteAborted =>
assert(endIndex == idx)
testsuite.errors += 1
testsuite.time = e.timeStamp - testsuite.timeStamp
idx += 1
case e: SuiteCompleted =>
assert(endIndex == idx)
testsuite.time = e.timeStamp - testsuite.timeStamp
idx += 1
case e: TestIgnored =>
val testcase = Testcase(e.testName, e.suiteClassName, e.timeStamp)
testcase.ignored = true
testsuite.testcases += testcase
idx += 1
case e: InfoProvided => idx += 1
case e: MarkupProvided => idx += 1
case e: ScopeOpened => idx += 1
case e: ScopeClosed => idx += 1
case e: TestPending => unexpected(e)
case e: TestCanceled => unexpected(e)
case e: RunStarting => unexpected(e)
case e: RunCompleted => unexpected(e)
case e: RunStopped => unexpected(e)
case e: RunAborted => unexpected(e)
case e: TestSucceeded => unexpected(e)
case e: TestFailed => unexpected(e)
case e: SuiteStarting => unexpected(e)
}
}
testsuite
}
//
// Finds the indexes for the SuiteStarted and SuiteCompleted or
// SuiteAborted endpoints of a test suite within an ordered array of
// events, given the terminating SuiteCompleted or SuiteAborted event.
//
// Searches sequentially through the array to find the specified
// SuiteCompleted event and its preceding SuiteStarting event.
//
// (The orderedEvents array does not contain any SuiteStarting events
// from nested suites running concurrently because of the ordinal-prefix
// filtering performed in getTestsuite(). It does not contain any from
// nested suites running sequentially because those get removed when they
// are processed upon occurrence of their corresponding SuiteCompleted
// events.)
//
private def locateSuite(orderedEvents: Array[Event],
endEvent: Event):
(Int, Int) = {
require(orderedEvents.size > 0)
require(endEvent.isInstanceOf[SuiteCompleted] ||
endEvent.isInstanceOf[SuiteAborted])
var startIndex = 0
var endIndex = 0
var idx = 0
while ((idx < orderedEvents.size) && (endIndex == 0)) {
val event = orderedEvents(idx)
event match {
case e: SuiteStarting =>
startIndex = idx
case e: SuiteCompleted =>
if (event == endEvent) {
endIndex = idx
assert(
e.suiteName ==
orderedEvents(startIndex).asInstanceOf[SuiteStarting].
suiteName)
}
case e: SuiteAborted =>
if (event == endEvent) {
endIndex = idx
assert(
e.suiteName ==
orderedEvents(startIndex).asInstanceOf[SuiteStarting].
suiteName)
}
case _ =>
}
idx += 1
}
assert(endIndex > 0)
assert(orderedEvents(startIndex).isInstanceOf[SuiteStarting])
(startIndex, endIndex)
}
private def idxAdjustmentForRecordedEvents(recordedEvents: collection.immutable.IndexedSeq[RecordableEvent]) =
recordedEvents.filter(e => e.isInstanceOf[InfoProvided] || e.isInstanceOf[MarkupProvided]).size
//
// Constructs a Testcase object from events in orderedEvents array.
//
// Accepts a TestStarting event and its index within orderedEvents.
// Returns a Testcase object plus the index to its corresponding
// test completion event. Removes events from class's events Set
// as they are processed.
//
private def processTest(orderedEvents: Array[Event],
startEvent: TestStarting, startIndex: Int):
(Int, Testcase) = {
val testcase = Testcase(startEvent.testName, startEvent.suiteClassName,
startEvent.timeStamp)
var endIndex = 0
var idx = startIndex + 1
while ((idx < orderedEvents.size) && (endIndex == 0)) {
val event = orderedEvents(idx)
events -= event
event match {
case e: TestSucceeded =>
endIndex = idx
testcase.time = e.timeStamp - testcase.timeStamp
idx += idxAdjustmentForRecordedEvents(e.recordedEvents)
case e: TestFailed =>
endIndex = idx
testcase.failure = Some(e)
testcase.time = e.timeStamp - testcase.timeStamp
idx += idxAdjustmentForRecordedEvents(e.recordedEvents)
case e: TestPending =>
endIndex = idx
testcase.pending = true
idx += idxAdjustmentForRecordedEvents(e.recordedEvents)
case e: TestCanceled =>
endIndex = idx
testcase.canceled = true
idx += idxAdjustmentForRecordedEvents(e.recordedEvents)
case e: ScopeOpened => idx += 1
case e: ScopeClosed => idx += 1
case e: InfoProvided => idx += 1
case e: MarkupProvided => idx += 1
case e: SuiteCompleted => unexpected(e)
case e: TestStarting => unexpected(e)
case e: TestIgnored => unexpected(e)
case e: SuiteStarting => unexpected(e)
case e: RunStarting => unexpected(e)
case e: RunCompleted => unexpected(e)
case e: RunStopped => unexpected(e)
case e: RunAborted => unexpected(e)
case e: SuiteAborted => unexpected(e)
}
}
(endIndex, testcase)
}
//
// Creates an xml string describing a run of a test suite.
//
def xmlify(testsuite: Testsuite): String = {
val xmlVal =
{ propertiesXml }
{
for (testcase <- testsuite.testcases) yield {
{
if (testcase.ignored || testcase.pending || testcase.canceled)
else
failureXml(testcase.failure)
}
}
}
val prettified = (new xml.PrettyPrinter(76, 2)).format(xmlVal)
// scala xml strips out the elements, so restore them here
val withCDATA =
prettified.
replace(" ",
" ").
replace(" ",
" ")
"\n" + withCDATA
}
//
// Returns string representation of stack trace for specified Throwable,
// including any nested exceptions.
//
def getStackTrace(throwable: Throwable): String = {
"" + throwable +
Array.concat(throwable.getStackTrace).mkString("\n at ",
"\n at ", "\n") +
{
if (throwable.getCause != null) {
" Cause: " +
getStackTrace(throwable.getCause)
}
else ""
}
}
//
// Generates xml for TestFailed event, if specified Option
// contains one.
//
private def failureXml(failureOption: Option[TestFailed]): xml.NodeSeq = {
failureOption match {
case None =>
xml.NodeSeq.Empty
case Some(failure) =>
val (throwableType, throwableText) =
failure.throwable match {
case None => ("", "")
case Some(throwable) =>
val throwableType = "" + throwable.getClass
val throwableText = getStackTrace(throwable)
(throwableType, throwableText)
}
{ throwableText }
}
}
//
// Returns toString value of option contents if Some, or empty string if
// None.
//
private def strVal(option: Option[Any]): String = {
option match {
case Some(x) => "" + x
case None => ""
}
}
//
// Determines hostname of local machine.
//
private def findHostname: String = {
try {
val localMachine = InetAddress.getLocalHost();
localMachine.getHostName
} catch {
case e: UnknownHostException => "unknown"
}
}
//
// Generates element of xml.
//
private def genPropertiesXml: xml.Elem = {
val sysprops = System.getProperties
{
for (name <- propertyNames(sysprops))
yield
}
}
//
// Returns a list of the names of properties in a Properties object.
//
private def propertyNames(props: Properties): List[String] = {
val listBuf = new ListBuffer[String]
val enumeration = props.propertyNames
while (enumeration.hasMoreElements)
listBuf += "" + enumeration.nextElement
listBuf.toList
}
//
// Formats timestamp into a string for display, e.g. "2009-08-31T14:59:37"
//
private def formatTimeStamp(timeStamp: Long): String = {
val dateFmt = new SimpleDateFormat("yyyy-MM-dd")
val timeFmt = new SimpleDateFormat("HH:mm:ss")
dateFmt.format(timeStamp) + "T" + timeFmt.format(timeStamp)
}
//
// Throws an exception if an unexpected Event is encountered.
//
def unexpected(event: Event) {
throw new RuntimeException("unexpected event [" + event + "]")
}
//
// Class to hold information about an execution of a test suite.
//
private case class Testsuite(name: String, timeStamp: Long) {
var errors = 0
var failures = 0
var time = 0L
val testcases = new ListBuffer[Testcase]
}
//
// Class to hold information about an execution of a testcase.
//
private case class Testcase(name: String, className: Option[String],
timeStamp: Long) {
var time = 0L
var pending = false
var canceled = false
var ignored = false
var failure: Option[TestFailed] = None
}
}