@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(); } }
protected VirtualTimeScheduler() { directWorker = createWorker(); }
@Test public void virtualTimeSchedulerUseExactlySupplied() { VirtualTimeScheduler vts1 = VirtualTimeScheduler.create(); VirtualTimeScheduler vts2 = VirtualTimeScheduler.create(); VirtualTimeScheduler.getOrSet(vts1); StepVerifier.withVirtualTime(Mono::empty, () -> vts2, Long.MAX_VALUE) .then(() -> assertThat(VirtualTimeScheduler.get()).isSameAs(vts2)) .verifyComplete(); assertThat(vts1.isDisposed()).isFalse(); assertThat(vts2.isDisposed()).isTrue(); assertThat(VirtualTimeScheduler.isFactoryEnabled()).isFalse(); }
@Test public void apiTakeSchedulerShortcircuits() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); StepVerifier.create( Mono.delay(Duration.ofMillis(200)) .take(Duration.ofSeconds(10), vts) ) .then(() -> vts.advanceTimeBy(Duration.ofSeconds(10))) .verifyComplete(); }
@Test(timeout = 4000) public void manuallyManagedVirtualTime() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); try { VirtualTimeScheduler.getOrSet(vts); assertThat(VirtualTimeScheduler.get()).isSameAs(vts); Flux<String> flux = Flux.just("foo").delayElements(Duration.ofSeconds(4)); DefaultVerifySubscriber<String> s = new DefaultStepVerifierBuilder<String>(StepVerifierOptions.create() .initialRequest(Long.MAX_VALUE) .virtualTimeSchedulerSupplier(() -> vts), null)//important to avoid triggering of vts capture-and-enable .thenAwait(Duration.ofSeconds(1)) .expectNext("foo") .expectComplete() .toSubscriber(); flux.subscribe(s); vts.advanceTimeBy(Duration.ofSeconds(3)); s.verify(); assertThat(s.virtualTimeScheduler()).isSameAs(vts); assertThat(VirtualTimeScheduler.get()).isSameAs(vts); } finally { VirtualTimeScheduler.reset(); } }
@Test public void timedError() throws Exception { VirtualTimeScheduler.getOrSet(); ReplayProcessor<Integer> rp = ReplayProcessor.createTimeout(Duration.ofSeconds(1)); for (int i = 0; i < 5; i++) { rp.onNext(i); } VirtualTimeScheduler.get().advanceTimeBy(Duration.ofSeconds(2)); for (int i = 5; i < 10; i++) { rp.onNext(i); } rp.onError(new Exception("test")); StepVerifier.create(rp.hide()) .expectNext(5,6,7,8,9) .verifyErrorMessage("test"); }
@Test public void timedAndBoundFusedError() throws Exception { ReplayProcessor<Integer> rp = ReplayProcessor.createSizeAndTimeout(5, Duration.ofSeconds(1)); for (int i = 0; i < 10; i++) { rp.onNext(i); } VirtualTimeScheduler.get().advanceTimeBy(Duration.ofSeconds(2)); for (int i = 10; i < 20; i++) { rp.onNext(i); } rp.onError(new Exception("test")); StepVerifier.create(rp) .expectFusion(Fuseable.ASYNC) .expectNext(15,16,17,18,19) .verifyErrorMessage("test"); Assert.assertFalse("Has subscribers?", rp.hasDownstreams()); }
@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(); }
/** * Triggers any tasks that have not yet been executed and that are scheduled to be * executed at or before this {@link VirtualTimeScheduler}'s present time. */ public void advanceTime() { advanceTimeBy(Duration.ZERO); }
@Test public void errorHandlingIntervalMillisNotContinued() throws InterruptedException { VirtualTimeScheduler virtualTimeScheduler = VirtualTimeScheduler.create(); VirtualTimeScheduler.set(virtualTimeScheduler); Flux<String> flux = Flux.interval(Duration.ofMillis(250)) .map(input -> { if (input < 3) return "tick " + input; throw new RuntimeException("boom"); }) .onErrorReturn("Uh oh"); flux.subscribe(System.out::println); //Thread.sleep(2100); // <1> virtualTimeScheduler.advanceTimeBy(Duration.ofHours(1)); StepVerifier.withVirtualTime(() -> flux, () -> virtualTimeScheduler, Long.MAX_VALUE) .thenAwait(Duration.ofSeconds(3)) .expectNext("tick 0") .expectNext("tick 1") .expectNext("tick 2") .expectNext("Uh oh") .verifyComplete(); }
@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 downstreamDemandShouldBeAbleToDecreaseOnTimeSpan() { Subscription[] subscriptionsHolder = new Subscription[1]; CoreSubscriber<List<String>> actual = new LambdaSubscriber<>(null, e -> {}, null, s -> subscriptionsHolder[0] = s); VirtualTimeScheduler timeScheduler = VirtualTimeScheduler.getOrSet(); FluxBufferTimeout.BufferTimeoutSubscriber<String, List<String>> test = new FluxBufferTimeout.BufferTimeoutSubscriber<String, List<String>>( actual, 5, 100, timeScheduler.createWorker(), ArrayList::new); Subscription subscription = Operators.emptySubscription(); test.onSubscribe(subscription); subscriptionsHolder[0].request(1); assertThat(test.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(1L); timeScheduler.advanceTimeBy(Duration.ofMillis(100)); assertThat(test.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(1L); test.onNext(String.valueOf("0")); timeScheduler.advanceTimeBy(Duration.ofMillis(100)); assertThat(test.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(0L); }
@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); } }
@After public void teardownVirtualTime(){ VirtualTimeScheduler.reset(); } }
@Test public void enableTwoSimilarSchedulersUsesFirst() { VirtualTimeScheduler vts1 = VirtualTimeScheduler.create(); VirtualTimeScheduler vts2 = VirtualTimeScheduler.create(); VirtualTimeScheduler firstEnableResult = VirtualTimeScheduler.getOrSet(vts1); VirtualTimeScheduler secondEnableResult = VirtualTimeScheduler.getOrSet(vts2); Assert.assertSame(vts1, firstEnableResult); Assert.assertSame(vts1, secondEnableResult); Assert.assertSame(vts1, uncache(Schedulers.single())); Assert.assertFalse(vts1.shutdown); }
/** * Assign a single newly created {@link VirtualTimeScheduler} to all {@link reactor.core.scheduler.Schedulers.Factory} * factories. While the method is thread safe, its usually advised to execute such * wide-impact BEFORE all tested code runs (setup etc). The created scheduler is returned. * * @return the VirtualTimeScheduler that was created and set through the factory */ public static VirtualTimeScheduler getOrSet() { return enable(VirtualTimeScheduler::new, false); }
final void advanceTime(long timeShiftInNanoseconds) { Operators.addCap(DEFERRED_NANO_TIME, this, timeShiftInNanoseconds); drain(); }
@Test public void suppliedVirtualTimeButNoSourceDoesntEnableScheduler() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); new DefaultStepVerifierBuilder<String>(StepVerifierOptions.create() .initialRequest(Long.MAX_VALUE) .virtualTimeSchedulerSupplier(() -> vts), null) //important to avoid triggering of vts capture-and-enable .expectNoEvent(Duration.ofSeconds(4)) .expectComplete() .toSubscriber(); try { //also test the side effect case where VTS has been enabled and not reset VirtualTimeScheduler current = VirtualTimeScheduler.get(); assertThat(current).isNotSameAs(vts); } catch (IllegalStateException e) { assertThat(e).hasMessageContaining("VirtualTimeScheduler"); } }
@Test public void monoApiTestMillisAndTimer() { VirtualTimeScheduler vts = VirtualTimeScheduler.create(); StepVerifier.withVirtualTime( () -> Mono.just("foo").delayElement(Duration.ofMillis(5000L), vts), () -> vts, Long.MAX_VALUE) .expectSubscription() .expectNoEvent(Duration.ofSeconds(5)) .expectNext("foo") .verifyComplete(); }