zio / zio

ZIO — A type-safe, composable library for async and concurrent programming in Scala
https://zio.dev
Apache License 2.0
4.09k stars 1.28k forks source link

ZIO Test, TestClock: Time should advance while suspended #3395

Open runtologist opened 4 years ago

runtologist commented 4 years ago

TestClock semantics has changed in RC18 in order to fix an issue with Schedules in forked fibers https://github.com/zio/zio/pull/2677 . I think the new semantics has a different issue. It violates causality and makes it impossible to test certain scenarios, which were testable before RC18. An ideal future version of TestClock would additionally pass the following test:

testM("time should advance when suspended") {
  for {
    now <- clock.currentDateTime
    p1 <- Promise.make[Nothing, Unit]
    p2 <- Promise.make[Nothing, OffsetDateTime]
    _ <- (p1.await *> clock.currentDateTime.orDie.to(p2)).fork
    _ <- TestClock.adjust(1.minute)
    _ <- clock.sleep(1.minute)
    later <- clock.currentDateTime
    _ <- p1.succeed(())
    laterFiber <- p2.await
  } yield assert(now)(isLessThan(later)) && assert(later)(isLessThanEqualTo(laterFiber))
}

From the flow of the main fiber we know that time has passed. There is no possible way for the test to succeed, if time does not also advance in the forked fiber. In RC18 TestClock advances fiber time only during sleep. As the forked fiber never sleeps, it is impossible to update the time in the forked fiber. Even if we assume computations to be instantaneous, time should be allowed to pass during suspension due to EffectAsync. This is related to tick as proposed in https://github.com/zio/zio/pull/3304, but tick alone does not solve it.

I'm not sure what a fix would look like. When a forked fiber suspends due to EffectAsync, its fiber time should be fast forwarded to wall clock time. TestClock would need to know when a forked fiber gets resumed, but that information is not available to TestClock.

fanf commented 4 years ago

Perhaps it's a similar problem discussed on discord here: image

The corresponding test used to pass on RC16 and not anymore on RC18-2: https://scastie.scala-lang.org/7tkii9q5QLKWS1GZSe8EFw

Summary: I have a scheduler that wait at least 10 minutes before starting an action so that triggers coming in [1-10]min interval are delayed. If a trigger comes after 12 min, it is seen by the scheduler as coming 2 minutes before (which breaks the goal of the test case to check that it happened after 12 minutes).

adamgfraser commented 4 years ago

@fanf Thank you! Your issue has given me some good inspiration. Should have something soon!