Skip to content

Commit

Permalink
Version 1.2.1.
Browse files Browse the repository at this point in the history
All changes in the T module; no (intended) changes inside S or J.
  • Loading branch information
Michel Charpentier authored and Michel Charpentier committed Apr 13, 2024
1 parent 895ee0c commit fe49c2b
Show file tree
Hide file tree
Showing 87 changed files with 461 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ def assertExpr[A, B](actual: A)(expected: B)(using Prettifier, CanEqual[A, B]):
*
* @since 1.1
*/
def assertExpr[A, B](actual: A, clue: Any)(
expected: B
)(using Prettifier, CanEqual[A, B]): Assertion = assertResult(expected, clue)(actual)
def assertExpr[A, B](actual: A, clue: Any)(expected: B)(using Prettifier, CanEqual[A, B]): Assertion =
assertResult(expected, clue)(actual)
6 changes: 0 additions & 6 deletions T/src/main/scala/tinyscalautils/test/grading/GradingRun.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package tinyscalautils.test.grading

import org.scalactic.{ Prettifier, SizeLimit }
import org.scalatest.concurrent.{ Signaler, ThreadSignaler }
import org.scalatest.time.Span
import org.scalatest.time.SpanSugar.convertIntToGrainOfTime
Expand Down Expand Up @@ -33,11 +32,6 @@ trait GradingRun extends Grading, DualTimeLimits, NoStackOverflowError:
val shortTimeLimit: Span = 1.second
val longTimeLimit: Span = 1.minute

/** Default prettifier, which truncates collections after 32 elements. */
protected def prettifier: Prettifier = Prettifier.truncateAt(SizeLimit(32))

given Prettifier = prettifier

abstract override def withFixture(test: NoArgTest): Outcome =
val failed = test.configMap.getWithDefault[Set[String]]("failed", Set.empty)
if failed(test.name) then Failed(s"test name in failed set")
Expand Down
11 changes: 5 additions & 6 deletions T/src/main/scala/tinyscalautils/test/mixins/DualTimeLimits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ trait DualTimeLimits extends TimeLimitedTests:
final def timeLimit: Span = currentTimeLimit

abstract override def withFixture(test: NoArgTest): Outcome =
currentTimeLimit = shortTimeLimit // default
if test.tags.nonEmpty then
val tags = test.tags.filter: str =>
str == Slow.name || str == NoTimeout.name || str.startsWith(Timeout.name)
if test.tags(NoTimeout.name) then currentTimeLimit = Span.Max
else
currentTimeLimit = shortTimeLimit // default
val tags = test.tags.filter(str => str == Slow.name || str.startsWith(Timeout.name))
if tags.size > 1 then return Canceled(s"""conflicting tags: ${tags.mkString(", ")}""")
if tags.nonEmpty then
else if tags.nonEmpty then
val tag = tags.head
if tag == Slow.name then currentTimeLimit = longTimeLimit
else if tag == NoTimeout.name then currentTimeLimit = Span.Max
else // Timeout
Timeout.regex.findFirstMatchIn(tag).flatMap(_.group(1).toDoubleOption) match
case Some(value) if value > 0.0 => currentTimeLimit = value.seconds
Expand Down
8 changes: 1 addition & 7 deletions T/src/main/scala/tinyscalautils/test/tagobjects/Fail.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ import scala.util.matching.Regex
*
* @since 1.1
*/
class Fail private (name: String) extends Tag(name):
/** A mechanism to fail programmatically. */
inline def when(condition: Boolean): this.type | NoTag.type = if condition then this else NoTag

/** A mechanism to fail programmatically. */
inline def unless(condition: Boolean): this.type | NoTag.type = if condition then NoTag else this
end Fail
class Fail private (name: String) extends Tag(name)

/** A failed tag with no message
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ package tinyscalautils.test.tagobjects
import org.scalatest.Tag

/** A "no timeout" tag. This tag can be used within `DualTimeLimits` to disable the timeout for a
* single test. Internally, this uses a `Span.Max` timeout.
* single test. This overrides any other timeout tag, if present. Internally, `NoTimeout` uses a
* `Span.Max` timeout.
*
* @see
* [[tinyscalautils.test.mixins.DualTimeLimits]]
Expand Down
12 changes: 6 additions & 6 deletions T/src/main/scala/tinyscalautils/test/tagobjects/Slow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import org.scalatest.tagobjects.Slow
@deprecated("use NoTag instead", since = "1.1")
object Fast extends Tag("tinyscalautils.test.tags.Fast"):
@deprecated("use Slow.unless instead", since = "1.1")
inline def when(condition: Boolean): Fast.type | Slow.type = if condition then Fast else Slow
def when(condition: Boolean): Fast.type | Slow.type = if condition then Fast else Slow

extension (slow: Slow.type)
/** A way to conditionally tag as Slow.
extension (tag: Tag)
/** A way to conditionally set a tag.
*
* @since 1.1
*/
inline def when(condition: Boolean): Slow.type | NoTag.type = if condition then slow else NoTag
def when(condition: Boolean): tag.type | NoTag.type = if condition then tag else NoTag

