Scala 3.5.0, cleanup, reorganization, and more tests. (#1)

Reviewed-on: #1
This commit is contained in:
Pat Garrity 2024-08-23 02:45:31 +00:00
parent d9192843da
commit c9b23251d5
11 changed files with 217 additions and 110 deletions

View file

@ -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 := {}
)

View file

@ -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

View 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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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*))

View file

@ -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

View file

@ -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")