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

org.specs.specification.ExampleLifeCycle.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2007-2009 Eric Torreborre 
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 * the Software. Neither the name of specs nor the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written permission.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
package org.specs.specification
import org.specs.execute.{ FailureException, SkippedException }
import org.specs.util.Configuration
import org.specs.util.ExtendedThrowable._

/**
 * This trait models the execution cycle of an example.
 * 
 * LifeCycles can be chained with a "parent" relationship, where the parent LifeCycle of an example is his 
 * enclosing sus and the parent LifeCycle of a Sus is its enclosing specification.
 * 
 * Since a Specification mixes in this trait, some methods can be overriden to provide more specialized behaviour:
 * 
    *
  • before(/after)Example: code to execute before(/after) the example
  • *
  • before(/after)Expectations: code to execute before(/after) the example expectations. The difference with beforeExample is that * any FailureException thrown by this method will be counted as an example failure (whereas it is counted as an exception in beforeExample)
  • *
  • executeExpectations: execute the example expectations
  • *
  • executeExample: strategy for executing the example. The default strategy is implemented in the ExampleLifeCycle trait. * In this trait, we check if the example is the first one to execute. If so, it is executed right away. If not, the execution is * delegated to the parent LifeCycle which ultimately is the SpecificationExecutor trait which executes the example in * isolation from other examples by executing it in a clone of the Specification. *
  • *
* */ trait LifeCycle extends SequentialExecution { /** lifecycles can be chained via parent-child relationships */ private[specs] var parent: Option[LifeCycle] = None /** * current list of examples defining the context of this execution. Every example created during by the executeExpectations method * will be attached to this list */ private[specs] var current: Option[Examples] = None /** a predicate which will decide if an example must be re-executed */ private[specs] var untilPredicate: Option[() => Boolean] = None /** if this variable is true then the doFirst block is not executed and the example execution must fail */ private[specification] var beforeSystemFailure: Option[FailureException] = None /** execute a block of code with a specific list of examples as the container to use for examples created by the block */ private[specs] def withCurrent(ex: Examples)(a: => Any) = { val c = current.orElse(parent.flatMap(_.current)) setCurrent(Some(ex)) try { a } finally { setCurrent(c) } } /** set an example as the current example for this lifecycle and its parent */ private[specs] def setCurrent(ex: Option[Examples]): Unit = { current = ex parent.map(_.setCurrent(ex)) } /** @return true if there is an until predicate, and if it is true */ def until: Boolean = parent.map(_.until).getOrElse(true) && untilPredicate.getOrElse(() => true)() /** define the actions to be done before an example is executed */ def beforeExample(ex: Examples) = {} /** define the actions to be done after an example has been executed */ def afterExample(ex: Examples) = {} /** define actions to be done just before the example expectations, as if they were part of the example */ def beforeExpectations(ex: Examples): Unit = { parent.map(_.beforeExpectations(ex)) } /** define actions to be done just after the example expectations, as if they were part of the example */ def afterExpectations(ex: Examples): Unit = parent.map(_.afterExpectations(ex)) /** define how to execute the example expectations */ def executeExpectations(ex: Examples, t: =>Any): Any = parent.map(_.executeExpectations(ex, t)).getOrElse(t) /** define how to execute the example itself. This method has to be overriden by subtraits */ def executeExample(ex: Examples): this.type = this } /** * Implementation of a LifeCycle backed up by an ExampleStructure which can * be added any subexample created during the Example execution. * * It will also check that the executed example contains expectations, * otherwise will mark the example as "PENDING" */ trait ExampleLifeCycle extends LifeCycle with ExampleStructure { /** * object containing the method for executing the example expectations, including when and how * the LifeCycle methods should be called. */ private[specs] var execution: Option[ExampleExecution] = None /** @return true if the execution has been executed */ private[specs] def executed = execution.map(_.executed).getOrElse(true) /** abstract method (defined in Example) executing the example itself */ def executeThis: Unit /** * executing the expectations of an example contained by this LifeCycle, * skipping the example if it doesn't contain any expectations */ override def afterExpectations(ex: Examples) = { super.afterExpectations(ex) skipIfNoExpectations() } /** * execute one sub example either right away if this is the first example * of this list of example, otherwise the execution is delegated to the parent lifecycle * for isolated execution */ override def executeExample(ex: Examples): this.type = { if (!exampleList.isEmpty && exampleList.head == ex) ex.executeThis else parent.map(_.executeExample(ex)) // forward the execution strategy to the parent this } /** * copy the execution results from another example. * This method is used when an example has been executed in isolation in another spec. */ def copyExecutionResults(other: Examples) = { copyFrom(other) execution.map(_.executed = true) } /** * @throw a skipped exception if there are no expectations in that example. This behaviour can be overriden by setting the * "examplesWithoutExpectationsMustBePending" property in the Configuration object */ protected def skipIfNoExpectations() = { if (thisExpectationsNumber == 0 && exampleList.isEmpty && thisSkipped.isEmpty && thisFailures.isEmpty && thisErrors.isEmpty && Configuration.config.examplesWithoutExpectationsMustBePending) throw new SkippedException("PENDING: not yet implemented").removeTracesAsFarAsNameMatches("(specification.Example|LiterateSpecification)") } /** reset in order to be able to run the example again. This is only used for testing */ override def resetForExecution: this.type = { super.resetForExecution execution.map(_.resetForExecution) exampleList.foreach(_.resetForExecution) this } } /** Default LifeCycle with no actions before or after. */ object DefaultLifeCycle extends Example("default life cycle") /** This trait defines if the examples must be executed as soon as they are defined */ trait SequentialExecution { /** this variable defines if examples should be executed as soon as defined */ private[specs] protected var sequential = false /** @return true if if examples should be executed as soon as defined */ def isSequential = sequential /** examples should be executed as soon as defined */ def setSequential() = sequential = true /** examples should not be executed as soon as defined */ def setNotSequential() = sequential = false } /** * This class encapsulates the execution of an example. * It will check if the example is tagged for execution ("accepted") and it will execute the expectations as * long as the untilr predicate is not satisfied (if there is one) */ class ExampleExecution(var example: Examples, var expectations: Examples => Any) { /** function containing the expectations to be run */ private var toRun: () => Any = () => { if (example.isAccepted) { execution() while (!example.parent.map(_.until).getOrElse(true)) execution() } else example.addSkipped(new SkippedException("not tagged for execution")) } /** flag used to memorize if the example has already been executed once. In that case, it will not be re-executed */ private[specification] var executed = false /** * execution of the example expectations. The example lifecycle methods are used here:
    *
  • beforeExample: add an error and skip the rest if an exception is thrown
  • *
  • beforeExpectations
  • *
  • executeExpectations
  • *
  • afterExpectations
  • *
  • afterExample: add an error if an exception is thrown
  • *
      */ val execution = () => { var failed = false // try the "before" methods. If there is an exception, add an error and return the current example try { example.beforeExample(example) } catch { case t: Throwable => { example.addError(t) failed = true } } // execute the expectations parameter. If it contains expectations with matchers they will be automatically executed try { if (!failed) { example.beforeExpectations(example) example.executeExpectations(example, expectations(example)) example.afterExpectations(example) } } catch { // failed expectations will launch a FailureException // skipped expectations will launch a SkippedException case f: FailureException => example.addFailure(f) case s: SkippedException => example.addSkipped(s) case t: Throwable => example.addError(t) } // try the "after" methods. If there is an exception, add an error and return the current example try { if (!failed) example.afterExample(example) } catch { case t: Throwable => example.addError(t) } example } /** execute the example, setting a flag to make sure that it is only executed once */ def execute = { if (!executed) { toRun() executed = true } } /** reset the execution so that a new call to execute will indeed execute the expectations again */ def resetForExecution = executed = false }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy