From edb900d518a022846a4ac646a4d66b757d6dc1eb Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Fri, 15 Nov 2019 14:56:14 +0100 Subject: [PATCH] Add a JUnit 5 version of LogTester --- build.gradle | 1 + sonar-plugin-api/build.gradle | 2 + .../api/utils/log/AbstractLogTester.java | 84 +++++++++++++ .../org/sonar/api/utils/log/LogTester.java | 79 +++--------- .../sonar/api/utils/log/LogTesterJUnit5.java | 70 +++++++++++ .../api/utils/log/LogTesterJUnit5Test.java | 119 ++++++++++++++++++ 6 files changed, 294 insertions(+), 61 deletions(-) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/utils/log/AbstractLogTester.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTesterJUnit5.java create mode 100644 sonar-plugin-api/src/test/java/org/sonar/api/utils/log/LogTesterJUnit5Test.java diff --git a/build.gradle b/build.gradle index a7a3a0b8fce..339f4e24d7c 100644 --- a/build.gradle +++ b/build.gradle @@ -212,6 +212,7 @@ subprojects { dependency 'javax.servlet:javax.servlet-api:3.1.0' dependency 'javax.xml.bind:jaxb-api:2.3.0' dependency 'junit:junit:4.12' + dependency 'org.junit.jupiter:junit-jupiter-api:5.5.2' dependency 'net.jpountz.lz4:lz4:1.3.0' dependency 'net.lightbody.bmp:littleproxy:1.1.0-beta-bmp-17' dependency 'org.awaitility:awaitility:4.0.1' diff --git a/sonar-plugin-api/build.gradle b/sonar-plugin-api/build.gradle index 580f068d29e..37ee4e5521e 100644 --- a/sonar-plugin-api/build.gradle +++ b/sonar-plugin-api/build.gradle @@ -25,6 +25,8 @@ dependencies { compileOnly 'com.google.code.findbugs:jsr305' compileOnly 'javax.servlet:javax.servlet-api' compileOnly 'junit:junit' + // Used by LogTesterJUnit5 + compileOnly 'org.junit.jupiter:junit-jupiter-api' compileOnly 'org.slf4j:slf4j-api' testCompile 'com.google.guava:guava' diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/AbstractLogTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/AbstractLogTester.java new file mode 100644 index 00000000000..e6b8ad41864 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/AbstractLogTester.java @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.utils.log; + +import java.util.List; + +class AbstractLogTester { + + protected void before() { + // this shared instance breaks compatibility with parallel execution of tests + LogInterceptors.set(new ListInterceptor()); + setLevel(LoggerLevel.INFO); + } + + protected void after() { + LogInterceptors.set(NullInterceptor.NULL_INSTANCE); + setLevel(LoggerLevel.INFO); + } + + LoggerLevel getLevel() { + return Loggers.getFactory().getLevel(); + } + + /** + * Enable/disable debug logs. Info, warn and error logs are always enabled. + * By default INFO logs are enabled when LogTester is started. + */ + public AbstractLogTester setLevel(LoggerLevel level) { + Loggers.getFactory().setLevel(level); + return this; + } + + /** + * Logs in chronological order (item at index 0 is the oldest one) + */ + public List logs() { + return ((ListInterceptor) LogInterceptors.get()).logs(); + } + + /** + * Logs in chronological order (item at index 0 is the oldest one) for + * a given level + */ + public List logs(LoggerLevel level) { + return ((ListInterceptor) LogInterceptors.get()).logs(level); + } + + /** + * Logs with arguments in chronological order (item at index 0 is the oldest one) + */ + public List getLogs() { + return ((ListInterceptor) LogInterceptors.get()).getLogs(); + } + + /** + * Logs with arguments in chronological order (item at index 0 is the oldest one) for + * a given level + */ + public List getLogs(LoggerLevel level) { + return ((ListInterceptor) LogInterceptors.get()).getLogs(level); + } + + public AbstractLogTester clear() { + ((ListInterceptor) LogInterceptors.get()).clear(); + return this; + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTester.java index 6ad69b11135..0a85da680a4 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTester.java @@ -19,8 +19,9 @@ */ package org.sonar.api.utils.log; -import java.util.List; -import org.junit.rules.ExternalResource; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; /** * For tests only @@ -55,66 +56,22 @@ import org.junit.rules.ExternalResource; * * @since 5.1 */ -public class LogTester extends ExternalResource { - - @Override - protected void before() throws Throwable { - // this shared instance breaks compatibility with parallel execution of tests - LogInterceptors.set(new ListInterceptor()); - setLevel(LoggerLevel.INFO); - } - - @Override - protected void after() { - LogInterceptors.set(NullInterceptor.NULL_INSTANCE); - setLevel(LoggerLevel.INFO); - } - - LoggerLevel getLevel() { - return Loggers.getFactory().getLevel(); - } - - /** - * Enable/disable debug logs. Info, warn and error logs are always enabled. - * By default INFO logs are enabled when LogTester is started. - */ - public LogTester setLevel(LoggerLevel level) { - Loggers.getFactory().setLevel(level); - return this; - } - - /** - * Logs in chronological order (item at index 0 is the oldest one) - */ - public List logs() { - return ((ListInterceptor) LogInterceptors.get()).logs(); - } - - /** - * Logs in chronological order (item at index 0 is the oldest one) for - * a given level - */ - public List logs(LoggerLevel level) { - return ((ListInterceptor) LogInterceptors.get()).logs(level); - } - - /** - * Logs with arguments in chronological order (item at index 0 is the oldest one) - */ - public List getLogs() { - return ((ListInterceptor) LogInterceptors.get()).getLogs(); - } - - /** - * Logs with arguments in chronological order (item at index 0 is the oldest one) for - * a given level - */ - public List getLogs(LoggerLevel level) { - return ((ListInterceptor) LogInterceptors.get()).getLogs(level); +public class LogTester extends AbstractLogTester implements TestRule { + public Statement apply(Statement base, Description description) { + return statement(base); } - public LogTester clear() { - ((ListInterceptor) LogInterceptors.get()).clear(); - return this; + private Statement statement(final Statement base) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + before(); + try { + base.evaluate(); + } finally { + after(); + } + } + }; } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTesterJUnit5.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTesterJUnit5.java new file mode 100644 index 00000000000..7ed6a717078 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/LogTesterJUnit5.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.utils.log; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * For tests only + *
+ * This JUnit 5 extension allows to configure and access logs in tests. By default + * trace level is enabled. + *
+ * Warning - not compatible with parallel execution of tests in the same JVM fork. + *
+ * Example: + *
+ * public class MyClass {
+ *   private final Logger logger = Loggers.get("logger_name");
+ *
+ *   public void doSomething() {
+ *     logger.info("foo");
+ *   }
+ * }
+ *
+ * class MyClassTests {
+ *   @org.junit.jupiter.api.extension.RegisterExtension
+ *   LogTesterJUnit5 logTester = new LogTesterJUnit5();
+ *
+ *   @org.junit.jupiter.api.Test
+ *   public void test_log() {
+ *     new MyClass().doSomething();
+ *
+ *     assertThat(logTester.logs()).containsOnly("foo");
+ *   }
+ * }
+ * 
+ * + * @since 8.1 + */ +public class LogTesterJUnit5 extends AbstractLogTester implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + before(); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + after(); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/utils/log/LogTesterJUnit5Test.java b/sonar-plugin-api/src/test/java/org/sonar/api/utils/log/LogTesterJUnit5Test.java new file mode 100644 index 00000000000..939799c1d55 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/utils/log/LogTesterJUnit5Test.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.utils.log; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class LogTesterJUnit5Test { + + LogTesterJUnit5 underTest = new LogTesterJUnit5(); + + @Test + public void info_level_by_default() throws Throwable { + // when LogTester is used, then info logs are enabled by default + underTest.beforeTestExecution(null); + assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.INFO); + assertThat(Loggers.getFactory().getLevel()).isEqualTo(LoggerLevel.INFO); + + // change + underTest.setLevel(LoggerLevel.DEBUG); + assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.DEBUG); + assertThat(Loggers.getFactory().getLevel()).isEqualTo(LoggerLevel.DEBUG); + + // reset to initial level after execution of test + underTest.afterTestExecution(null); + assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.INFO); + assertThat(Loggers.getFactory().getLevel()).isEqualTo(LoggerLevel.INFO); + } + + @Test + public void intercept_logs() throws Throwable { + underTest.beforeTestExecution(null); + Loggers.get("logger1").info("an information"); + Loggers.get("logger2").warn("warning: {}", 42); + + assertThat(underTest.logs()).containsExactly("an information", "warning: 42"); + assertThat(underTest.logs(LoggerLevel.ERROR)).isEmpty(); + assertThat(underTest.logs(LoggerLevel.INFO)).containsOnly("an information"); + assertThat(underTest.logs(LoggerLevel.WARN)).containsOnly("warning: 42"); + + underTest.clear(); + assertThat(underTest.logs()).isEmpty(); + assertThat(underTest.logs(LoggerLevel.INFO)).isEmpty(); + + underTest.afterTestExecution(null); + assertThat(LogInterceptors.get()).isSameAs(NullInterceptor.NULL_INSTANCE); + } + + @Test + public void use_suppliers() throws Throwable { + // when LogTester is used, then info logs are enabled by default + underTest.beforeTestExecution(null); + AtomicBoolean touchedTrace = new AtomicBoolean(); + AtomicBoolean touchedDebug = new AtomicBoolean(); + Loggers.get("logger1").trace(() -> { + touchedTrace.set(true); + return "a trace information"; + }); + Loggers.get("logger1").debug(() -> { + touchedDebug.set(true); + return "a debug information"; + }); + + assertThat(underTest.logs()).isEmpty(); + assertThat(touchedTrace.get()).isFalse(); + assertThat(touchedDebug.get()).isFalse(); + + // change level to DEBUG + underTest.setLevel(LoggerLevel.DEBUG); + Loggers.get("logger1").trace(() -> { + touchedTrace.set(true); + return "a trace information"; + }); + Loggers.get("logger1").debug(() -> { + touchedDebug.set(true); + return "a debug information"; + }); + + assertThat(underTest.logs()).containsOnly("a debug information"); + assertThat(touchedTrace.get()).isFalse(); + assertThat(touchedDebug.get()).isTrue(); + touchedDebug.set(false); + underTest.clear(); + + // change level to TRACE + underTest.setLevel(LoggerLevel.TRACE); + Loggers.get("logger1").trace(() -> { + touchedTrace.set(true); + return "a trace information"; + }); + Loggers.get("logger1").debug(() -> { + touchedDebug.set(true); + return "a debug information"; + }); + + assertThat(underTest.logs()).containsExactly("a trace information", "a debug information"); + assertThat(touchedTrace.get()).isTrue(); + assertThat(touchedDebug.get()).isTrue(); + } +} -- 2.39.5