Pre-commit.
This commit is contained in:
parent
789fc594a2
commit
79e6672bdf
15 changed files with 519 additions and 440 deletions
36
build.sbt
36
build.sbt
|
@ -35,7 +35,7 @@ val Deps = new {
|
||||||
}
|
}
|
||||||
|
|
||||||
val Gs = new {
|
val Gs = new {
|
||||||
val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.3.0"
|
val Uuid: ModuleID = "gs" %% "gs-uuid-v0" % "0.3.0"
|
||||||
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.2.0"
|
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.2.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ val Deps = new {
|
||||||
|
|
||||||
lazy val testSettings = Seq(
|
lazy val testSettings = Seq(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
Deps.MUnit % Test,
|
Deps.MUnit % Test,
|
||||||
Deps.Gs.Datagen % Test
|
Deps.Gs.Datagen % Test
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -62,20 +62,28 @@ lazy val `api-definition` = project
|
||||||
.in(file("modules/api-definition"))
|
.in(file("modules/api-definition"))
|
||||||
.settings(sharedSettings)
|
.settings(sharedSettings)
|
||||||
.settings(testSettings)
|
.settings(testSettings)
|
||||||
.settings(name := s"${gsProjectName.value}-api-definition-v${semVerMajor.value}")
|
.settings(
|
||||||
.settings(libraryDependencies ++= Seq(
|
name := s"${gsProjectName.value}-api-definition-v${semVerMajor.value}"
|
||||||
Deps.Cats.Core,
|
)
|
||||||
Deps.Cats.Effect,
|
.settings(
|
||||||
Deps.Fs2.Core
|
libraryDependencies ++= Seq(
|
||||||
))
|
Deps.Cats.Core,
|
||||||
|
Deps.Cats.Effect,
|
||||||
|
Deps.Fs2.Core
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
lazy val `api-execution` = project
|
lazy val `api-execution` = project
|
||||||
.in(file("modules/api-execution"))
|
.in(file("modules/api-execution"))
|
||||||
.settings(sharedSettings)
|
.settings(sharedSettings)
|
||||||
.settings(testSettings)
|
.settings(testSettings)
|
||||||
.settings(name := s"${gsProjectName.value}-api-execution-v${semVerMajor.value}")
|
.settings(
|
||||||
.settings(libraryDependencies ++= Seq(
|
name := s"${gsProjectName.value}-api-execution-v${semVerMajor.value}"
|
||||||
Deps.Cats.Core,
|
)
|
||||||
Deps.Cats.Effect,
|
.settings(
|
||||||
Deps.Fs2.Core
|
libraryDependencies ++= Seq(
|
||||||
))
|
Deps.Cats.Core,
|
||||||
|
Deps.Cats.Effect,
|
||||||
|
Deps.Fs2.Core
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -13,32 +13,40 @@ object Assertion:
|
||||||
// TODO: Code Position
|
// TODO: Code Position
|
||||||
|
|
||||||
case object IsEqualTo extends Assertion("isEqualTo"):
|
case object IsEqualTo extends Assertion("isEqualTo"):
|
||||||
|
|
||||||
def evaluate[A: ClassTag](
|
def evaluate[A: ClassTag](
|
||||||
candidate: A,
|
candidate: A,
|
||||||
expected: A
|
expected: A
|
||||||
)(using CanEqual[A, A]): Either[TestFailure, Unit] =
|
)(
|
||||||
if candidate == expected then
|
using
|
||||||
success()
|
CanEqual[A, A]
|
||||||
|
): Either[TestFailure, Unit] =
|
||||||
|
if candidate == expected then success()
|
||||||
else
|
else
|
||||||
val runtimeType = classTag[A].runtimeClass.getName()
|
val runtimeType = classTag[A].runtimeClass.getName()
|
||||||
Left(TestFailure.AssertionFailed(
|
Left(
|
||||||
assertionName = name,
|
TestFailure.AssertionFailed(
|
||||||
inputs = Map(
|
assertionName = name,
|
||||||
"candidate" -> runtimeType,
|
inputs = Map(
|
||||||
"expected" -> runtimeType
|
"candidate" -> runtimeType,
|
||||||
),
|
"expected" -> runtimeType
|
||||||
message = s"'${renderInput(candidate)}' was not equal to '${renderInput(candidate)}'"
|
),
|
||||||
))
|
message =
|
||||||
|
s"'${renderInput(candidate)}' was not equal to '${renderInput(candidate)}'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
case object IsTrue extends Assertion("isTrue"):
|
case object IsTrue extends Assertion("isTrue"):
|
||||||
|
|
||||||
def evaluate(candidate: Boolean): Either[TestFailure, Unit] =
|
def evaluate(candidate: Boolean): Either[TestFailure, Unit] =
|
||||||
if candidate then
|
if candidate then success()
|
||||||
success()
|
|
||||||
else
|
else
|
||||||
Left(TestFailure.AssertionFailed(
|
Left(
|
||||||
assertionName = name,
|
TestFailure.AssertionFailed(
|
||||||
inputs = Map("candidate" -> "Boolean"),
|
assertionName = name,
|
||||||
message = s"Expected '$candidate' to be 'true'."
|
inputs = Map("candidate" -> "Boolean"),
|
||||||
))
|
message = s"Expected '$candidate' to be 'true'."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
end Assertion
|
end Assertion
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import scala.reflect.ClassTag
|
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
|
import scala.reflect.ClassTag
|
||||||
|
|
||||||
opaque type Check[A] = A
|
opaque type Check[A] = A
|
||||||
|
|
||||||
|
@ -12,20 +12,29 @@ object Check:
|
||||||
def apply[A](candidate: A): Check[A] = candidate
|
def apply[A](candidate: A): Check[A] = candidate
|
||||||
|
|
||||||
extension [A: ClassTag](check: Check[A])
|
extension [A: ClassTag](check: Check[A])
|
||||||
/**
|
/** @return
|
||||||
* @return The unwrapped value of this [[Check]].
|
* The unwrapped value of this [[Check]].
|
||||||
*/
|
*/
|
||||||
def unwrap(): A = check
|
def unwrap(): A = check
|
||||||
|
|
||||||
def isEqualTo(expected: A)(using CanEqual[A, A]): TestResult =
|
def isEqualTo(
|
||||||
|
expected: A
|
||||||
|
)(
|
||||||
|
using
|
||||||
|
CanEqual[A, A]
|
||||||
|
): TestResult =
|
||||||
Assertion.IsEqualTo.evaluate(check, expected)
|
Assertion.IsEqualTo.evaluate(check, expected)
|
||||||
|
|
||||||
def isEqualToF[F[_]: Sync](
|
def isEqualToF[F[_]: Sync](
|
||||||
expected: A
|
expected: A
|
||||||
)(using CanEqual[A, A]): F[TestResult] =
|
)(
|
||||||
|
using
|
||||||
|
CanEqual[A, A]
|
||||||
|
): F[TestResult] =
|
||||||
Sync[F].delay(isEqualTo(expected))
|
Sync[F].delay(isEqualTo(expected))
|
||||||
|
|
||||||
extension (check: Check[Boolean])
|
extension (check: Check[Boolean])
|
||||||
|
|
||||||
def isTrue(): TestResult =
|
def isTrue(): TestResult =
|
||||||
Assertion.IsTrue.evaluate(check)
|
Assertion.IsTrue.evaluate(check)
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
/**
|
/** Enumeration for _Markers_, special tokens which "mark" a test to change
|
||||||
* Enumeration for _Markers_, special tokens which "mark" a test to change
|
* execution functionality.
|
||||||
* execution functionality.
|
*
|
||||||
*
|
* The basic case for this enumeration is allowing tests to be ignored.
|
||||||
* The basic case for this enumeration is allowing tests to be ignored.
|
*
|
||||||
*
|
* @param name
|
||||||
* @param name The formal serialized name of the marker.
|
* The formal serialized name of the marker.
|
||||||
*/
|
*/
|
||||||
sealed abstract class Marker(val name: String)
|
sealed abstract class Marker(val name: String)
|
||||||
|
|
||||||
object Marker:
|
object Marker:
|
||||||
|
|
||||||
given CanEqual[Marker, Marker] = CanEqual.derived
|
given CanEqual[Marker, Marker] = CanEqual.derived
|
||||||
|
|
||||||
/**
|
/** If this [[Marker]] is present on a test, the test will be ignored.
|
||||||
* If this [[Marker]] is present on a test, the test will be ignored.
|
*/
|
||||||
*/
|
|
||||||
case object Ignored extends Marker("ignored")
|
case object Ignored extends Marker("ignored")
|
||||||
|
|
||||||
end Marker
|
end Marker
|
||||||
|
|
|
@ -2,8 +2,7 @@ package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
/**
|
/** Opaque type representing some _permanent identifier_. These are
|
||||||
* Opaque type representing some _permanent identifier_. These are
|
|
||||||
* user-assigned strings that are expected to _not change over time_ for some
|
* user-assigned strings that are expected to _not change over time_ for some
|
||||||
* test. This allows tests to be deterministically tracked. The only constraint
|
* test. This allows tests to be deterministically tracked. The only constraint
|
||||||
* for a permanent identifier is that it must not be blank.
|
* for a permanent identifier is that it must not be blank.
|
||||||
|
@ -18,18 +17,21 @@ opaque type PermanentId = String
|
||||||
|
|
||||||
object PermanentId:
|
object PermanentId:
|
||||||
|
|
||||||
/**
|
/** Instantiate a new [[PermanentId]].
|
||||||
* Instantiate a new [[PermanentId]].
|
*
|
||||||
*
|
* @param candidate
|
||||||
* @param candidate The candidate string.
|
* The candidate string.
|
||||||
* @return The new [[PermanentId]] instance.
|
* @return
|
||||||
* @throws IllegalArgumentException If the candidate string is blank.
|
* The new [[PermanentId]] instance.
|
||||||
*/
|
* @throws IllegalArgumentException
|
||||||
|
* If the candidate string is blank.
|
||||||
|
*/
|
||||||
def apply(candidate: String): PermanentId =
|
def apply(candidate: String): PermanentId =
|
||||||
if candidate.isBlank() then
|
if candidate.isBlank() then
|
||||||
throw new IllegalArgumentException("Permanent Identifiers must be non-blank.")
|
throw new IllegalArgumentException(
|
||||||
else
|
"Permanent Identifiers must be non-blank."
|
||||||
candidate
|
)
|
||||||
|
else candidate
|
||||||
|
|
||||||
given CanEqual[PermanentId, PermanentId] = CanEqual.derived
|
given CanEqual[PermanentId, PermanentId] = CanEqual.derived
|
||||||
|
|
||||||
|
|
|
@ -2,19 +2,19 @@ package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
/**
|
/** Opaque type representing tags that may be assigned to a [[Test]].
|
||||||
* Opaque type representing tags that may be assigned to a [[Test]].
|
*/
|
||||||
*/
|
|
||||||
opaque type Tag = String
|
opaque type Tag = String
|
||||||
|
|
||||||
object Tag:
|
object Tag:
|
||||||
|
|
||||||
/**
|
/** Instantiate a new [[Tag]].
|
||||||
* Instantiate a new [[Tag]].
|
*
|
||||||
*
|
* @param tag
|
||||||
* @param tag The candidate string.
|
* The candidate string.
|
||||||
* @return The new [[Tag]] instance.
|
* @return
|
||||||
*/
|
* The new [[Tag]] instance.
|
||||||
|
*/
|
||||||
def apply(tag: String): Tag = tag
|
def apply(tag: String): Tag = tag
|
||||||
|
|
||||||
given CanEqual[Tag, Tag] = CanEqual.derived
|
given CanEqual[Tag, Tag] = CanEqual.derived
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.data.EitherT
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
import cats.data.EitherT
|
||||||
|
|
||||||
/**
|
/** Each instance of this class indicates the _definition_ of some test.
|
||||||
* Each instance of this class indicates the _definition_ of some test.
|
|
||||||
*
|
*
|
||||||
* @param name The display name of the test. Not considered to be unique.
|
* @param name
|
||||||
* @param permanentId The [[PermanentId]] for this test.
|
* The display name of the test. Not considered to be unique.
|
||||||
* @param documentation The documentation for this test.
|
* @param permanentId
|
||||||
* @param tags The list of [[Tag]] applicable to this test.
|
* The [[PermanentId]] for this test.
|
||||||
* @param markers The list of [[Marker]] applicable to this test.
|
* @param documentation
|
||||||
* @param iterations The number of iterations of this test to run.
|
* The documentation for this test.
|
||||||
* @param unitOfWork The function that the test evaluates.
|
* @param tags
|
||||||
|
* The list of [[Tag]] applicable to this test.
|
||||||
|
* @param markers
|
||||||
|
* The list of [[Marker]] applicable to this test.
|
||||||
|
* @param iterations
|
||||||
|
* The number of iterations of this test to run.
|
||||||
|
* @param unitOfWork
|
||||||
|
* The function that the test evaluates.
|
||||||
*/
|
*/
|
||||||
final class TestDefinition[F[_]](
|
final class TestDefinition[F[_]](
|
||||||
val name: TestDefinition.Name,
|
val name: TestDefinition.Name,
|
||||||
|
@ -26,20 +32,20 @@ final class TestDefinition[F[_]](
|
||||||
|
|
||||||
object TestDefinition:
|
object TestDefinition:
|
||||||
|
|
||||||
/**
|
/** Opaque type representing names that may be assigned to [[Test]].
|
||||||
* Opaque type representing names that may be assigned to [[Test]].
|
*/
|
||||||
*/
|
|
||||||
opaque type Name = String
|
opaque type Name = String
|
||||||
|
|
||||||
object Name:
|
object Name:
|
||||||
|
|
||||||
/**
|
/** Instantiate a new [[Test.Name]]. This name is not unique, has no
|
||||||
* Instantiate a new [[Test.Name]]. This name is not unique, has no
|
* constraints, and only exists for display purposes.
|
||||||
* constraints, and only exists for display purposes.
|
*
|
||||||
*
|
* @param name
|
||||||
* @param name The candidate string.
|
* The candidate string.
|
||||||
* @return The new [[Test.Name]] instance.
|
* @return
|
||||||
*/
|
* The new [[Test.Name]] instance.
|
||||||
|
*/
|
||||||
def apply(name: String): Name = name
|
def apply(name: String): Name = name
|
||||||
|
|
||||||
given CanEqual[Name, Name] = CanEqual.derived
|
given CanEqual[Name, Name] = CanEqual.derived
|
||||||
|
|
|
@ -1,40 +1,41 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
/**
|
/** Base trait for all failures recognized by gs-test.
|
||||||
* Base trait for all failures recognized by gs-test.
|
|
||||||
*/
|
*/
|
||||||
sealed trait TestFailure
|
sealed trait TestFailure
|
||||||
|
|
||||||
object TestFailure:
|
object TestFailure:
|
||||||
|
|
||||||
/**
|
/** Returned when assertions in this library fail. Assertions understand how
|
||||||
* Returned when assertions in this library fail. Assertions understand how to
|
* to populate these values.
|
||||||
* populate these values.
|
*
|
||||||
*
|
* @param assertionName
|
||||||
* @param assertionName The name of the assertion.
|
* The name of the assertion.
|
||||||
* @param inputs The names and calculated types of each input to the assertion.
|
* @param inputs
|
||||||
* @param message The message produced by the assertion.
|
* The names and calculated types of each input to the assertion.
|
||||||
*/
|
* @param message
|
||||||
|
* The message produced by the assertion.
|
||||||
|
*/
|
||||||
case class AssertionFailed(
|
case class AssertionFailed(
|
||||||
assertionName: String,
|
assertionName: String,
|
||||||
inputs: Map[String, String],
|
inputs: Map[String, String],
|
||||||
message: String
|
message: String
|
||||||
) extends TestFailure
|
) extends TestFailure
|
||||||
|
|
||||||
/**
|
/** Return when a test explicitly calls `fail("...")` or some variant thereof.
|
||||||
* Return when a test explicitly calls `fail("...")` or some variant thereof.
|
*
|
||||||
*
|
* @param message
|
||||||
* @param message The failure message provided by the test author.
|
* The failure message provided by the test author.
|
||||||
*/
|
*/
|
||||||
case class TestRequestedFailure(
|
case class TestRequestedFailure(
|
||||||
message: String
|
message: String
|
||||||
) extends TestFailure
|
) extends TestFailure
|
||||||
|
|
||||||
/**
|
/** Used when the test fails due to an exception.
|
||||||
* Used when the test fails due to an exception.
|
*
|
||||||
*
|
* @param cause
|
||||||
* @param cause The underlying cause of failure.
|
* The underlying cause of failure.
|
||||||
*/
|
*/
|
||||||
case class ExceptionThrown(
|
case class ExceptionThrown(
|
||||||
cause: Throwable
|
cause: Throwable
|
||||||
) extends TestFailure
|
) extends TestFailure
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import cats.effect.Async
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
|
import cats.effect.Async
|
||||||
|
import cats.syntax.all.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import scala.collection.mutable.ListBuffer
|
||||||
import scala.jdk.CollectionConverters.*
|
import scala.jdk.CollectionConverters.*
|
||||||
|
|
||||||
/**
|
/** Base class for defining groups of related tests. Users should extend this
|
||||||
* Base class for defining groups of related tests. Users should extend this
|
|
||||||
* class to define their tests.
|
* class to define their tests.
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
|
@ -27,38 +26,38 @@ import scala.jdk.CollectionConverters.*
|
||||||
* }}}
|
* }}}
|
||||||
*/
|
*/
|
||||||
abstract class TestGroup[F[_]: Async]:
|
abstract class TestGroup[F[_]: Async]:
|
||||||
/**
|
/** @return
|
||||||
* @return The display name for this group.
|
* The display name for this group.
|
||||||
*/
|
*/
|
||||||
def name: String
|
def name: String
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return List of [[Tag]] that apply to all tests within this group.
|
* List of [[Tag]] that apply to all tests within this group.
|
||||||
*/
|
*/
|
||||||
def tags: List[Tag] = List.empty
|
def tags: List[Tag] = List.empty
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return List of all [[Marker]] that apply to all tests within this group.
|
* List of all [[Marker]] that apply to all tests within this group.
|
||||||
*/
|
*/
|
||||||
def markers: List[Marker] = List.empty
|
def markers: List[Marker] = List.empty
|
||||||
|
|
||||||
/**
|
/** @return
|
||||||
* @return The documentation for this group.
|
* The documentation for this group.
|
||||||
*/
|
*/
|
||||||
def documentation: Option[String] = None
|
def documentation: Option[String] = None
|
||||||
|
|
||||||
private var beforeGroupValue: Option[F[Unit]] = None
|
private var beforeGroupValue: Option[F[Unit]] = None
|
||||||
private var afterGroupValue: Option[F[Unit]] = None
|
private var afterGroupValue: Option[F[Unit]] = None
|
||||||
private var beforeEachTestValue: Option[F[Unit]] = None
|
private var beforeEachTestValue: Option[F[Unit]] = None
|
||||||
private var afterEachTestValue: Option[F[Unit]] = None
|
private var afterEachTestValue: Option[F[Unit]] = None
|
||||||
|
|
||||||
private val registry: TestGroup.Registry[F] = new TestGroup.Registry[F]
|
private val registry: TestGroup.Registry[F] = new TestGroup.Registry[F]
|
||||||
|
|
||||||
/**
|
/** Compile the contents of this [[TestGroup]] for delivery to the engine.
|
||||||
* Compile the contents of this [[TestGroup]] for delivery to the engine.
|
*
|
||||||
*
|
* @return
|
||||||
* @return The immutable, compiled form of this [[TestGroup]].
|
* The immutable, compiled form of this [[TestGroup]].
|
||||||
*/
|
*/
|
||||||
def compile(): TestGroupDefinition[F] =
|
def compile(): TestGroupDefinition[F] =
|
||||||
new TestGroupDefinition[F](
|
new TestGroupDefinition[F](
|
||||||
name = TestGroupDefinition.Name(name),
|
name = TestGroupDefinition.Name(name),
|
||||||
|
@ -72,67 +71,69 @@ abstract class TestGroup[F[_]: Async]:
|
||||||
tests = registry.toList()
|
tests = registry.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/** Provide an effect that must run before any of the tests within this group
|
||||||
* Provide an effect that must run before any of the tests within this group
|
* are executed.
|
||||||
* are executed.
|
*
|
||||||
*
|
* @param f
|
||||||
* @param f The effect to run.
|
* The effect to run.
|
||||||
*/
|
*/
|
||||||
protected def beforeGroup(f: => F[Unit]): Unit =
|
protected def beforeGroup(f: => F[Unit]): Unit =
|
||||||
beforeGroupValue = Some(f)
|
beforeGroupValue = Some(f)
|
||||||
()
|
()
|
||||||
|
|
||||||
/**
|
/** Provide an effect that must run after all tests within this group have
|
||||||
* Provide an effect that must run after all tests within this group have
|
* finished execution.
|
||||||
* finished execution.
|
*
|
||||||
*
|
* @param f
|
||||||
* @param f The effect to run.
|
* The effect to run.
|
||||||
*/
|
*/
|
||||||
protected def afterGroup(f: => F[Unit]): Unit =
|
protected def afterGroup(f: => F[Unit]): Unit =
|
||||||
afterGroupValue = Some(f)
|
afterGroupValue = Some(f)
|
||||||
()
|
()
|
||||||
|
|
||||||
/**
|
/** Provide an effect that must run before each test within this group.
|
||||||
* Provide an effect that must run before each test within this group.
|
*
|
||||||
*
|
* @param f
|
||||||
* @param f The effect to run.
|
* The effect to run.
|
||||||
*/
|
*/
|
||||||
protected def beforeEachTest(f: => F[Unit]): Unit =
|
protected def beforeEachTest(f: => F[Unit]): Unit =
|
||||||
beforeEachTestValue = Some(f)
|
beforeEachTestValue = Some(f)
|
||||||
()
|
()
|
||||||
|
|
||||||
/**
|
/** Provide an effect that must run after each test within this group.
|
||||||
* Provide an effect that must run after each test within this group.
|
*
|
||||||
*
|
* @param f
|
||||||
* @param f The effect to run.
|
* The effect to run.
|
||||||
*/
|
*/
|
||||||
protected def afterEachTest(f: => F[Unit]): Unit =
|
protected def afterEachTest(f: => F[Unit]): Unit =
|
||||||
afterEachTestValue = Some(f)
|
afterEachTestValue = Some(f)
|
||||||
()
|
()
|
||||||
|
|
||||||
/**
|
/** Define a new test.
|
||||||
* Define a new test.
|
*
|
||||||
*
|
* ## Required Information
|
||||||
* ## Required Information
|
*
|
||||||
*
|
* All tests require 3 things, at minimum:
|
||||||
* All tests require 3 things, at minimum:
|
*
|
||||||
*
|
* - [[PermanentId]]
|
||||||
* - [[PermanentId]]
|
* - Display Name
|
||||||
* - Display Name
|
* - Unit of Work (the code to execute)
|
||||||
* - Unit of Work (the code to execute)
|
*
|
||||||
*
|
* The [[PermanentId]] must be unique within the [[TestSuite]].
|
||||||
* The [[PermanentId]] must be unique within the [[TestSuite]].
|
*
|
||||||
*
|
* ## Default Values
|
||||||
* ## Default Values
|
*
|
||||||
*
|
* Tests iterate 1 time by default. Tags and Markers are inherited from the
|
||||||
* Tests iterate 1 time by default. Tags and Markers are inherited from the
|
* parent group. If this group contains tag "foo", any test within this group
|
||||||
* parent group. If this group contains tag "foo", any test within this group
|
* will also get tag "foo".
|
||||||
* will also get tag "foo".
|
*
|
||||||
*
|
* @param permanentId
|
||||||
* @param permanentId The [[PermanentId]] for this test.
|
* The [[PermanentId]] for this test.
|
||||||
* @param name The display name for this test.
|
* @param name
|
||||||
* @return A builder, to help complete test definition.
|
* The display name for this test.
|
||||||
*/
|
* @return
|
||||||
|
* A builder, to help complete test definition.
|
||||||
|
*/
|
||||||
protected def test(
|
protected def test(
|
||||||
permanentId: PermanentId,
|
permanentId: PermanentId,
|
||||||
name: String
|
name: String
|
||||||
|
@ -142,93 +143,106 @@ abstract class TestGroup[F[_]: Async]:
|
||||||
name = TestDefinition.Name(name),
|
name = TestDefinition.Name(name),
|
||||||
permanentId = permanentId,
|
permanentId = permanentId,
|
||||||
tags = ListBuffer(tags*),
|
tags = ListBuffer(tags*),
|
||||||
markers = ListBuffer(markers*),
|
markers = ListBuffer(markers*)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
object TestGroup:
|
object TestGroup:
|
||||||
|
|
||||||
/**
|
/** Specialization of [[TestGroup]] for `cats.effect.IO`, the typical use
|
||||||
* Specialization of [[TestGroup]] for `cats.effect.IO`, the typical use case.
|
* case.
|
||||||
*/
|
*/
|
||||||
abstract class IO extends TestGroup[cats.effect.IO]
|
abstract class IO extends TestGroup[cats.effect.IO]
|
||||||
|
|
||||||
/**
|
/** Builder to assist with defining tests.
|
||||||
* Builder to assist with defining tests.
|
*
|
||||||
*
|
* @param registry
|
||||||
* @param registry Registry instance internal to a [[TestGroup]] for recording completed definitions.
|
* Registry instance internal to a [[TestGroup]] for recording completed
|
||||||
* @param name The name of the test.
|
* definitions.
|
||||||
* @param permanentId The [[PermanentId]] of the test.
|
* @param name
|
||||||
* @param tags List of [[TestDefinition.Tag]] applicable to this test.
|
* The name of the test.
|
||||||
* @param markers List of [[TestDefinition.Marker]] applicable to this test.
|
* @param permanentId
|
||||||
* @param documentation The documentation for this test.
|
* The [[PermanentId]] of the test.
|
||||||
* @param iterations Number of iterations to run this test.
|
* @param tags
|
||||||
*/
|
* List of [[TestDefinition.Tag]] applicable to this test.
|
||||||
protected final class TestBuilder[F[_]: Async](
|
* @param markers
|
||||||
|
* List of [[TestDefinition.Marker]] applicable to this test.
|
||||||
|
* @param documentation
|
||||||
|
* The documentation for this test.
|
||||||
|
* @param iterations
|
||||||
|
* Number of iterations to run this test.
|
||||||
|
*/
|
||||||
|
final protected class TestBuilder[F[_]: Async](
|
||||||
val registry: Registry[F],
|
val registry: Registry[F],
|
||||||
val name: TestDefinition.Name,
|
val name: TestDefinition.Name,
|
||||||
val permanentId: PermanentId,
|
val permanentId: PermanentId,
|
||||||
private val tags: ListBuffer[Tag],
|
private val tags: ListBuffer[Tag],
|
||||||
private val markers: ListBuffer[Marker],
|
private val markers: ListBuffer[Marker],
|
||||||
private var documentation: Option[String] = None,
|
private var documentation: Option[String] = None,
|
||||||
private var iterations: TestIterations = TestIterations.One,
|
private var iterations: TestIterations = TestIterations.One
|
||||||
):
|
):
|
||||||
/**
|
|
||||||
* Supply documentation for this test.
|
/** Supply documentation for this test.
|
||||||
*
|
*
|
||||||
* @param docs The documentation for this test.
|
* @param docs
|
||||||
* @return This builder.
|
* The documentation for this test.
|
||||||
*/
|
* @return
|
||||||
|
* This builder.
|
||||||
|
*/
|
||||||
def document(docs: String): TestBuilder[F] =
|
def document(docs: String): TestBuilder[F] =
|
||||||
documentation = Some(docs)
|
documentation = Some(docs)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add additional [[Test.Tag]] to this test definition.
|
||||||
* Add additional [[Test.Tag]] to this test definition.
|
*
|
||||||
*
|
* @param additionalTags
|
||||||
* @param additionalTags The list of new tags.
|
* The list of new tags.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def tagged(additionalTags: Tag*): TestBuilder[F] =
|
def tagged(additionalTags: Tag*): TestBuilder[F] =
|
||||||
val _ = tags.addAll(additionalTags)
|
val _ = tags.addAll(additionalTags)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add the [[TestDefinition.Marker.Ignored]] marker to this test
|
||||||
* Add the [[TestDefinition.Marker.Ignored]] marker to this test definition.
|
* definition.
|
||||||
*
|
*
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def ignored(): TestBuilder[F] =
|
def ignored(): TestBuilder[F] =
|
||||||
val _ = markers.addOne(Marker.Ignored)
|
val _ = markers.addOne(Marker.Ignored)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add one or more [[TestDefinition.Marker]] to this test definition.
|
||||||
* Add one or more [[TestDefinition.Marker]] to this test definition.
|
*
|
||||||
*
|
* @param additionalMarkers
|
||||||
* @param additionalMarkers The list of markers to add.
|
* The list of markers to add.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def marked(additionalMarkers: Marker*): TestBuilder[F] =
|
def marked(additionalMarkers: Marker*): TestBuilder[F] =
|
||||||
val _ = markers.addAll(additionalMarkers)
|
val _ = markers.addAll(additionalMarkers)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Set the number of times this test should iterate.
|
||||||
* Set the number of times this test should iterate.
|
*
|
||||||
*
|
* @param iters
|
||||||
* @param iters The number of iterations.
|
* The number of iterations.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def iterate(iters: TestIterations): TestBuilder[F] =
|
def iterate(iters: TestIterations): TestBuilder[F] =
|
||||||
iterations = iters
|
iterations = iters
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Provide an input supplier for this test. Note that each iteration of the
|
||||||
* Provide an input supplier for this test. Note that each iteration of the
|
* test results in the input function being evaluated.
|
||||||
* test results in the input function being evaluated.
|
*
|
||||||
*
|
* @param f
|
||||||
* @param f The input function.
|
* The input function.
|
||||||
* @return Builder that supports input.
|
* @return
|
||||||
*/
|
* Builder that supports input.
|
||||||
|
*/
|
||||||
def input[Input](f: F[Input]): InputTestBuilder[F, Input] =
|
def input[Input](f: F[Input]): InputTestBuilder[F, Input] =
|
||||||
new InputTestBuilder[F, Input](
|
new InputTestBuilder[F, Input](
|
||||||
registry = registry,
|
registry = registry,
|
||||||
|
@ -240,52 +254,62 @@ object TestGroup:
|
||||||
iterations = iterations
|
iterations = iterations
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with a pure unit of work.
|
||||||
* Finalize and register this test with a pure unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def pure(unitOfWork: => Either[TestFailure, Unit]): Unit =
|
def pure(unitOfWork: => Either[TestFailure, Unit]): Unit =
|
||||||
apply(EitherT.fromEither[F](unitOfWork))
|
apply(EitherT.fromEither[F](unitOfWork))
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
* Finalize and register this test with an effectful unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def effectful(unitOfWork: => F[Either[TestFailure, Unit]]): Unit =
|
def effectful(unitOfWork: => F[Either[TestFailure, Unit]]): Unit =
|
||||||
apply(EitherT(unitOfWork))
|
apply(EitherT(unitOfWork))
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
* Finalize and register this test with an effectful unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def apply(unitOfWork: => EitherT[F, TestFailure, Unit]): Unit =
|
def apply(unitOfWork: => EitherT[F, TestFailure, Unit]): Unit =
|
||||||
registry.register(new TestDefinition[F](
|
registry.register(
|
||||||
name = name,
|
new TestDefinition[F](
|
||||||
permanentId = permanentId,
|
name = name,
|
||||||
documentation = documentation,
|
permanentId = permanentId,
|
||||||
tags = tags.distinct.toList,
|
documentation = documentation,
|
||||||
markers = markers.distinct.toList,
|
tags = tags.distinct.toList,
|
||||||
iterations = iterations,
|
markers = markers.distinct.toList,
|
||||||
unitOfWork = unitOfWork
|
iterations = iterations,
|
||||||
))
|
unitOfWork = unitOfWork
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/** Builder to assist with defining tests. This builder is for tests which
|
||||||
* Builder to assist with defining tests. This builder is for tests which
|
* accept input via some producing function.
|
||||||
* accept input via some producing function.
|
*
|
||||||
*
|
* @param registry
|
||||||
* @param registry Registry instance internal to a [[TestGroup]] for recording completed definitions.
|
* Registry instance internal to a [[TestGroup]] for recording completed
|
||||||
* @param name The name of the test.
|
* definitions.
|
||||||
* @param permanentId The [[PermanentId]] of the test.
|
* @param name
|
||||||
* @param inputFunction The function that provides input to this test.
|
* The name of the test.
|
||||||
* @param tags List of [[TestDefinition.Tag]] applicable to this test.
|
* @param permanentId
|
||||||
* @param markers List of [[TestDefinition.Marker]] applicable to this test.
|
* The [[PermanentId]] of the test.
|
||||||
* @param documentation The documentation for this test.
|
* @param inputFunction
|
||||||
* @param iterations Number of iterations to run this test.
|
* The function that provides input to this test.
|
||||||
*/
|
* @param tags
|
||||||
protected final class InputTestBuilder[F[_]: Async, Input](
|
* List of [[TestDefinition.Tag]] applicable to this test.
|
||||||
|
* @param markers
|
||||||
|
* List of [[TestDefinition.Marker]] applicable to this test.
|
||||||
|
* @param documentation
|
||||||
|
* The documentation for this test.
|
||||||
|
* @param iterations
|
||||||
|
* Number of iterations to run this test.
|
||||||
|
*/
|
||||||
|
final protected class InputTestBuilder[F[_]: Async, Input](
|
||||||
val registry: Registry[F],
|
val registry: Registry[F],
|
||||||
val name: TestDefinition.Name,
|
val name: TestDefinition.Name,
|
||||||
val permanentId: PermanentId,
|
val permanentId: PermanentId,
|
||||||
|
@ -293,90 +317,99 @@ object TestGroup:
|
||||||
private val tags: ListBuffer[Tag],
|
private val tags: ListBuffer[Tag],
|
||||||
private val markers: ListBuffer[Marker],
|
private val markers: ListBuffer[Marker],
|
||||||
private var documentation: Option[String] = None,
|
private var documentation: Option[String] = None,
|
||||||
private var iterations: TestIterations = TestIterations.One,
|
private var iterations: TestIterations = TestIterations.One
|
||||||
):
|
):
|
||||||
/**
|
|
||||||
* Supply documentation for this test.
|
/** Supply documentation for this test.
|
||||||
*
|
*
|
||||||
* @param docs The documentation for this test.
|
* @param docs
|
||||||
* @return This builder.
|
* The documentation for this test.
|
||||||
*/
|
* @return
|
||||||
|
* This builder.
|
||||||
|
*/
|
||||||
def document(docs: String): InputTestBuilder[F, Input] =
|
def document(docs: String): InputTestBuilder[F, Input] =
|
||||||
documentation = Some(docs)
|
documentation = Some(docs)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add additional [[Test.Tag]] to this test definition.
|
||||||
* Add additional [[Test.Tag]] to this test definition.
|
*
|
||||||
*
|
* @param additionalTags
|
||||||
* @param additionalTags The list of new tags.
|
* The list of new tags.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def tagged(additionalTags: Tag*): InputTestBuilder[F, Input] =
|
def tagged(additionalTags: Tag*): InputTestBuilder[F, Input] =
|
||||||
val _ = tags.addAll(additionalTags)
|
val _ = tags.addAll(additionalTags)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add the [[TestDefinition.Marker.Ignored]] marker to this test
|
||||||
* Add the [[TestDefinition.Marker.Ignored]] marker to this test definition.
|
* definition.
|
||||||
*
|
*
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def ignored(): InputTestBuilder[F, Input] =
|
def ignored(): InputTestBuilder[F, Input] =
|
||||||
val _ = markers.addOne(Marker.Ignored)
|
val _ = markers.addOne(Marker.Ignored)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Add one or more [[TestDefinition.Marker]] to this test definition.
|
||||||
* Add one or more [[TestDefinition.Marker]] to this test definition.
|
*
|
||||||
*
|
* @param additionalMarkers
|
||||||
* @param additionalMarkers The list of markers to add.
|
* The list of markers to add.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def marked(additionalMarkers: Marker*): InputTestBuilder[F, Input] =
|
def marked(additionalMarkers: Marker*): InputTestBuilder[F, Input] =
|
||||||
val _ = markers.addAll(additionalMarkers)
|
val _ = markers.addAll(additionalMarkers)
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Set the number of times this test should iterate.
|
||||||
* Set the number of times this test should iterate.
|
*
|
||||||
*
|
* @param iters
|
||||||
* @param iters The number of iterations.
|
* The number of iterations.
|
||||||
* @return This builder.
|
* @return
|
||||||
*/
|
* This builder.
|
||||||
|
*/
|
||||||
def iterate(iters: TestIterations): InputTestBuilder[F, Input] =
|
def iterate(iters: TestIterations): InputTestBuilder[F, Input] =
|
||||||
iterations = iters
|
iterations = iters
|
||||||
this
|
this
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with a pure unit of work.
|
||||||
* Finalize and register this test with a pure unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def pure(unitOfWork: Input => Either[TestFailure, Unit]): Unit =
|
def pure(unitOfWork: Input => Either[TestFailure, Unit]): Unit =
|
||||||
apply(input => EitherT(Async[F].delay(unitOfWork(input))))
|
apply(input => EitherT(Async[F].delay(unitOfWork(input))))
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
* Finalize and register this test with an effectful unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def effectful(unitOfWork: Input => F[Either[TestFailure, Unit]]): Unit =
|
def effectful(unitOfWork: Input => F[Either[TestFailure, Unit]]): Unit =
|
||||||
apply(input => EitherT(unitOfWork(input)))
|
apply(input => EitherT(unitOfWork(input)))
|
||||||
|
|
||||||
/**
|
/** Finalize and register this test with an effectful unit of work.
|
||||||
* Finalize and register this test with an effectful unit of work.
|
*
|
||||||
*
|
* @param unitOfWork
|
||||||
* @param unitOfWork The function this test will execute.
|
* The function this test will execute.
|
||||||
*/
|
*/
|
||||||
def apply(unitOfWork: Input => EitherT[F, TestFailure, Unit]): Unit =
|
def apply(unitOfWork: Input => EitherT[F, TestFailure, Unit]): Unit =
|
||||||
registry.register(new TestDefinition[F](
|
registry.register(
|
||||||
name = name,
|
new TestDefinition[F](
|
||||||
permanentId = permanentId,
|
name = name,
|
||||||
documentation = documentation,
|
permanentId = permanentId,
|
||||||
tags = tags.distinct.toList,
|
documentation = documentation,
|
||||||
markers = markers.distinct.toList,
|
tags = tags.distinct.toList,
|
||||||
iterations = iterations,
|
markers = markers.distinct.toList,
|
||||||
unitOfWork = EitherT.right(inputFunction).flatMap(unitOfWork)
|
iterations = iterations,
|
||||||
))
|
unitOfWork = EitherT.right(inputFunction).flatMap(unitOfWork)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
final protected class Registry[F[_]]:
|
||||||
|
|
||||||
protected final class Registry[F[_]]:
|
|
||||||
val mapping: ConcurrentHashMap[PermanentId, TestDefinition[F]] =
|
val mapping: ConcurrentHashMap[PermanentId, TestDefinition[F]] =
|
||||||
new ConcurrentHashMap[PermanentId, TestDefinition[F]]
|
new ConcurrentHashMap[PermanentId, TestDefinition[F]]
|
||||||
|
|
||||||
|
@ -385,8 +418,7 @@ object TestGroup:
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Attempted to register test with duplicate Permanent ID '${test.permanentId.show}'."
|
s"Attempted to register test with duplicate Permanent ID '${test.permanentId.show}'."
|
||||||
)
|
)
|
||||||
else
|
else mapping.put(test.permanentId, test)
|
||||||
mapping.put(test.permanentId, test)
|
|
||||||
|
|
||||||
def toList(): List[TestDefinition[F]] = mapping.values().asScala.toList
|
def toList(): List[TestDefinition[F]] = mapping.values().asScala.toList
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,20 @@ package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
/**
|
/** Each group is comprised of a list of [[Test]]. This list may be empty.
|
||||||
* Each group is comprised of a list of [[Test]]. This list may be empty.
|
|
||||||
*
|
*
|
||||||
* Groups are essentially metadata for tests for viewing/organization purposes.
|
* Groups are essentially metadata for tests for viewing/organization purposes.
|
||||||
*
|
*
|
||||||
* @param name The group name. Not considered to be unique.
|
* @param name
|
||||||
* @param documentation Arbitrary documentation for this group of tests.
|
* The group name. Not considered to be unique.
|
||||||
* @param testTags Set of tags applied to all [[TestDefinition]] within the group.
|
* @param documentation
|
||||||
* @param testMarkers Set of markers applied to all [[TestDefinition]] within the group.
|
* Arbitrary documentation for this group of tests.
|
||||||
* @param tests The list of tests in this group.
|
* @param testTags
|
||||||
|
* Set of tags applied to all [[TestDefinition]] within the group.
|
||||||
|
* @param testMarkers
|
||||||
|
* Set of markers applied to all [[TestDefinition]] within the group.
|
||||||
|
* @param tests
|
||||||
|
* The list of tests in this group.
|
||||||
*/
|
*/
|
||||||
final class TestGroupDefinition[F[_]](
|
final class TestGroupDefinition[F[_]](
|
||||||
val name: TestGroupDefinition.Name,
|
val name: TestGroupDefinition.Name,
|
||||||
|
@ -27,20 +31,20 @@ final class TestGroupDefinition[F[_]](
|
||||||
|
|
||||||
object TestGroupDefinition:
|
object TestGroupDefinition:
|
||||||
|
|
||||||
/**
|
/** Opaque type representing names that may be assigned to test groups.
|
||||||
* Opaque type representing names that may be assigned to test groups.
|
*/
|
||||||
*/
|
|
||||||
opaque type Name = String
|
opaque type Name = String
|
||||||
|
|
||||||
object Name:
|
object Name:
|
||||||
|
|
||||||
/**
|
/** Instantiate a new [[TestGroup.Name]]. This name is not unique, has no
|
||||||
* Instantiate a new [[TestGroup.Name]]. This name is not unique, has no
|
* constraints, and only exists for display purposes.
|
||||||
* constraints, and only exists for display purposes.
|
*
|
||||||
*
|
* @param name
|
||||||
* @param name The candidate string.
|
* The candidate string.
|
||||||
* @return The new [[TestGroup.Name]] instance.
|
* @return
|
||||||
*/
|
* The new [[TestGroup.Name]] instance.
|
||||||
|
*/
|
||||||
def apply(name: String): Name = name
|
def apply(name: String): Name = name
|
||||||
|
|
||||||
given CanEqual[Name, Name] = CanEqual.derived
|
given CanEqual[Name, Name] = CanEqual.derived
|
||||||
|
|
|
@ -2,36 +2,34 @@ package gs.test.v0.definition
|
||||||
|
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
/**
|
/** Opaque type that represents the number of iterations a test should run. This
|
||||||
* Opaque type that represents the number of iterations a test should run.
|
* value must be at least `1` (the default). To ignore a test, use the
|
||||||
* This value must be at least `1` (the default). To ignore a test, use the
|
* [[Test.Marker.Ignored]] marker.
|
||||||
* [[Test.Marker.Ignored]] marker.
|
*/
|
||||||
*/
|
|
||||||
opaque type TestIterations = Int
|
opaque type TestIterations = Int
|
||||||
|
|
||||||
object TestIterations:
|
object TestIterations:
|
||||||
|
|
||||||
def One: TestIterations = 1
|
def One: TestIterations = 1
|
||||||
|
|
||||||
/**
|
/** Validate and instantiate a new [[TestIterations]] instance.
|
||||||
* Validate and instantiate a new [[TestIterations]] instance.
|
*
|
||||||
*
|
* @param candidate
|
||||||
* @param candidate The candidate value. Must be 1 or greater.
|
* The candidate value. Must be 1 or greater.
|
||||||
* @return The new [[TestIterations]], or an error if an invalid input is given.
|
* @return
|
||||||
*/
|
* The new [[TestIterations]], or an error if an invalid input is given.
|
||||||
|
*/
|
||||||
def apply(candidate: Int): TestIterations =
|
def apply(candidate: Int): TestIterations =
|
||||||
if candidate < 1 then
|
if candidate < 1 then
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Tests must iterate at least once. Received candidate '$candidate'."
|
s"Tests must iterate at least once. Received candidate '$candidate'."
|
||||||
)
|
)
|
||||||
else
|
else candidate
|
||||||
candidate
|
|
||||||
|
|
||||||
given CanEqual[TestIterations, TestIterations] = CanEqual.derived
|
given CanEqual[TestIterations, TestIterations] = CanEqual.derived
|
||||||
|
|
||||||
given Show[TestIterations] = iters => iters.toString()
|
given Show[TestIterations] = iters => iters.toString()
|
||||||
|
|
||||||
extension (iters: TestIterations)
|
extension (iters: TestIterations) def toInt(): Int = iters
|
||||||
def toInt(): Int = iters
|
|
||||||
|
|
||||||
end TestIterations
|
end TestIterations
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
/**
|
/** The Test Suite is the primary unit of organization within `gs-test` -- each
|
||||||
* The Test Suite is the primary unit of organization within `gs-test` -- each
|
|
||||||
* execution _typically_ runs a single test suite. For example, the unit tests
|
* execution _typically_ runs a single test suite. For example, the unit tests
|
||||||
* for some project would likely comprise of a single suite.
|
* for some project would likely comprise of a single suite.
|
||||||
*
|
*
|
||||||
* Within each suite is a list of [[TestGroup]], arbitrary ways to organize
|
* Within each suite is a list of [[TestGroup]], arbitrary ways to organize
|
||||||
* individual [[Test]] definitions.
|
* individual [[Test]] definitions.
|
||||||
*
|
*
|
||||||
* @param name The name of this test suite.
|
* @param name
|
||||||
* @param documentation Arbitrary documentation for this suite of tests.
|
* The name of this test suite.
|
||||||
* @param groups List of [[TestGroup]] owned by this suite.
|
* @param documentation
|
||||||
|
* Arbitrary documentation for this suite of tests.
|
||||||
|
* @param groups
|
||||||
|
* List of [[TestGroup]] owned by this suite.
|
||||||
*/
|
*/
|
||||||
case class TestSuite[F[_]](
|
case class TestSuite[F[_]](
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -5,8 +5,7 @@ import cats.data.EitherT
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
|
|
||||||
/**
|
/** String interpolator for [[Tag]]. Shorthand for producing new [[Tag]]
|
||||||
* String interpolator for [[Tag]]. Shorthand for producing new [[Tag]]
|
|
||||||
* instances.
|
* instances.
|
||||||
*
|
*
|
||||||
* {{{
|
* {{{
|
||||||
|
@ -14,11 +13,9 @@ import cats.syntax.all.*
|
||||||
* val tag1: TestDefinition.Tag = tag"example"
|
* val tag1: TestDefinition.Tag = tag"example"
|
||||||
* }}}
|
* }}}
|
||||||
*/
|
*/
|
||||||
extension (sc: StringContext)
|
extension (sc: StringContext) def tag(args: Any*): Tag = Tag(sc.s(args*))
|
||||||
def tag(args: Any*): Tag = Tag(sc.s(args*))
|
|
||||||
|
|
||||||
/**
|
/** String interpolator for [[PermanentId]]. Shorthand for producing new
|
||||||
* String interpolator for [[PermanentId]]. Shorthand for producing new
|
|
||||||
* [[PermanentId]] instances.
|
* [[PermanentId]] instances.
|
||||||
*
|
*
|
||||||
* {{{
|
* {{{
|
||||||
|
@ -29,35 +26,37 @@ extension (sc: StringContext)
|
||||||
extension (sc: StringContext)
|
extension (sc: StringContext)
|
||||||
def pid(args: Any*): PermanentId = PermanentId(sc.s(args*))
|
def pid(args: Any*): PermanentId = PermanentId(sc.s(args*))
|
||||||
|
|
||||||
/**
|
/** Request this test to fail (pure form).
|
||||||
* Request this test to fail (pure form).
|
|
||||||
*
|
*
|
||||||
* @param message The message to report - why did this test fail?
|
* @param message
|
||||||
* @return The failing test result.
|
* The message to report - why did this test fail?
|
||||||
|
* @return
|
||||||
|
* The failing test result.
|
||||||
*/
|
*/
|
||||||
def fail(message: String): Either[TestFailure, Unit] =
|
def fail(message: String): Either[TestFailure, Unit] =
|
||||||
Left(TestFailure.TestRequestedFailure(message))
|
Left(TestFailure.TestRequestedFailure(message))
|
||||||
|
|
||||||
/**
|
/** Request this test to fail (lifted into F).
|
||||||
* Request this test to fail (lifted into F).
|
|
||||||
*
|
*
|
||||||
* @param message The message to report - why did this test fail?
|
* @param message
|
||||||
* @return The failing test result.
|
* The message to report - why did this test fail?
|
||||||
|
* @return
|
||||||
|
* The failing test result.
|
||||||
*/
|
*/
|
||||||
def failF[F[_]: Applicative](message: String): F[Either[TestFailure, Unit]] =
|
def failF[F[_]: Applicative](message: String): F[Either[TestFailure, Unit]] =
|
||||||
Applicative[F].pure(fail(message))
|
Applicative[F].pure(fail(message))
|
||||||
|
|
||||||
/**
|
/** Request this test to fail (lifted into EitherT).
|
||||||
* Request this test to fail (lifted into EitherT).
|
|
||||||
*
|
*
|
||||||
* @param message The message to report - why did this test fail?
|
* @param message
|
||||||
* @return The failing test result.
|
* The message to report - why did this test fail?
|
||||||
|
* @return
|
||||||
|
* The failing test result.
|
||||||
*/
|
*/
|
||||||
def failT[F[_]: Applicative](message: String): EitherT[F, TestFailure, Unit] =
|
def failT[F[_]: Applicative](message: String): EitherT[F, TestFailure, Unit] =
|
||||||
EitherT(failF(message))
|
EitherT(failF(message))
|
||||||
|
|
||||||
/**
|
/** Shorthand for indicating a passing test (pure form).
|
||||||
* Shorthand for indicating a passing test (pure form).
|
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
|
@ -70,12 +69,12 @@ def failT[F[_]: Applicative](message: String): EitherT[F, TestFailure, Unit] =
|
||||||
* test(pid"ex", "Example Test").pure { pass() }
|
* test(pid"ex", "Example Test").pure { pass() }
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* @return The passing test result.
|
* @return
|
||||||
|
* The passing test result.
|
||||||
*/
|
*/
|
||||||
def pass(): Either[TestFailure, Unit] = Right(())
|
def pass(): Either[TestFailure, Unit] = Right(())
|
||||||
|
|
||||||
/**
|
/** Shorthand for indicating a passing test (lifted into F).
|
||||||
* Shorthand for indicating a passing test (lifted into F).
|
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
|
@ -88,13 +87,13 @@ def pass(): Either[TestFailure, Unit] = Right(())
|
||||||
* test(pid"ex", "Example Test").effectful { passF() }
|
* test(pid"ex", "Example Test").effectful { passF() }
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* @return The passing test result.
|
* @return
|
||||||
|
* The passing test result.
|
||||||
*/
|
*/
|
||||||
def passF[F[_]: Applicative](): F[Either[TestFailure, Unit]] =
|
def passF[F[_]: Applicative](): F[Either[TestFailure, Unit]] =
|
||||||
Applicative[F].pure(Right(()))
|
Applicative[F].pure(Right(()))
|
||||||
|
|
||||||
/**
|
/** Shorthand for indicating a passing test (lifted into EitherT).
|
||||||
* Shorthand for indicating a passing test (lifted into EitherT).
|
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
|
@ -107,35 +106,45 @@ def passF[F[_]: Applicative](): F[Either[TestFailure, Unit]] =
|
||||||
* test(pid"ex", "Example Test") { passT() }
|
* test(pid"ex", "Example Test") { passT() }
|
||||||
* }}}
|
* }}}
|
||||||
*
|
*
|
||||||
* @return The passing test result.
|
* @return
|
||||||
|
* The passing test result.
|
||||||
*/
|
*/
|
||||||
def passT[F[_]: Applicative](): EitherT[F, TestFailure, Unit] =
|
def passT[F[_]: Applicative](): EitherT[F, TestFailure, Unit] =
|
||||||
EitherT(passF())
|
EitherT(passF())
|
||||||
|
|
||||||
/**
|
/** Check all of the given results, returning the first failure, or a successful
|
||||||
* Check all of the given results, returning the first failure, or a successful
|
|
||||||
* result if no result failed.
|
* result if no result failed.
|
||||||
*
|
*
|
||||||
* @param results The list of results to check.
|
* @param results
|
||||||
* @return Successful result or the first failure.
|
* The list of results to check.
|
||||||
|
* @return
|
||||||
|
* Successful result or the first failure.
|
||||||
*/
|
*/
|
||||||
def checkAll(
|
def checkAll(
|
||||||
results: Either[TestFailure, Unit]*
|
results: Either[TestFailure, Unit]*
|
||||||
): Either[TestFailure, Unit] =
|
): Either[TestFailure, Unit] =
|
||||||
val initial: Either[TestFailure, Unit] = Right(())
|
val initial: Either[TestFailure, Unit] = Right(())
|
||||||
results.foldLeft(initial) { (acc, result) =>
|
results.foldLeft(initial) {
|
||||||
acc match
|
(
|
||||||
case Left(_) => acc
|
acc,
|
||||||
case Right(_) => result
|
result
|
||||||
|
) =>
|
||||||
|
acc match
|
||||||
|
case Left(_) => acc
|
||||||
|
case Right(_) => result
|
||||||
}
|
}
|
||||||
|
|
||||||
def checkAllF[F[_]: Sync](
|
def checkAllF[F[_]: Sync](
|
||||||
checks: F[Either[TestFailure, Unit]]*
|
checks: F[Either[TestFailure, Unit]]*
|
||||||
): F[Either[TestFailure, Unit]] =
|
): F[Either[TestFailure, Unit]] =
|
||||||
val initial: F[Either[TestFailure, Unit]] = Sync[F].delay(Right(()))
|
val initial: F[Either[TestFailure, Unit]] = Sync[F].delay(Right(()))
|
||||||
checks.foldLeft(initial) { (acc, result) =>
|
checks.foldLeft(initial) {
|
||||||
acc.flatMap {
|
(
|
||||||
case Right(_) => result
|
acc,
|
||||||
case err => Sync[F].pure(err)
|
result
|
||||||
}
|
) =>
|
||||||
|
acc.flatMap {
|
||||||
|
case Right(_) => result
|
||||||
|
case err => Sync[F].pure(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
package gs.test.v0.definition
|
package gs.test.v0.definition
|
||||||
|
|
||||||
import munit.*
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
|
|
||||||
import gs.test.v0.definition.{Tag => GsTag}
|
import gs.test.v0.definition.{Tag => GsTag}
|
||||||
|
import munit.*
|
||||||
|
|
||||||
class GroupImplementationTests extends FunSuite:
|
class GroupImplementationTests extends FunSuite:
|
||||||
import GroupImplementationTests.*
|
import GroupImplementationTests.*
|
||||||
|
|
||||||
test("should support a group with a simple, pure, test") {
|
test("should support a group with a simple, pure, test") {
|
||||||
val g1 = new G1
|
val g1 = new G1
|
||||||
val group = g1.compile()
|
val group = g1.compile()
|
||||||
assertEquals(group.name, TestGroupDefinition.Name("G1"))
|
assertEquals(group.name, TestGroupDefinition.Name("G1"))
|
||||||
assertEquals(group.documentation, None)
|
assertEquals(group.documentation, None)
|
||||||
|
@ -33,7 +32,7 @@ class GroupImplementationTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should support a group with all values set") {
|
test("should support a group with all values set") {
|
||||||
val g2 = new G2
|
val g2 = new G2
|
||||||
val group = g2.compile()
|
val group = g2.compile()
|
||||||
assertEquals(group.name, TestGroupDefinition.Name("G2"))
|
assertEquals(group.name, TestGroupDefinition.Name("G2"))
|
||||||
assertEquals(group.documentation, Some("docs"))
|
assertEquals(group.documentation, Some("docs"))
|
||||||
|
@ -57,7 +56,7 @@ class GroupImplementationTests extends FunSuite:
|
||||||
}
|
}
|
||||||
|
|
||||||
test("should support a simple group with a configured test") {
|
test("should support a simple group with a configured test") {
|
||||||
val g3 = new G3
|
val g3 = new G3
|
||||||
val group = g3.compile()
|
val group = g3.compile()
|
||||||
assertEquals(group.name, TestGroupDefinition.Name("G3"))
|
assertEquals(group.name, TestGroupDefinition.Name("G3"))
|
||||||
assertEquals(group.documentation, None)
|
assertEquals(group.documentation, None)
|
||||||
|
@ -92,10 +91,11 @@ object GroupImplementationTests:
|
||||||
|
|
||||||
class G1 extends TestGroup.IO:
|
class G1 extends TestGroup.IO:
|
||||||
override def name: String = "G1"
|
override def name: String = "G1"
|
||||||
test(Ids.T1, "simple").pure { Right(()) }
|
test(Ids.T1, "simple").pure(Right(()))
|
||||||
end G1
|
end G1
|
||||||
|
|
||||||
class G2 extends TestGroup.IO:
|
class G2 extends TestGroup.IO:
|
||||||
|
|
||||||
override def name: String =
|
override def name: String =
|
||||||
"G2"
|
"G2"
|
||||||
|
|
||||||
|
@ -108,12 +108,12 @@ object GroupImplementationTests:
|
||||||
override def markers: List[Marker] =
|
override def markers: List[Marker] =
|
||||||
List(Marker.Ignored)
|
List(Marker.Ignored)
|
||||||
|
|
||||||
beforeGroup { IO.unit }
|
beforeGroup(IO.unit)
|
||||||
afterGroup { IO.unit }
|
afterGroup(IO.unit)
|
||||||
beforeEachTest { IO.unit }
|
beforeEachTest(IO.unit)
|
||||||
afterEachTest { IO.unit }
|
afterEachTest(IO.unit)
|
||||||
|
|
||||||
test(Ids.T2, "inherit from group").pure { Right(()) }
|
test(Ids.T2, "inherit from group").pure(Right(()))
|
||||||
end G2
|
end G2
|
||||||
|
|
||||||
class G3 extends TestGroup.IO:
|
class G3 extends TestGroup.IO:
|
||||||
|
@ -124,7 +124,8 @@ object GroupImplementationTests:
|
||||||
.tagged(tag"tag1", tag"tag2")
|
.tagged(tag"tag1", tag"tag2")
|
||||||
.marked(Marker.Ignored)
|
.marked(Marker.Ignored)
|
||||||
.iterate(TestIterations(2))
|
.iterate(TestIterations(2))
|
||||||
.pure { Right(()) }
|
.pure(Right(()))
|
||||||
|
|
||||||
end G3
|
end G3
|
||||||
|
|
||||||
end GroupImplementationTests
|
end GroupImplementationTests
|
||||||
|
|
Loading…
Add table
Reference in a new issue