@Test public void constructor_works_with_valid_args() { // given ChannelPipelineFinalizerHandler handler = new ChannelPipelineFinalizerHandler( mock(ExceptionHandlingHandler.class), mock(ResponseSender.class), null, null, workerChannelIdleTimeoutMillis ); // expect assertThat(handler, notNullValue()); }
@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause); finalizeChannelPipeline(ctx, null, state, cause); return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }
protected void logErrorWithTracing(String msg, Throwable ex, HttpProcessingState state) { Deque<Span> distributedTraceStackToLink = null; Map<String, String> mdcContextMapToLink = null; if (state != null) { distributedTraceStackToLink = state.getDistributedTraceStack(); mdcContextMapToLink = state.getLoggerMdcContextMap(); } logErrorWithTracing(msg, ex, distributedTraceStackToLink, mdcContextMapToLink); }
@Test public void doChannelRead_do_nothing_and_return_DO_NOT_FIRE_CONTINUE_EVENT_if_msg_is_not_LastOutboundMessage() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); Object msg = new Object(); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy, times(0)).finalizeChannelPipeline(eq(ctxMock), eq(msg), eq(state), any(Throwable.class)); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
@Test public void doExceptionCaught_gets_state_from_getStateAndCreateIfNeeded_method_and_then_calls_finalizeChannelPipeline_and_then_returns_DO_NOT_FIRE_CONTINUE_EVENT() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); Exception cause = new Exception("intentional test exception"); state.setResponseWriterFinalChunkChannelFuture(mock(ChannelFuture.class)); // when PipelineContinuationBehavior result = handlerSpy.doExceptionCaught(ctxMock, cause); // then verify(handlerSpy).getStateAndCreateIfNeeded(ctxMock, cause); verify(handlerSpy).finalizeChannelPipeline(ctxMock, null, state, cause); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
+ "was not null. This should not be possible! " + "current_span={}", Tracer.getInstance().getCurrentSpan()); releaseProxyRouterStateResources(proxyRouterState, ctx); logErrorWithTracing( "An unexpected error occurred while trying to finalize distributed tracing. " + "This exception will be swallowed.", t, httpState logErrorWithTracing( "An unexpected error occurred while trying to finalize access logging. " + "This exception will be swallowed.", t, httpState handleMetricsForCompletedRequestIfNotAlreadyDone(httpState); logErrorWithTracing( "An unexpected error occurred while trying to release request resources. " + "This exception will be swallowed.", t, httpState releaseProxyRouterStateResources(proxyRouterState, ctx); logErrorWithTracing( "An unexpected error occurred while trying to release ProxyRouter state resources. " + "This exception will be swallowed.", t, httpState logErrorWithTracing( "An unexpected error occurred during ChannelPipelineFinalizerHandler.doChannelInactive() - this " + "should not happen and indicates a bug that needs to be fixed in Riposte.", t, ctx
String errorMsg = "Discovered a request that snuck through without a response being sent. This should not " + "be possible and indicates a major problem in the channel pipeline."; logErrorWithTracing(errorMsg, new Exception("Wrapper exception", cause), state); handleMetricsForCompletedRequestIfNotAlreadyDone(state); logErrorWithTracing( "Received an error in ChannelPipelineFinalizerHandler after response sending was started, but " + "before it finished. Closing the channel. unexpected_error=" + cause.toString(),
@Test public void finalizeChannelPipeline_should_add_idle_channel_timeout_handler_first_in_pipeline_if_workerChannelIdleTimeoutMillis_is_greater_than_0() throws JsonProcessingException { // given LastOutboundMessage msg = mock(LastOutboundMessage.class); // when handler.finalizeChannelPipeline(ctxMock, msg, state, null); // then ArgumentCaptor<ChannelHandler> idleHandlerArgCaptor = ArgumentCaptor.forClass(ChannelHandler.class); verify(pipelineMock).addFirst(eq(IDLE_CHANNEL_TIMEOUT_HANDLER_NAME), idleHandlerArgCaptor.capture()); ChannelHandler handlerRegistered = idleHandlerArgCaptor.getValue(); assertThat(handlerRegistered, instanceOf(IdleChannelTimeoutHandler.class)); IdleChannelTimeoutHandler idleHandler = (IdleChannelTimeoutHandler)handlerRegistered; long idleValue = (long) Whitebox.getInternalState(idleHandler, "idleTimeoutMillis"); assertThat(idleValue, is(workerChannelIdleTimeoutMillis)); }
@DataProvider(value = { "true", "false" }) @Test public void doChannelInactive_completes_metrics_if_necessary_and_HttpProcessingState_is_not_null( boolean stateIsNull ) throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); if (stateIsNull) { doReturn(null).when(stateAttributeMock).get(); } // when PipelineContinuationBehavior result = handlerSpy.doChannelInactive(ctxMock); // then if (stateIsNull) { verify(handlerSpy, never()).handleMetricsForCompletedRequestIfNotAlreadyDone(any(HttpProcessingState.class)); } else { verify(handlerSpy).handleMetricsForCompletedRequestIfNotAlreadyDone(state); } }
@Test public void getStateAndCreateIfNeeded_creates_new_state_if_ctx_state_is_null() { // given doReturn(null).when(stateAttributeMock).get(); // when HttpProcessingState result = handler.getStateAndCreateIfNeeded(ctxMock, null); // then assertThat(result, notNullValue()); assertThat(result, not(state)); verify(stateAttributeMock).set(result); }
@Test public void handleMetricsForCompletedRequestIfNotAlreadyDone_does_not_propagate_unexpected_exceptions() { // given HttpProcessingState stateMock = mock(HttpProcessingState.class); doThrow(new RuntimeException("intentional exception")).when(stateMock).isRequestMetricsRecordedOrScheduled(); // when Throwable ex = catchThrowable(() -> handler.handleMetricsForCompletedRequestIfNotAlreadyDone(stateMock)); // then Assertions.assertThat(ex).isNull(); verify(stateMock).isRequestMetricsRecordedOrScheduled(); }
@Test public void code_coverage_hoops() throws Exception { // jump! // doChannelInactive() does some debug logging if the logger has debug logging enabled. Logger loggerMock = mock(Logger.class); doReturn(true).when(loggerMock).isDebugEnabled(); Whitebox.setInternalState(handler, "logger", loggerMock); handler.doChannelInactive(ctxMock); doReturn(false).when(loggerMock).isDebugEnabled(); handler.doChannelInactive(ctxMock); } }
@Test public void argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo_returns_false() { // expect Assertions.assertThat( handler.argsAreEligibleForLinkingAndUnlinkingDistributedTracingInfo(null, null, null, null) ).isFalse(); }
+ "was not null. This should not be possible! " + "current_span={}", Tracer.getInstance().getCurrentSpan()); releaseProxyRouterStateResources(proxyRouterState, ctx); logErrorWithTracing( "An unexpected error occurred while trying to finalize distributed tracing. " + "This exception will be swallowed.", t, httpState logErrorWithTracing( "An unexpected error occurred while trying to finalize access logging. " + "This exception will be swallowed.", t, httpState handleMetricsForCompletedRequestIfNotAlreadyDone(httpState); logErrorWithTracing( "An unexpected error occurred while trying to release request resources. " + "This exception will be swallowed.", t, httpState releaseProxyRouterStateResources(proxyRouterState, ctx); logErrorWithTracing( "An unexpected error occurred while trying to release ProxyRouter state resources. " + "This exception will be swallowed.", t, httpState logErrorWithTracing( "An unexpected error occurred during ChannelPipelineFinalizerHandler.doChannelInactive() - this " + "should not happen and indicates a bug that needs to be fixed in Riposte.", t, ctx
String errorMsg = "Discovered a request that snuck through without a response being sent. This should not " + "be possible and indicates a major problem in the channel pipeline."; logErrorWithTracing(errorMsg, new Exception("Wrapper exception", cause), state); handleMetricsForCompletedRequestIfNotAlreadyDone(state); logErrorWithTracing( "Received an error in ChannelPipelineFinalizerHandler after response sending was started, but " + "before it finished. Closing the channel. unexpected_error=" + cause.toString(),
@Test public void finalizeChannelPipeline_should_send_event_to_metricsListener_for_failure_response_and_flush_context() throws Exception { // given ChannelFuture responseWriterChannelFuture = mock(ChannelFuture.class); state.setResponseWriterFinalChunkChannelFuture(responseWriterChannelFuture); HttpProcessingState stateSpy = spy(state); doReturn(stateSpy).when(stateAttributeMock).get(); ChannelFuture responseWriteFutureResult = mock(ChannelFuture.class); doReturn(false).when(responseWriteFutureResult).isSuccess(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isFalse(); // when handler.finalizeChannelPipeline(ctxMock, null, stateSpy, null); // then ArgumentCaptor<GenericFutureListener> channelFutureListenerArgumentCaptor = ArgumentCaptor.forClass(GenericFutureListener.class); verify(responseWriterChannelFuture).addListener(channelFutureListenerArgumentCaptor.capture()); GenericFutureListener futureListener = channelFutureListenerArgumentCaptor.getValue(); assertThat(futureListener, notNullValue()); futureListener.operationComplete(responseWriteFutureResult); verify(metricsListenerMock).onEvent(ServerMetricsEvent.RESPONSE_WRITE_FAILED, null); verify(ctxMock).flush(); Assertions.assertThat(stateSpy.isRequestMetricsRecordedOrScheduled()).isTrue(); }
@Test public void doChannelRead_gets_state_from_getStateAndCreateIfNeeded_method_and_then_calls_finalizeChannelPipeline_and_then_returns_DO_NOT_FIRE_CONTINUE_EVENT_if_msg_is_LastOutboundMessage() throws Exception { // given ChannelPipelineFinalizerHandler handlerSpy = spy(handler); LastOutboundMessage msg = mock(LastOutboundMessage.class); state.setResponseWriterFinalChunkChannelFuture(mock(ChannelFuture.class)); // when PipelineContinuationBehavior result = handlerSpy.doChannelRead(ctxMock, msg); // then verify(handlerSpy).finalizeChannelPipeline(eq(ctxMock), eq(msg), eq(state), any(Throwable.class)); assertThat(result, is(PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT)); }
@Test public void getStateAndCreateIfNeeded_uses_state_from_ctx_if_available() { // expect assertThat(handler.getStateAndCreateIfNeeded(ctxMock, null), is(state)); }
@Test public void doChannelInactive_does_not_explode_if_crazy_exception_occurs() throws Exception { // given doThrow(new RuntimeException("kaboom")).when(proxyRouterProcessingStateAttributeMock).get(); // when PipelineContinuationBehavior result = handler.doChannelInactive(ctxMock); // then Assertions.assertThat(result).isEqualTo(PipelineContinuationBehavior.CONTINUE); }
@Override public PipelineContinuationBehavior doExceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { HttpProcessingState state = getStateAndCreateIfNeeded(ctx, cause); finalizeChannelPipeline(ctx, null, state, cause); return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT; }