/** A way to conditionally tag as Slow.
/** A way to conditionally set a tag.
*
* @since 1.1
*/
inline def unless(condition: Boolean): Slow.type | NoTag.type = if condition then NoTag else slow
def unless(condition: Boolean): tag.type | NoTag.type = if condition then NoTag else tag
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tinyscalautils.test.text

import org.scalactic.{ Prettifier, PrettyPair }
import tinyscalautils.text.short

/** A truncating prettifier. It can be used to guarantee that failed tests do not produce humongous
* outputs.
*
* @constructor
* @param prettifier
* the underlying prettifier.
* @param limit
* the maximum length of individual strings; must be at least 3.
*/
class TruncatingPrettifier(prettifier: Prettifier, limit: Int) extends Prettifier:
require(limit >= 3, s"limit $limit must be at least 3")

/** Uses implicit prettifier as underlying. */
def this(limit: Int)(using prettifier: Prettifier) = this(prettifier, limit)

/** Specifies underlying prettifier in curried form, e.g.:
* {{{
* TruncatingPrettifier(256): o =>
* ...
* }}}
*/
def this(limit: Int)(using DummyImplicit)(prettifier: Prettifier) = this(prettifier, limit)

private def s(str: String) = str.short(limit)

def apply(o: Any): String = s(prettifier(o))

override def apply(left: Any, right: Any): PrettyPair =
val PrettyPair(l, r, a) = super.apply(left, right)
PrettyPair(s(l), s(r), a.map(s))
end TruncatingPrettifier

extension (prettifier: Prettifier)
/** Deactivates "analysis", which runs very slow on large data structures. */
def noAnalysis: Prettifier = new Prettifier:
def apply(o: Any): String = prettifier(o)

override def apply(left: Any, right: Any): PrettyPair =
PrettyPair(prettifier(left), prettifier(right), None)
end noAnalysis
27 changes: 14 additions & 13 deletions T/src/test/scala/DualTimeLimitsTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ class DualTimeLimitsTests extends AnyFunSuite with Tolerance:
test("fast, successful 2", NoTag)(sleep(0.4))
test("slow, successful", Slow)(sleep(0.6))
test("timeout, successful", Timeout(1.5))(sleep(1.1))
test("no timeout, successful", NoTimeout)(sleep(1.1))
test("no timeout 1", NoTimeout)(sleep(1.1))
test("no timeout 2", Slow, NoTimeout)(sleep(1.1))
test("no timeout 3", Timeout(0.5), NoTimeout)(sleep(1.1))
test("no timeout 4", NoTag, NoTimeout)(sleep(1.1))
test("fast, failed 1")(sleep(0.6))
test("fast, failed 2", NoTag)(sleep(0.6))
test("slow, failed", Slow)(sleep(1.1))
Expand All @@ -30,7 +33,10 @@ class DualTimeLimitsTests extends AnyFunSuite with Tolerance:
assert(suite.run(Some("fast, successful 2"), silent).succeeds())
assert(suite.run(Some("slow, successful"), silent).succeeds())
assert(suite.run(Some("timeout, successful"), silent).succeeds())
assert(suite.run(Some("no timeout, successful"), silent).succeeds())
assert(suite.run(Some("no timeout 1"), silent).succeeds())
assert(suite.run(Some("no timeout 2"), silent).succeeds())
assert(suite.run(Some("no timeout 3"), silent).succeeds())
assert(suite.run(Some("no timeout 4"), silent).succeeds())
assert(!suite.run(Some("fast, failed 1"), silent).succeeds())
assert(!suite.run(Some("fast, failed 2"), silent).succeeds())
assert(!suite.run(Some("slow, failed"), silent).succeeds())
Expand All @@ -43,18 +49,10 @@ class DualTimeLimitsTests extends AnyFunSuite with Tolerance:

test("Timeout + Timeout", Timeout(2), Timeout(1)) {}
test("Slow + Timeout", Slow, Timeout(1)) {}
test("Slow + NoTimeout", Slow, NoTimeout) {}
test("Timeout + NoTimeout", Timeout(1), NoTimeout) {}
end Tests

val suite = Tests()
for str <- Seq(
"Timeout + Timeout",
"Slow + Timeout",
"Slow + NoTimeout",
"Timeout + NoTimeout"
)
do
for str <- Seq("Timeout + Timeout", "Slow + Timeout") do
assert(suite.run(Some(str), Args(R)).succeeds())
R.lastEvent match
case Some(ev: TestCanceled) => assert(ev.message.startsWith("conflicting tags:"))
Expand Down Expand Up @@ -92,10 +90,12 @@ class DualTimeLimitsTests extends AnyFunSuite with Tolerance:
override val shortTimeLimit = 0.1.seconds
override val longTimeLimit = 0.5.seconds

