org.scalatest.events.Ordinal.scala Maven / Gradle / Ivy
Show all versions of scalatest_2.9.3 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.events
import org.scalatest._
import java.util.Arrays
/**
* Class used to specify a sequential order for events reported during a test run, so they
* can be arranged in that order in a report even if the events were fired in some other order
* during concurrent or distributed execution.
*
*
* An Ordinal is an immutable object holding a run stamp and a sequence
* of stamps.
* The run stamp is an integer that identifies a particular run. All events
* reported during the same run should share the same run stamp. By contrast, each
* event reported during a particular run should have a different stamps sequence.
* One use case for the run stamp is that the initial run from ScalaTest's GUI
* will have run stamp 0. Subsequent reruns will have run stamps 1,
* 2, 3, etc., so that reports in the GUI can simply be sorted in "ordinal" order. Another
* use case is a set of servers used to run multiple tests simultaneously in a distributed
* fashion. The run stamp can be used to identify the run to which an event belongs.
*
*
*
* The stamps sequence is designed to allow a sequential order of events to be specified during
* concurrent execution of ScalaTest suites. ScalaTest's model for concurrent execution is that
* the suites that make up a run may be executed concurrently, but the tests within a single suite
* will be executed sequentially. In addition to tests, suites may contain nested suites. The default implementation
* of execute in class Suite will first invoke runNestedSuites and
* then runTests. If no Distributor is passed to execute, the
* runNestedSuites method will execute the nested suites sequentially via the same thread
* that invoked runNestedSuites. As a result, suites will by default executed in depth first order
* when executed sequentially. If a Distributor is passed to execute, the
* runNestedSuites method will simply put its nested suites into the Distributor
* and return. Some other threads or processes must then execute those nested suites. Given the default
* implementations of execute and runNestedSuites described here, the Ordinal
* will allow the events from a concurrent run to be sorted in the same depth-first order that the events
* from a corresponding sequential run would arrive.
*
*
*
* Each event reported during a run should be given a unique Ordinal. An Ordinal is required
* by all Event subclasses, instances of which are used to send information to the report
* function passed to a Suite's execute method. The first Ordinal for a run
* can be produced by passing a run stamp to Ordinal's lone public constructor:
*
*
*
* val firstOrdinal = new Ordinal(99)
*
*
*
* The run stamp can be any integer. The Ordinal created in this way can be passed along with the first
* reported event of the run, such as a RunStarting event. Thereafter, new Ordinals for the same run
* can be obtained by calling either next or nextNewOldPair on the previously obtained Ordinal.
* In other words, given an Ordinal, you can obtain the next Ordinal by invoking one of these two
* "next" methods on the Ordinal you have in hand. Before executing a new Suite, the nextNewOldPair
* method should be invoked. This will return two new Ordinals, one for the new Suite about to be executed, and
* one for the currently executing entity (either a Suite or some sort of test runner). At any other time, the next Ordinal
* can be obtained by simply invoking next on the current Ordinal.
*
*
*
* You can convert an Ordinal to a List by invoking toList on it. The resulting List will contain
* the run stamp as its first element, and the contents of its stamps sequence as the subsequent elements. The stamps
* sequence will initially be composed of a single element with the value 0. Thus, toList invoked on the firstOrdinal shown above will
* result in:
*
*
*
* firstOrdinal.toList // results in: List(99, 0)
*
*
*
* Each time next is invoked, the rightmost integer returned by toList will increment:
*
*
*
* val secondOrdinal = firstOrdinal.next
* secondOrdinal.toList // results in: List(99, 1)
*
* val thirdOrdinal = secondOrdinal.next
* thirdOrdinal.toList // result is : List(99, 2)
*
*
*
* When nextNewOldPair is invoked the result will be a tuple whose first element is the first Ordinal for
* the new Suite about to be executed (for example, a nested Suite of the currently executing Suite). The
* second element is the next Ordinal for the currently executing Suite or other entity:
*
*
*
* val (nextForNewSuite, nextForThisRunner) = thirdOrdinal.nextNewOldPair
* nextForNewSuite.toList // results in: (99, 2, 0)
* nextForThisRunner.toList // results in: (99, 3)
*
*
*
* The toList method of the Ordinal for the new suite starts with the same sequence of elements as the Ordinal from which it was
* created, but has one more element, a 0, appended at the end. Subsequent invocations of next on this series of Ordinals will
* increment that last element:
*
*
*
* val newSuiteOrdinal2 = nextForNewSuite.next
* newSuiteOrdinal2.toList // results in: List(99, 2, 1)
*
* val newSuiteOrdinal3 = newSuiteOrdinal2.next
* newSuiteOrdinal3.toList // result is : List(99, 2, 2)
*
*
*
* This behavior allows events fired by Suite running concurrently to be reordered in a pre-determined sequence after all the events
* have been reported. The ordering of two Ordinals can be determined by first comparing the first element of the Lists obtained
* by invoking toList on both Ordinals. These values represent the runStamp. If one run stamp is a lower number than
* the other, that Ordinal comes first. For example, an Ordinal with a run stamp of 98 is ordered before an Ordinal with
* a run stamp of 99. If the run stamps are equal, the next number in the list is inspected. As with the run stamps, an Ordinal with a lower
* number is ordered before an Ordinal with a higher number. If two corresponding elements are equal, the next pair of elements will be inspected.
* This will continue no down the length of the Lists until a position is found where the element values are not equal, or the end of one or both of
* the Lists are reached. If the two Lists are identical all the way to the end, and both Lists have the same lengths,
* then the Ordinals are equal. (Equal Ordinals will not happen if correctly used by creating a new Ordinal for
* each fired event and each new Suite.). If the two Lists are identical all the way to the end of one, but the other List
* is longer (has more elements), then the shorter list is ordered before the longer one.
*
*
*
* As an example, here are some Ordinal List forms in order:
*
*
*
* List(99, 0)
* List(99, 1)
* List(99, 2)
* List(99, 2, 0)
* List(99, 2, 1)
* List(99, 2, 2)
* List(99, 2, 2, 0)
* List(99, 2, 2, 1)
* List(99, 2, 2, 2)
* List(99, 2, 3)
* List(99, 2, 4)
* List(99, 2, 4, 0)
* List(99, 2, 4, 1)
* List(99, 2, 4, 2)
* List(99, 3)
* List(99, 4)
* List(99, 4, 0)
* List(99, 4, 1)
* List(99, 5)
*
*
* @author Bill Venners
*/
final class Ordinal private (val runStamp: Int, private val stamps: Array[Int]) extends Ordered[Ordinal] with java.io.Serializable {
/**
* Construct a the first Ordinal for a run.
*
* @param runStamp a number that identifies a particular run
*/
def this(runStamp: Int) = this(runStamp, Array(0))
/**
* Construct the next Ordinal for the current suite or other entity, such as a runner.
*/
def next: Ordinal = {
val newArray = new Array[Int](stamps.length) // Can't seem to clone
val zipped = stamps.zipWithIndex
for ((num, idx) <- zipped)
newArray(idx) = num
newArray(stamps.length - 1) += 1
new Ordinal(runStamp, newArray)
}
/**
* Construct two new Ordinals, one for a new Suite about to be executed and
* one for the current Suite or other entity, such as a runner. The Ordinal
* for the new Suite is the first (_1) element in the tuple:
*
*
* val (nextOrdinalForNewSuite, nextOrdinalForThisSuite) currentOrdinal.nextNewOldPair
*
*
*
* The reason the next Ordinal for the new Suite is first is because it will
* be ordered before the next Ordinal for the current Suite (or other
* entity such as a runner). In fact, any event reported within the context of the new Suite or
* its nested Suites will be ordered before the next Ordinal for the current Suite.
*
*
* @return a tuple whose first element is the first Ordinal for the new Suite and whose
* second element is the next Ordinal for the current Suite or other entity, such
* as a runner.
*/
def nextNewOldPair: (Ordinal, Ordinal) = {
val newArrayForNewSuite = new Array[Int](stamps.length + 1)
val newArrayForOldSuite = new Array[Int](stamps.length)
val zipped = stamps.zipWithIndex
for ((num, idx) <- zipped) {
newArrayForNewSuite(idx) = num
newArrayForOldSuite(idx) = num
}
newArrayForOldSuite(stamps.length - 1) += 1
(new Ordinal(runStamp, newArrayForNewSuite), new Ordinal(runStamp, newArrayForOldSuite))
}
/**
* Returns a List[Int] representation of this Ordinal. A set of Ordinals will be ordered
* in the same order as the set of List[Int]s that are returned by invoking this method on each of the Ordinals.
* The first element of the returned List[Int] is the runStamp.
*
* @return a List[Int] representation of this Ordinal.
*/
def toList: List[Int] = runStamp :: stamps.toList
/**
* Compares this Ordinal with the passed Ordinal for order. If this object is "less than" (ordered before)
* the passed object, compare will return a negative integer. If this class is "greater than" (ordered after)
* the passed object, compare will return a positive integer. Otherwise, this Ordinal is equal to
* the passed object, and compare will return 0.
*
* @return a negative integer, 0, or positive integer indicating this Ordinal is less than, equal to, or greater than the passed Ordinal.
*/
def compare(that: Ordinal) = {
val runStampDiff = this.runStamp - that.runStamp
if (runStampDiff == 0) {
val shorterLength =
if (this.stamps.length < that.stamps.length)
this.stamps.length
else
that.stamps.length
var i = 0
var diff = 0
while (diff == 0 && i < shorterLength) {
diff = this.stamps(i) - that.stamps(i)
i += 1
}
// If they were equal all the way to the shorterLength, the longest array
// one is the greater ordinal. This is because the newSuite stuff happens
// before the next thing that happens in the old suite.
if (diff != 0) diff
else this.stamps.length - that.stamps.length
}
else runStampDiff
}
/**
* Indicates whether the passed object is equal to this one.
*
* @param the object with which to compare this one for equality
* @return true if the passed object is equal to this one
*/
override def equals(other: Any): Boolean =
other match {
case that: Ordinal =>
runStamp == that.runStamp &&
(stamps.deep equals that.stamps.deep)
case _ => false
}
/**
* Returns a hash code value for this object.
*
* @return a hash code for this object
*/
override def hashCode: Int =
41 * (
41 + runStamp
) + Arrays.hashCode(stamps)
/**
* Returns a string that includes the integers returned by toList.
*/
override def toString: String = toList.mkString("Ordinal(", ", ", ")")
}