@Test public void nestedSchedule() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); List<Long> singleExecutionsTimestamps = new ArrayList<>(); try { vts.schedule(() -> vts.schedule( () -> singleExecutionsTimestamps.add(vts.now(TimeUnit.MILLISECONDS)), 100, TimeUnit.MILLISECONDS ), 300, TimeUnit.MILLISECONDS); vts.advanceTimeBy(Duration.ofMillis(1000)); assertThat(singleExecutionsTimestamps) .as("single executions") .containsExactly(400L); } finally { vts.dispose(); } }
@Test public void captureNowInScheduledTask() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); List<Long> singleExecutionsTimestamps = new ArrayList<>(); List<Long> periodicExecutionTimestamps = new ArrayList<>(); try { vts.advanceTimeBy(Duration.ofMillis(100)); vts.schedule(() -> singleExecutionsTimestamps.add(vts.now(TimeUnit.MILLISECONDS)), 100, TimeUnit.MILLISECONDS); vts.schedule(() -> singleExecutionsTimestamps.add(vts.now(TimeUnit.MILLISECONDS)), 456, TimeUnit.MILLISECONDS); vts.schedulePeriodically(() -> periodicExecutionTimestamps.add(vts.now(TimeUnit.MILLISECONDS)), 0, 100, TimeUnit.MILLISECONDS); vts.advanceTimeBy(Duration.ofMillis(1000)); assertThat(singleExecutionsTimestamps) .as("single executions") .containsExactly(100L, 456L + 100L); assertThat(periodicExecutionTimestamps) .as("periodic executions") .containsExactly(100L, 200L, 300L, 400L, 500L, 600L, 700L, 800L, 900L, 1000L, 1100L); } finally { vts.dispose(); } }
@Test public void contextFromFirstSubscriberCached() { AtomicInteger contextFillCount = new AtomicInteger(); VirtualTimeScheduler vts = VirtualTimeScheduler.create(); Mono<Context> cached = Mono.subscriberContext() .as(m -> new MonoCacheTime<>(m, Duration.ofMillis(500), vts)) .subscriberContext(ctx -> ctx.put("a", "GOOD" + contextFillCount.incrementAndGet())); //at first pass, the context is captured String cacheMiss = cached.map(x -> x.getOrDefault("a", "BAD")).block(); assertThat(cacheMiss).as("cacheMiss").isEqualTo("GOOD1"); assertThat(contextFillCount).as("cacheMiss").hasValue(1); //at second subscribe, the Context fill attempt is still done, but ultimately ignored since Mono.subscriberContext() result is cached String cacheHit = cached.map(x -> x.getOrDefault("a", "BAD")).block(); assertThat(cacheHit).as("cacheHit").isEqualTo("GOOD1"); //value from the cache assertThat(contextFillCount).as("cacheHit").hasValue(2); //function was still invoked vts.advanceTimeBy(Duration.ofMillis(501)); //at third subscribe, after the expiration delay, function is called for the 3rd time, but this time the resulting context is cached String cacheExpired = cached.map(x -> x.getOrDefault("a", "BAD")).block(); assertThat(cacheExpired).as("cacheExpired").isEqualTo("GOOD3"); assertThat(contextFillCount).as("cacheExpired").hasValue(3); //at fourth subscribe, function is called but ignored, the cached context is visible String cachePostExpired = cached.map(x -> x.getOrDefault("a", "BAD")).block(); assertThat(cachePostExpired).as("cachePostExpired").isEqualTo("GOOD3"); assertThat(contextFillCount).as("cachePostExpired").hasValue(4); vts.dispose(); }
@Test public void disposedSchedulerIsStillCleanedUp() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); vts.dispose(); assertThat(VirtualTimeScheduler.isFactoryEnabled()).isFalse(); StepVerifier.withVirtualTime(() -> Mono.just("foo"), () -> vts, Long.MAX_VALUE) .then(() -> assertThat(VirtualTimeScheduler.isFactoryEnabled()).isTrue()) .then(() -> assertThat(VirtualTimeScheduler.get()).isSameAs(vts)) .expectNext("foo") .verifyComplete(); assertThat(VirtualTimeScheduler.isFactoryEnabled()).isFalse(); StepVerifier.withVirtualTime(() -> Mono.just("foo")) .then(() -> assertThat(VirtualTimeScheduler.isFactoryEnabled()).isTrue()) .then(() -> assertThat(VirtualTimeScheduler.get()).isNotSameAs(vts)) .expectNext("foo") .verifyComplete(); assertThat(VirtualTimeScheduler.isFactoryEnabled()).isFalse(); }
@Test public void cancelUpstreamOnceWhenRejected() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); vts.dispose(); AtomicLong upstreamCancelCount = new AtomicLong(); Mono<String> source = Mono.just("foo").log().hide() .doOnCancel(upstreamCancelCount::incrementAndGet); try { StepVerifier.withVirtualTime( () -> new MonoDelayElement<>(source, 2, TimeUnit.SECONDS, vts).log(), () -> vts, Long.MAX_VALUE) .expectSubscription() .verifyComplete(); } catch (Throwable e) { assertThat(e).hasMessageContaining("Scheduler unavailable"); } finally { assertThat(upstreamCancelCount.get()).isEqualTo(1); } }
@Test public void cacheContextTime() { AtomicInteger contextFillCount = new AtomicInteger(); VirtualTimeScheduler vts = VirtualTimeScheduler.create(); Flux<String> cached = Flux.just(1) .flatMap(i -> Mono.subscriberContext() .map(ctx -> ctx.getOrDefault("a", "BAD")) ) .replay(Duration.ofMillis(500), vts) .autoConnect() .subscriberContext(ctx -> ctx.put("a", "GOOD" + contextFillCount.incrementAndGet())); //at first pass, the context is captured String cacheMiss = cached.blockLast(); assertThat(cacheMiss).as("cacheMiss").isEqualTo("GOOD1"); assertThat(contextFillCount).as("cacheMiss").hasValue(1); //at second subscribe, the Context fill attempt is still done, but ultimately ignored since Mono.subscriberContext() result is cached String cacheHit = cached.blockLast(); assertThat(cacheHit).as("cacheHit").isEqualTo("GOOD1"); //value from the cache assertThat(contextFillCount).as("cacheHit").hasValue(2); //function was still invoked vts.advanceTimeBy(Duration.ofMillis(501)); //at third subscribe, after the expiration delay, function is called for the 3rd time, but this time the resulting context is cached String cacheExpired = cached.blockLast(); assertThat(cacheExpired).as("cacheExpired").isEqualTo("GOOD3"); assertThat(contextFillCount).as("cacheExpired").hasValue(3); //at fourth subscribe, function is called but ignored, the cached context is visible String cachePostExpired = cached.blockLast(); assertThat(cachePostExpired).as("cachePostExpired").isEqualTo("GOOD3"); assertThat(contextFillCount).as("cachePostExpired").hasValue(4); vts.dispose(); }