Skip to content
This repository has been archived by the owner on Jan 5, 2025. It is now read-only.

Commit

Permalink
Add logger filter predicate
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalijr2 committed Nov 5, 2024
1 parent f82ade8 commit 136b14d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* class name, i.e. separated by dots.
* <p>
* An empty string is the default acceptance filter. A zero string is the default rejection filter.
*
* @since 1.2.0
*/
class MockLoggerFilter implements Predicate<String[]> {

Expand All @@ -35,7 +37,7 @@ class MockLoggerFilter implements Predicate<String[]> {
if (prefix == null) {
this.prefix = new String[0];
} else {
this.prefix = prefix.split("\\.");
this.prefix = prefix.trim().split("\\.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,23 @@
*/
package io.github.vitalijr2.mock.jdk.platform.logging;

import static java.util.Objects.requireNonNullElse;
import static org.mockito.Mockito.mock;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.System.Logger;
import java.lang.System.LoggerFinder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

/**
Expand All @@ -44,6 +55,8 @@ public class MockLoggerFinder extends LoggerFinder {

private final Map<String, Logger> loggers;

private final Predicate<String[]> loggerFilters;

Check notice

Code scanning / Pmd (reported by Codacy)

Avoid unused private fields such as 'loggerFilters'. Note

Avoid unused private fields such as 'loggerFilters'.

Check notice

Code scanning / Pmd (reported by Codacy)

Perhaps 'loggerFilters' could be replaced by a local variable. Note

Perhaps 'loggerFilters' could be replaced by a local variable.

/**
* Create a map-based logger finder. The finder uses a concurrent map: a logger name is a key.
*/
Expand All @@ -54,14 +67,72 @@ public MockLoggerFinder() {
@VisibleForTesting
MockLoggerFinder(Map<String, Logger> loggers) {
this.loggers = loggers;

var configurationProperties = loadProperties("mock-loggers.properties");

loggerFilters = craftLoggerFilters(configurationProperties.getProperty("excludes"),
configurationProperties.getProperty("includes"));
}

/**
* Prepares sets of acceptance and rejection filters.
*
* @param excludes rejection comma-separated list
* @param includes acceptance comma-separated list
* @return filter predicate
*/
@VisibleForTesting
static Predicate<String[]> craftLoggerFilters(@Nullable String excludes, @Nullable String includes) {
Set<MockLoggerFilter> excludeFilters;
Set<MockLoggerFilter> includeFilters;

if (excludes == null) {
// default rejection filter
excludeFilters = Collections.singleton(new MockLoggerFilter(null));
} else {
excludeFilters = Arrays.stream(excludes.split(",")).map(MockLoggerFilter::new).collect(Collectors.toSet());
}
if (includes == null) {
// default acceptance filter
includeFilters = Collections.singleton(new MockLoggerFilter(""));
} else {
includeFilters = Arrays.stream(includes.split(",")).map(MockLoggerFilter::new).collect(Collectors.toSet());
}

return splitLoggerName -> excludeFilters.stream().noneMatch(excludeFilter -> excludeFilter.test(splitLoggerName))
&& includeFilters.stream().anyMatch(includeFilter -> includeFilter.test(splitLoggerName));
}

/**
* Loads configuration properties.
* <p>
* If a property file is not found that default acceptance filter is used.
*
* @param configurationFile filename
* @return configuration properties
*/
@VisibleForTesting
static Properties loadProperties(String configurationFile) {
var fallbackInputStream = new ByteArrayInputStream("includes=".getBytes());

try (InputStream configurationInputStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream(configurationFile)) {
var properties = new Properties();

properties.load(requireNonNullElse(configurationInputStream, fallbackInputStream));

return properties;
} catch (IOException exception) {
throw new RuntimeException("Could not load configuration properties", exception);

Check notice

Code scanning / Pmd (reported by Codacy)

Avoid throwing raw exception types. Note

Avoid throwing raw exception types.
}
}

/**
* Returns an instance of Logger for the given name, module is ignored.
*
* @param name logging name
* @param module logging module
* @return mock logger
* @return logger, mock or real
*/
@Override
public Logger getLogger(String name, Module module) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ void unknownLoggerFinder() {
loggerFinder.when(LoggerFinder::getLoggerFinder).thenReturn(mock(LoggerFinder.class));

// when
var exception = assertThrows(ExtensionConfigurationException.class,
MockLoggerExtension::getMockLoggerFinder);
var exception = assertThrows(ExtensionConfigurationException.class, MockLoggerExtension::getMockLoggerFinder);

// then
assertEquals("The logger finder is not a MockLoggerFinder", exception.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,29 @@ void rejectionFilter(String loggerName) {
@DisplayName("Accept logger names which start with similar prefix")
@ParameterizedTest
@ValueSource(strings = {"qwerty.abc", "qwerty.abc.xyz"})
void accept(String loggerName) {
void acceptWithSimilarPrefix(String loggerName) {
// given
var qwertyAbcFilter = new MockLoggerFilter("qwerty.abc");

// when and then
assertTrue(qwertyAbcFilter.test(loggerName.split("\\.")));
}

@DisplayName("Prefix is trimmed")
@ParameterizedTest
@ValueSource(strings = {"qwerty.abc", "qwerty.abc.xyz"})
void prefixIsTrimmed(String loggerName) {
// given
var qwertyAbcFilter = new MockLoggerFilter(" qwerty.abc ");

// when and then
assertTrue(qwertyAbcFilter.test(loggerName.split("\\.")));
}

@DisplayName("Reject logger names which do not start with similar prefix")
@ParameterizedTest
@ValueSource(strings = {"qwerty", "qwerty.abc123", "qwerty.xyz.xyz", "qwerty.ab.xyz",})
void reject(String loggerName) {
void rejectWithUnrelatedPrefix(String loggerName) {
// given
var qwertyAbcFilter = new MockLoggerFilter("qwerty.abc");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package io.github.vitalijr2.mock.jdk.platform.logging;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
Expand All @@ -10,6 +15,7 @@
import org.junit.jupiter.api.BeforeAll;
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.ValueSource;

Expand Down Expand Up @@ -42,4 +48,39 @@ void test(Level level) {
verify(logger).log(level, "test message");
}

@DisplayName("Property file does not exists, use defaults")
@Test
void propertyFileDoesNotExist() {
// when
var properties = assertDoesNotThrow(() -> MockLoggerFinder.loadProperties("i-do-not-exist.properties"));

// then
assertAll("Properties from fallback", () -> assertEquals(1, properties.size()),
() -> assertEquals("", properties.getProperty("includes")));
}

@DisplayName("Property file exists")
@Test
void propertyFileExists() {
// when
var properties = assertDoesNotThrow(() -> MockLoggerFinder.loadProperties("i-am-a-test.properties"));

// then
assertAll("Properties from fallback", () -> assertEquals(2, properties.size()),
() -> assertEquals("test-include", properties.getProperty("includes")),
() -> assertEquals("test-exclude", properties.getProperty("excludes")));
}

@DisplayName("Logger filter")
@Test
void loggerFilter() {
// when
var loggerFilter = MockLoggerFinder.craftLoggerFilters("abc.qwerty.xyz", "abc.qwerty");

// then
assertTrue(loggerFilter.test("abc.qwerty.uv".split("\\.")));
assertFalse(loggerFilter.test("abc.qwerty.xyz.st".split("\\.")));
assertFalse(loggerFilter.test("abc.ij".split("\\.")));
}

}
2 changes: 2 additions & 0 deletions src/test/resources/i-am-a-test.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
includes=test-include
excludes=test-exclude

0 comments on commit 136b14d

Please sign in to comment.