/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.github.rodionmoiseev.c10n.plugins.logging; import com.github.rodionmoiseev.c10n.C10NMessages; import com.github.rodionmoiseev.c10n.InvocationDetails; import com.github.rodionmoiseev.c10n.TestUtil; import com.github.rodionmoiseev.c10n.annotations.En; import com.github.rodionmoiseev.c10n.plugin.PluginResult; import org.junit.Test; import org.mockito.Mockito; import java.lang.reflect.Method; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; @SuppressWarnings("unused") public class LoggingPluginTest { @C10NMessages @Logger(LoggingPluginTest.class) @Level(LoggingLevel.ERROR) public interface LoggingMessagesWithClassDecls { void usesClassLogger(); @Logger(name = "methodLogger") @Level(LoggingLevel.TRACE) void usesMethodLogger(); void withArg(String name); @Level(LoggingLevel.WARN) void withCause(String name, Throwable cause); } @Logger @C10NMessages public interface LoggingMessagesWithoutClassDecls { void defaultLogger(); @Logger(name = "methodLogger") @Level(LoggingLevel.TRACE) void usesMethodLogger(); } @Logger @C10NMessages public interface WithUtils extends LoggingBase { } @C10NMessages public interface UnrelatedC10NMessageBundle { @En("noArg") String noArg(); @En("withArg") String withArg(String name); } @Test public void loggingRequestsInterruptThePluginPipeline() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); PluginResult res = plugin.format("test message", "test message", new InvocationDetails(null, LoggingMessagesWithClassDecls.class, TestUtil.method(LoggingMessagesWithClassDecls.class, "usesClassLogger"), null)); assertThat(res.isInterrupt(), is(equalTo(true))); } @Test public void classDeclarationsAreTakenIntoAccountUnlessOveriddenOnMethod() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); check(plugin, "test message", LoggingMessagesWithClassDecls.class, "usesClassLogger", null, logger, LoggingPluginTest.class.getName(), LoggingLevel.ERROR); check(plugin, "test message", LoggingMessagesWithClassDecls.class, "usesMethodLogger", null, logger, "methodLogger", LoggingLevel.TRACE); } @Test public void defaultDeclarationsAreUsedUnlessOveriddenOnMethod() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); check(plugin, "test message", LoggingMessagesWithoutClassDecls.class, "defaultLogger", null, logger, LoggingMessagesWithoutClassDecls.class.getName(), LoggingLevel.INFO); check(plugin, "test message", LoggingMessagesWithoutClassDecls.class, "usesMethodLogger", null, logger, "methodLogger", LoggingLevel.TRACE); } @Test public void methodArgumentsArePassedInsideInvocationDetails() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); Method withArgMethod = TestUtil.method(LoggingMessagesWithClassDecls.class, "withArg"); Object[] args = {"rodion"}; check(plugin, "test message", LoggingMessagesWithClassDecls.class, "withArg", args, logger, LoggingPluginTest.class.getName(), LoggingLevel.ERROR); } @Test public void lastThrowableArgumentIsTreatedAsCause() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); Object[] args = {"rodion", new AnException("intentional")}; check(plugin, "message and stack", LoggingMessagesWithClassDecls.class, "withCause", args, new AnException("intentional"), logger, LoggingPluginTest.class.getName(), LoggingLevel.WARN); } @Test public void unrelatedC10NMessageClassesAreNotPassedThroughThePlugin() { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); PluginResult res = plugin.format("noArg", "noArg", new InvocationDetails(null, UnrelatedC10NMessageBundle.class, TestUtil.method(UnrelatedC10NMessageBundle.class, "noArg"), null)); assertThat(res.isInterrupt(), is(equalTo(false))); assertThat(res.getValue(), is(equalTo("noArg"))); verifyZeroInteractions(logger); } @Test public void extendingLoggerBaseGainsAccessToUnderlyingLoggerFunctionality() throws Exception { LoggerImplementation logger = Mockito.mock(LoggerImplementation.class); when(logger.isDebugEnabled()).thenReturn(true); when(logger.isLevelEnabled(any(LoggingLevel.class))).thenReturn(false); LoggingPlugin plugin = new LoggingPlugin(LoggingLevel.INFO, logger); PluginResult res = plugin.format(null, null, new InvocationDetails(null, WithUtils.class, TestUtil.method(LoggingBase.class, "isDebugEnabled"), null)); assertThat(res.isInterrupt(), is(equalTo(true))); assertThat(res.getValue(), is(equalTo(true))); PluginResult res2 = plugin.format(null, null, new InvocationDetails(null, WithUtils.class, TestUtil.method(LoggingBase.class, "isLevelEnabled"), new Object[]{LoggingLevel.ERROR})); assertThat(res2.isInterrupt(), is(equalTo(true))); assertThat(res2.getValue(), is(equalTo(false))); verify(logger).isLevelEnabled(LoggingLevel.ERROR); } private void check(LoggingPlugin plugin, String message, Class<?> c10nClass, String methodName, Object[] args, LoggerImplementation logger, String loggerName, LoggingLevel level) { check(plugin, message, c10nClass, methodName, args, null, logger, loggerName, level); } private void check(LoggingPlugin plugin, String message, Class<?> c10nClass, String methodName, Object[] args, Throwable cause, LoggerImplementation logger, String loggerName, LoggingLevel level) { Method method = TestUtil.method(c10nClass, methodName); plugin.format(message, message, new InvocationDetails(null, c10nClass, method, args )); verify(logger).log( eq(loggerName), eq(level), eq(message), eq(cause), eq(new InvocationDetails(null, c10nClass, method, args)) ); } private static final class AnException extends Exception { AnException(String message) { super(message); } @Override public int hashCode() { return getMessage().hashCode(); } @Override public boolean equals(Object obj) { return (obj instanceof AnException) && ((AnException) obj).getMessage().equals(this.getMessage()); } } }