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 / versionScheme := Some("semver-spec")
|
||||
|
@ -9,6 +9,10 @@ ThisBuild / externalResolvers := Seq(
|
|||
"Garrity Software Releases" at "https://maven.garrity.co/gs"
|
||||
)
|
||||
|
||||
ThisBuild / licenses := Seq(
|
||||
"MIT" -> url("https://garrity.co/MIT.html")
|
||||
)
|
||||
|
||||
val noPublishSettings = Seq(
|
||||
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.Show
|
||||
import gs.test.v0.GsTestError.TestDefinitionError.InvalidIterations
|
||||
|
||||
/**
|
||||
* 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 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 f The effect that the test evaluates.
|
||||
* @param unitOfWork The function that the test evaluates.
|
||||
*/
|
||||
final class TestDefinition[F[_]](
|
||||
val name: TestDefinition.Name,
|
||||
val permanentId: PermanentId,
|
||||
val documentation: Option[String],
|
||||
val tags: List[TestDefinition.Tag],
|
||||
val markers: List[TestDefinition.Marker],
|
||||
val iterations: TestDefinition.Iterations,
|
||||
val tags: List[Tag],
|
||||
val markers: List[Marker],
|
||||
val iterations: TestIterations,
|
||||
val unitOfWork: EitherT[F, TestFailure, Unit]
|
||||
)
|
||||
|
||||
|
@ -47,78 +48,4 @@ object TestDefinition:
|
|||
|
||||
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
|
||||
|
|
|
@ -3,9 +3,6 @@ package gs.test.v0
|
|||
import cats.syntax.all.*
|
||||
import cats.effect.Async
|
||||
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 java.util.concurrent.ConcurrentHashMap
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
@ -117,7 +114,7 @@ object TestGroup:
|
|||
private val tags: ListBuffer[Tag],
|
||||
private val markers: ListBuffer[Marker],
|
||||
private var documentation: Option[String] = None,
|
||||
private var iterations: TestDefinition.Iterations = Iterations.One,
|
||||
private var iterations: TestIterations = TestIterations.One,
|
||||
):
|
||||
/**
|
||||
* Supply documentation for this test.
|
||||
|
@ -164,7 +161,7 @@ object TestGroup:
|
|||
* @param iters The number of iterations.
|
||||
* @return This builder.
|
||||
*/
|
||||
def iterate(iters: Iterations): TestBuilder[F] =
|
||||
def iterate(iters: TestIterations): TestBuilder[F] =
|
||||
iterations = iters
|
||||
this
|
||||
|
||||
|
@ -239,7 +236,7 @@ object TestGroup:
|
|||
private val tags: ListBuffer[Tag],
|
||||
private val markers: ListBuffer[Marker],
|
||||
private var documentation: Option[String] = None,
|
||||
private var iterations: TestDefinition.Iterations = Iterations.One,
|
||||
private var iterations: TestIterations = TestIterations.One,
|
||||
):
|
||||
/**
|
||||
* Supply documentation for this test.
|
||||
|
@ -286,7 +283,7 @@ object TestGroup:
|
|||
* @param iters The number of iterations.
|
||||
* @return This builder.
|
||||
*/
|
||||
def iterate(iters: Iterations): InputTestBuilder[F, Input] =
|
||||
def iterate(iters: TestIterations): InputTestBuilder[F, Input] =
|
||||
iterations = iters
|
||||
this
|
||||
|
||||
|
|
|
@ -9,14 +9,15 @@ import cats.Show
|
|||
*
|
||||
* @param name The group name. Not considered to be unique.
|
||||
* @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.
|
||||
*/
|
||||
final class TestGroupDefinition[F[_]](
|
||||
val name: TestGroupDefinition.Name,
|
||||
val documentation: Option[String],
|
||||
val testTags: List[TestDefinition.Tag],
|
||||
val testMarkers: List[TestDefinition.Marker],
|
||||
val testTags: List[Tag],
|
||||
val testMarkers: List[Marker],
|
||||
val beforeGroup: Option[F[Unit]],
|
||||
val afterGroup: Option[F[Unit]],
|
||||
val beforeEachTest: Option[F[Unit]],
|
||||
|
@ -27,7 +28,7 @@ final class TestGroupDefinition[F[_]](
|
|||
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
|
||||
|
||||
|
|
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
|
||||
|
||||
import munit.*
|
||||
import cats.effect.IO
|
||||
|
||||
import gs.test.v0.{Tag => GsTag}
|
||||
|
||||
class GroupImplementationTests extends FunSuite:
|
||||
import GroupImplementationTests.*
|
||||
|
@ -25,7 +28,55 @@ class GroupImplementationTests extends FunSuite:
|
|||
assertEquals(t.documentation, None)
|
||||
assertEquals(t.tags, 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.")
|
||||
}
|
||||
|
||||
|
@ -33,7 +84,9 @@ object GroupImplementationTests:
|
|||
|
||||
object Ids:
|
||||
|
||||
val T1: PermanentId = PermanentId("t1")
|
||||
val T1: PermanentId = pid"t1"
|
||||
val T2: PermanentId = pid"t2"
|
||||
val T3: PermanentId = pid"t3"
|
||||
|
||||
end Ids
|
||||
|
||||
|
@ -42,4 +95,36 @@ object GroupImplementationTests:
|
|||
test(Ids.T1, "simple").pure { Right(()) }
|
||||
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
|
||||
|
|
|
@ -29,5 +29,5 @@ externalResolvers := Seq(
|
|||
)
|
||||
|
||||
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")
|
||||
|
|
Loading…
Add table
Reference in a new issue