diff --git a/build.sbt b/build.sbt index 2fd5eaf..5b0a85f 100644 --- a/build.sbt +++ b/build.sbt @@ -10,8 +10,10 @@ ThisBuild / versionScheme := Some("semver-spec") ThisBuild / gsProjectName := "gs-timing" lazy val sharedSettings = Seq( - scalaVersion := scala3, - version := semVerSelected.value + scalaVersion := scala3, + version := semVerSelected.value, + coverageMinimumStmtTotal := 100, + coverageMinimumBranchTotal := 100 ) lazy val testSettings = Seq( diff --git a/src/main/scala/gs/timing/v0/Timing.scala b/src/main/scala/gs/timing/v0/Timing.scala index 632a90c..b20c6d8 100644 --- a/src/main/scala/gs/timing/v0/Timing.scala +++ b/src/main/scala/gs/timing/v0/Timing.scala @@ -53,7 +53,8 @@ object Timing: * A new [[Timing]] instance, always initialized to 0, that is manually * controlled by the user. */ - def manual[F[_]: Sync]: F[Timing[F]] = - MonotonicProvider.manual[F].map(new Timing[F](_)) + def manual[F[_]: Sync] + : F[(MonotonicProvider.ManualTickProvider[F], Timing[F])] = + MonotonicProvider.manual[F].map(p => p -> new Timing[F](p)) end Timing diff --git a/src/test/scala/gs/timing/v0/ElapsedTimeTests.scala b/src/test/scala/gs/timing/v0/ElapsedTimeTests.scala new file mode 100644 index 0000000..fcda128 --- /dev/null +++ b/src/test/scala/gs/timing/v0/ElapsedTimeTests.scala @@ -0,0 +1,25 @@ +package gs.timing.v0 + +import java.util.concurrent.TimeUnit +import munit.* +import scala.concurrent.duration.FiniteDuration + +class ElapsedTimeTests extends FunSuite: + + test("should convert to nanoseconds, milliseconds, and seconds") { + val start = 0L + val end = 1000000L * 2000L // 2 seconds, 2000 millis + val nanos = end - start + val millis = nanos / 1000000L + val seconds = millis / 1000L + + val elapsed = ElapsedTime( + start = start, + end = end, + duration = FiniteDuration(nanos, TimeUnit.NANOSECONDS) + ) + + assertEquals(elapsed.toNanoseconds(), nanos) + assertEquals(elapsed.toMilliseconds(), millis) + assertEquals(elapsed.toSeconds(), seconds) + } diff --git a/src/test/scala/gs/timing/v0/TimingTests.scala b/src/test/scala/gs/timing/v0/TimingTests.scala new file mode 100644 index 0000000..13de2b3 --- /dev/null +++ b/src/test/scala/gs/timing/v0/TimingTests.scala @@ -0,0 +1,80 @@ +package gs.test.v0 + +import cats.effect.IO +import cats.effect.unsafe.IORuntime +import gs.timing.v0.ElapsedTime +import gs.timing.v0.Timing +import java.util.concurrent.TimeUnit +import munit.* +import scala.concurrent.duration.FiniteDuration + +class TimingTests extends FunSuite: + + given IORuntime = IORuntime.global + + iotest("should retrieve monotonic time") { + for + (provider, timing) <- Timing.manual[IO] + t1 <- timing.monotonic() + t2 <- timing.monotonic() + _ <- provider.tick() + t3 <- timing.monotonic() + _ <- provider.tick() + t4 <- timing.monotonic() + _ <- provider.reset() + t5 <- timing.monotonic() + yield + assertEquals(t1, 0L) + assertEquals(t2, 0L) + assertEquals(t3, 1L) + assertEquals(t4, 2L) + assertEquals(t5, 0L) + } + + iotest("should create a timer based on the current provider") { + val start = 0L + val end = 1000000L * 2000L // 2 seconds, 2000 millis + val nanos = end - start + val millis = nanos / 1000000L + val seconds = millis / 1000L + + for + (provider, timing) <- Timing.manual[IO] + timer <- timing.start() + e1 <- timer.checkpoint() + _ <- provider.set(end) + e2 <- timer.checkpoint() + yield + assertEquals( + e1, + ElapsedTime(start, 0L, FiniteDuration(0L, TimeUnit.NANOSECONDS)) + ) + assertEquals( + e2, + ElapsedTime(start, end, FiniteDuration(nanos, TimeUnit.NANOSECONDS)) + ) + assertEquals(e2.toMilliseconds(), millis) + assertEquals(e2.toSeconds(), seconds) + } + + iotest("should capture system time") { + for + timing <- IO(Timing.system[IO]) + t1 <- timing.monotonic() + _ <- IO.sleep(FiniteDuration(1L, TimeUnit.MILLISECONDS)) + t2 <- timing.monotonic() + yield assert( + t2 > t1, + "The system clock should detect that at least 1 nanosecond has passed after sleeping for 1 millisecond." + ) + } + + private def iotest( + name: String + )( + body: => IO[Any] + )( + implicit + loc: Location + ): Unit = + test(name)(body.unsafeRunSync())