org.scalatest.DispatchReporter.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scalatest_2.11.0-M3 Show documentation
Show all versions of scalatest_2.11.0-M3 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
import java.util.concurrent.CountDownLatch
import scala.actors.Exit
import scala.actors.Actor
import scala.actors.Actor.actor
import scala.actors.Actor.loop
import scala.actors.Actor.receive
import java.io.PrintStream
import org.scalatest.events._
import DispatchReporter.propagateDispose
/**
* A Reporter
that dispatches test results to other Reporter
s.
* Attempts to dispatch each method invocation to each contained Reporter
,
* even if some Reporter
methods throw Exception
s. Catches
* Exception
s thrown by Reporter
methods and prints error
* messages to the standard error stream.
*
* The primary constructor creates a new DispatchReporter
with specified Reporter
s list.
* Each object in the reporters
list must implement Reporter
.
*
* @param reporters the initial Reporter
s list for this
* DispatchReporter
* @throws NullPointerException if reporters
is null
.
* @author Bill Venners
*/
private[scalatest] class DispatchReporter(val reporters: List[Reporter], out: PrintStream) extends Reporter {
private case object Dispose
private val latch = new CountDownLatch(1)
private val julia = actor {
var alive = true // local variable. Only used by the Actor's thread, so no need for synchronization
class Counter {
var testsSucceededCount = 0
var testsFailedCount = 0
var testsIgnoredCount = 0
var testsPendingCount = 0
var suitesCompletedCount = 0
var suitesAbortedCount = 0
}
val counterMap = scala.collection.mutable.Map[Int, Counter]()
def incrementCount(event: Event, f: (Counter) => Unit) {
val runStamp = event.ordinal.runStamp
if (counterMap.contains(runStamp)) {
val counter = counterMap(runStamp)
f(counter)
}
else {
val counter = new Counter
f(counter)
counterMap(runStamp) = counter
}
}
// If None, that means don't update the summary so forward the old event. If Some,
// create a new event with everything the same except the old summary replaced by the new one
def updatedSummary(oldSummary: Option[Summary], ordinal: Ordinal): Option[Summary] = {
oldSummary match {
case None if (counterMap.contains(ordinal.runStamp)) => {
// Update the RunAborted so that it is the same except it has a new Some(Summary)
val counter = counterMap(ordinal.runStamp)
Some(
Summary(
counter.testsSucceededCount,
counter.testsFailedCount,
counter.testsIgnoredCount,
counter.testsPendingCount,
counter.suitesCompletedCount,
counter.suitesAbortedCount
)
)
}
case _ => None // Also pass the old None summary through if it isn't in the counterMap
}
}
while (alive) {
receive {
case event: Event =>
try {
// The event will only actually be updated if it it is a RunCompleted/Aborted/Stopped event with None
// as its summary and its runstamp has a counter entry. In that case, it will be given a Summary taken
// from the counter. (And the counter will be removed from the counterMap.) These are counted here, because
// they need to be counted on this side of any FilterReporters that may be in place. (In early versions of
// ScalaTest, these were wrongly being counted by the reporters themselves, so if a FilterReporter filtered
// out TestSucceeded events, then they just weren't being counted.
val updatedEvent =
event match {
case _: RunStarting => counterMap(event.ordinal.runStamp) = new Counter; event
case _: TestSucceeded => incrementCount(event, _.testsSucceededCount += 1); event
case _: TestFailed => incrementCount(event, _.testsFailedCount += 1); event
case _: TestIgnored => incrementCount(event, _.testsIgnoredCount += 1); event
case _: TestPending => incrementCount(event, _.testsPendingCount += 1); event
case _: SuiteCompleted => incrementCount(event, _.suitesCompletedCount += 1); event
case _: SuiteAborted => incrementCount(event, _.suitesAbortedCount += 1); event
case oldRunCompleted @ RunCompleted(ordinal, duration, summary, formatter, payload, threadName, timeStamp) =>
updatedSummary(summary, ordinal) match {
case None => oldRunCompleted
case newSummary @ Some(_) =>
counterMap.remove(ordinal.runStamp)
// Update the RunCompleted so that it is the same except it has a new Some(Summary)
RunCompleted(ordinal, duration, newSummary, formatter, payload, threadName, timeStamp)
}
case oldRunStopped @ RunStopped(ordinal, duration, summary, formatter, payload, threadName, timeStamp) =>
updatedSummary(summary, ordinal) match {
case None => oldRunStopped
case newSummary @ Some(_) =>
counterMap.remove(ordinal.runStamp)
// Update the RunStopped so that it is the same except it has a new Some(Summary)
RunStopped(ordinal, duration, newSummary, formatter, payload, threadName, timeStamp)
}
case oldRunAborted @ RunAborted(ordinal, message, throwable, duration, summary, formatter, payload, threadName, timeStamp) =>
updatedSummary(summary, ordinal) match {
case None => oldRunAborted
case newSummary @ Some(_) =>
counterMap.remove(ordinal.runStamp)
// Update the RunAborted so that it is the same except it has a new Some(Summary)
RunAborted(ordinal, message, throwable, duration, newSummary, formatter, payload, threadName, timeStamp)
}
case _ => event
}
for (report <- reporters)
report(updatedEvent)
}
catch {
case e: Exception =>
val stringToPrint = Resources("reporterThrew", event)
out.println(stringToPrint)
e.printStackTrace(out)
}
case Dispose =>
try {
for (reporter <- reporters)
propagateDispose(reporter)
}
catch {
case e: Exception =>
val stringToPrint = Resources("reporterDisposeThrew")
out.println(stringToPrint)
e.printStackTrace(out)
}
finally {
alive = false
latch.countDown()
}
}
}
}
def this(reporters: List[Reporter]) = this(reporters, System.out)
def this(reporter: Reporter) = this(List(reporter), System.out)
// Invokes dispose on each Reporter in this DispatchReporter's reporters list.
// This method fires an event at the actor that is taking care of serializing
// events, and at some time later the actor's thread will attempts to invoke
// dispose on each contained Reporter, even if some Reporter's dispose methods throw
// Exceptions. This method catches any Exception thrown by
// a dispose method and handles it by printing an error message to the
// standard error stream. Once finished with that, the actor's thread will return.
//
// This method will not return until the actor's thread has exited.
//
def dispatchDisposeAndWaitUntilDone() {
julia ! Dispose
latch.await()
}
def apply(event: Event) {
julia ! event
}
}
// TODO: Not a real problem, but if a DispatchReporter ever got itself in
// its list of reporters, this would end up being an infinite loop. But
// That first part, a DispatchReporter getting itself in there would be the real
// bug.
private[scalatest] object DispatchReporter {
def propagateDispose(reporter: Reporter) {
reporter match {
case dispatchReporter: DispatchReporter => dispatchReporter.dispatchDisposeAndWaitUntilDone()
case catchReporter: CatchReporter => catchReporter.catchDispose()
case resourcefulReporter: ResourcefulReporter => resourcefulReporter.dispose()
case _ =>
}
}
}