<artifactId>sonar-scanner-java-library</artifactId>
<version>3.0.0.114</version>
</dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.5.6</version>
+ </dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <version>5.10.1</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ <exclude>META-INF/LICENSE.txt</exclude>
+ <exclude>META-INF/NOTICE.txt</exclude>
+ <exclude>META-INF/MANIFEST.MF</exclude>
+ <exclude>**/module-info.class</exclude>
+ </excludes>
+ </filter>
+ <filter>
+ <artifact>ch.qos.logback:logback-classic</artifact>
+ <includes>
+ <include>**</include>
+ </includes>
+ </filter>
+ </filters>
</configuration>
</execution>
</executions>
<configuration>
<rules>
<requireFilesSize>
- <minsize>3400000</minsize>
- <maxsize>3500000</maxsize>
+ <minsize>4100000</minsize>
+ <maxsize>4200000</maxsize>
<files>
<file>${project.build.directory}/sonar-scanner-${project.version}.zip</file>
</files>
*/
package org.sonarsource.scanner.cli;
+import ch.qos.logback.classic.Level;
import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import static java.util.Arrays.asList;
class Cli {
+ private static final Logger LOG = LoggerFactory.getLogger(Cli.class);
+
private boolean debugEnabled = false;
private boolean displayVersionOnly = false;
private boolean embedded = false;
private String invokedFrom = "";
private final Properties props = new Properties();
private final Exit exit;
- private final Logs logger;
- public Cli(Exit exit, Logs logger) {
+ public Cli(Exit exit) {
this.exit = exit;
- this.logger = logger;
}
boolean isDebugEnabled() {
displayVersionOnly = true;
} else if (asList("-e", "--errors").contains(arg)) {
- logger
+ LOG
.info("Option -e/--errors is no longer supported and will be ignored");
} else if (asList("-X", "--debug").contains(arg)) {
props.setProperty("sonar.verbose", "true");
debugEnabled = true;
- logger.setDebugEnabled(true);
-
+ var rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ rootLogger.setLevel(Level.DEBUG);
} else if (asList("-D", "--define").contains(arg)) {
return processProp(args, pos);
} else if ("--embedded".equals(arg)) {
- logger.info(
+ LOG.info(
"Option --embedded is deprecated and will be removed in a future release.");
embedded = true;
displayVersionOnly = false;
}
- private void appendPropertyTo(String arg, Properties props) {
+ private static void appendPropertyTo(String arg, Properties props) {
final String key;
final String value;
int j = arg.indexOf('=');
}
Object oldValue = props.setProperty(key, value);
if (oldValue != null) {
- logger.warn("Property '" + key + "' with value '" + oldValue + "' is "
- + "overridden with value '" + value + "'");
+ LOG.warn("Property '{}' with value '{}' is overridden with value '{}'", key, oldValue, value);
}
}
private void printErrorAndExit(String message) {
- logger.error(message);
+ LOG.error(message);
printUsage();
exit.exit(Exit.INTERNAL_ERROR);
}
- private void printUsage() {
- logger.info("");
- logger.info("usage: sonar-scanner [options]");
- logger.info("");
- logger.info("Options:");
- logger.info(" -D,--define <arg> Define property");
- logger.info(" -h,--help Display help information");
- logger.info(" -v,--version Display version information");
- logger.info(" -X,--debug Produce execution debug output");
+ private static void printUsage() {
+ System.out.println();
+ System.out.println("usage: sonar-scanner [options]");
+ System.out.println();
+ System.out.println("Options:");
+ System.out.println(" -D,--define <arg> Define property");
+ System.out.println(" -h,--help Display help information");
+ System.out.println(" -v,--version Display version information");
+ System.out.println(" -X,--debug Produce execution debug output");
}
}
import java.util.Map;
import java.util.Properties;
import javax.annotation.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonarsource.scanner.lib.EnvironmentConfig;
class Conf {
+ private static final Logger LOG = LoggerFactory.getLogger(Conf.class);
+
private static final String SCANNER_HOME = "scanner.home";
private static final String SCANNER_SETTINGS = "scanner.settings";
private static final String PROJECT_HOME = "project.home";
private static final String BOOTSTRAP_START_TIME = "sonar.scanner.bootstrapStartTime";
private final Cli cli;
- private final Logs logger;
private final Map<String, String> env;
private final long startTimeMs;
- Conf(Cli cli, Logs logger, Map<String, String> env) {
+ Conf(Cli cli, Map<String, String> env) {
this.cli = cli;
- this.logger = logger;
this.env = env;
this.startTimeMs = System.currentTimeMillis();
}
}
private Map<String, String> loadEnvironmentProperties() {
- return EnvironmentConfig.load(logger.getLogOutputAdapter());
+ return EnvironmentConfig.load(new Slf4jLogOutput());
}
private Properties loadGlobalProperties() {
Path settingsFile = locatePropertiesFile(knownPropsAtThatPoint, SCANNER_HOME, "conf/sonar-scanner.properties",
SCANNER_SETTINGS);
if (settingsFile != null && Files.isRegularFile(settingsFile)) {
- logger.info("Scanner configuration file: " + settingsFile);
+ LOG.info("Scanner configuration file: {}", settingsFile);
return toProperties(settingsFile);
}
- logger.info("Scanner configuration file: NONE");
+ LOG.info("Scanner configuration file: NONE");
return new Properties();
}
Path defaultRootSettingsFile = getRootProjectBaseDir(knownPropsAtThatPoint).resolve(SONAR_PROJECT_PROPERTIES_FILENAME);
Path rootSettingsFile = locatePropertiesFile(defaultRootSettingsFile, knownPropsAtThatPoint, PROJECT_SETTINGS);
if (rootSettingsFile != null && Files.isRegularFile(rootSettingsFile)) {
- logger.info("Project root configuration file: " + rootSettingsFile);
+ LOG.info("Project root configuration file: {}", rootSettingsFile);
rootProps.putAll(toProperties(rootSettingsFile));
} else {
- logger.info("Project root configuration file: NONE");
+ LOG.info("Project root configuration file: NONE");
}
Properties projectProps = new Properties();
/**
* Transforms a comma-separated list String property in to a array of
* trimmed strings.
- *
+ * <p>
* This works even if they are separated by whitespace characters (space
* char, EOL, ...)
- *
*/
static String[] getListFromProperty(Properties properties, String key) {
String value = properties.getProperty(key, "").trim();
}
String[] values = value.split(",");
List<String> trimmedValues = new ArrayList<>();
- for (int i = 0; i < values.length; i++) {
- String trimmedValue = values[i].trim();
+ for (String s : values) {
+ String trimmedValue = s.trim();
if (!trimmedValue.isEmpty()) {
trimmedValues.add(trimmedValue);
}
}
- return trimmedValues.toArray(new String[trimmedValues.size()]);
+ return trimmedValues.toArray(new String[0]);
}
}
+++ /dev/null
-/*
- * SonarScanner CLI
- * Copyright (C) 2011-2024 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.sonarsource.scanner.cli;
-
-import java.io.PrintStream;
-import java.time.LocalTime;
-import java.time.format.DateTimeFormatter;
-import org.sonarsource.scanner.lib.LogOutput;
-
-public class Logs {
- private DateTimeFormatter timeFormatter;
- private boolean debugEnabled = false;
- private PrintStream stdOut;
- private PrintStream stdErr;
-
- public Logs(PrintStream stdOut, PrintStream stdErr) {
- this.stdErr = stdErr;
- this.stdOut = stdOut;
- this.timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
- }
-
- public void setDebugEnabled(boolean debugEnabled) {
- this.debugEnabled = debugEnabled;
- }
-
- public boolean isDebugEnabled() {
- return debugEnabled;
- }
-
- public void debug(String message) {
- if (isDebugEnabled()) {
- LocalTime currentTime = LocalTime.now();
- String timestamp = currentTime.format(timeFormatter);
- stdOut.println(timestamp + " DEBUG: " + message);
- }
- }
-
- public void info(String message) {
- print(stdOut, "INFO: " + message);
- }
-
- public void warn(String message) {
- print(stdOut, "WARN: " + message);
- }
-
- public void error(String message) {
- print(stdErr, "ERROR: " + message);
- }
-
- public void error(String message, Throwable t) {
- print(stdErr, "ERROR: " + message);
- t.printStackTrace(stdErr);
- }
-
- private void print(PrintStream stream, String msg) {
- if (debugEnabled) {
- LocalTime currentTime = LocalTime.now();
- String timestamp = currentTime.format(timeFormatter);
- stream.println(timestamp + " " + msg);
- } else {
- stream.println(msg);
- }
- }
-
- /**
- * Adapter for the scanner library.
- */
- public LogOutput getLogOutputAdapter() {
- return new LogOutputAdapter(this);
- }
-
- static class LogOutputAdapter implements LogOutput {
- private final Logs logs;
-
- public LogOutputAdapter(Logs logs) {
- this.logs = logs;
- }
-
- @Override
- public void log(String formattedMessage, Level level) {
- switch (level) {
- case TRACE, DEBUG:
- logs.debug(formattedMessage);
- break;
- case ERROR:
- logs.error(formattedMessage);
- break;
- case WARN:
- logs.warn(formattedMessage);
- break;
- case INFO:
- default:
- logs.info(formattedMessage);
- }
- }
- }
-}
*/
package org.sonarsource.scanner.cli;
+import ch.qos.logback.classic.Level;
import java.util.Map;
import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonarsource.scanner.lib.ScannerEngineBootstrapper;
import org.sonarsource.scanner.lib.ScannerEngineFacade;
import org.sonarsource.scanner.lib.ScannerProperties;
* @since 1.0
*/
public class Main {
+ private static final Logger LOG = LoggerFactory.getLogger(Main.class);
+
private static final String SEPARATOR = "------------------------------------------------------------------------";
private final Exit exit;
private final Cli cli;
private final Conf conf;
private ScannerEngineBootstrapper scannerEngineBootstrapper;
private final ScannerEngineBootstrapperFactory bootstrapperFactory;
- private final Logs logger;
- Main(Exit exit, Cli cli, Conf conf, ScannerEngineBootstrapperFactory bootstrapperFactory, Logs logger) {
+ Main(Exit exit, Cli cli, Conf conf, ScannerEngineBootstrapperFactory bootstrapperFactory) {
this.exit = exit;
this.cli = cli;
this.conf = conf;
this.bootstrapperFactory = bootstrapperFactory;
- this.logger = logger;
}
public static void main(String[] args) {
- Logs logs = new Logs(System.out, System.err);
Exit exit = new Exit();
- Cli cli = new Cli(exit, logs).parse(args);
- Main main = new Main(exit, cli, new Conf(cli, logs, System.getenv()), new ScannerEngineBootstrapperFactory(logs), logs);
+ Cli cli = new Cli(exit).parse(args);
+ Main main = new Main(exit, cli, new Conf(cli, System.getenv()), new ScannerEngineBootstrapperFactory());
main.analyze();
}
void analyze() {
- Stats stats = new Stats(logger).start();
+ Stats stats = new Stats().start();
int status = Exit.INTERNAL_ERROR;
try {
}
}
- private void logServerType(ScannerEngineFacade engine) {
+ private static void logServerType(ScannerEngineFacade engine) {
if (engine.isSonarCloud()) {
- logger.info("Communicating with SonarCloud");
+ LOG.info("Communicating with SonarCloud");
} else {
String serverVersion = engine.getServerVersion();
- logger.info(String.format("Communicating with SonarQube Server %s", serverVersion));
+ LOG.info("Communicating with SonarQube Server {}", serverVersion);
}
}
private void checkSkip(Properties properties) {
if ("true".equalsIgnoreCase(properties.getProperty(ScannerProperties.SKIP))) {
- logger.info("SonarScanner CLI analysis skipped");
+ LOG.info("SonarScanner CLI analysis skipped");
exit.exit(Exit.SUCCESS);
}
}
private void init(Properties p) {
- SystemInfo.print(logger);
+ SystemInfo.print();
if (cli.isDisplayVersionOnly()) {
exit.exit(Exit.SUCCESS);
}
scannerEngineBootstrapper = bootstrapperFactory.create(p, cli.getInvokedFrom());
}
- private void configureLogging(Properties props) {
+ private static void configureLogging(Properties props) {
if ("true".equals(props.getProperty("sonar.verbose"))
|| "DEBUG".equalsIgnoreCase(props.getProperty("sonar.log.level"))
|| "TRACE".equalsIgnoreCase(props.getProperty("sonar.log.level"))) {
- logger.setDebugEnabled(true);
+ var rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ rootLogger.setLevel(Level.DEBUG);
}
}
- private void displayExecutionResult(Stats stats, String resultMsg) {
- logger.info(SEPARATOR);
- logger.info("EXECUTION " + resultMsg);
- logger.info(SEPARATOR);
+ private static void displayExecutionResult(Stats stats, String resultMsg) {
+ LOG.info(SEPARATOR);
+ LOG.info("EXECUTION {}", resultMsg);
+ LOG.info(SEPARATOR);
stats.stop();
- logger.info(SEPARATOR);
+ LOG.info(SEPARATOR);
}
private void showError(String message, Throwable e, boolean debug) {
if (debug || !isUserError(e)) {
- logger.error(message, e);
+ LOG.error(message, e);
} else {
- logger.error(message);
- logger.error(e.getMessage());
+ LOG.error(message);
+ LOG.error(e.getMessage());
String previousMsg = "";
for (Throwable cause = e.getCause(); cause != null
&& cause.getMessage() != null
&& !cause.getMessage().equals(previousMsg); cause = cause.getCause()) {
- logger.error("Caused by: " + cause.getMessage());
+ LOG.error("Caused by: {}", cause.getMessage());
previousMsg = cause.getMessage();
}
}
if (!cli.isDebugEnabled()) {
- logger.error("");
+ LOG.error("");
suggestDebugMode();
}
}
private void suggestDebugMode() {
if (!cli.isEmbedded()) {
- logger.error("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
+ LOG.error("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
}
}
private final Properties props;
private final Properties resolved;
private final List<String> queue;
- private Map<String, String> env;
+ private final Map<String, String> env;
public PropertyResolver(Properties props, Map<String, String> env) {
this.props = props;
}
Matcher m = placeholderPattern.matcher(propValue);
- StringBuffer sb = new StringBuffer();
+ var sb = new StringBuilder();
while (m.find()) {
String varName = (null == m.group(1)) ? m.group(2) : m.group(1);
class ScannerEngineBootstrapperFactory {
- private final Logs logger;
-
- public ScannerEngineBootstrapperFactory(Logs logger) {
- this.logger = logger;
- }
-
ScannerEngineBootstrapper create(Properties props, String isInvokedFrom) {
String appName = "ScannerCLI";
String appVersion = ScannerVersion.version();
}
ScannerEngineBootstrapper newScannerEngineBootstrapper(String appName, String appVersion) {
- return ScannerEngineBootstrapper.create(appName, appVersion, logger.getLogOutputAdapter());
+ return ScannerEngineBootstrapper.create(appName, appVersion, new Slf4jLogOutput());
}
*/
package org.sonarsource.scanner.cli;
+import java.nio.charset.StandardCharsets;
import java.util.Scanner;
+import static java.util.Objects.requireNonNull;
+
public enum ScannerVersion {
INSTANCE;
- private String version;
+ private final String version;
ScannerVersion() {
- try (Scanner scanner = new Scanner(getClass().getResourceAsStream("/version.txt"), "UTF-8")) {
+ try (Scanner scanner = new Scanner(requireNonNull(getClass().getResourceAsStream("/version.txt")), StandardCharsets.UTF_8)) {
this.version = scanner.next();
}
}
--- /dev/null
+/*
+ * SonarScanner CLI
+ * Copyright (C) 2011-2024 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.sonarsource.scanner.cli;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonarsource.scanner.lib.LogOutput;
+
+public class Slf4jLogOutput implements LogOutput {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Slf4jLogOutput.class);
+
+ @Override
+ public void log(String s, Level level) {
+ switch (level) {
+ case TRACE:
+ LOG.trace(s);
+ break;
+ case DEBUG:
+ LOG.debug(s);
+ break;
+ case INFO:
+ LOG.info(s);
+ break;
+ case WARN:
+ LOG.warn(s);
+ break;
+ case ERROR:
+ LOG.error(s);
+ break;
+ }
+ }
+}
*/
package org.sonarsource.scanner.cli;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
class Stats {
- private final Logs logger;
+ private static final Logger LOG = LoggerFactory.getLogger(Stats.class);
private long startTime;
- Stats(Logs logger) {
- this.logger = logger;
- }
-
Stats start() {
startTime = System.currentTimeMillis();
return this;
Stats stop() {
long stopTime = System.currentTimeMillis() - startTime;
- logger.info("Total time: " + formatTime(stopTime));
+ LOG.atInfo().addArgument(() -> formatTime(stopTime)).log("Total time: {}");
System.gc();
Runtime r = Runtime.getRuntime();
long mb = 1024L * 1024;
- logger.info("Final Memory: " + (r.totalMemory() - r.freeMemory()) / mb + "M/" + r.totalMemory() / mb + "M");
+ LOG.atInfo().addArgument((r.totalMemory() - r.freeMemory()) / mb + "M/" + r.totalMemory() / mb + "M").log("Final Memory: {}");
return this;
}
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
class SystemInfo {
+ private static final Logger LOG = LoggerFactory.getLogger(SystemInfo.class);
+
private static final Set<String> SENSITIVE_JVM_ARGUMENTS = Set.of(
- "-Dsonar.login",
- "-Dsonar.password",
- "-Dsonar.token");
+ "-Dsonar.login",
+ "-Dsonar.password",
+ "-Dsonar.token");
private static final Pattern PATTERN_ARGUMENT_SEPARATOR = Pattern.compile("\\s+");
private static System2 system = new System2();
SystemInfo.system = system;
}
- static void print(Logs logger) {
- logger.info("SonarScanner CLI " + ScannerVersion.version());
- logger.info(java());
- logger.info(os());
+ static void print() {
+ LOG.info("SonarScanner CLI {}", ScannerVersion.version());
+ LOG.atInfo().log(SystemInfo::java);
+ LOG.atInfo().log(SystemInfo::os);
String scannerOpts = system.getenv("SONAR_SCANNER_OPTS");
if (scannerOpts != null) {
- logger.info("SONAR_SCANNER_OPTS=" + redactSensitiveArguments(scannerOpts));
+ LOG.atInfo().addArgument(() -> redactSensitiveArguments(scannerOpts)).log("SONAR_SCANNER_OPTS={}");
}
}
}
static String os() {
- StringBuilder sb = new StringBuilder();
- sb
- .append(system.getProperty("os.name"))
- .append(" ")
- .append(system.getProperty("os.version"))
- .append(" ")
- .append(system.getProperty("os.arch"));
- return sb.toString();
+ return system.getProperty("os.name")
+ + " "
+ + system.getProperty("os.version")
+ + " "
+ + system.getProperty("os.arch");
}
static class System2 {
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration>
+
+<configuration scan="false">
+ <import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
+ <import class="ch.qos.logback.core.ConsoleAppender"/>
+
+ <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
+ <target>System.err</target>
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>ERROR</level>
+ </filter>
+ <encoder class="PatternLayoutEncoder">
+ <pattern>%date{HH:mm:ss.SSS} %-5level %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <target>System.out</target>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>TRACE</level>
+ <onMatch>ACCEPT</onMatch>
+ </filter>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>DEBUG</level>
+ <onMatch>ACCEPT</onMatch>
+ </filter>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>INFO</level>
+ <onMatch>ACCEPT</onMatch>
+ </filter>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>WARN</level>
+ <onMatch>ACCEPT</onMatch>
+ </filter>
+ <filter class="ch.qos.logback.classic.filter.LevelFilter">
+ <level>ERROR</level>
+ <onMatch>DENY</onMatch>
+ </filter>
+ <encoder class="PatternLayoutEncoder">
+ <pattern>%date{HH:mm:ss.SSS} %-5level %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="STDOUT"/>
+ <appender-ref ref="STDERR"/>
+ </root>
+
+</configuration>
*/
package org.sonarsource.scanner.cli;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.slf4j.event.Level;
+import testutils.LogTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.verify;
class CliTest {
+
+ @RegisterExtension
+ LogTester logTester = new LogTester();
+
private final Exit exit = mock(Exit.class);
- private Logs logs = new Logs(System.out, System.err);
- private Cli cli = new Cli(exit, logs);
+ private Cli cli = new Cli(exit);
@Test
void should_parse_empty_arguments() {
@Test
void should_warn_on_duplicate_properties() {
- logs = mock(Logs.class);
- cli = new Cli(exit, logs);
+ cli = new Cli(exit);
cli.parse(new String[]{"-D", "foo=bar", "--define", "foo=baz"});
- verify(logs).warn("Property 'foo' with value 'bar' is overridden with value 'baz'");
+ assertThat(logTester.logs(Level.WARN)).contains("Property 'foo' with value 'bar' is overridden with value 'baz'");
}
@Test
void should_fail_on_missing_prop() {
- logs = mock(Logs.class);
- cli = new Cli(exit, logs);
+ cli = new Cli(exit);
cli.parse(new String[]{"-D"});
- verify(logs).error("Missing argument for option -D/--define");
+ assertThat(logTester.logs(Level.ERROR)).contains("Missing argument for option -D/--define");
verify(exit).exit(Exit.INTERNAL_ERROR);
}
assertThat(cli.properties().get("sonar.verbose")).isNull();
}
- @Test
- void should_show_usage() {
- logs = mock(Logs.class);
- cli = new Cli(exit, logs);
- cli.parse(new String[]{"-h"});
- verify(logs).info("usage: sonar-scanner [options]");
+ @ParameterizedTest
+ @ValueSource(strings = {"-h", "--help"})
+ void should_show_usage(String arg) {
+ var baos = parseAndCaptureStdOut(arg);
+ assertThat(baos.toString()).contains("usage: sonar-scanner [options]");
verify(exit).exit(Exit.SUCCESS);
}
- @Test
- void should_show_usage_full() {
- logs = mock(Logs.class);
- cli = new Cli(exit, logs);
- cli.parse(new String[]{"--help"});
- verify(logs).info("usage: sonar-scanner [options]");
- verify(exit).exit(Exit.SUCCESS);
+ private ByteArrayOutputStream parseAndCaptureStdOut(String arg) {
+ var baos = new ByteArrayOutputStream();
+ var savedOut = System.out;
+ try {
+ System.setOut(new PrintStream(baos));
+ cli = new Cli(exit);
+ cli.parse(new String[]{arg});
+ } finally {
+ System.setOut(savedOut);
+ }
+ return baos;
}
@Test
void should_show_usage_on_bad_syntax() {
- logs = mock(Logs.class);
- cli = new Cli(exit, logs);
- cli.parse(new String[]{"-w"});
- verify(logs).error("Unrecognized option: -w");
- verify(logs).info("usage: sonar-scanner [options]");
+ var baos = parseAndCaptureStdOut("-w");
+ assertThat(baos.toString()).contains("usage: sonar-scanner [options]");
+ assertThat(logTester.logs(Level.ERROR)).contains("Unrecognized option: -w");
verify(exit).exit(Exit.INTERNAL_ERROR);
}
private final Map<String, String> env = new HashMap<>();
private final Properties args = new Properties();
- private final Logs logs = new Logs(System.out, System.err);
private final Cli cli = mock(Cli.class);
- private final Conf conf = new Conf(cli, logs, env);
+ private final Conf conf = new Conf(cli, env);
@BeforeEach
void initConf() {
+++ /dev/null
-/*
- * SonarScanner CLI
- * Copyright (C) 2011-2024 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.sonarsource.scanner.cli;
-
-import java.io.PrintStream;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.sonarsource.scanner.lib.LogOutput;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-class LogsTest {
- @Mock
- private PrintStream stdOut;
-
- @Mock
- private PrintStream stdErr;
-
- private Logs logs;
-
- @BeforeEach
- void setUp() {
- MockitoAnnotations.initMocks(this);
- logs = new Logs(stdOut, stdErr);
- }
-
- @Test
- void testInfo() {
- logs.info("info");
- verify(stdOut).println("INFO: info");
- verifyNoMoreInteractions(stdOut, stdErr);
- }
-
- @Test
- void testWarn() {
- logs.warn("warn");
- verify(stdOut).println("WARN: warn");
- verifyNoMoreInteractions(stdOut, stdErr);
- }
-
- @Test
- void testWarnWithTimestamp() {
- logs.setDebugEnabled(true);
- logs.warn("warn");
- verify(stdOut).println(ArgumentMatchers.matches("\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d WARN: warn"));
- verifyNoMoreInteractions(stdOut, stdErr);
- }
-
- @Test
- void testError() {
- Exception e = new NullPointerException("exception");
- logs.error("error1");
- verify(stdErr).println("ERROR: error1");
-
- logs.error("error2", e);
- verify(stdErr).println("ERROR: error2");
- verify(stdErr).println(e);
- // other interactions to print the exception..
- }
-
- @Test
- void testDebug() {
- logs.setDebugEnabled(true);
-
- logs.debug("debug");
- verify(stdOut).println(ArgumentMatchers.matches("\\d\\d:\\d\\d:\\d\\d.\\d\\d\\d DEBUG: debug$"));
-
- logs.setDebugEnabled(false);
- logs.debug("debug");
- verifyNoMoreInteractions(stdOut, stdErr);
- }
-
- @Test
- void should_forward_logs() {
- var mockedLogs = mock(Logs.class);
- var logOutput = new Logs.LogOutputAdapter(mockedLogs);
-
- String msg = "test";
-
- logOutput.log(msg, LogOutput.Level.DEBUG);
- verify(mockedLogs).debug(msg);
- verifyNoMoreInteractions(mockedLogs);
- reset(mockedLogs);
-
- logOutput.log(msg, LogOutput.Level.INFO);
- verify(mockedLogs).info(msg);
- verifyNoMoreInteractions(mockedLogs);
- reset(mockedLogs);
-
- logOutput.log(msg, LogOutput.Level.ERROR);
- verify(mockedLogs).error(msg);
- verifyNoMoreInteractions(mockedLogs);
- reset(mockedLogs);
-
- logOutput.log(msg, LogOutput.Level.WARN);
- verify(mockedLogs).warn(msg);
- verifyNoMoreInteractions(mockedLogs);
- reset(mockedLogs);
-
- logOutput.log(msg, LogOutput.Level.TRACE);
- verify(mockedLogs).debug(msg);
- verifyNoMoreInteractions(mockedLogs);
- reset(mockedLogs);
- }
-}
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
-import org.mockito.Mock;
import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
import org.sonar.api.utils.MessageException;
import org.sonarsource.scanner.lib.ScannerEngineBootstrapper;
import org.sonarsource.scanner.lib.ScannerEngineFacade;
import org.sonarsource.scanner.lib.ScannerProperties;
+import testutils.LogTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-public class MainTest {
-
- @Mock
- private Exit exit;
- @Mock
- private Cli cli;
- @Mock
- private Conf conf;
- @Mock
- private Properties properties;
- @Mock
- private ScannerEngineBootstrapperFactory scannerEngineBootstrapperFactory;
- @Mock
- private ScannerEngineBootstrapper bootstrapper;
- @Mock
- private ScannerEngineFacade engine;
- @Mock
- private Logs logs;
+class MainTest {
+
+ @RegisterExtension
+ LogTester logTester = new LogTester();
+
+ private final Exit exit = mock();
+ private final Cli cli = mock();
+ private final Conf conf = mock();
+ private final Properties properties = mock();
+ private final ScannerEngineBootstrapperFactory scannerEngineBootstrapperFactory = mock();
+ private final ScannerEngineBootstrapper bootstrapper = mock();
+ private final ScannerEngineFacade engine = mock();
@BeforeEach
void setUp() {
- MockitoAnnotations.initMocks(this);
when(scannerEngineBootstrapperFactory.create(any(Properties.class), any(String.class))).thenReturn(bootstrapper);
when(bootstrapper.bootstrap()).thenReturn(engine);
when(conf.properties()).thenReturn(properties);
@Test
void should_execute_scanner_engine() {
when(cli.getInvokedFrom()).thenReturn("");
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
verify(exit).exit(Exit.SUCCESS);
doThrow(e).when(engine).analyze(any());
when(cli.getInvokedFrom()).thenReturn("");
when(cli.isDebugEnabled()).thenReturn(true);
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
verify(exit).exit(Exit.INTERNAL_ERROR);
- verify(logs).error("Error during SonarScanner CLI execution", e);
+ assertThat(logTester.logs(Level.ERROR)).contains("Error during SonarScanner CLI execution");
}
@Test
when(cli.getInvokedFrom()).thenReturn("");
when(cli.isDebugEnabled()).thenReturn(true);
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
verify(bootstrapper).bootstrap();
verify(engine, never()).analyze(any());
verify(exit).exit(Exit.INTERNAL_ERROR);
- verify(logs).error("Error during SonarScanner CLI execution", e);
+ assertThat(logTester.logs(Level.ERROR)).contains("Error during SonarScanner CLI execution");
}
@Test
Exception e = createException(false);
testException(e, false, false, Exit.INTERNAL_ERROR);
- verify(logs).error("Error during SonarScanner CLI execution", e);
- verify(logs).error("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
+ assertThat(logTester.logs(Level.ERROR)).contains("Error during SonarScanner CLI execution");
+ assertThat(logTester.logs(Level.ERROR)).contains("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
}
@Test
Exception e = createException(true);
testException(e, false, false, Exit.USER_ERROR);
- verify(logs, times(5)).error(anyString());
- verify(logs).error("Error during SonarScanner CLI execution");
- verify(logs).error("my message");
- verify(logs).error("Caused by: A functional cause");
- verify(logs).error("");
- verify(logs).error("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("Error during SonarScanner CLI execution",
+ "my message",
+ "Caused by: A functional cause",
+ "",
+ "Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
}
@Test
Exception e = createException(true);
testException(e, false, true, Exit.USER_ERROR);
- verify(logs, times(4)).error(anyString());
- verify(logs).error("Error during SonarScanner CLI execution");
- verify(logs).error("my message");
- verify(logs).error("Caused by: A functional cause");
- verify(logs).error("");
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("Error during SonarScanner CLI execution",
+ "my message",
+ "Caused by: A functional cause",
+ "");
}
@Test
Exception e = createException(true);
testException(e, true, false, Exit.USER_ERROR);
- verify(logs, times(1)).error(anyString(), any(Throwable.class));
- verify(logs).error("Error during SonarScanner CLI execution", e);
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("Error during SonarScanner CLI execution");
}
@Test
Exception e = createException(true);
testException(e, true, true, Exit.USER_ERROR);
- verify(logs, times(1)).error(anyString(), any(Throwable.class));
- verify(logs).error("Error during SonarScanner CLI execution", e);
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("Error during SonarScanner CLI execution");
}
@Test
Exception e = createException(false);
testException(e, true, false, Exit.INTERNAL_ERROR);
- verify(logs).error("Error during SonarScanner CLI execution", e);
- verify(logs, never()).error("Re-run SonarScanner CLI using the -X switch to enable full debug logging.");
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("Error during SonarScanner CLI execution");
}
private void testException(Exception e, boolean debugEnabled, boolean isEmbedded, int expectedExitCode) {
when(scannerEngineBootstrapperFactory.create(any(Properties.class), any(String.class))).thenReturn(bootstrapper);
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
verify(exit).exit(expectedExitCode);
when(cli.getInvokedFrom()).thenReturn("");
when(conf.properties()).thenReturn(p);
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
InOrder inOrder = Mockito.inOrder(exit, scannerEngineBootstrapperFactory);
when(conf.properties()).thenReturn(p);
when(cli.getInvokedFrom()).thenReturn("");
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
- verify(logs).info("SonarScanner CLI analysis skipped");
+ assertThat(logTester.logs(Level.INFO)).contains("SonarScanner CLI analysis skipped");
InOrder inOrder = Mockito.inOrder(exit, scannerEngineBootstrapperFactory);
inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
when(cli.getInvokedFrom()).thenReturn("");
when(conf.properties()).thenReturn(p);
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
- verify(logs).info("Communicating with SonarQube Server 5.5");
+ assertThat(logTester.logs(Level.INFO)).contains("Communicating with SonarQube Server 5.5");
}
@Test
when(conf.properties()).thenReturn(p);
when(cli.getInvokedFrom()).thenReturn("");
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
- verify(logs).info("Communicating with SonarCloud");
+ assertThat(logTester.logs(Level.INFO)).contains("Communicating with SonarCloud");
}
@Test
Properties actualProps = execute(propKey, propValue);
// Logger used for callback should have debug enabled
- verify(logs).setDebugEnabled(true);
+ assertThat(LoggerFactory.getLogger(getClass()).isDebugEnabled()).isTrue();
return actualProps;
}
when(conf.properties()).thenReturn(p);
when(cli.getInvokedFrom()).thenReturn("");
- Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory, logs);
+ Main main = new Main(exit, cli, conf, scannerEngineBootstrapperFactory);
main.analyze();
ArgumentCaptor<Properties> propertiesCapture = ArgumentCaptor.forClass(Properties.class);
class ScannerEngineBootstrapperFactoryTest {
private final Properties props = new Properties();
- private final Logs logs = mock(Logs.class);
- private final ScannerEngineBootstrapperFactory underTest = new ScannerEngineBootstrapperFactory(logs);
+ private final ScannerEngineBootstrapperFactory underTest = new ScannerEngineBootstrapperFactory();
@Test
void should_create_engine_bootstrapper_and_pass_app_and_properties() {
--- /dev/null
+/*
+ * SonarScanner CLI
+ * Copyright (C) 2011-2024 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.sonarsource.scanner.cli;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.event.Level;
+import org.sonarsource.scanner.lib.LogOutput;
+import testutils.LogTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class Slf4jLogOutputTest {
+
+ @RegisterExtension
+ LogTester logTester = new LogTester().setLevel(Level.TRACE);
+
+ @Test
+ void make_coverage_happy() {
+ var underTest = new Slf4jLogOutput();
+ underTest.log("trace", LogOutput.Level.TRACE);
+ underTest.log("debug", LogOutput.Level.DEBUG);
+ underTest.log("info", LogOutput.Level.INFO);
+ underTest.log("warn", LogOutput.Level.WARN);
+ underTest.log("error", LogOutput.Level.ERROR);
+
+ assertThat(logTester.logs(Level.TRACE)).containsOnly("trace");
+ assertThat(logTester.logs(Level.DEBUG)).containsOnly("debug");
+ assertThat(logTester.logs(Level.INFO)).containsOnly("info");
+ assertThat(logTester.logs(Level.WARN)).containsOnly("warn");
+ assertThat(logTester.logs(Level.ERROR)).containsOnly("error");
+ }
+
+}
*/
package org.sonarsource.scanner.cli;
-import java.io.PrintStream;
import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.event.Level;
+import testutils.LogTester;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
class StatsTest {
- private final PrintStream stdOut = mock(PrintStream.class);
- private final PrintStream stdErr = mock(PrintStream.class);
- private final Logs logs = new Logs(stdOut, stdErr);
+ @RegisterExtension
+ LogTester logTester = new LogTester();
@Test
void shouldPrintStats() {
- new Stats(logs).start().stop();
+ new Stats().start().stop();
- verify(stdOut).println(Mockito.contains("Total time: "));
- verify(stdOut).println(Mockito.contains("Final Memory: "));
+ assertThat(logTester.logs(Level.INFO)).hasSize(2);
+ assertThat(logTester.logs(Level.INFO).get(0)).startsWith("Total time: ");
+ assertThat(logTester.logs(Level.INFO).get(1)).startsWith("Final Memory: ");
}
@Test
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.event.Level;
import org.sonarsource.scanner.cli.SystemInfo.System2;
+import testutils.LogTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
class SystemInfoTest {
+ @RegisterExtension
+ LogTester logTester = new LogTester();
+
private final System2 mockSystem = mock(System2.class);
- private final Logs logs = mock(Logs.class);
@BeforeEach
void setUp() {
mockJava();
when(mockSystem.getenv("SONAR_SCANNER_OPTS")).thenReturn("arg");
- SystemInfo.print(logs);
+ SystemInfo.print();
verify(mockSystem).getProperty("java.version");
verify(mockSystem).getProperty("os.version");
verify(mockSystem).getenv("SONAR_SCANNER_OPTS");
- verify(logs, never()).info("SonarScanner null");
- verify(logs).info("SonarScanner CLI " + ScannerVersion.version());
- verify(logs).info("Java 1.9 oracle (64-bit)");
- verify(logs).info("linux 2.5 x64");
- verify(logs).info("SONAR_SCANNER_OPTS=arg");
- verifyNoMoreInteractions(logs);
+ assertThat(logTester.logs(Level.INFO))
+ .containsOnly("SonarScanner CLI " + ScannerVersion.version(), "Java 1.9 oracle (64-bit)", "linux 2.5 x64", "SONAR_SCANNER_OPTS=arg");
}
@Test
when(mockSystem.getenv("SONAR_SCANNER_OPTS"))
.thenReturn("-Dsonar.login=login -Dsonar.whatever=whatever -Dsonar.password=password -Dsonar.whatever2=whatever2 -Dsonar.token=token");
- SystemInfo.print(logs);
+ SystemInfo.print();
- verify(logs).info("SONAR_SCANNER_OPTS=-Dsonar.login=* -Dsonar.whatever=whatever -Dsonar.password=* -Dsonar.whatever2=whatever2 -Dsonar.token=*");
+ assertThat(logTester.logs(Level.INFO)).contains("SONAR_SCANNER_OPTS=-Dsonar.login=* -Dsonar.whatever=whatever -Dsonar.password=* -Dsonar.whatever2=whatever2 -Dsonar.token=*");
}
}
--- /dev/null
+/*
+ * SonarScanner CLI
+ * Copyright (C) 2011-2024 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 testutils;
+
+import ch.qos.logback.core.AppenderBase;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class ConcurrentListAppender<E> extends AppenderBase<E> {
+ public final Queue<E> list = new ConcurrentLinkedQueue<E>();
+
+ protected void append(E e) {
+ list.add(e);
+ }
+}
--- /dev/null
+/*
+ * SonarScanner CLI
+ * Copyright (C) 2011-2024 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 testutils;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import java.util.List;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.LoggerFactory;
+import org.slf4j.event.Level;
+
+public class LogTester implements BeforeEachCallback, AfterEachCallback {
+
+ private final ConcurrentListAppender<ILoggingEvent> listAppender = new ConcurrentListAppender<>();
+
+ public LogTester() {
+ setLevel(Level.INFO);
+ }
+
+ /**
+ * Change log level.
+ * By default, INFO logs are enabled when LogTester is started.
+ */
+ public LogTester setLevel(Level level) {
+ getRootLogger().setLevel(ch.qos.logback.classic.Level.fromLocationAwareLoggerInteger(level.toInt()));
+ return this;
+ }
+
+ private static ch.qos.logback.classic.Logger getRootLogger() {
+ return (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+ }
+
+ /**
+ * Logs in chronological order (item at index 0 is the oldest one)
+ */
+ public List<String> logs() {
+ return listAppender.list.stream().map(e -> (LoggingEvent) e)
+ .map(LoggingEvent::getFormattedMessage)
+ .toList();
+ }
+
+ /**
+ * Logs in chronological order (item at index 0 is the oldest one) for
+ * a given level
+ */
+ public List<String> logs(Level level) {
+ return listAppender.list.stream().map(e -> (LoggingEvent) e)
+ .filter(e -> e.getLevel().equals(ch.qos.logback.classic.Level.fromLocationAwareLoggerInteger(level.toInt())))
+ .map(LoggingEvent::getFormattedMessage)
+ .toList();
+ }
+
+ public LogTester clear() {
+ listAppender.list.clear();
+ return this;
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ getRootLogger().addAppender(listAppender);
+ listAppender.start();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ listAppender.stop();
+ listAppender.list.clear();
+ getRootLogger().detachAppender(listAppender);
+ // Reset the level for following-up test suites
+ setLevel(Level.INFO);
+ }
+}