org.scalatest.Status.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.11.0-RC2 Show documentation
/*
* 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
import org.scalatest.tools.SuiteRunner
import java.util.concurrent.CountDownLatch
import scala.collection.GenSet
import java.util.concurrent.ConcurrentLinkedQueue
import collection.JavaConverters._
import java.io.Serializable
/**
* The result status of running a test or a suite.
*
*
* This trait is the return type of the "run" lifecycle methods of trait Suite
: run
, runNestedSuites
,
* runTests
, and runTest
. It can be used to determine whether a test or suite has completed, and if completed,
* whether it succeeded or failed. The main use case for this trait in ScalaTest is to enable BeforeAndAfterAll
's afterAll
* method to wait until all relevant tests and nested suites have completed before performing the "after all" code, even if those tests are
* nested suites are run in parallel.
*
*
* @author cheeseng
*/
trait Status {
/**
* Blocking call that waits until completion, then returns returns true
if no tests failed and no suites aborted, else returns false
.
*
*
* This only reports false
if there was a failed test or aborted suite in the context of the "run" lifecycle method it was returned from. For example,
* if you call succeeds
on the return Status
of runTest
, it returns (after that test has completed) true
if the
* test whose name was passed to runTest
succeeded, false
if that test failed (or the suite aborts). If you call
* succeeds
on the return value of runTests
, by contrast, it returns (after the suite's tests have completed) true
only
* if all tests in the suite succeeded. If any test in the suite fails (or the whole suite aborts), the succeeds
call will return false
.
* The Status
returned from runNestedSuites
will return true only if all tests in all nested suites (and their nested suites, etc.) fired
* off by that runNestedSuites
call succeed and no suites abort.
* Simlarly, the Status
returned from run
will return true only if all tests in all nested suites (and their nested suites, etc.) fired
* off by that run
call succeed and no suites abort.
*
*
* @return true
if no tests failed and no suites aborted, false
otherwise
*/
def succeeds(): Boolean
/**
* Non-blocking call that indicates whether the all the tests or nested suites fired off by the run method that returned the Status
have completed.
* Because this is non-blocking, you can use this to poll the completion status.
*
* @return true
if the test or suite run is already completed, false
otherwise.
*/
def isCompleted: Boolean
/**
* Blocking call that returns only after the underlying test or suite is completed.
*/
def waitUntilCompleted()
/**
* Registers the passed function to be executed when this status completes.
*
*
* You may register multiple functions, which on completion will be executed in an undefined
* order.
*
*/
def whenCompleted(f: Boolean => Unit)
}
/**
* Singleton status that represents an already completed run with no tests failed and no suites aborted.
*
*
* Note: the difference between this SucceededStatus
object and the similarly named Succeeded
* object is that the Succeeded
object indicates one test succeeded, whereas this SucceededStatus
object indicates the absence
* of any failed tests or aborted suites during a run. Both are used as the result type of Suite
lifecycle methods, but Succeeded
* is a possible result of withFixture
, whereas SucceededStatus
is a possible result of run
, runNestedSuites
,
* runTests
, or runTest
. In short, Succeeded
is always just about one test, whereas SucceededStatus
could be
* about something larger: multiple tests or an entire suite.
*
*/
object SucceededStatus extends Status with Serializable {
/**
* Always returns true
.
*
* @return true
*/
def succeeds() = true
/**
* Always returns true
.
*
* @return true
*/
def isCompleted = true
/**
* Always returns immediately.
*/
def waitUntilCompleted() {}
/**
* Executes the passed function immediately on the calling thread.
*/
def whenCompleted(f: Boolean => Unit) { f(true) }
}
/**
* Singleton status that represents an already completed run with at least one failed test or aborted suite.
*
*
* Note: the difference between this FailedStatus
object and the similarly named Failed
* class is that a Failed
instance indicates one test failed, whereas this FailedStatus
object indicates either one or more tests failed
* and/or one or more suites aborted during a run. Both are used as the result type of Suite
lifecycle methods, but Failed
* is a possible result of withFixture
, whereas FailedStatus
is a possible result of run
, runNestedSuites
,
* runTests
, or runTest
. In short, Failed
is always just about one test, whereas FailedStatus
could be
* about something larger: multiple tests or an entire suite.
*
*/
object FailedStatus extends Status with Serializable {
/**
* Always returns false
.
*
* @return true
*/
def succeeds() = false
/**
* Always returns true
.
*
* @return true
*/
def isCompleted = true
/**
* Always returns immediately.
*/
def waitUntilCompleted() {}
/**
* Executes the passed function immediately on the calling thread.
*/
def whenCompleted(f: Boolean => Unit) { f(false) }
}
// Used internally in ScalaTest. We don't use the StatefulStatus, because
// then user code could pattern match on it and then access the setCompleted
// and setFailed methods. We wouldn't want that.
private[scalatest] final class ScalaTestStatefulStatus extends Status with Serializable {
@transient private final val latch = new CountDownLatch(1)
@volatile private var succeeded = true
private final val queue = new ConcurrentLinkedQueue[Boolean => Unit]
def succeeds() = {
waitUntilCompleted()
succeeded
}
def isCompleted = latch.getCount == 0L
def waitUntilCompleted() {
latch.await()
}
def setFailed() {
if (isCompleted)
throw new IllegalStateException("status is already completed")
succeeded = false
}
def setCompleted() {
synchronized {
latch.countDown()
}
for (f <- queue.iterator.asScala)
f(succeeded)
}
def whenCompleted(f: Boolean => Unit) {
var executeLocally = false
synchronized {
if (!isCompleted)
queue.add(f)
else
executeLocally = true
}
if (executeLocally)
f(succeeded)
}
}
/**
* Status implementation that can change its state over time.
*
*
* A StatefulStatus
begins its life in a successful state, and will remain successful unless setFailed
is called.
* Once setFailed
is called, the status will remain at failed. The setFailed
method can be called multiple times (even
* though invoking it once is sufficient to permanently set the status to failed), but only up until setCompleted
has been called.
* After setCompleted
has been called, any invocation of setFailed
will be greeted with an IllegalStateException
.
*
*
*
* Instances of this class are thread safe.
*
*/
final class StatefulStatus extends Status with Serializable {
@transient private final val latch = new CountDownLatch(1)
@volatile private var succeeded = true
private final val queue = new ConcurrentLinkedQueue[Boolean => Unit]
/**
* Blocking call that waits until completion, as indicated by an invocation of setCompleted
on this instance, then returns returns false
* if setFailed
was called on this instance, else returns true
.
*
* @return true
if no tests failed and no suites aborted, false
otherwise
*/
def succeeds() = {
waitUntilCompleted()
succeeded
}
/**
* Non-blocking call that returns true
if setCompleted
has been invoked on this instance, false
otherwise.
*
* @return true
if the test or suite run is already completed, false
otherwise.
*/
def isCompleted = latch.getCount == 0L
/**
* Blocking call that returns only after setCompleted
has been invoked on this StatefulStatus
instance.
*/
def waitUntilCompleted() {
latch.await()
}
/**
* Sets the status to failed without changing the completion status.
*
*
* This method may be invoked repeatedly, even though invoking it once is sufficient to set the state of the Status
to failed, but only
* up until setCompleted
has been called. Once setCompleted
has been called, invoking this method will result in a
* thrown IllegalStateException
.
*
*
* @throws IllegalStateException if this method is invoked on this instance after setCompleted
has been invoked on this instance.
*/
def setFailed() {
if (isCompleted)
throw new IllegalStateException("status is already completed")
succeeded = false
}
/**
* Sets the status to completed.
*
*
* This method may be invoked repeatedly, even though invoking it once is sufficient to set the state of the Status
to completed.
*
*/
def setCompleted() {
synchronized {
latch.countDown()
}
for (f <- queue.iterator.asScala)
f(succeeded)
}
/**
* Registers the passed function to be executed when this status completes.
*
*
* You may register multiple functions, which on completion will be executed in an undefined
* order.
*
*/
def whenCompleted(f: Boolean => Unit) {
var executeLocally = false
synchronized {
if (!isCompleted)
queue.add(f)
else
executeLocally = true
}
if (executeLocally)
f(succeeded)
}
}
/**
* Composite Status
that aggregates its completion and failed states of set of other Status
es passed to its constructor.
*
* @param status the Status
es out of which this status is composed.
*/
final class CompositeStatus(statuses: Set[Status]) extends Status with Serializable {
// TODO: Ensure this is visible to another thread, because I'm letting the reference
// escape with my for loop below prior to finishing this object's construction.
@transient private final val latch = new CountDownLatch(statuses.size)
@volatile private var succeeded = true
private final val queue = new ConcurrentLinkedQueue[Boolean => Unit]
for (status <- statuses) {
status.whenCompleted { st =>
synchronized {
latch.countDown()
}
if (!st)
succeeded = false
if (latch.getCount == 0) {
for (f <- queue.iterator.asScala)
f(succeeded)
}
}
}
/**
* Blocking call that waits until all composite Status
es have completed, then returns
* true
only if all of the composite Status
es succeeded. If any Status
passed in the statuses
set fails, this method
* will return false
.
*
* @return true
if all composite Status
es succeed, false
otherwise.
*/
def succeeds() = statuses.forall(_.succeeds())
/**
* Non-blocking call to check if the test or suite run is completed, returns true
if all composite Status
es have completed,
* false
otherwise. You can use this to poll the run status.
*
* @return true
if all composite Status
es have completed, false
otherwise.
*/
def isCompleted = statuses.forall(_.isCompleted)
/**
* Blocking call that returns only after all composite Status
s have completed.
*/
def waitUntilCompleted() {
statuses.foreach(_.waitUntilCompleted())
}
/**
* Registers the passed function to be executed when this status completes.
*
*
* You may register multiple functions, which on completion will be executed in an undefined
* order.
*
*/
def whenCompleted(f: Boolean => Unit) {
var executeLocally = false
synchronized {
if (!isCompleted)
queue.add(f)
else
executeLocally = true
}
if (executeLocally)
f(succeeded)
}
}