org.scalatest.fixture.Suite.scala Maven / Gradle / Ivy
/* * 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.fixture /** * Base trait for a family of style traits that can pass a fixture object into tests. * * @author Bill Venners */ 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 /* * A
. * */ // Can just inherit the supertrait implementation of testnames that returns an empty set /* 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 } */ /** * Run a test. * *Set
of test names. If thisfixture.Suite
contains no tests, this method returns an emptySet
. * ** This trait's implementation of this method uses Java reflection to discover all public methods whose name starts with
* *"test"
, * which take either nothing, a singleInformer
, a singleFixtureParam
or two parameters of typeFixtureParam
* andInformer
. For each discovered test method, it assigns a test name * comprised of just the method name if the method takes no parameters, or the method name plus(Informer)
orFixtureParam
* if the method takes aInformer
orFixtureParam
. * Here are a few method signatures and the names that this trait's implementation assigns them: ** def testCat() {} // test name: "testCat" * def testCat(Informer) {} // test name: "testCat(Informer)" * def testCat(FixtureParam) {} // test name: "testCat(FixtureParam)" * def testCat(FixtureParam, Informer) {} // test name: "testCat(FixtureParam, Informer)" * def testDog() {} // test name: "testDog" * def testDog(Informer) {} // test name: "testDog(Informer)" * def testDog(FixtureParam) {} // test name: "testDog(FixtureParam)" * def testDog(FixtureParam, Informer) {} // test name: "testDog(FixtureParam, Informer)" * def test() {} // test name: "test" * def test(Informer) {} // test name: "test(Informer)" * def test(FixtureParam) {} // test name: "test(FixtureParam)" * def test(FixtureParam, Informer) {} // test name: "test(FixtureParam, Informer)" ** ** This trait's implementation of this method returns an immutable
* *Set
of all such names, excluding the name *testNames
. The iterator obtained by invokingelements
on this * returnedSet
will produce the test names in their natural order, as determined byString
's *compareTo
method. ** This trait's implementation of
* *runTests
invokes this method * and callsrunTest
for each test name in the order they appear in the returnedSet
's iterator. * Although this trait's implementation of this method returns aSet
whose iterator producesString
* test names in a well-defined order, the contract of this method does not required a defined order. Subclasses are free to * override this method and return test names in an undefined order, or in a defined order that's different fromString
's * natural order. ** Subclasses may override this method to produce test names in a custom manner. One potential reason to override
* *testNames
is * to run tests in a different order, for example, to ensure that tests that depend on other tests are run after those other tests. * Another potential reason to override is allow tests to be defined in a different manner, such as methods annotated@Test
annotations * (as is done inJUnitSuite
andTestNGSuite
) or test functions registered during construction (as is * done inFunSuite
andFunSpec
). ** In ScalaTest's event model, a test may be surrounded by “scopes.” Each test and scope is associated with string of text. * A test's name is concatenation of the text of any surrounding scopes followed by the text provided with the test * itself, after each text element has been trimmed and one space inserted between each component. Here's an example: *
* ** package org.scalatest.examples.fixture.freespec * * import org.scalatest._ * * class SetSpec extends fixture.FreeSpec with fixture.UnitFixture { * * "A Set" - { * "when empty" - { * "should have size 0" in { * assert(Set.empty.size === 0) * } * * "should produce NoSuchElementException when head is invoked" in { * assertThrows[NoSuchElementException] { * Set.empty.head * } * } * } * } * } ** ** The above
FreeSpec
contains two tests, both nested inside the same two scopes. The outermost scope names * the subject,A Set
. The nested scope qualifies the subject withwhen empty
. Inside that * scope are the two tests. The text of the tests are: ** *
*
* *- *
should have size 0
- *
should produce NoSuchElementException when head is invoked
* Therefore, the names of these two tests are: *
* **
* *- *
A Stack when empty should have size 0
- *
A Stack when empty should produce NoSuchElementException when head is invoked
* Note that because the component scope and test text strings are trimmed, any leading or trailing space will be dropped * before they are strung together to form the test name, with each trimmed component separated by a space. If the scopes * in the above example had text
" A Set "
and" when empty "
, and the first test had text *" should have size 0 "
, its test name would still be the same, "A Set when empty should have size 0"* This trait's implementation uses Java reflection to invoke on this object the test method identified by the passed
* *testName
. ** Implementations of this method are responsible for ensuring a
* * @param testName the name of one test to run. * @param args theTestStarting
event * is fired to theReporter
before executing any test, and eitherTestSucceeded
, *TestFailed
,TestPending
orTestCanceled
after executing any nested *Suite
. (If a test is marked with theorg.scalatest.Ignore
tag, the *runTests
method is responsible for ensuring aTestIgnored
event is fired and that * thisrunTest
method is not invoked for that ignored test.) *Args
for this run * @return aStatus
object that indicates when the test started by this method has completed, and whether or not it failed . * * @throws NullArgumentException if any oftestName
orargs
isnull
. * @throws IllegalArgumentException iftestName
is defined, but no test with the specified test name * exists in thisSuite
*/ // Can just inheirt the supertrait implementation of runTest that /* 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(thisSuite, stopper, reporter, testName) reportTestStarting(thisSuite, report, tracker, testName, testName, thisSuite.rerunner, Some(getTopOfMethod(thisSuite, 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)).toUnit } 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)).toUnit } val duration = System.currentTimeMillis - testStartTime reportTestSucceeded(thisSuite, report, tracker, testName, testName, messageRecorderForThisTest.recordedEvents(false, false), duration, formatter, thisSuite.rerunner, Some(getTopOfMethod(thisSuite, 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(thisSuite, 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(thisSuite, method)), thisSuite.rerunner)) SucceededStatus case e if !anExceptionThatShouldCauseAnAbort(e) => val duration = System.currentTimeMillis - testStartTime handleFailedTest(thisSuite, t, testName, messageRecorderForThisTest.recordedEvents(false, false), report, tracker, getEscapedIndentedTextForTest(testName, 1, true), duration) FailedStatus case e => throw e } case e if !anExceptionThatShouldCauseAnAbort(e) => val duration = System.currentTimeMillis - testStartTime handleFailedTest(thisSuite, 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(theSuite: org.scalatest.Suite, testName: String): Method = { val candidateMethods = theSuite.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. * * @returnorg.scalatest.fixture.Suite
*/ 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]) } */