@@ -12,24 +12,36 @@ object AsyncReadyLayerSpec extends ZIOSpecDefault {
1212 ): ZLayer [Any , Throwable , TestFeatureProvider with FeatureFlags ] =
1313 Scope .default >>> TestFeatureProvider .asyncReadyLayer(flags, delay)
1414
15+ // Bound the polling loop so a real bug surfaces as a test failure instead of a hang.
16+ private val waitForReady : ZIO [FeatureFlags , Nothing , ProviderStatus ] =
17+ FeatureFlags .providerStatus
18+ .repeatUntil(_ == ProviderStatus .Ready )
19+ .timeoutTo(ProviderStatus .NotReady )(identity)(5 .seconds)
20+ .withClock(Clock .ClockLive )
21+
1522 def spec = suite(" asyncReadyLayer" )(
23+ // Under TestClock the forked transition fiber inside asyncReadyLayer suspends on its `ZIO.sleep`
24+ // until we explicitly adjust the clock. This eliminates the wall-clock race that made the
25+ // previous `withLiveClock` version flaky on slow CI runners.
1626 test(" starts in NotReady then auto-transitions to Ready" ) {
1727 for {
1828 before <- FeatureFlags .providerStatus
19- _ <- ZIO .sleep( 500 .millis)
20- after <- FeatureFlags .providerStatus
29+ _ <- TestClock .adjust( 60 .millis)
30+ after <- waitForReady
2131 } yield assertTrue(before == ProviderStatus .NotReady ) && assertTrue(after == ProviderStatus .Ready )
2232 }.provide(readyLayer(Map .empty, 50 .millis)),
2333 test(" evaluations work after auto-transition" ) {
2434 for {
25- _ <- ZIO .sleep(500 .millis)
35+ _ <- TestClock .adjust(60 .millis)
36+ _ <- waitForReady
2637 value <- FeatureFlags .boolean(" flag" , default = false )
2738 } yield assertTrue(value == true )
2839 }.provide(readyLayer(Map (" flag" -> true ), 50 .millis)),
2940 test(" evaluations fail before init delay elapses" ) {
41+ // No clock adjustment: the forked transition fiber stays suspended, status remains NotReady.
3042 for {
3143 result <- FeatureFlags .boolean(" flag" , default = false ).either
3244 } yield assertTrue(result.isLeft)
3345 }.provide(readyLayer(Map (" flag" -> true ), 10 .seconds))
34- ) @@ TestAspect .withLiveClock
46+ )
3547}
0 commit comments