diff --git a/pom.xml b/pom.xml index 38ad633..fd1508b 100644 --- a/pom.xml +++ b/pom.xml @@ -412,6 +412,7 @@ core jdk-platform-logging slf4j + tinylog-provider Mock Loggers pom diff --git a/tinylog-provider/pom.xml b/tinylog-provider/pom.xml new file mode 100644 index 0000000..4b54f9d --- /dev/null +++ b/tinylog-provider/pom.xml @@ -0,0 +1,98 @@ + + + + mock-loggers-tinylog-provider + + + + maven-surefire-plugin + org.apache.maven.plugins + + + maven-failsafe-plugin + org.apache.maven.plugins + + + jacoco-maven-plugin + org.jacoco + + + + + + + annotations + org.jetbrains + provided + + + mock-loggers-core + io.github.vitalijr2.logging + 1.0.0 + + + mockito-core + org.mockito + provided + + + tinylog-api + org.tinylog + provided + 2.7.0 + + + + junit-jupiter-engine + org.junit.jupiter + provided + + + junit-jupiter-params + org.junit.jupiter + provided + + + mockito-junit-jupiter + org.mockito + provided + + + Mock loggers for tinylog backed by Mockito. + 4.0.0 + Mock loggers for tinylog + + mock-loggers + io.github.vitalijr2.logging + 1.0.0 + + + + + + + maven-invoker-plugin + org.apache.maven.plugins + + + + run-its + + + \ No newline at end of file diff --git a/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java b/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java new file mode 100644 index 0000000..c98e5d4 --- /dev/null +++ b/tinylog-provider/src/main/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProvider.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024 Vitalij Berdinskih + * + * 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 io.github.vitalijr2.logging.mock.tinylog; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; + +import io.github.vitalijr2.logging.mock.MockLoggerCleaner; +import java.util.List; +import org.tinylog.Level; +import org.tinylog.format.MessageFormatter; +import org.tinylog.provider.ContextProvider; +import org.tinylog.provider.LoggingProvider; + +public class MockLoggerProvider implements LoggingProvider, MockLoggerCleaner { + + static final LoggingProvider MOCK_INSTANCE; + + static { + MOCK_INSTANCE = mock(LoggingProvider.class); + prepareMockInstance(); + } + + public MockLoggerProvider() { + subscribeToNotifications(); + } + + private static void prepareMockInstance() { + clearInvocations(MOCK_INSTANCE); + reset(MOCK_INSTANCE); + } + + @Override + public List cleanAndReset() { + prepareMockInstance(); + return List.of("tinylog"); + } + + @Override + public ContextProvider getContextProvider() { + return MOCK_INSTANCE.getContextProvider(); + } + + @Override + public Level getMinimumLevel() { + return MOCK_INSTANCE.getMinimumLevel(); + } + + @Override + public Level getMinimumLevel(String tag) { + return MOCK_INSTANCE.getMinimumLevel(tag); + } + + @Override + public boolean isEnabled(int depth, String tag, Level level) { + return MOCK_INSTANCE.isEnabled(depth, tag, level); + } + + @Override + public boolean isEnabled(String loggerClassName, String tag, Level level) { + return MOCK_INSTANCE.isEnabled(loggerClassName, tag, level); + } + + @Override + public void log(int depth, String tag, Level level, Throwable exception, MessageFormatter formatter, Object obj, + Object... arguments) { + MOCK_INSTANCE.log(depth, tag, level, exception, formatter, obj, arguments); + } + + @Override + public void log(String loggerClassName, String tag, Level level, Throwable exception, MessageFormatter formatter, + Object obj, Object... arguments) { + MOCK_INSTANCE.log(loggerClassName, tag, level, exception, formatter, obj, arguments); + } + + @Override + public void shutdown() throws InterruptedException { + MOCK_INSTANCE.shutdown(); + } + +} diff --git a/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java new file mode 100644 index 0000000..f67b0ac --- /dev/null +++ b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderFastTest.java @@ -0,0 +1,185 @@ +package io.github.vitalijr2.logging.mock.tinylog; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.tinylog.Level; +import org.tinylog.format.MessageFormatter; +import org.tinylog.provider.NopContextProvider; + + +@Tag("fast") +class MockLoggerProviderFastTest { + + private MockLoggerProvider provider; + + @BeforeEach + void setUp() { + provider = new MockLoggerProvider(); + } + + @AfterEach + void tearDown() { + clearInvocations(MockLoggerProvider.MOCK_INSTANCE); + reset(MockLoggerProvider.MOCK_INSTANCE); + } + + // Move to slow + @DisplayName("Clear and reset the mock instance") + @Test + void cleanAndReset() { + // when + provider.cleanAndReset(); + + // then + verifyNoInteractions(MockLoggerProvider.MOCK_INSTANCE); + } + + @DisplayName("Get a context provider") + @Test + void getContextProvider() { + // given + when(MockLoggerProvider.MOCK_INSTANCE.getContextProvider()).thenReturn(new NopContextProvider()); + + // when and then + assertNotNull(provider.getContextProvider()); + + verify(MockLoggerProvider.MOCK_INSTANCE).getContextProvider(); + } + + @DisplayName("Get a minimum level") + @ParameterizedTest + @ValueSource(strings = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}) + void getMinimumLevel(Level expectedlevel) { + // given + when(MockLoggerProvider.MOCK_INSTANCE.getMinimumLevel()).thenReturn(expectedlevel); + + // when + var actualLevel = provider.getMinimumLevel(); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).getMinimumLevel(); + + assertEquals(expectedlevel, actualLevel); + } + + @DisplayName("Get a minimum level for a classname") + @ParameterizedTest + @ValueSource(strings = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"}) + void getMinimumLevelByTagWithCustomLevel(Level expectedlevel) { + // given + when(MockLoggerProvider.MOCK_INSTANCE.getMinimumLevel(anyString())).thenReturn(expectedlevel); + + // when + var actualLevel = provider.getMinimumLevel("test tag"); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).getMinimumLevel("test tag"); + + assertEquals(expectedlevel, actualLevel); + } + + @DisplayName("Is it enabled?") + @ParameterizedTest + @CsvSource(value = {"TRACE,true", "TRACE,false", "DEBUG,true", "DEBUG,false", "INFO,true", "INFO,false", "WARN,true", + "WARN,false", "ERROR,true", "ERROR,false", "OFF,true", "OFF,false"}) + void isEnabled(Level level, boolean expectedResult) { + // given + when(MockLoggerProvider.MOCK_INSTANCE.isEnabled(anyInt(), anyString(), isA(Level.class))).thenReturn( + expectedResult); + + // when + var actualResult = provider.isEnabled(123, "test tag", level); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).isEnabled(123, "test tag", level); + + assertEquals(expectedResult, actualResult); + } + + @DisplayName("Is it enabled for a classname?") + @ParameterizedTest + @CsvSource(value = {"TRACE,true", "TRACE,false", "DEBUG,true", "DEBUG,false", "INFO,true", "INFO,false", "WARN,true", + "WARN,false", "ERROR,true", "ERROR,false", "OFF,true", "OFF,false"}, nullValues = "N/A") + void isEnabledForClassname(Level level, boolean expectedResult) { + // given + when(MockLoggerProvider.MOCK_INSTANCE.isEnabled(anyString(), anyString(), isA(Level.class))).thenReturn( + expectedResult); + + // when + var actualResult = provider.isEnabled("test.class", "test tag", level); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).isEnabled("test.class", "test tag", level); + + assertEquals(expectedResult, actualResult); + } + + @DisplayName("Log") + @Test + void log() { + // when + provider.log(123, "test tag", Level.TRACE, new Throwable("test throwable"), mock(MessageFormatter.class), this, "a", + "b", "c"); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).log(eq(123), eq("test tag"), eq(Level.TRACE), isA(Throwable.class), + isA(MessageFormatter.class), notNull(), eq("a"), eq("b"), eq("c")); + } + + @DisplayName("Log for a classname") + @Test + void logForClassname() { + // when + provider.log("test.class", "test tag", Level.TRACE, new Throwable("test throwable"), mock(MessageFormatter.class), + this, "a", "b", "c"); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).log(eq("test.class"), eq("test tag"), eq(Level.TRACE), + isA(Throwable.class), isA(MessageFormatter.class), notNull(), eq("a"), eq("b"), eq("c")); + } + + @DisplayName("Shutdown") + @Test + void shutdown() throws InterruptedException { + // when + provider.shutdown(); + + // then + verify(MockLoggerProvider.MOCK_INSTANCE).shutdown(); + } + + @DisplayName("Shutdown with an exception") + @Test + void shutdownWithException() throws InterruptedException { + // given + doThrow(new InterruptedException("test")).when(MockLoggerProvider.MOCK_INSTANCE).shutdown(); + + // when and then + var exception = assertThrows(InterruptedException.class, () -> provider.shutdown()); + + assertEquals("test", exception.getMessage()); + } + +} \ No newline at end of file diff --git a/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java new file mode 100644 index 0000000..860ec30 --- /dev/null +++ b/tinylog-provider/src/test/java/io/github/vitalijr2/logging/mock/tinylog/MockLoggerProviderSlowTest.java @@ -0,0 +1,42 @@ +package io.github.vitalijr2.logging.mock.tinylog; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verifyNoInteractions; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("slow") +class MockLoggerProviderSlowTest { + + private MockLoggerProvider provider; + + @BeforeEach + void setUp() { + provider = new MockLoggerProvider(); + } + + @AfterEach + void tearDown() { + clearInvocations(MockLoggerProvider.MOCK_INSTANCE); + reset(MockLoggerProvider.MOCK_INSTANCE); + } + + // Move to slow + @DisplayName("Clear and reset the mock instance") + @Test + void cleanAndReset() throws InterruptedException { + // when + provider.shutdown(); + provider.cleanAndReset(); + + // then + verifyNoInteractions(MockLoggerProvider.MOCK_INSTANCE); + } + +} \ No newline at end of file