All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.tools.JUnitXmlReporter.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2013 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 Suite.unparsedXml
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): Unit = {
    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): Unit = {
    require(endEvent.isInstanceOf[SuiteCompleted] ||
            endEvent.isInstanceOf[SuiteAborted])

    val testsuite = getTestsuite(endEvent, suiteId)
    val xmlStr    = xmlify(testsuite)
    val filespec  = directory + "/TEST-" + suiteId + ".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: AlertProvided   => 
          e.nameInfo match {
            case Some(nameInfo) => 
              nameInfo.suiteId == suiteId
            case None => false
          }
        case e: NoteProvided   => 
          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.abortedError = e.throwable
          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: AlertProvided  => idx += 1
        case e: NoteProvided   => idx += 1
        case e: MarkupProvided => idx += 1
        case e: ScopeOpened    => idx += 1
        case e: ScopeClosed    => idx += 1
        case e: ScopePending   => 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)
        case e: DiscoveryStarting  => unexpected(e)
        case e: DiscoveryCompleted => 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.failed = true
          testcase.failure = e.throwable
          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: ScopePending   => idx += 1
        case e: InfoProvided   => idx += 1
        case e: MarkupProvided => idx += 1
        case e: AlertProvided  => idx += 1
        case e: NoteProvided   => 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   => 
          endIndex = idx
          testcase.failed = true
          testcase.failure = e.throwable
          testcase.time = e.timeStamp - testcase.timeStamp
          idx += idxAdjustmentForRecordedEvents(collection.immutable.IndexedSeq.empty[RecordableEvent])

        case e: DiscoveryStarting  => unexpected(e)
        case e: DiscoveryCompleted => unexpected(e)
      }
    }
    (endIndex, testcase)
  }

  //
  // Creates an xml string describing a run of a test suite.
  //
  def xmlify(testsuite: Testsuite): String = {

    val errMsg = testsuite.abortedError.map(getStackTrace(_)).getOrElse("")

    val xmlVal =
      
      { propertiesXml }
      {
        for (testcase <- testsuite.testcases) yield {
          
          {
            if (testcase.ignored || testcase.pending || testcase.canceled)
              
            else
              failureXml(testcase.failed, testcase.failure)
          }
          
        }
      }
        
        {scala.xml.Unparsed("".format(errMsg))}
      

    val prettified = (new PrettyPrinter(76, 2, true)).format(xmlVal)

    "\n" + prettified
  }

  //
  // 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 Throwable, if specified Option
  // contains one.
  //
  private def failureXml(failed: Boolean, failureOption: Option[Throwable]): xml.NodeSeq = 
    if (failed) {
      val (throwableType, throwableMessage, throwableText) =
        failureOption match {
          case None =>
            ("", "", "")

          case Some(failure) =>
            val throwableType = "" + failure.getClass
            val throwableMessage = failure.getMessage()
            val throwableText = getStackTrace(failure)
            (throwableType, throwableMessage, throwableText)
        }
      
        { throwableText }
      
    }
    else
      xml.NodeSeq.Empty

  //
  // 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.
  //
  lazy val hostname: 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): Unit = {
    throw new RuntimeException("unexpected event [" + event + "]")
  }

  //
  // Class to hold information about an execution of a test suite.
  //
  private[scalatest] case class Testsuite(name: String, timeStamp: Long) {
    var errors   = 0
    var failures = 0
    var time     = 0L
    var abortedError: Option[Throwable] = None
    val testcases = new ListBuffer[Testcase]
  }

  //
  // Class to hold information about an execution of a testcase.
  //
  private[scalatest] case class Testcase(name: String, className: Option[String],
                              timeStamp: Long) {
    var time = 0L
    var pending = false
    var canceled = false
    var ignored = false
    var failed = false
    var failure: Option[Throwable] = None
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy