Scala 3.5.0, cleanup, reorganization, and more tests. #1
11 changed files with 217 additions and 110 deletions
|
@ -1,4 +1,4 @@
|
||||||
val scala3: String = "3.4.2"
|
val scala3: String = "3.5.0"
|
||||||
|
|
||||||
ThisBuild / scalaVersion := scala3
|
ThisBuild / scalaVersion := scala3
|
||||||
ThisBuild / versionScheme := Some("semver-spec")
|
ThisBuild / versionScheme := Some("semver-spec")
|
||||||
|
@ -9,6 +9,10 @@ ThisBuild / externalResolvers := Seq(
|
||||||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ThisBuild / licenses := Seq(
|
||||||
|
"MIT" -> url("https://garrity.co/MIT.html")
|
||||||
|
)
|
||||||
|
|
||||||
val noPublishSettings = Seq(
|
val noPublishSettings = Seq(
|
||||||
publish := {}
|
publish := {}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package gs.test.v0
|
|
||||||
|
|
||||||
sealed trait GsTestError
|
|
||||||
|
|
||||||
object GsTestError:
|
|
||||||
|
|
||||||
sealed trait TestDefinitionError extends GsTestError
|
|
||||||
|
|
||||||
object TestDefinitionError:
|
|
||||||
|
|
||||||
case class InvalidIterations(candidate: Int) extends TestDefinitionError
|
|
||||||
|
|
||||||
end TestDefinitionError
|
|
||||||
|
|
||||||
end GsTestError
|
|
22
modules/core/src/main/scala/gs/test/v0/Marker.scala
Normal file
22
modules/core/src/main/scala/gs/test/v0/Marker.scala
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package gs.test.v0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration for _Markers_, special tokens which "mark" a test to change
|
||||||
|
* execution functionality.
|
||||||
|
*
|
||||||
|
* The basic case for this enumeration is allowing tests to be ignored.
|
||||||
|
*
|
||||||
|
* @param name The formal serialized name of the marker.
|
||||||
|
*/
|
||||||
|
sealed abstract class Marker(val name: String)
|
||||||
|
|
||||||
|
object Marker:
|
||||||
|
|
||||||
|
given CanEqual[Marker, Marker] = CanEqual.derived
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this [[Marker]] is present on a test, the test will be ignored.
|
||||||
|
*/
|
||||||
|
case object Ignored extends Marker("ignored")
|
||||||
|
|
||||||
|
end Marker
|
24
modules/core/src/main/scala/gs/test/v0/Tag.scala
Normal file
24
modules/core/src/main/scala/gs/test/v0/Tag.scala
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package gs.test.v0
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque type representing tags that may be assigned to a [[Test]].
|
||||||
|
*/
|
||||||
|
opaque type Tag = String
|
||||||
|
|
||||||
|
object Tag:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a new [[Tag]].
|
||||||
|
*
|
||||||
|
* @param tag The candidate string.
|
||||||
|
* @return The new [[Tag]] instance.
|
||||||
|
*/
|
||||||
|
def apply(tag: String): Tag = tag
|
||||||
|
|
||||||
|
given CanEqual[Tag, Tag] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[Tag] = tag => tag
|
||||||
|
|
||||||
|
end Tag
|
|
@ -2,24 +2,25 @@ package gs.test.v0
|
||||||
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.Show
|
import cats.Show
|
||||||
import gs.test.v0.GsTestError.TestDefinitionError.InvalidIterations
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 The display name of the test. Not considered to be unique.
|
||||||
* @param permanentId The [[PermanentId]] for this test.
|
* @param permanentId The [[PermanentId]] for this test.
|
||||||
* @param tags The set of [[Test.Tag]] applicable to this test.
|
* @param documentation The documentation for this test.
|
||||||
|
* @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 iterations The number of iterations of this test to run.
|
||||||
* @param f The effect that the test evaluates.
|
* @param unitOfWork The function that the test evaluates.
|
||||||
*/
|
*/
|
||||||
final class TestDefinition[F[_]](
|
final class TestDefinition[F[_]](
|
||||||
val name: TestDefinition.Name,
|
val name: TestDefinition.Name,
|
||||||
val permanentId: PermanentId,
|
val permanentId: PermanentId,
|
||||||
val documentation: Option[String],
|
val documentation: Option[String],
|
||||||
val tags: List[TestDefinition.Tag],
|
val tags: List[Tag],
|
||||||
val markers: List[TestDefinition.Marker],
|
val markers: List[Marker],
|
||||||
val iterations: TestDefinition.Iterations,
|
val iterations: TestIterations,
|
||||||
val unitOfWork: EitherT[F, TestFailure, Unit]
|
val unitOfWork: EitherT[F, TestFailure, Unit]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -47,78 +48,4 @@ object TestDefinition:
|
||||||
|
|
||||||
end Name
|
end Name
|
||||||
|
|
||||||
/**
|
|
||||||
* Opaque type representing tags that may be assigned to a [[Test]].
|
|
||||||
*/
|
|
||||||
opaque type Tag = String
|
|
||||||
|
|
||||||
object Tag:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate a new [[Test.Tag]].
|
|
||||||
*
|
|
||||||
* @param tag The candidate string.
|
|
||||||
* @return The new [[Test.Tag]] instance.
|
|
||||||
*/
|
|
||||||
def apply(tag: String): Tag = tag
|
|
||||||
|
|
||||||
given CanEqual[Tag, Tag] = CanEqual.derived
|
|
||||||
|
|
||||||
given Show[Tag] = tag => tag
|
|
||||||
|
|
||||||
end Tag
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enumeration for _Markers_, special tokens which "mark" a test to change
|
|
||||||
* execution functionality.
|
|
||||||
*
|
|
||||||
* The basic case for this enumeration is allowing tests to be ignored.
|
|
||||||
*
|
|
||||||
* @param name The formal serialized name of the marker.
|
|
||||||
*/
|
|
||||||
sealed abstract class Marker(val name: String)
|
|
||||||
|
|
||||||
object Marker:
|
|
||||||
|
|
||||||
given CanEqual[Marker, Marker] = CanEqual.derived
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this [[Test.Marker]] is present on a test, the test will be ignored.
|
|
||||||
*/
|
|
||||||
case object Ignored extends Marker("ignored")
|
|
||||||
|
|
||||||
end Marker
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opaque type that represents the number of iterations a test should run.
|
|
||||||
* This value must be at least `1` (the default). To ignore a test, use the
|
|
||||||
* [[Test.Marker.Ignored]] marker.
|
|
||||||
*/
|
|
||||||
opaque type Iterations = Int
|
|
||||||
|
|
||||||
object Iterations:
|
|
||||||
|
|
||||||
def One: Iterations = 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate and instantiate a new [[Iterations]] instance.
|
|
||||||
*
|
|
||||||
* @param candidate The candidate value. Must be 1 or greater.
|
|
||||||
* @return The new [[Iterations]], or an error if an invalid input is given.
|
|
||||||
*/
|
|
||||||
def validate(candidate: Int): Either[GsTestError, Iterations] =
|
|
||||||
if candidate < 1 then
|
|
||||||
Left(InvalidIterations(candidate))
|
|
||||||
else
|
|
||||||
Right(candidate)
|
|
||||||
|
|
||||||
given CanEqual[Iterations, Iterations] = CanEqual.derived
|
|
||||||
|
|
||||||
given Show[Iterations] = iters => iters.toString()
|
|
||||||
|
|
||||||
extension (iters: Iterations)
|
|
||||||
def toInt(): Int = iters
|
|
||||||
|
|
||||||
end Iterations
|
|
||||||
|
|
||||||
end TestDefinition
|
end TestDefinition
|
||||||
|
|
|
@ -3,9 +3,6 @@ package gs.test.v0
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import cats.effect.Async
|
import cats.effect.Async
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
import gs.test.v0.TestDefinition.Tag
|
|
||||||
import gs.test.v0.TestDefinition.Marker
|
|
||||||
import gs.test.v0.TestDefinition.Iterations
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import scala.jdk.CollectionConverters.*
|
import scala.jdk.CollectionConverters.*
|
||||||
|
@ -117,7 +114,7 @@ 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: TestDefinition.Iterations = Iterations.One,
|
private var iterations: TestIterations = TestIterations.One,
|
||||||
):
|
):
|
||||||
/**
|
/**
|
||||||
* Supply documentation for this test.
|
* Supply documentation for this test.
|
||||||
|
@ -164,7 +161,7 @@ object TestGroup:
|
||||||
* @param iters The number of iterations.
|
* @param iters The number of iterations.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
def iterate(iters: Iterations): TestBuilder[F] =
|
def iterate(iters: TestIterations): TestBuilder[F] =
|
||||||
iterations = iters
|
iterations = iters
|
||||||
this
|
this
|
||||||
|
|
||||||
|
@ -239,7 +236,7 @@ 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: TestDefinition.Iterations = Iterations.One,
|
private var iterations: TestIterations = TestIterations.One,
|
||||||
):
|
):
|
||||||
/**
|
/**
|
||||||
* Supply documentation for this test.
|
* Supply documentation for this test.
|
||||||
|
@ -286,7 +283,7 @@ object TestGroup:
|
||||||
* @param iters The number of iterations.
|
* @param iters The number of iterations.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
def iterate(iters: Iterations): InputTestBuilder[F, Input] =
|
def iterate(iters: TestIterations): InputTestBuilder[F, Input] =
|
||||||
iterations = iters
|
iterations = iters
|
||||||
this
|
this
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,15 @@ import cats.Show
|
||||||
*
|
*
|
||||||
* @param name The group name. Not considered to be unique.
|
* @param name The group name. Not considered to be unique.
|
||||||
* @param documentation Arbitrary documentation for this group of tests.
|
* @param documentation Arbitrary documentation for this group of tests.
|
||||||
* @param testTags Set of tags applied to all [[Test]] within the 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.
|
* @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,
|
||||||
val documentation: Option[String],
|
val documentation: Option[String],
|
||||||
val testTags: List[TestDefinition.Tag],
|
val testTags: List[Tag],
|
||||||
val testMarkers: List[TestDefinition.Marker],
|
val testMarkers: List[Marker],
|
||||||
val beforeGroup: Option[F[Unit]],
|
val beforeGroup: Option[F[Unit]],
|
||||||
val afterGroup: Option[F[Unit]],
|
val afterGroup: Option[F[Unit]],
|
||||||
val beforeEachTest: Option[F[Unit]],
|
val beforeEachTest: Option[F[Unit]],
|
||||||
|
@ -27,7 +28,7 @@ final class TestGroupDefinition[F[_]](
|
||||||
object TestGroupDefinition:
|
object TestGroupDefinition:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opaque type representing names that may be assigned to [[TestGroup]].
|
* Opaque type representing names that may be assigned to test groups.
|
||||||
*/
|
*/
|
||||||
opaque type Name = String
|
opaque type Name = String
|
||||||
|
|
||||||
|
|
37
modules/core/src/main/scala/gs/test/v0/TestIterations.scala
Normal file
37
modules/core/src/main/scala/gs/test/v0/TestIterations.scala
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package gs.test.v0
|
||||||
|
|
||||||
|
import cats.Show
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque type that represents the number of iterations a test should run.
|
||||||
|
* This value must be at least `1` (the default). To ignore a test, use the
|
||||||
|
* [[Test.Marker.Ignored]] marker.
|
||||||
|
*/
|
||||||
|
opaque type TestIterations = Int
|
||||||
|
|
||||||
|
object TestIterations:
|
||||||
|
|
||||||
|
def One: TestIterations = 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and instantiate a new [[TestIterations]] instance.
|
||||||
|
*
|
||||||
|
* @param candidate The candidate value. Must be 1 or greater.
|
||||||
|
* @return The new [[TestIterations]], or an error if an invalid input is given.
|
||||||
|
*/
|
||||||
|
def apply(candidate: Int): TestIterations =
|
||||||
|
if candidate < 1 then
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
s"Tests must iterate at least once. Received candidate '$candidate'."
|
||||||
|
)
|
||||||
|
else
|
||||||
|
candidate
|
||||||
|
|
||||||
|
given CanEqual[TestIterations, TestIterations] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[TestIterations] = iters => iters.toString()
|
||||||
|
|
||||||
|
extension (iters: TestIterations)
|
||||||
|
def toInt(): Int = iters
|
||||||
|
|
||||||
|
end TestIterations
|
25
modules/core/src/main/scala/gs/test/v0/syntax.scala
Normal file
25
modules/core/src/main/scala/gs/test/v0/syntax.scala
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package gs.test.v0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String interpolator for [[Tag]]. Shorthand for producing new [[Tag]]
|
||||||
|
* instances.
|
||||||
|
*
|
||||||
|
* {{{
|
||||||
|
* import gs.test.v0.*
|
||||||
|
* val tag1: TestDefinition.Tag = tag"example"
|
||||||
|
* }}}
|
||||||
|
*/
|
||||||
|
extension (sc: StringContext)
|
||||||
|
def tag(args: Any*): Tag = Tag(sc.s(args*))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String interpolator for [[PermanentId]]. Shorthand for producing new
|
||||||
|
* [[PermanentId]] instances.
|
||||||
|
*
|
||||||
|
* {{{
|
||||||
|
* import gs.test.v0.*
|
||||||
|
* val permanentId: PermanentId = pid"example"
|
||||||
|
* }}}
|
||||||
|
*/
|
||||||
|
extension (sc: StringContext)
|
||||||
|
def pid(args: Any*): PermanentId = PermanentId(sc.s(args*))
|
|
@ -1,6 +1,9 @@
|
||||||
package gs.test.v0
|
package gs.test.v0
|
||||||
|
|
||||||
import munit.*
|
import munit.*
|
||||||
|
import cats.effect.IO
|
||||||
|
|
||||||
|
import gs.test.v0.{Tag => GsTag}
|
||||||
|
|
||||||
class GroupImplementationTests extends FunSuite:
|
class GroupImplementationTests extends FunSuite:
|
||||||
import GroupImplementationTests.*
|
import GroupImplementationTests.*
|
||||||
|
@ -25,7 +28,55 @@ class GroupImplementationTests extends FunSuite:
|
||||||
assertEquals(t.documentation, None)
|
assertEquals(t.documentation, None)
|
||||||
assertEquals(t.tags, List.empty)
|
assertEquals(t.tags, List.empty)
|
||||||
assertEquals(t.markers, List.empty)
|
assertEquals(t.markers, List.empty)
|
||||||
assertEquals(t.iterations, TestDefinition.Iterations.One)
|
assertEquals(t.iterations, TestIterations.One)
|
||||||
|
case _ => fail("Unexpected number of defined tests.")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support a group with all values set") {
|
||||||
|
val g2 = new G2
|
||||||
|
val group = g2.toGroupDefinition()
|
||||||
|
assertEquals(group.name, TestGroupDefinition.Name("G2"))
|
||||||
|
assertEquals(group.documentation, Some("docs"))
|
||||||
|
assertEquals(group.testTags, List(GsTag("tag")))
|
||||||
|
assertEquals(group.testMarkers, List(Marker.Ignored))
|
||||||
|
assertEquals(group.beforeGroup.isDefined, true)
|
||||||
|
assertEquals(group.afterGroup.isDefined, true)
|
||||||
|
assertEquals(group.beforeEachTest.isDefined, true)
|
||||||
|
assertEquals(group.afterEachTest.isDefined, true)
|
||||||
|
assertEquals(group.tests.size, 1)
|
||||||
|
|
||||||
|
group.tests match
|
||||||
|
case t :: Nil =>
|
||||||
|
assertEquals(t.name, TestDefinition.Name("inherit from group"))
|
||||||
|
assertEquals(t.permanentId, Ids.T2)
|
||||||
|
assertEquals(t.documentation, None)
|
||||||
|
assertEquals(t.tags, List(GsTag("tag")))
|
||||||
|
assertEquals(t.markers, List(Marker.Ignored))
|
||||||
|
assertEquals(t.iterations, TestIterations.One)
|
||||||
|
case _ => fail("Unexpected number of defined tests.")
|
||||||
|
}
|
||||||
|
|
||||||
|
test("should support a simple group with a configured test") {
|
||||||
|
val g3 = new G3
|
||||||
|
val group = g3.toGroupDefinition()
|
||||||
|
assertEquals(group.name, TestGroupDefinition.Name("G3"))
|
||||||
|
assertEquals(group.documentation, None)
|
||||||
|
assertEquals(group.testTags, List.empty)
|
||||||
|
assertEquals(group.testMarkers, List.empty)
|
||||||
|
assertEquals(group.beforeGroup, None)
|
||||||
|
assertEquals(group.afterGroup, None)
|
||||||
|
assertEquals(group.beforeEachTest, None)
|
||||||
|
assertEquals(group.afterEachTest, None)
|
||||||
|
assertEquals(group.tests.size, 1)
|
||||||
|
|
||||||
|
group.tests match
|
||||||
|
case t :: Nil =>
|
||||||
|
assertEquals(t.name, TestDefinition.Name("configure test"))
|
||||||
|
assertEquals(t.permanentId, Ids.T3)
|
||||||
|
assertEquals(t.documentation, Some("docs"))
|
||||||
|
assertEquals(t.tags, List(tag"tag1", tag"tag2"))
|
||||||
|
assertEquals(t.markers, List(Marker.Ignored))
|
||||||
|
assertEquals(t.iterations, TestIterations(2))
|
||||||
case _ => fail("Unexpected number of defined tests.")
|
case _ => fail("Unexpected number of defined tests.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +84,9 @@ object GroupImplementationTests:
|
||||||
|
|
||||||
object Ids:
|
object Ids:
|
||||||
|
|
||||||
val T1: PermanentId = PermanentId("t1")
|
val T1: PermanentId = pid"t1"
|
||||||
|
val T2: PermanentId = pid"t2"
|
||||||
|
val T3: PermanentId = pid"t3"
|
||||||
|
|
||||||
end Ids
|
end Ids
|
||||||
|
|
||||||
|
@ -42,4 +95,36 @@ object GroupImplementationTests:
|
||||||
test(Ids.T1, "simple").pure { Right(()) }
|
test(Ids.T1, "simple").pure { Right(()) }
|
||||||
end G1
|
end G1
|
||||||
|
|
||||||
|
class G2 extends TestGroup.IO:
|
||||||
|
override def name: String =
|
||||||
|
"G2"
|
||||||
|
|
||||||
|
override def documentation: Option[String] =
|
||||||
|
Some("docs")
|
||||||
|
|
||||||
|
override def tags: List[GsTag] =
|
||||||
|
List(GsTag("tag"))
|
||||||
|
|
||||||
|
override def markers: List[Marker] =
|
||||||
|
List(Marker.Ignored)
|
||||||
|
|
||||||
|
beforeGroup { IO.unit }
|
||||||
|
afterGroup { IO.unit }
|
||||||
|
beforeEachTest { IO.unit }
|
||||||
|
afterEachTest { IO.unit }
|
||||||
|
|
||||||
|
test(Ids.T2, "inherit from group").pure { Right(()) }
|
||||||
|
end G2
|
||||||
|
|
||||||
|
class G3 extends TestGroup.IO:
|
||||||
|
override def name: String = "G3"
|
||||||
|
|
||||||
|
test(Ids.T3, "configure test")
|
||||||
|
.document("docs")
|
||||||
|
.tagged(tag"tag1", tag"tag2")
|
||||||
|
.marked(Marker.Ignored)
|
||||||
|
.iterate(TestIterations(2))
|
||||||
|
.pure { Right(()) }
|
||||||
|
end G3
|
||||||
|
|
||||||
end GroupImplementationTests
|
end GroupImplementationTests
|
||||||
|
|
|
@ -29,5 +29,5 @@ externalResolvers := Seq(
|
||||||
)
|
)
|
||||||
|
|
||||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
|
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
|
||||||
addSbtPlugin("gs" % "sbt-garrity-software" % "0.3.0")
|
addSbtPlugin("gs" % "sbt-garrity-software" % "0.4.0")
|
||||||
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
addSbtPlugin("gs" % "sbt-gs-semver" % "0.3.0")
|
||||||
|
|
Loading…
Add table
Reference in a new issue