test("slow")(sleep(0.6))
test("slow 1")(sleep(0.6))
test("slow 2", Slow)(sleep(0.6))
end Tests

assert(Tests().run(Some("slow"), silent).succeeds())
assert(Tests().run(Some("slow 1"), silent).succeeds())
assert(Tests().run(Some("slow 2"), silent).succeeds())

test("when/unless"):
class Tests extends AnyFunSuite with DualTimeLimits:
Expand All @@ -105,6 +105,7 @@ class DualTimeLimitsTests extends AnyFunSuite with Tolerance:
for time <- Seq(0.1, 0.2, 1.1, 1.2) do
test(s"time 1: $time", Slow.when(time > 1.0))(sleep(time))
test(s"time 2: $time", Slow.unless(time < 1.0))(sleep(time))
test(s"time 3: $time", NoTimeout.when(time > 1.0))(sleep(time))
end Tests

val suite = Tests()
Expand Down
43 changes: 18 additions & 25 deletions T/src/test/scala/GradingRunTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ import tinyscalautils.threads.Executors.global
import tinyscalautils.threads.runAsync
import tinyscalautils.timing.{getTime, sleep, timeOf}
import tinyscalautils.lang.unit
import tinyscalautils.test.text.TruncatingPrettifier

class GradingRunTests extends AnyFunSuite with Tolerance:
private val nums = List.range(0, 42)

test("prettifier and grade"):
class Tests extends AnyFunSuite with GradingRun:
test("default truncating prettifier"):
class Tests(using Prettifier) extends AnyFunSuite with GradingRun:
test("default prettifier"):
val e = intercept[TestFailedException](assert(nums.isEmpty))
assert(e.message.exists(_.endsWith(", 31, ...) was not empty")))

test("truncating prettifier"):
test("overridden prettifier"):
given Prettifier = Prettifier.truncateAt(SizeLimit(10))

val e = intercept[TestFailedException](assert(nums.isEmpty))
Expand All @@ -31,37 +32,29 @@ class GradingRunTests extends AnyFunSuite with Tolerance:
test("failed [3pts]")(fail())
end Tests

val suite = Tests()
val suite = Tests(using Prettifier.truncateAt(SizeLimit(32)))
suite.run(None, silent)
assert(suite.grader.grade == 0.4)

test("overridden prettifier and grade"):
class Tests extends AnyFunSuite with GradingRun:
override val prettifier = Prettifier.truncateAt(SizeLimit(10))

test("overridden truncating prettifier"):
test("truncating default prettifier and grade"):
class Tests(using Prettifier) extends AnyFunSuite with GradingRun:
test("prettifier"):
val e = intercept[TestFailedException](assert(nums.isEmpty))
assert(e.message.exists(_.endsWith(", 9, ...) was not empty")))
assert(e.message.exists(_.endsWith(", 10, 1... was not empty")))

val suite = Tests()
val suite = Tests(using TruncatingPrettifier(43))
suite.run(None, silent)
assert(suite.grader.grade == 1.0)

test("super overridden prettifier and grade"):
val Foo = 0
class Tests extends AnyFunSuite with GradingRun:
override val prettifier = Prettifier {
case Foo => "zero"
case o => super.prettifier(o)
}
test("truncating prettifier and grade"):
given Prettifier = Prettifier.truncateAt(SizeLimit(5))

test("overridden truncating prettifier"):
val e1 = intercept[TestFailedException](assert(nums.isEmpty))
assert(e1.message.exists(_.endsWith(", 31, ...) was not empty")))
val e2 = intercept[TestFailedException](assert(Foo == 1))
assert(e2.message.contains("zero did not equal 1"))
class Tests(using Prettifier) extends AnyFunSuite with GradingRun:
test("prettifier"):
val e = intercept[TestFailedException](assert(nums.isEmpty))
assert(e.message.exists(_.endsWith(", 4, ... was not empty")))

val suite = Tests()
val suite = Tests(using TruncatingPrettifier(23))
suite.run(None, silent)
assert(suite.grader.grade == 1.0)

Expand Down Expand Up @@ -212,4 +205,4 @@ class GradingRunTests extends AnyFunSuite with Tolerance:
assert(!Tests().run(None, Args(R).copy(configMap = config)).succeeds())
R.lastEvent match
case Some(ev: TestFailed) => assert(ev.message == "test name in failed set")
case other => fail(s"unexpected: $other ")
case other => fail(s"unexpected: $other ")
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ val jcip = "net.jcip" % "jcip-annotations" % "1.0"
val ScalaTest = "org.scalatest" %% "scalatest" % "3.2.18"
val JUnit = "org.junit.jupiter" % "junit-jupiter" % "5.10.2"

ThisBuild / version := "1.2.0"
ThisBuild / version := "1.2.1"
ThisBuild / scalaVersion := "3.3.3"
ThisBuild / versionScheme := Some("semver-spec")

Expand Down
Loading

0 comments on commit fe49c2b

Please sign in to comment.