/** * Read whether this signal is on error and carries the cause. * * @return a boolean indicating whether this signal has an error */ default boolean hasError() { return isOnError() && getThrowable() != null; }
@Override public DefaultStepVerifier<T> expectError(Class<? extends Throwable> clazz) { Objects.requireNonNull(clazz, "clazz"); SignalEvent<T> event = new SignalEvent<>((signal, se) -> { if (!signal.isOnError()) { return errorFormatter.failOptional(se, "expected: onError(%s); actual: %s", clazz.getSimpleName(), signal); } else if (!clazz.isInstance(signal.getThrowable())) { return errorFormatter.failOptional(se, "expected error of type: %s; actual type: %s", clazz.getSimpleName(), signal.getThrowable()); } else { return Optional.empty(); } }, "expectError(Class)"); this.script.add(event); return build(); }
MonoCacheTime(Mono<? extends T> source, Function<? super T, Duration> valueTtlGenerator, Function<Throwable, Duration> errorTtlGenerator, Supplier<Duration> emptyTtlGenerator, Scheduler clock) { super(source); this.ttlGenerator = sig -> { if (sig.isOnNext()) return valueTtlGenerator.apply(sig.get()); if (sig.isOnError()) return errorTtlGenerator.apply(sig.getThrowable()); return emptyTtlGenerator.get(); }; this.clock = clock; @SuppressWarnings("unchecked") Signal<T> emptyState = (Signal<T>) EMPTY; this.state = emptyState; }
@Override public DefaultStepVerifier<T> expectErrorMatches(Predicate<Throwable> predicate) { Objects.requireNonNull(predicate, "predicate"); SignalEvent<T> event = new SignalEvent<>((signal, se) -> { if (!signal.isOnError()) { return errorFormatter.failOptional(se, "expected: onError(); actual: %s", signal); } else if (!predicate.test(signal.getThrowable())) { return errorFormatter.failOptional(se, "predicate failed on exception: %s", signal.getThrowable()); } else { return Optional.empty(); } }, "expectErrorMatches"); this.script.add(event); return build(); }
private <T> Predicate<? super Signal<T>> signalErrorMessage(String expectedMessage) { return signal -> signal.isOnError() && signal.getThrowable() != null && expectedMessage.equals(signal.getThrowable().getMessage()); }
private DefaultStepVerifier<T> consumeErrorWith(Consumer<Throwable> assertionConsumer, String description, boolean wrap) { Objects.requireNonNull(assertionConsumer, "assertionConsumer"); SignalEvent<T> event = new SignalEvent<>((signal, se) -> { if (!signal.isOnError()) { return errorFormatter.failOptional(se, "expected: onError(); actual: %s", signal); } else { try { assertionConsumer.accept(signal.getThrowable()); return Optional.empty(); } catch (AssertionError e) { if (wrap) return errorFormatter.failOptional(se, "assertion failed on exception <%s>: %s", signal.getThrowable(), e.getMessage()); throw e; } } }, description); this.script.add(event); return build(); }
@Override public DefaultStepVerifier<T> expectErrorMessage(String errorMessage) { SignalEvent<T> event = new SignalEvent<>((signal, se) -> { if (!signal.isOnError()) { return errorFormatter.failOptional(se, "expected: onError(\"%s\"); actual: %s", errorMessage, signal); } else if (!Objects.equals(errorMessage, signal.getThrowable() .getMessage())) { return errorFormatter.failOptional(se, "expected error message: \"%s\"; " + "actual " + "message: %s", errorMessage, signal.getThrowable() .getMessage()); } else { return Optional.empty(); } }, "expectErrorMessage"); this.script.add(event); return build(); }
final boolean onSignalCount(Signal<T> actualSignal, SignalCountEvent<T> event) { if (unasserted >= event.count) { this.script.poll(); unasserted -= event.count; } else { if (event.count != 0) { Optional<AssertionError> error = this.checkCountMismatch(event, actualSignal); if (error.isPresent()) { Exceptions.addThrowable(ERRORS, this, error.get()); if(actualSignal.isOnError()) { // #55 ensure the onError is added as a suppressed to the AssertionError error.get().addSuppressed(actualSignal.getThrowable()); } maybeCancel(actualSignal); this.completeLatch.countDown(); } } return true; } return false; }
boolean onSignal(Signal<T> actualSignal) { SignalEvent<T> signalEvent = (SignalEvent<T>) this.script.poll(); Optional<AssertionError> error = signalEvent.test(actualSignal); if (error.isPresent()) { Exceptions.addThrowable(ERRORS, this, error.get()); // #55 ensure the onError is added as a suppressed to the AssertionError if(actualSignal.isOnError()) { error.get().addSuppressed(actualSignal.getThrowable()); } maybeCancel(actualSignal); this.completeLatch.countDown(); return true; } if (actualSignal.isOnNext()) { unasserted--; } return false; }
@Override @Nullable public Object scanUnsafe(Attr key) { if (key == Attr.PARENT) return s; if (key == Attr.TERMINATED) return terminalSignal != null; if (key == Attr.ERROR) return terminalSignal != null ? terminalSignal.getThrowable() : null; if (key == Attr.CANCELLED) return getAsBoolean(); if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; if (key == Attr.BUFFERED) return size(); return InnerOperator.super.scanUnsafe(key); }
@Test public void errorBeforeFirstItem() throws Exception { IllegalStateException error = new IllegalStateException("boo"); Mono<Void> completion = Mono.<String>error(error).as(this::sendOperator); Signal<Void> signal = completion.materialize().block(); assertNotNull(signal); assertSame("Unexpected signal: " + signal, error, signal.getThrowable()); }
@Override protected List<Scenario<String, Signal<String>>> scenarios_errorFromUpstreamFailure() { return Arrays.asList( scenario(Flux::materialize) .verifier(step -> { Hooks.onErrorDropped(c -> assertThat(c).hasMessage("dropped")); Hooks.onNextDropped(c -> assertThat(c).isEqualTo("dropped")); step.assertNext(s -> assertThat(s .getThrowable()).hasMessage("test")) .verifyComplete(); }) ); }
@Test public void testDoOnEachSignalWithError() { List<Signal<Integer>> signals = new ArrayList<>(4); Flux<Integer> flux = Flux.<Integer>error(new IllegalArgumentException("foo")) .doOnEach(signals::add); StepVerifier.create(flux) .expectSubscription() .expectErrorMessage("foo") .verify(); assertThat(signals.size(), is(1)); assertTrue("onError expected", signals.get(0).isOnError()); assertThat("plain exception expected", signals.get(0).getThrowable().getMessage(), is("foo")); }
@Test public void errorAfterMultipleItems() throws Exception { IllegalStateException error = new IllegalStateException("boo"); Flux<String> publisher = Flux.generate(() -> 0, (idx , subscriber) -> { int i = ++idx; subscriber.next(String.valueOf(i)); if (i == 3) { subscriber.error(error); } return i; }); Mono<Void> completion = publisher.as(this::sendOperator); Signal<Void> signal = completion.materialize().block(); assertNotNull(signal); assertSame("Unexpected signal: " + signal, error, signal.getThrowable()); assertEquals(3, this.writer.items.size()); assertEquals("1", this.writer.items.get(0)); assertEquals("2", this.writer.items.get(1)); assertEquals("3", this.writer.items.get(2)); assertSame(error, this.writer.error); }
@Test public void testDoOnEachSignalWithError() { List<Signal<Integer>> signals = new ArrayList<>(4); Mono<Integer> mono = Mono.<Integer>error(new IllegalArgumentException("foo")) .doOnEach(signals::add); StepVerifier.create(mono) .expectSubscription() .expectErrorMessage("foo") .verify(); assertThat(signals.size(), is(1)); assertTrue("onError expected", signals.get(0).isOnError()); assertThat("plain exception expected", signals.get(0).getThrowable().getMessage(), is("foo")); }
@Test public void normal() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); AtomicInteger onNext = new AtomicInteger(); AtomicReference<Throwable> onError = new AtomicReference<>(); AtomicBoolean onComplete = new AtomicBoolean(); Mono.just(1) .hide() .doOnEach(s -> { if (s.isOnNext()) { onNext.incrementAndGet(); } else if (s.isOnError()) { onError.set(s.getThrowable()); } else if (s.isOnComplete()) { onComplete.set(true); } }) .subscribe(ts); assertThat(onNext.get()).isEqualTo(1); assertThat(onError.get()).isNull(); assertThat(onComplete.get()).isTrue(); }
@Test public void empty() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); AtomicInteger onNext = new AtomicInteger(); AtomicReference<Throwable> onError = new AtomicReference<>(); AtomicBoolean onComplete = new AtomicBoolean(); Mono.<Integer>empty() .doOnEach(s -> { if (s.isOnNext()) { onNext.incrementAndGet(); } else if (s.isOnError()) { onError.set(s.getThrowable()); } else if (s.isOnComplete()) { onComplete.set(true); } }) .subscribe(ts); assertThat(onNext.get()).isZero(); assertThat(onError.get()).isNull(); assertThat(onComplete.get()).isTrue(); }
@Test public void never() { AssertSubscriber<Integer> ts = AssertSubscriber.create(); AtomicInteger onNext = new AtomicInteger(); AtomicReference<Throwable> onError = new AtomicReference<>(); AtomicBoolean onComplete = new AtomicBoolean(); Mono.<Integer>never() .doOnEach(s -> { if (s.isOnNext()) { onNext.incrementAndGet(); } else if (s.isOnError()) { onError.set(s.getThrowable()); } else if (s.isOnComplete()) { onComplete.set(true); } }) .subscribe(ts); assertThat(onNext.get()).isZero(); assertThat(onError.get()).isNull(); assertThat(onComplete.get()).isFalse(); }
@Test public void testDoOnEachSignalWithError() throws InterruptedException { List<Signal<Integer>> signals = Collections.synchronizedList(new ArrayList<>(4)); ParallelFlux<Integer> flux = Flux.<Integer>error(new IllegalArgumentException("boom")).parallel(2) .runOn(Schedulers.parallel()) .doOnEach(signals::add); //we use a lambda subscriber and latch to avoid using `sequential` CountDownLatch latch = new CountDownLatch(2); flux.subscribe(v -> { }, e -> latch.countDown(), latch::countDown); assertTrue(latch.await(2, TimeUnit.SECONDS)); assertThat(signals).hasSize(2); assertTrue("rail 1 onError expected", signals.get(0) .isOnError()); assertTrue("rail 2 onError expected", signals.get(1) .isOnError()); assertThat(signals.get(0).getThrowable()).as("plain exception rail 1 expected") .hasMessage("boom"); assertThat(signals.get(1).getThrowable()).as("plain exception rail 2 expected") .hasMessage("boom"); }
@Test public void materialize2() { StepVerifier.create(Flux.just("Three", "Two") .concatWith(Flux.error(new RuntimeException("test"))) .materialize()) .expectNextMatches(s -> s.isOnNext() && "Three".equals(s.get())) .expectNextMatches(s -> s.isOnNext() && "Two".equals(s.get())) .expectNextMatches(s -> s.isOnError() && s.getThrowable() != null && "test".equals(s.getThrowable().getMessage())) .verifyComplete(); }