org.specs.specification.Context.scala Maven / Gradle / Ivy
Show all versions of specs_2.8.0.Beta1-RC8
/**
* 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.util._
import org.specs.util.ExtendedString._
import scala.xml._
import org.specs.matcher._
import scala.collection.mutable._
import org.specs.runner._
import org.specs.matcher.MatcherUtils._
import org.specs.SpecUtils._
import org.specs.specification._
import org.specs.util.ExtendedThrowable._
import scala.reflect.Manifest
import org.specs.execute._
/**
* This traits adds before / after capabilities to specifications, so that a context can be defined for
* each system under test being specified and trigger some actions before or after each example.
*/
trait BeforeAfter { outer: BaseSpecification =>
/** adds a "before" function to the last sus being defined */
def doBefore(actions: =>Any) = usingBefore(() => actions)
/** adds a "around" function to the last sus being defined */
def doAroundExpectations(actions: (=>Any) =>Any) = current.map(_.aroundExpectations = Some(actions))
/** adds an "after" function to the last sus being defined */
def doAfter(actions: =>Any) = usingAfter(() => actions)
/** adds a "firstActions" function to the last sus being defined */
def doFirst(actions: =>Any) = current.map(stackFirstActions(_, actions))
/** adds a "lastActions" function to the last sus being defined */
def doLast(actions: =>Any) = current.map(stackLastActions(_, actions))
/** adds a "beforeSpec" function to the current specification */
def doBeforeSpec(actions: =>Any) = beforeSpec = stackActions(() => actions, beforeSpec)
/** adds a "afterSpec" function to the current specification */
def doAfterSpec(actions: =>Any) = afterSpec = reverseStackActions(() => actions, afterSpec)
/** adds a "before" function to the last sus being defined */
private def usingBefore(actions: () => Any) = current.map(stackBeforeActions(_, actions))
/** adds an "after" function to the last sus being defined */
private def usingAfter(actions: () => Any) = current.map(stackAfterActions(_, actions))
/** actions are stacked so that several before actions can be triggered one after the other */
private[specs] def stackBeforeActions(sus: Examples, actions: () => Any) = sus.before = stackActions(actions, sus.before)
/** actions are stacked so that several before actions can be triggered one after the other */
private[specs] def stackAroundActions(sus: Examples, actions: (=>Any) => Any) = sus.aroundExpectations = stackAround(actions, sus.aroundExpectations)
/** adds an "after" function to a sus */
private[specs] def stackAfterActions(sus: Examples, actions: () => Any) = sus.after = reverseStackActions(actions, sus.after)
/** adds "firstActions" to a sus */
private[specs] def stackFirstActions(sus: Examples, actions: =>Any) = sus.firstActions = stackActions(() => actions, sus.firstActions)
/** adds "lastActions" to a sus */
private[specs] def stackLastActions(sus: Examples, actions: =>Any) = sus.lastActions = reverseStackActions(() => actions, sus.lastActions)
/** repeats examples according to a predicate */
def until(predicate: =>Boolean): Unit = current.map(until(_, predicate))
/** repeats examples according to a predicate */
def until(sus: Examples, predicate: =>Boolean) = sus.untilPredicate = Some(() => { predicate })
/** @return a function with actions being executed after the previous actions. */
private def stackActions(actions: () => Any, previousActions: Option[() => Any]) = {
Some(() => {
previousActions.map(a => a())
actions()
})
}
/** @return a function with actions being stacked around. */
private def stackAround(actions: (=>Any) => Any, previousActions: Option[(=>Any) => Any]) = {
def newActions(a: =>Any) = {
previousActions match {
case None => actions(a)
case Some(previous) => actions(previous(a))
}
}
Some(newActions(_))
}
/** @return a function with actions being executed before the previous actions. */
private def reverseStackActions(actions: () => Any, previousActions: Option[() => Any]) = {
Some(() => {
actions()
previousActions.map(a => a())
})
}
/**
* Syntactic sugar for before/after actions.
* Usage: "a system" should { createObjects.before
* ...
*
*/
implicit def toShortActions(actions: =>Unit) = new ShortActions(actions)
/**
* Syntactic sugar for before/after actions.
* Usage: "a system" should { createObjects.before
* ...
*
*/
class ShortActions(actions: =>Unit) {
def before = doBefore(actions)
def after = doAfter(actions)
def doFirst: Unit = outer.doFirst(actions)
def doLast: Unit = outer.doLast(actions)
def beforeSpec = outer.doBeforeSpec(actions)
def afterSpec = outer.doAfterSpec(actions)
}
/**
* Syntactic sugar for actions around expectations.
* Usage: "a system" should { (a => createObjects(a)).aroundExpectations
* ...
*
*/
implicit def toShortActions2(actions: Any => Any) = new ShortActions2(actions)
/**
* Syntactic sugar for actions around expectations.
* Usage: "a system" should { (a => createObjects(a)).aroundExpectations
* ...
*
*/
class ShortActions2(actions: Any => Any) {
def aroundExpectations = doAroundExpectations(actions)
}
}
/**
* This trait helps creating Context objects encapsulating the before/after actions which can be associated to a sus.
*
* It provides factory methods to create Context objects, with before example actions only, or after example actions only, etc,...
*
* A context object can be passed to a sus description:
* "a system" ->-(context) should { ... }
*
*
* A context for a whole specification can also be created:
*
* object specWithDatabase extends Specification {
* new SpecContext {
* beforeSpec(initDb)
* beforeExample(deleteUserTable)
* }
* "A repository" should { ... }
* }
*
*
* This context can contain beforeSpec/afterSpec methods and will be applied to every system in the specification.
*/
trait Contexts extends BaseSpecification with BeforeAfter { outer =>
/** Factory method to create a context with beforeAll only actions */
def contextFirst(actions: => Any) = new Context { first(actions) }
/** Factory method to create a context with before only actions */
def beforeContext(actions: => Any) = new Context { before(actions) }
/** Factory method to create a context with around only actions */
def aroundExpectationsContext(actions: (=>Any) => Any) = new Context { aroundExpectations(actions) }
/** Factory method to create a context with before only actions and an until predicate */
def beforeContext(actions: => Any, predicate: =>Boolean) = new Context { before(actions); until(predicate()) }
/** Factory method to create a context with after only actions */
def afterContext(actions: => Any) = new Context { after(actions) }
/** Factory method to create a context with afterAll actions */
def contextLast(actions: => Any) = new Context { last(actions) }
/** Factory method to create a context with after only actions and an until predicate */
def afterContext(actions: => Any, predicate: =>Boolean) = new Context { after(actions); until(predicate()) }
/** Factory method to create a context with after only actions */
def context(b: => Any, a: =>Any) = new Context { before(b); after(a) }
/** Factory method to create a context with before/after actions */
def globalContext(b: => Any, a: =>Any) = new Context { first(b); last(a) }
/** Factory method to create a context with before/after actions and an until predicate */
def context(b: => Any, a: =>Any, predicate: =>Boolean) = new Context { before(b); after(a); until(predicate()) }
/**
* Syntactic sugar to create pass a new context before creating a sus.
* Usage: "a system" ->-(context) should {
* ..
*
* In that case before/after actions defined in the context will be set on the defined sus.
*/
implicit def whenInContext(s: String) = ToContext(s)
/**
* Syntactic sugar to create pass a new context before creating a sus.
* Usage: "a system" ->(context) should {
* ...
*
* In that case before/after actions defined in the context will be set on the defined sus.
*/
case class ToContext(desc: String) {
def when(context: Context): Sus = ->-(context)
def definedAs(context: Context): Sus = ->-(context)
def ->-(context: =>Context): Sus = {
// if (context == null) throw new NullPointerException("the context is null")
specifySus(context, desc)
}
}
/**
* Create a sus with a specific context
*/
private def specifySus(context: =>Context, desc: String): Sus = {
// if (context == null) throw new NullPointerException("the context is null")
transferActionsToSus(specify(desc), context)
}
/**
* add all the actions defined on a Context object to a sus
*/
private def transferActionsToSus(sus: Sus, context: =>Context) ={
stackFirstActions(sus, context.firstActions())
stackBeforeActions(sus, () => context.beforeActions())
stackAroundActions(sus, x => context.aroundExpectationsActions(x))
stackAfterActions(sus, () => context.afterActions())
stackLastActions(sus, context.lastActions())
until(sus, context.predicate())
sus
}
/**
* optional specification context
*/
private[specs] var specContext: Option[Context] = None
/**
* Specification context possibly adding actions, before and after the specification.
*
* This object can be created in a given specification and passed on to another specification with the apply method:
* object DatabaseContext extends Specification {
* val setup = new SpecContext {
* beforeSpec(initDb)
* beforeExample(deleteUserTable)
* }
* }
* object DatabaseSpec1 extends Specification {
* DatabaseContext.setup(this) // setup the context of this specification
* ...
* }
*
*/
case class SpecContext() extends Context { thisContext =>
/** the specification is automatically updated with this specification context */
specContext = Some(this)
/**
* specification which must be setup with this context
*
* By default the specification that needs to be setup with this context is 'outer'
* However this needs to be changed if this context is applied to another specification
*/
private var specification: BaseSpecification with BeforeAfter = outer
/**
* create a new context object and setup spec with it
* @return a new SpecContext setup on spec
*/
def apply(spec: Contexts with BeforeAfter): SpecContext = {
val originalSpecification = outer
val newContext = new SpecContext {
specification = spec
originalSpecification.beforeSpec.map(actions => beforeSpec(actions()))
originalSpecification.afterSpec.map(actions => afterSpec(actions()))
first(thisContext.firstActions())
last(thisContext.lastActions())
before(thisContext.beforeActions())
after(thisContext.afterActions())
def around(a: =>Any) = thisContext.aroundExpectationsActions(a)
aroundExpectations(around(_))
until(thisContext.predicate())
}
spec.specContext = Some(newContext)
newContext
}
/** adds a "beforeSpec" function to the current specification */
def beforeSpec(actions: =>Any) = specification.doBeforeSpec(actions)
/** adds a "afterSpec" function to the current specification */
def afterSpec(actions: =>Any) = specification.doAfterSpec(actions)
/** register the before actions and propagate them to the specification systems */
override def before(actions: =>Any) = {
super.before(actions);
specification.systems.map(sus => stackBeforeActions(sus, () => beforeActions()))
this
}
/** register the around actions and propagate them to the specification systems */
override def aroundExpectations(actions: (=>Any) =>Any) = {
super.aroundExpectations(actions);
specification.systems.map(sus => stackAroundActions(sus, aroundExpectationsActions))
this
}
/** register the after actions and propagate them to the specification systems */
override def after(actions: =>Any) = {
super.after(actions)
specification.systems.map(sus => stackAfterActions(sus, () => afterActions()))
this
}
/** register the first actions and propagate them to the specification systems */
override def first(actions: =>Any) = {
super.first(actions)
specification.systems.map(sus => stackFirstActions(sus, () => firstActions()))
this
}
/** register the last actions and propagate them to the specification systems */
override def last(actions: =>Any) = {
super.last(actions)
specification.systems.map(sus => stackLastActions(sus, () => lastActions()))
this
}
/** register the until predicate and propagate it to the specification systems */
override def until(predicate: =>Boolean) = {
super.until(predicate)
specification.systems.map(sus => specification.until(sus, predicate))
this
}
}
/**
* When a sus is specified, transfer the context actions to it
*/
override implicit def specifySus(desc: String): SpecifiedSus = {
val sus = new Sus(desc, this)
specContext map { c => transferActionsToSus(sus, c) }
new SpecifiedSus(addSus(sus))
}
}
/**
* class holding before and after functions to be set on a system under test.
* Context objects are usually created using the factory methods of the Contexts trait:
*
* // this method returns a context object which can be passed to a System under test (with "a system" ->(context) should {... )
* // so that initSystem is done before each example and so that each example is repeated until enoughTestsAreExecuted is true
* beforeContext(initSystem).until(enoughTestsAreExecuted)
*
*/
class Context {
private[specs] var firstActions: () => Any = () => ()
private[specs] var lastActions: () => Any = () => ()
private[specs] var beforeActions: () => Any = () => ()
private[specs] var aroundExpectationsActions: (=>Any) => Any = {
def id(a: =>Any) = a
id(_)
}
private[specs] var afterActions: () => Any = () => ()
private[specs] var predicate: () => Boolean = () => true
/** actions to execute before each example */
def before(actions: =>Any) = { beforeActions = () => actions; this }
/** alias for before */
def beforeExample(actions: =>Any) = before(actions)
/** around a simple example expectations */
def aroundExpectations(actions: (=>Any) =>Any) = { aroundExpectationsActions = actions; this }
/** actions to execute after each example */
def after(actions: =>Any) = { afterActions = () => actions; this }
/** alias for after */
def afterExample(actions: =>Any) = after(actions)
/** actions to execute before each sus */
def first(actions: =>Any) = { firstActions = () => actions; this }
/** alias for first */
def beforeSus(actions: =>Any) = first(actions)
/** actions to execute after each sus */
def last(actions: =>Any) = { lastActions = () => actions; this }
/** alias for last */
def afterSus(actions: =>Any) = last(actions)
/** predicate used to determine when example execution should be stopped */
def until(predicate: =>Boolean) = { this.predicate = () => predicate; this }
}