/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reactor; import java.time.Duration; import java.util.Collections; import java.util.Queue; import java.util.concurrent.LinkedTransferQueue; import java.util.logging.Level; import org.junit.Assert; import org.junit.Test; import reactor.core.publisher.ConnectableFlux; import reactor.core.publisher.Flux; import reactor.core.publisher.Hooks; import reactor.core.publisher.Mono; import reactor.core.publisher.Operators; import reactor.core.publisher.ParallelFlux; import reactor.core.publisher.SignalType; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import reactor.test.subscriber.AssertSubscriber; /** * @author Stephane Maldini */ public class HooksTest { void simpleFlux(){ Flux.just(1) .map(d -> d + 1) .doOnNext(d -> {throw new RuntimeException("test");}) .collectList() .onErrorReturn(Collections.singletonList(2)) .block(); } static final class TestException extends RuntimeException { public TestException(String message) { super(message); } } @Test public void errorHooks() throws Exception { Hooks.onOperatorError((e, s) -> new TestException(s.toString())); Hooks.onNextDropped(d -> { throw new TestException(d.toString()); }); Hooks.onErrorDropped(e -> { throw new TestException("errorDrop"); }); Throwable w = Operators.onOperatorError(null, new Exception(), "hello"); Assert.assertTrue(w instanceof TestException); Assert.assertTrue(w.getMessage() .equals("hello")); try { Operators.onNextDropped("hello"); Assert.fail(); } catch (Throwable t) { t.printStackTrace(); Assert.assertTrue(t instanceof TestException); Assert.assertTrue(t.getMessage() .equals("hello")); } try { Operators.onErrorDropped(new Exception()); Assert.fail(); } catch (Throwable t) { Assert.assertTrue(t instanceof TestException); Assert.assertTrue(t.getMessage() .equals("errorDrop")); } Hooks.resetOnOperatorError(); Hooks.resetOnNextDropped(); Hooks.resetOnErrorDropped(); } @Test public void parallelModeFused() { Hooks.onOperator(h -> h.log("", Level.INFO, true, SignalType.ON_COMPLETE).operatorStacktrace()); Flux<Integer> source = Mono.just(1) .flux() .repeat(1000) .publish() .autoConnect(); int ncpu = Math.max(8, Runtime.getRuntime() .availableProcessors()); Scheduler scheduler = Schedulers.newParallel("test", ncpu); try { Flux<Integer> result = ParallelFlux.from(source, ncpu) .runOn(scheduler) .map(v -> v + 1) .log("test", Level.INFO, true, SignalType.ON_SUBSCRIBE) .sequential(); AssertSubscriber<Integer> ts = AssertSubscriber.create(); result.subscribe(ts); ts.await(Duration.ofSeconds(10)); ts.assertSubscribed() .assertValueCount(1000) .assertComplete() .assertNoError(); } finally { Hooks.resetOnOperator(); scheduler.dispose(); } } @Test public void verboseExtension() { Queue<String> q = new LinkedTransferQueue<>(); Hooks.onOperator(hooks -> hooks.operatorStacktrace() .doOnEach(d -> q.offer(hooks.publisher() + ": " + d), t -> q.offer(hooks.publisher() + "! " + (t.getSuppressed().length != 0)), null, null)); simpleFlux(); Assert.assertArrayEquals(q.toArray(), new String[]{"FluxJust: 1", "{ \"operator\" : \"MapFuseable\" }: 2", "{ \"operator\" : \"PeekFuseable\" }! false", "{ \"operator\" : \"CollectList\" }! true", "MonoJust: [2]", "{ \"operator\" : \"OnErrorResume\" }: [2]"}); q.clear(); Hooks.onOperator(hooks -> hooks.log("reactor", true) .doOnEach(d -> q.offer(hooks.publisher() + ": " + d), t -> q.offer(hooks.publisher() + "! " + (t.getSuppressed().length != 0)), null, null)); simpleFlux(); Assert.assertArrayEquals(q.toArray(), new String[]{"FluxJust: 1", "{ \"operator\" : \"MapFuseable\" }: 2", "{ \"operator\" : \"PeekFuseable\" }! false", "{ \"operator\" : \"CollectList\" }! false", "MonoJust: [2]", "{ \"operator\" : \"OnErrorResume\" }: [2]"}); q.clear(); Hooks.onOperator(hooks -> hooks.log("reactor")); simpleFlux(); Assert.assertArrayEquals(q.toArray(), new String[0]); q.clear(); Hooks.resetOnOperator(); simpleFlux(); } @Test public void testTrace() throws Exception { Hooks.onOperator(Hooks.OperatorHook::operatorStacktrace); try { Mono.fromCallable(() -> { throw new RuntimeException(); }) .map(d -> d) .block(); } catch(Exception e){ e.printStackTrace(); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains("MonoCallable")); return; } finally { Hooks.resetOnOperator(); } throw new IllegalStateException(); } @Test public void testTrace2() throws Exception { Hooks.onOperator(hooks -> hooks.ifName("map", "filter") .operatorStacktrace()); try { Mono.just(1) .map(d -> { throw new RuntimeException(); }) .filter(d -> true) .doOnNext(d -> System.currentTimeMillis()) .map(d -> d) .block(); } catch(Exception e){ e.printStackTrace(); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("HooksTest.java:")); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains("|_\tMono.map" + "(HooksTest.java:")); return; } finally { Hooks.resetOnOperator(); } throw new IllegalStateException(); } @Test public void testTrace3() throws Exception { Hooks.onOperator(hooks -> hooks.operatorStacktrace()); try { Flux.just(1) .map(d -> { throw new RuntimeException(); }) .share() .filter(d -> true) .doOnNext(d -> System.currentTimeMillis()) .map(d -> d) .blockLast(); } catch(Exception e){ e.printStackTrace(); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("HooksTest.java:")); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains("|_\tFlux" + ".publish" + "(HooksTest.java:")); return; } finally { Hooks.resetOnOperator(); } throw new IllegalStateException(); } @Test public void testTraceComposed() throws Exception { Hooks.onOperator(hooks -> hooks.operatorStacktrace()); try { Mono.just(1) .flatMap(d -> Mono.error(new RuntimeException()) ) .filter(d -> true) .doOnNext(d -> System.currentTimeMillis()) .map(d -> d) .block(); } catch(Exception e){ e.printStackTrace(); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("HooksTest.java:")); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains("|_\tMono" + ".flatMap" + "(HooksTest.java:")); return; } finally { Hooks.resetOnOperator(); } throw new IllegalStateException(); } @Test public void testTraceComposed2() throws Exception { Hooks.onOperator(hooks -> hooks.operatorStacktrace()); try { Flux.just(1) .flatMap(d -> { throw new RuntimeException(); }) .filter(d -> true) .doOnNext(d -> System.currentTimeMillis()) .map(d -> d) .blockLast(); } catch(Exception e){ e.printStackTrace(); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("HooksTest.java:")); Assert.assertTrue(e.getSuppressed()[0].getMessage().contains("|_\tFlux" + ".flatMap" + "(HooksTest.java:")); return; } finally { Hooks.resetOnOperator(); } throw new IllegalStateException(); } @Test public void testMultiReceiver() throws Exception { Hooks.onOperator(hooks -> hooks.operatorStacktrace()); try { ConnectableFlux<?> t = Flux.empty() .then(Mono.defer(() -> { throw new RuntimeException(); })).flux().publish(); t.map(d -> d).subscribe(null, e -> Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("\t|_\tFlux.publish"))); t.filter(d -> true).subscribe(null, e -> Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("\t\t|_\tFlux.publish"))); t.distinct().subscribe(null, e -> Assert.assertTrue(e.getSuppressed()[0].getMessage().contains ("\t\t\t|_\tFlux.publish"))); t.connect(); } finally { Hooks.resetOnOperator(); } } }