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

org.scalatest.Status.scala Maven / Gradle / Ivy

There is a newer version: 3.3.0-SNAP3
Show 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

import scala.collection.GenSet
import java.io.Serializable
import scala.concurrent.Future
import scala.concurrent.Promise
import scala.util.{Try, Success, Failure}

/**
 * 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 */ sealed trait Status { thisStatus => // SKIP-SCALATESTJS-START /** * 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 // SKIP-SCALATESTJS-END /** * 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 // SKIP-SCALATESTJS-START /** * Blocking call that returns only after the underlying test or suite is completed. */ def waitUntilCompleted() // SKIP-SCALATESTJS-END /** * 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: Try[Boolean] => Unit) final def thenRun(f: => Status): Status = { val returnedStatus = new ScalaTestStatefulStatus whenCompleted { _ => val innerStatus = f innerStatus.whenCompleted { tri => tri match { case Success(false) => returnedStatus.setFailed() case Failure(ex) => returnedStatus.setFailed() returnedStatus.setUnreportedException(ex) case _ => } returnedStatus.setCompleted() } } returnedStatus } // True means succeeded, false means a test failure or suite abort happened. final def toFuture: Future[Boolean] = { val promise = Promise[Boolean] whenCompleted { t => promise.complete(t) } promise.future } def unreportedException: Option[Throwable] = None def withAfterEffect(f: => Option[Throwable]): Status = { val returnedStatus = new ScalaTestStatefulStatus whenCompleted { tri => tri match { case Success(result) => f match { case None => if (!result) returnedStatus.setFailed() returnedStatus.setCompleted() case Some(ex) => returnedStatus.setUnreportedException(ex) returnedStatus.setCompleted() } case Failure(originalEx) => f match { case None => returnedStatus.setUnreportedException(originalEx) returnedStatus.setCompleted() case Some(ex) => returnedStatus.setUnreportedException(originalEx) println("ScalaTest can't report this exception because another preceded it, so printing its stack trace:") ex.printStackTrace() returnedStatus.setCompleted() } } } returnedStatus } } /** * 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 { // SKIP-SCALATESTJS-START /** * Always returns true. * * @return true */ def succeeds() = true // SKIP-SCALATESTJS-END /** * Always returns true. * * @return true */ def isCompleted = true // SKIP-SCALATESTJS-START /** * Always returns immediately. */ def waitUntilCompleted() {} // SKIP-SCALATESTJS-END /** * Executes the passed function immediately on the calling thread. */ def whenCompleted(f: Try[Boolean] => Unit) { f(Success(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 { // SKIP-SCALATESTJS-START /** * Always returns false. * * @return true */ def succeeds() = false // SKIP-SCALATESTJS-END /** * Always returns true. * * @return true */ def isCompleted = true // SKIP-SCALATESTJS-START /** * Always returns immediately. */ def waitUntilCompleted() {} // SKIP-SCALATESTJS-END /** * Executes the passed function immediately on the calling thread. */ def whenCompleted(f: Try[Boolean] => Unit) { f(Success(false)) } } case class AbortedStatus(ex: Throwable) extends Status with Serializable { thisAbortedStatus => // SKIP-SCALATESTJS-START /** * Always returns false. * * @return true */ def succeeds() = false // SKIP-SCALATESTJS-END /** * Always returns true. * * @return true */ def isCompleted = true // SKIP-SCALATESTJS-START /** * Always returns immediately. */ def waitUntilCompleted() {} // SKIP-SCALATESTJS-END /** * Executes the passed function immediately on the calling thread. */ def whenCompleted(f: Try[Boolean] => Unit) { f(Failure(unreportedException.get)) } override val unreportedException: Option[Throwable] = Some(ex) } // 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) private var succeeded = true private final val queue = new ConcurrentLinkedQueue[Try[Boolean] => Unit] private var asyncException: Option[Throwable] = None override def unreportedException: Option[Throwable] = { synchronized { asyncException } } // SKIP-SCALATESTJS-START def succeeds() = { waitUntilCompleted() synchronized { succeeded } } // SKIP-SCALATESTJS-END def isCompleted = synchronized { latch.getCount == 0L } // SKIP-SCALATESTJS-START def waitUntilCompleted() { synchronized { latch }.await() } // SKIP-SCALATESTJS-END def setFailed() { synchronized { if (isCompleted) throw new IllegalStateException("status is already completed") succeeded = false } } def setUnreportedException(ex: Throwable): Unit = { // TODO: Throw an exception if it is a fatal one? // TODO: Throw an exception if already have an exception in here. synchronized { if (isCompleted) throw new IllegalStateException("status is already completed") succeeded = false asyncException = Some(ex) } } def setCompleted() { // Moved the for loop after the countdown, to avoid what I think is a race condition whereby we register a call back while // we are iterating through the list of callbacks prior to adding the last one. val it = synchronized { // OLD, OUTDATED COMMENT, left in here to ponder the depths of its meaning a bit longer: // Only release the latch after the callbacks finish execution, to avoid race condition with other thread(s) that wait // for this Status to complete. latch.countDown() queue.iterator } val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } for (f <- it) f(tri) } def whenCompleted(f: Try[Boolean] => Unit) { var executeLocally = false synchronized { if (!isCompleted) queue.add(f) else executeLocally = true } if (executeLocally) { val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } f(tri) } } } /** * 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) private var succeeded = true private final val queue = new ConcurrentLinkedQueue[Try[Boolean] => Unit] // SKIP-SCALATESTJS-START /** * 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() synchronized { succeeded } } // SKIP-SCALATESTJS-END /** * 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 = synchronized { latch.getCount == 0L } // SKIP-SCALATESTJS-START /** * Blocking call that returns only after setCompleted has been invoked on this StatefulStatus instance. */ def waitUntilCompleted() { synchronized { latch }.await() } // SKIP-SCALATESTJS-END /** * 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() { synchronized { 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() { // Moved the for loop after the countdown, to avoid what I think is a race condition whereby we register a call back while // we are iterating through the list of callbacks prior to adding the last one. val it = synchronized { // OLD, OUTDATED COMMENT, left in here to ponder the depths of its meaning a bit longer: // Only release the latch after the callbacks finish execution, to avoid race condition with other thread(s) that wait // for this Status to complete. latch.countDown() queue.iterator } val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } for (f <- it) f(tri) } /** * 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: Try[Boolean] => Unit) { var executeLocally = false synchronized { if (!isCompleted) queue.add(f) else executeLocally = true } if (executeLocally) { val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } f(tri) } } } /** * Composite Status that aggregates its completion and failed states of set of other Statuses passed to its constructor. * * @param status the Statuses 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 var asyncException: Option[Throwable] = None private final val queue = new ConcurrentLinkedQueue[Try[Boolean] => Unit] for (status <- statuses) { status.whenCompleted { tri => val youCompleteMe = synchronized { latch.countDown() tri match { case Success(res) => if (!res) succeeded = false case Failure(ex) => succeeded = false if (asyncException.isEmpty) asyncException = Some(ex) else { println("ScalaTest can't report this exception because another preceded it, so printing its stack trace:") ex.printStackTrace() } } latch.getCount == 0 } if (youCompleteMe) { val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } for (f <- queue.iterator) f(tri) } } } // SKIP-SCALATESTJS-START /** * Blocking call that waits until all composite Statuses have completed, then returns * true only if all of the composite Statuses succeeded. If any Status passed in the statuses set fails, this method * will return false. * * @return true if all composite Statuses succeed, false otherwise. */ def succeeds() = { synchronized { latch }.await() synchronized { statuses }.forall(_.succeeds()) } // SKIP-SCALATESTJS-END /** * Non-blocking call to check if the test or suite run is completed, returns true if all composite Statuses have completed, * false otherwise. You can use this to poll the run status. * * @return true if all composite Statuses have completed, false otherwise. */ def isCompleted = synchronized { statuses }.forall(_.isCompleted) // SKIP-SCALATESTJS-START /** * Blocking call that returns only after all composite Statuss have completed. */ def waitUntilCompleted() { // statuses.foreach(_.waitUntilCompleted()) synchronized { latch }.await() } // SKIP-SCALATESTJS-END /** * 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: Try[Boolean] => Unit) { var executeLocally = false synchronized { if (!isCompleted) queue.add(f) else executeLocally = true } if (executeLocally) { val tri: Try[Boolean] = unreportedException match { case Some(ex) => Failure(ex) case None => Success(succeeded) } f(tri) } } override def unreportedException: Option[Throwable] = synchronized { asyncException } /* synchronized { statuses.map(_.unreportedException).find(_.isDefined).flatten } */ }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy