org.scalatest.time.Span.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.time import java.io.Serializable import Span.totalNanosForDoubleLength import Span.totalNanosForLongLength /** * A time span. * *
Long, as in: * * ** A
* *Span
is used to express time spans in ScalaTest, in constructs such as the *failAfter
method of traitTimeouts
, * thetimeLimit
field of trait *TimeLimitedTests
, and * the timeouts of traitsEventually
, * andAsyncAssertions
. Here's an example: ** import org.scalatest.time.Span * import org.scalatest.time.Millis * import org.scalatest.concurrent.Timeouts._ * * failAfter(Span(100, Millis)) { * // ... * } ** ** If you prefer you can mix in or import the members of
* *SpanSugar
and place a units value after the timeout value. * Here are some examples: ** import org.scalatest.time.SpanSugar._ * import org.scalatest.concurrent.Timeouts._ * * failAfter(100 millis) { * // ... * } * * failAfter(1 second) { * // ... * } ** ** In addition to expression the numeric value with an
* *Int
or aLong
, you * can also express it via aFloat
orDouble
. Here are some examples: ** import org.scalatest.time.Span * import org.scalatest.time.Seconds * import org.scalatest.concurrent.Timeouts._ * * failAfter(Span(1.5, Seconds)) { * // ... * } * * import org.scalatest.time.SpanSugar._ * * failAfter(0.8 seconds) { * // ... * } ** ** Internally, a
* *Span
is expressed in terms of aLong
number of nanoseconds. Thus, the maximum * time span that can be represented isLong.MaxValue
nanoseconds, or approximately 292 years. * Hopefully these won't be "famous last words," but 292 years should be sufficient for software testing purposes. * Any attempt to create aSpan
longer thanLong.MaxValue
nanoseconds will be met with * anIllegalArgumentException
: ** Span(Long.MaxValue, Nanoseconds) // Produces the longest possible time.Span * Span(Long.MaxValue, Seconds) // Produces an IllegalArgumentException ** ** All of class
* *Span
's constructors are private. The only way you can create a newSpan
is * via one of the twoapply
factory methods inSpan
's * companion object. Here is a table showing one example of each numeric type and unit value: **
* ** ** *Int
** *Long
** *Float
** *Double
** ** Span(1, Nanosecond) * ** Span(1L, Nanosecond) * ** Span(1.0F, Nanosecond) * ** Span(1.0, Nanosecond) * ** ** Span(100, Nanoseconds) * ** Span(100L, Nanoseconds) * ** Span(99.8F, Nanoseconds) * ** Span(99.8, Nanoseconds) * ** ** Span(1, Microsecond) * ** Span(1L, Microsecond) * ** Span(1.0F, Microsecond) * ** Span(1.0, Microsecond) * ** ** Span(100, Microseconds) * ** Span(100L, Microseconds) * ** Span(99.8F, Microseconds) * ** Span(99.8, Microseconds) * ** ** Span(1, Millisecond) * ** Span(1L, Millisecond) * ** Span(1.0F, Millisecond) * ** Span(1.0, Millisecond) * ** ** Span(100, Milliseconds) * ** Span(100L, Milliseconds) * ** Span(99.8F, Milliseconds) * ** Span(99.8, Milliseconds) * ** ** Span(100, Millis) * ** Span(100L, Millis) * ** Span(99.8F, Millis) * ** Span(99.8, Millis) * ** ** Span(1, Second) * ** Span(1L, Second) * ** Span(1.0F, Second) * ** Span(1.0, Second) * ** ** Span(100, Seconds) * ** Span(100L, Seconds) * ** Span(99.8F, Seconds) * ** Span(99.8, Seconds) * ** ** Span(1, Minute) * ** Span(1L, Minute) * ** Span(1.0F, Minute) * ** Span(1.0, Minute) * ** ** Span(100, Minutes) * ** Span(100L, Minutes) * ** Span(99.8F, Minutes) * ** Span(99.8, Minutes) * ** ** Span(1, Hour) * ** Span(1L, Hour) * ** Span(1.0F, Hour) * ** Span(1.0, Hour) * ** ** Span(100, Hours) * ** Span(100L, Hours) * ** Span(99.8F, Hours) * ** Span(99.8, Hours) * ** ** Span(1, Day) * ** Span(1L, Day) * ** Span(1.0F, Day) * ** Span(1.0, Day) * ** ** Span(100, Days) * ** Span(100L, Days) * ** Span(99.8F, Days) * ** Span(99.8, Days) * ** Note that because of implicit conversions in the
* * @author Bill Venners */ final class Span private (totNanos: Long, lengthString: String, unitsMessageFun: String => String, unitsName: String) extends Serializable { private[time] def this(length: Long, units: Units) = { this( totalNanosForLongLength(length, units), length.toString, if (length == 1) units.singularMessageFun else units.pluralMessageFun, units.toString ) } private def this(length: Double, units: Units) = { this( totalNanosForDoubleLength(length, units), length.toString, if (length == 1.0) units.singularMessageFun else units.pluralMessageFun, units.toString ) } /** * The total number of nanoseconds in this time span. * * This number will never be negative, but can be zero. */ val totalNanos: Long = totNanos // Didn't use a parametric field because don't know how to scaladoc that in a // private constructor /** * This time span converted to milliseconds, computed viaSpan
companion object, you can use a *scala.concurrent.duration.Duration
where aSpan
is needed, and vice versa. *totalNanos / 1000000
, which * truncates off any leftover nanoseconds. * ** The
* *millisPart
andnanosPart
can be used, for example, when invoking *Thread.sleep
. For example, given aSpan
namedspan
, you could * write: ** Thread.sleep(span.millisPart, span.nanosPart) **/ lazy val millisPart: Long = totalNanos / 1000000 /** * The number of nanoseconds remaining when this time span is converted to milliseconds, computed via *(totalNanos % 1000000).toInt
. * ** The
* *millisPart
andnanosPart
can be used, for example, when invoking *Thread.sleep
. For example, given aSpan
namedspan
, you could * write: ** Thread.sleep(span.millisPart, span.nanosPart) **/ lazy val nanosPart: Int = (totalNanos % 1000000).toInt /** * Returns aSpan
representing thisSpan
scaled by the passed factor. * ** The passed
* *factor
can be any positive number or zero, including fractional numbers. A number * greater than one will scale theSpan
up to a larger value. A fractional number will scale it * down to a smaller value. A factor of 1.0 will cause the exact sameSpan
to be returned. A * factor of zero will causeSpan.Zero
to be returned. ** If overflow occurs,
* * @throws IllegalArgumentException if the passed value is less than zero */ def scaledBy(factor: Double) = { require(factor >= 0.0, "factor was less than zero") factor match { case 0.0 => Span.Zero case 1.0 => this case _ => val newNanos: Double = totNanos * factor newNanos match { case n if n > Long.MaxValue => Span.Max case 1 => Span(1, Nanosecond) case n if n < 1000 => if (n.longValue == n) Span(n.longValue, Nanoseconds) else Span(n, Nanoseconds) case 1000 => Span(1, Microsecond) case n if n < 1000 * 1000 => val v = n / 1000 if (v.longValue == v) Span(v.longValue, Microseconds) else Span(v, Microseconds) case 1000000 => Span(1, Millisecond) case n if n < 1000 * 1000 * 1000 => val v = n / 1000 / 1000 if (v.longValue == v) Span(v.longValue, Millis) else Span(v, Millis) case 1000000000L => Span(1, Second) case n if n < 1000L * 1000 * 1000 * 60 => val v = n / 1000 / 1000 / 1000 if (v.longValue == v) Span(v.longValue, Seconds) else Span(v, Seconds) case 60000000000L => Span(1, Minute) case n if n < 1000L * 1000 * 1000 * 60 * 60 => val v = n / 1000 / 1000 / 1000 / 60 if (v.longValue == v) Span(v.longValue, Minutes) else Span(v, Minutes) case 3600000000000L => Span(1, Hour) case n if n < 1000L * 1000 * 1000 * 60 * 60 * 24 => val v = n / 1000 / 1000 / 1000 / 60 / 60 if (v.longValue == v) Span(v.longValue, Hours) else Span(v, Hours) case 86400000000000L => Span(1, Day) case n => val v = n / 1000 / 1000 / 1000 / 60 / 60 / 24 if (v.longValue == v) Span(v.longValue, Days) else Span(v, Days) } } } /** * Returns a localized string suitable for presenting to a user that describes the time span represented * by thisSpan.Max
will be returned. If underflow occurs,Span.Zero
* will be returned. *Span
. * ** For example, for
* * @return a localized string describing thisSpan(1, Millisecond)
, this method would return"1 millisecond"
. * ForSpan(9.99, Seconds)
, this method would return"9.9 seconds"
. *Span
*/ lazy val prettyString: String = unitsMessageFun(lengthString) /** * Returns a string that looks similar to a factory method call that would have produced thisSpan
. * ** For example, for
* * @return a string that looks like a factory method call that would produce thisSpan(1, Millisecond)
, this method would return"Span(1, Millisecond)"
. * ForSpan(9.99, Seconds)
, this method would return"Span(9.99, Seconds)"
. *Span
*/ override def toString = "Span(" + lengthString + ", " + unitsName + ")" /** * Compares another object for equality. * ** If the passed object is a
* * @param other the object to compare with this one for equality * * @return true if the other object is aSpan
, this method will returntrue
only if the other *Span
returns the exact same value as thisSpan
fortotalNanos
. *Span
with the sametotalNanos
value. */ override def equals(other: Any): Boolean = { other match { case that: Span => totalNanos == that.totalNanos case _ => false } } /** * Returns a hash code for thisSpan
. * * @return a hash code based only on thetotalNanos
field. */ override def hashCode: Int = totalNanos.hashCode } /** * Companion object forSpan
that provides two factory methods for creatingSpan
instances. * ** The first argument to each factory method is a numeric value; the second argument is a
Units
value. * One factory method takes aLong
, so it can be invoked with either anInt
or ** import org.scalatest.time._ * * Span(1, Second) * Span(1L, Millisecond) ** ** The other factory method takes a
*Double
, so it can be invoked with either aFloat
or * aDouble
: ** import org.scalatest.time._ * * Span(2.5F, Seconds) * Span(99.9, Microseconds) ** * @author Bill Venners */ object Span { /** * Returns aSpan
representing the passedLong
length
of time in the * passedunits
. * ** If the requested time span is less than zero or greater than
* * @param length the length of time denominated by the passedLong.MaxValue
nanoseconds, this method will throw * anIllegalArgumentException
. (Note: a zero-length time span is allowed, just not a negative or * too-large time span.) *units
* @param units the units of time for the passedlength
* @return aSpan
representing the requested time span * @throws IllegalArgumentException if the requested time span is greater thanLong.MaxValue
* nanoseconds, the maximum time span expressible with aSpan
*/ def apply(length: Long, units: Units): Span = new Span(length, units) /** * Returns aSpan
representing the passedDouble
length
of time in the * passedunits
. * ** If the requested time span is less than
* * @param length the length of time denominated by the passed0.0
or, when converted toLong
number of nanoseconds, would be greater than *Long.MaxValue
nanoseconds, this method will throw anIllegalArgumentException
. * (Note: a zero-length time span is allowed, just not a negative or too-large time span.) *units
* @param units the units of time for the passedlength
* @return aSpan
representing the requested time span * @throws IllegalArgumentException if the requested time span, when converted toLong
number of * nanoseconds, would be greater thanLong.MaxValue
nanoseconds, the maximum time span * expressible with aSpan
*/ def apply(length: Double, units: Units): Span = new Span(length, units) /** * ASpan
with the maximum expressible value,Span(Long.MaxValue, Nanoseconds)
, * which is approximately 292 years. * ** One use case for this
* * @return aSpan
value is to help convert a duration concept from a different library to *Span
when that library's duration concept includes a notion of infinite durations. An infinite * duration can be converted toSpan.Max
. *Span
with the maximum expressible value,Long.MaxValue
nanoseconds. */ val Max: Span = new Span(Long.MaxValue.toDouble / 1000 / 1000 / 1000 / 60 / 60 / 24, Days) /** * ASpan
with representing a zero-length span of time. * * @return a zero-lengthSpan
. */ val Zero: Span = new Span(0, Nanoseconds) private def totalNanosForLongLength(length: Long, units: Units): Long = { require(length >= 0, "length must be greater than or equal to zero, but was: " + length) val MaxMicroseconds = Long.MaxValue / 1000 val MaxMilliseconds = Long.MaxValue / 1000 / 1000 val MaxSeconds = Long.MaxValue / 1000 / 1000 / 1000 val MaxMinutes = Long.MaxValue / 1000 / 1000 / 1000 / 60 val MaxHours = Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60 val MaxDays = Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60 / 24 require(units != Nanosecond || length == 1, singularErrorMsg("Nanosecond")) require(units != Microsecond || length == 1, singularErrorMsg("Microsecond")) require(units != Millisecond || length == 1, singularErrorMsg("Millisecond")) require(units != Second || length == 1, singularErrorMsg("Second")) require(units != Minute || length == 1, singularErrorMsg("Minute")) require(units != Hour || length == 1, singularErrorMsg("Hour")) require(units != Day || length == 1, singularErrorMsg("Day")) require(units != Microseconds || length <= MaxMicroseconds, "Passed length, " + length + ", is larger than the largest expressible number of microseconds: Long.MaxValue / 1000") require(units != Milliseconds && units != Millis || length <= MaxMilliseconds, "Passed length, " + length + ", is larger than the largest expressible number of millieconds: Long.MaxValue / 1000 / 1000") require(units != Seconds || length <= MaxSeconds, "Passed length, " + length + ", is larger than the largest expressible number of seconds: Long.MaxValue / 1000 / 1000 / 1000") require(units != Minutes || length <= MaxMinutes, "Passed length, " + length + ", is larger than the largest expressible number of minutes: Long.MaxValue / 1000 / 1000 / 1000 / 60") require(units != Hours || length <= MaxHours, "Passed length, " + length + ", is larger than the largest expressible number of hours: Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60") require(units != Days || length <= MaxDays, "Passed length, " + length + ", is larger than the largest expressible number of days: Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60 / 24") units match { case Nanosecond | Nanoseconds => length case Microsecond | Microseconds => length * 1000 case Millisecond | Milliseconds | Millis => length * 1000 * 1000 case Second | Seconds => length * 1000 * 1000 * 1000 case Minute | Minutes => length * 1000 * 1000 * 1000 * 60 case Hour | Hours => length * 1000 * 1000 * 1000 * 60 * 60 case Day | Days => length * 1000 * 1000 * 1000 * 60 * 60 * 24 } } private def totalNanosForDoubleLength(length: Double, units: Units): Long = { require(length >= 0, "length must be greater than or equal to zero, but was: " + length) val MaxNanoseconds = (Long.MaxValue).toDouble val MaxMicroseconds = Long.MaxValue.toDouble / 1000 val MaxMilliseconds = Long.MaxValue.toDouble / 1000 / 1000 val MaxSeconds = Long.MaxValue.toDouble / 1000 / 1000 / 1000 val MaxMinutes = Long.MaxValue.toDouble / 1000 / 1000 / 1000 / 60 val MaxHours = Long.MaxValue.toDouble / 1000 / 1000 / 1000 / 60 / 60 val MaxDays = Long.MaxValue.toDouble / 1000 / 1000 / 1000 / 60 / 60 / 24 require(units != Nanosecond || length == 1.0, singularErrorMsg("Nanosecond")) require(units != Microsecond || length == 1.0, singularErrorMsg("Microsecond")) require(units != Millisecond || length == 1.0, singularErrorMsg("Millisecond")) require(units != Second || length == 1.0, singularErrorMsg("Second")) require(units != Minute || length == 1.0, singularErrorMsg("Minute")) require(units != Hour || length == 1.0, singularErrorMsg("Hour")) require(units != Day || length == 1.0, singularErrorMsg("Day")) require(units != Nanoseconds || length <= MaxNanoseconds, "Passed length, " + length + ", is larger than the largest expressible number of nanoseconds: Long.MaxValue") require(units != Microseconds || length <= MaxMicroseconds, "Passed length, " + length + ", is larger than the largest expressible number of microseconds: Long.MaxValue / 1000") require(units != Milliseconds && units != Millis || length <= MaxMilliseconds, "Passed length, " + length + ", is larger than the largest expressible number of millieconds: Long.MaxValue / 1000 / 1000") require(units != Seconds || length <= MaxSeconds, "Passed length, " + length + ", is larger than the largest expressible number of seconds: Long.MaxValue / 1000 / 1000 / 1000") require(units != Minutes || length <= MaxMinutes, "Passed length, " + length + ", is larger than the largest expressible number of minutes: Long.MaxValue / 1000 / 1000 / 1000 / 60") require(units != Hours || length <= MaxHours, "Passed length, " + length + ", is larger than the largest expressible number of hours: Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60") require(units != Days || length <= MaxDays, "Passed length, " + length + ", is larger than the largest expressible number of days: Long.MaxValue / 1000 / 1000 / 1000 / 60 / 60 / 24") units match { case Nanosecond | Nanoseconds => length.toLong case Microsecond | Microseconds => (length * 1000).toLong case Millisecond | Milliseconds | Millis => (length * 1000 * 1000).toLong case Second | Seconds => (length * 1000 * 1000 * 1000).toLong case Minute | Minutes => (length * 1000 * 1000 * 1000 * 60).toLong case Hour | Hours => (length * 1000 * 1000 * 1000 * 60 * 60).toLong case Day | Days => (length * 1000 * 1000 * 1000 * 60 * 60 * 24).toLong } } // TODO: Put this in the bundle private def singularErrorMsg(unitsString: String) = { "Singular form of " + unitsString + " (i.e., without the trailing s) can only be used with the value 1. Use " + unitsString + "s (i.e., with an s) instead." } import scala.language.implicitConversions import scala.concurrent.duration.Duration import scala.concurrent.duration.FiniteDuration /** * Implicitly converts ascala.concurrent.duration.Duration
to aSpan
, * so that aDuration
can be used where aSpan
is needed. * ** This function transforms
*/ implicit def convertDurationToSpan(duration: Duration): Span = { duration match { case _ if duration.isFinite => Span(duration.toNanos, Nanoseconds) case Duration.MinusInf => Span.Zero case _ => Span.Max // Duration.Inf and Undefined } } /** * Implicitly converts aDuration.MinusInf
toSpan.Zero
,Duration.Inf
* andUndefined
toSpan.Max
, and all others to aSpan
containing a * corresponing number of nanoseconds. *Span
to ascala.concurrent.duration.FiniteDuration
, * so that aSpan
can be used where aFiniteDuration
is needed. */ implicit def convertSpanToDuration(span: Span): FiniteDuration = Duration.fromNanos(span.totalNanos) }