org.scalatest.fixture.Suite.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.9.0 Show documentation
/*
* 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.fixture
import org.scalatest._
import collection.immutable.TreeSet
import Suite._
import java.lang.reflect.{InvocationTargetException, Method, Modifier}
import org.scalatest.events._
import org.scalatest.Suite._
import exceptions.{TestCanceledException, TestPendingException}
/**
* Base trait for a family of style traits that can pass a fixture object into tests.
*
*
* Prior to ScalaTest 2.0.M4, trait fixture.Suite
served two purposes: 1) It served as the base
* class of ScalaTest's family of "fixture" style traits, and 2) It was itself a style trait in which tests are methods
* that take a fixture parameter. Although it will continue to serve its first purpose, fixture.Suite
has
* been deprecated as a style trait. Pre-existing code that used fixture.Suite
as a style trait to define
* tests as methods will continue to work during the deprecation period, but will generate a deprecation warning. Please
* change all such uses of fixture.Suite
to use trait fixture.Spec
instead.
*
*
* @author Bill Venners
*/
@Finders(Array("org.scalatest.finders.MethodFinder"))
trait Suite extends org.scalatest.Suite { thisSuite =>
/**
* The type of the fixture parameter that can be passed into tests in this suite.
*/
protected type FixtureParam
/**
* Trait whose instances encapsulate a test function that takes a fixture and config map.
*
*
* The fixture.Suite
trait's implementation of runTest
passes instances of this trait
* to fixture.Suite
's withFixture
method, such as:
*
*
*
* def testSomething(fixture: Fixture) {
* // ...
* }
* def testSomethingElse(fixture: Fixture, info: Informer) {
* // ...
* }
*
*
*
* For more detail and examples, see the
* documentation for trait fixture.Suite
.
*
*/
protected trait OneArgTest extends (FixtureParam => Unit) with TestData { thisOneArgTest =>
/**
* Run the test, using the passed FixtureParam
.
*/
def apply(fixture: FixtureParam)
/**
* Convert this OneArgTest
to a NoArgTest
whose
* name
and configMap
methods return the same values
* as this OneArgTest
, and whose apply
method invokes
* this OneArgTest
's apply method,
* passing in the given fixture
.
*
*
* This method makes it easier to invoke the withFixture
method
* that takes a NoArgTest
. For example, if a fixture.Suite
* mixes in SeveredStackTraces
, it will inherit an implementation
* of withFixture(NoArgTest)
provided by
* SeveredStackTraces
that implements the stack trace severing
* behavior. If the fixture.Suite
does not delegate to that
* withFixture(NoArgTest)
method, the stack trace severing behavior
* will not happen. Here's how that might look in a fixture.Suite
* whose FixtureParam
is StringBuilder
:
*
*
*
* def withFixture(test: OneArgTest) {
* withFixture(test.toNoArgTest(new StringBuilder))
* }
*
*/
def toNoArgTest(fixture: FixtureParam) =
new NoArgTest {
val name = thisOneArgTest.name
val configMap = thisOneArgTest.configMap
def apply() { thisOneArgTest(fixture) }
val scopes = thisOneArgTest.scopes
val text = thisOneArgTest.text
val tags = thisOneArgTest.tags
}
}
/**
* Run the passed test function with a fixture created by this method.
*
*
* This method should create the fixture object needed by the tests of the
* current suite, invoke the test function (passing in the fixture object),
* and if needed, perform any clean up needed after the test completes.
* For more detail and examples, see the main documentation for this trait.
*
*
* @param fun the OneArgTest
to invoke, passing in a fixture
*/
protected def withFixture(test: OneArgTest)
private[fixture] class TestFunAndConfigMap(val name: String, test: FixtureParam => Any, val configMap: Map[String, Any])
extends OneArgTest {
def apply(fixture: FixtureParam) {
test(fixture)
}
private val testData = testDataFor(name, configMap)
val scopes = testData.scopes
val text = testData.text
val tags = testData.tags
}
private[fixture] class FixturelessTestFunAndConfigMap(override val name: String, test: () => Any, override val configMap: Map[String, Any])
extends NoArgTest {
def apply() { test() }
private val testData = testDataFor(name, configMap)
val scopes = testData.scopes
val text = testData.text
val tags = testData.tags
}
// TODO: add documentation here, so people know they can pass an Informer as the second arg.
override def testNames: Set[String] = {
def takesTwoParamsOfTypesAnyAndInformer(m: Method) = {
val paramTypes = m.getParameterTypes
val hasTwoParams = paramTypes.length == 2
hasTwoParams && classOf[Informer].isAssignableFrom(paramTypes(1))
}
def takesOneParamOfAnyType(m: Method) = m.getParameterTypes.length == 1
def isTestMethod(m: Method) = {
// Factored out to share code with Suite.testNames
val (isInstanceMethod, simpleName, firstFour, paramTypes, hasNoParams, isTestNames, isTestTags, isTestDataFor) = isTestMethodGoodies(m)
// Also, will discover both
// testNames(Object) and testNames(Object, Informer). Reason is if I didn't discover these
// it would likely just be silently ignored, and that might waste users' time
isInstanceMethod && (firstFour == "test") && !isTestDataFor && ((hasNoParams && !isTestNames && !isTestTags) ||
takesInformer(m) || takesOneParamOfAnyType(m) || takesTwoParamsOfTypesAnyAndInformer(m))
}
val testNameArray =
for (m <- getClass.getMethods; if isTestMethod(m)) yield
if (takesInformer(m))
m.getName + InformerInParens
else if (takesOneParamOfAnyType(m))
m.getName + FixtureInParens
else if (takesTwoParamsOfTypesAnyAndInformer(m))
m.getName + FixtureAndInformerInParens
else m.getName
TreeSet[String]() ++ testNameArray
}
protected override def runTest(testName: String, args: Args): Status = {
if (testName == null)
throw new NullPointerException("testName was null")
if (args == null)
throw new NullPointerException("args was null")
import args._
val (stopRequested, report, method, testStartTime) =
getSuiteRunTestGoodies(stopper, reporter, testName)
reportTestStarting(thisSuite, report, tracker, testName, testName, thisSuite.rerunner, Some(getTopOfMethod(testName)))
val formatter = getEscapedIndentedTextForTest(testName, 1, true)
val messageRecorderForThisTest = new MessageRecorder(report)
val informerForThisTest =
MessageRecordingInformer(
messageRecorderForThisTest,
(message, payload, isConstructingThread, testWasPending, testWasCanceled, location) => createInfoProvided(thisSuite, report, tracker, Some(testName), message, payload, 2, location, isConstructingThread, true)
)
val documenterForThisTest =
MessageRecordingDocumenter(
messageRecorderForThisTest,
(message, _, isConstructingThread, testWasPending, testWasCanceled, location) => createInfoProvided(thisSuite, report, tracker, Some(testName), message, None, 2, location, isConstructingThread, true) // TODO: Need a test that fails because testWasCanceleed isn't being passed
)
// TODO: Use a message recorder in FixtureSuite. Maybe just allow the state and
// use Engine in Suite, though then I'd have two Engines in everything. Or even three down here.
// Nah, go ahead and use message recording informer here, and maybe find some other way to
// reduce the duplication between Suite, FixtureSuite, and Engine.
try {
if (testMethodTakesAFixtureAndInformer(testName) || testMethodTakesAFixture(testName)) {
val testFun: FixtureParam => Unit = {
(fixture: FixtureParam) => {
val anyRefFixture: AnyRef = fixture.asInstanceOf[AnyRef] // TODO zap this cast
val args: Array[Object] =
if (testMethodTakesAFixtureAndInformer(testName)) {
Array(anyRefFixture, informerForThisTest)
}
else
Array(anyRefFixture)
method.invoke(thisSuite, args: _*)
}
}
withFixture(new TestFunAndConfigMap(testName, testFun, configMap))
}
else { // Test method does not take a fixture
val testFun: () => Unit = {
() => {
val args: Array[Object] =
if (testMethodTakesAnInformer(testName))
Array(informerForThisTest)
else
Array()
method.invoke(this, args: _*)
}
}
withFixture(new FixturelessTestFunAndConfigMap(testName, testFun, configMap))
}
val duration = System.currentTimeMillis - testStartTime
reportTestSucceeded(thisSuite, report, tracker, testName, testName, messageRecorderForThisTest.recordedEvents(false, false), duration, formatter, thisSuite.rerunner, Some(getTopOfMethod(method)))
SucceededStatus
}
catch {
case ite: InvocationTargetException =>
val t = ite.getTargetException
t match {
case _: TestPendingException =>
val duration = System.currentTimeMillis - testStartTime
// testWasPending = true so info's printed out in the finally clause show up yellow
reportTestPending(thisSuite, report, tracker, testName, testName, messageRecorderForThisTest.recordedEvents(true, false), duration, formatter, Some(getTopOfMethod(method)))
SucceededStatus
case e: TestCanceledException =>
val duration = System.currentTimeMillis - testStartTime
val message = getMessageForException(e)
val formatter = getEscapedIndentedTextForTest(testName, 1, true)
// testWasCanceled = true so info's printed out in the finally clause show up yellow
report(TestCanceled(tracker.nextOrdinal(), message, thisSuite.suiteName, thisSuite.suiteId, Some(thisSuite.getClass.getName), testName, testName, messageRecorderForThisTest.recordedEvents(false, true), Some(e), Some(duration), Some(formatter), Some(getTopOfMethod(method)), thisSuite.rerunner))
SucceededStatus
case e if !anErrorThatShouldCauseAnAbort(e) =>
val duration = System.currentTimeMillis - testStartTime
handleFailedTest(t, testName, messageRecorderForThisTest.recordedEvents(false, false), report, tracker, getEscapedIndentedTextForTest(testName, 1, true), duration)
FailedStatus
case e => throw e
}
case e if !anErrorThatShouldCauseAnAbort(e) =>
val duration = System.currentTimeMillis - testStartTime
handleFailedTest(e, testName, messageRecorderForThisTest.recordedEvents(false, false), report, tracker, getEscapedIndentedTextForTest(testName, 1, true), duration)
FailedStatus
case e: Throwable => throw e
}
}
// Overriding this in fixture.Suite to reduce duplication of tags method
private[scalatest] override def getMethodForTestName(testName: String) = {
val candidateMethods = getClass.getMethods.filter(_.getName == Suite.simpleNameForTest(testName))
val found =
if (testMethodTakesAFixtureAndInformer(testName))
candidateMethods.find(
candidateMethod => {
val paramTypes = candidateMethod.getParameterTypes
paramTypes.length == 2 && paramTypes(1) == classOf[Informer]
}
)
else if (testMethodTakesAnInformer(testName))
candidateMethods.find(
candidateMethod => {
val paramTypes = candidateMethod.getParameterTypes
paramTypes.length == 1 && paramTypes(0) == classOf[Informer]
}
)
else if (testMethodTakesAFixture(testName))
candidateMethods.find(
candidateMethod => {
val paramTypes = candidateMethod.getParameterTypes
paramTypes.length == 1
}
)
else
candidateMethods.find(_.getParameterTypes.length == 0)
found match {
case Some(method) => method
case None =>
throw new IllegalArgumentException(Resources("testNotFound", testName))
}
}
/**
* Suite style name.
*/
override val styleName: String = "org.scalatest.fixture.Suite"
}
private[scalatest] object Suite {
val FixtureAndInformerInParens = "(FixtureParam, Informer)"
val FixtureInParens = "(FixtureParam)"
private def testMethodTakesAFixtureAndInformer(testName: String) = testName.endsWith(FixtureAndInformerInParens)
private[scalatest] def testMethodTakesAFixture(testName: String) = testName.endsWith(FixtureInParens)
private[scalatest] def simpleNameForTest(testName: String) =
if (testName.endsWith(FixtureAndInformerInParens))
testName.substring(0, testName.length - FixtureAndInformerInParens.length)
else if (testName.endsWith(FixtureInParens))
testName.substring(0, testName.length - FixtureInParens.length)
else if (testName.endsWith(InformerInParens))
testName.substring(0, testName.length - InformerInParens.length)
else
testName
private def argsArrayForTestName(testName: String): Array[Class[_]] =
if (testMethodTakesAFixtureAndInformer(testName))
Array(classOf[Object], classOf[Informer])
else
Array(classOf[Informer])
}