Skip to content

Commit

Permalink
PLUGINAPI-40 LogTester doesn't intercept logged exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
dbmeneses committed Apr 24, 2023
1 parent 0a05dc0 commit 5d3cf65
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ public List<LogAndArguments> getLogs() {
.collect(Collectors.toList());
}


/**
* Logs with arguments in chronological order (item at index 0 is the oldest one) for
* a given level
Expand All @@ -152,7 +151,7 @@ public List<LogAndArguments> getLogs(LoggerLevel sonarLevel) {
}

private static LogAndArguments convert(LoggingEvent e) {
return new LogAndArguments(e.getFormattedMessage(), e.getMessage(), e.getArgumentArray());
return new LogAndArguments(e.getFormattedMessage(), e.getMessage(), e.getThrowableProxy(), e.getArgumentArray());
}

public G clear() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,29 @@
*/
package org.sonar.api.testfixtures.log;

import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import java.util.Arrays;
import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

public final class LogAndArguments {
private final String rawMsg;
private final Throwable throwable;
private final Object[] args;
private final String msg;

LogAndArguments(String msg, String rawMsg) {
LogAndArguments(String msg, String rawMsg, @Nullable IThrowableProxy t, Object... args) {
this.rawMsg = rawMsg;
this.msg = msg;
this.args = null;
}

LogAndArguments(String msg, String rawMsg, @Nullable Object arg1) {
this.rawMsg = rawMsg;
this.args = new Object[] {arg1};
this.msg = msg;
}

LogAndArguments(String msg, String rawMsg, @Nullable Object arg1, @Nullable Object arg2) {
this.rawMsg = rawMsg;
this.msg = msg;
this.args = new Object[] {arg1, arg2};
this.throwable = t instanceof ThrowableProxy ? ((ThrowableProxy) t).getThrowable() : null;
this.args = args;
}

LogAndArguments(String msg, String rawMsg, Object... args) {
this.rawMsg = rawMsg;
this.msg = msg;
this.args = args;
@CheckForNull
public Throwable getThrowable() {
return throwable;
}

public String getRawMsg() {
Expand All @@ -66,10 +58,12 @@ public String getFormattedMsg() {

@Override
public String toString() {
String throwableStr = throwable != null ? (throwable.getClass().getName() + ": " + throwable.getMessage()) : null;
return "LogAndArguments{" +
"rawMsg='" + rawMsg + '\'' +
", args=" + Arrays.toString(args) +
", msg='" + msg + '\'' +
", throwable='" + throwableStr + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Sonar Plugin API
* Copyright (C) 2009-2023 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.testfixtures.log;

import ch.qos.logback.classic.spi.ThrowableProxy;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class LogAndArgumentsTest {
@Test
public void toString_displays_null_throwable() {
LogAndArguments logAndArguments = new LogAndArguments("msg", "raw", null, "arg1");
assertThat(logAndArguments).hasToString("LogAndArguments{rawMsg='raw', args=[arg1], msg='msg', throwable='null'}");
}

@Test
public void toString_includes_all_fields() {
Throwable t = new IllegalStateException("tmsg");
ThrowableProxy proxy = new ThrowableProxy(t);
LogAndArguments logAndArguments = new LogAndArguments("msg", "raw", proxy, "arg1");
assertThat(logAndArguments).hasToString("LogAndArguments{rawMsg='raw', args=[arg1], msg='msg', throwable='java.lang.IllegalStateException: tmsg'}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,71 @@
package org.sonar.api.testfixtures.log;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.event.Level;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.api.utils.log.Loggers;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

public class LogTesterTest {
private final LogTester underTest = new LogTester();

LogTester underTest = new LogTester();
@Before
public void prepare() {
underTest.before();
}

@After
public void reset_level() {
underTest.after();
}

@Test
public void info_level_by_default() throws Throwable {
public void info_level_by_default() {
// when LogTester is used, then info logs are enabled by default
underTest.before();
assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.INFO);
}

// change
underTest.setLevel(LoggerLevel.DEBUG);
@Test
public void change_log_level() {
// change level
underTest.setLevel(Level.DEBUG);
assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.DEBUG);
}

// reset to initial level after execution of test
underTest.after();
assertThat(underTest.getLevel()).isEqualTo(LoggerLevel.INFO);
@Test
public void intercept_sonar_logs_with_throwables() {
Exception e = new IllegalStateException("exception msg");
Loggers.get("logger2").error("error1: {}", 42, e);
assertThat(underTest.getLogs()).extracting(LogAndArguments::getFormattedMsg, l -> l.getThrowable().getClass().getName(), l -> l.getThrowable().getMessage())
.containsOnly(tuple("error1: 42", "java.lang.IllegalStateException", "exception msg"));
}

@Test
public void intercept_logs() throws Throwable {
underTest.before();
public void intercept_slf4j_logs_with_throwables() {
Exception e = new IllegalStateException("exception msg");
org.slf4j.LoggerFactory.getLogger("logger1").error("error1: {}", 42, e);
assertThat(underTest.getLogs()).extracting(LogAndArguments::getFormattedMsg, l -> l.getThrowable().getClass().getName(), l -> l.getThrowable().getMessage())
.containsOnly(tuple("error1: 42", "java.lang.IllegalStateException", "exception msg"));
}

@Test
public void intercept_slf4j_debug_logs() {
org.slf4j.LoggerFactory.getLogger("logger1").debug("debug0: {}", 41);

underTest.setLevel(Level.DEBUG);
org.slf4j.LoggerFactory.getLogger("logger1").debug("debug1: {}", 42);
assertThat(underTest.getLogs()).extracting(LogAndArguments::getFormattedMsg).containsOnly("debug1: 42");
assertThat(underTest.getLogs(Level.DEBUG)).extracting(LogAndArguments::getFormattedMsg).containsOnly("debug1: 42");
}

@Test
public void intercept_sonar_logs() {
Loggers.get("logger1").info("an information");
Loggers.get("logger2").warn("warning: {}", 42);

Expand All @@ -60,30 +94,27 @@ public void intercept_logs() throws Throwable {
tuple("an information", "an information", null),
tuple("warning: {}", "warning: 42", List.of(42)));

assertThat(underTest.logs(LoggerLevel.ERROR)).isEmpty();
assertThat(underTest.getLogs(LoggerLevel.ERROR)).isEmpty();
assertThat(underTest.logs(Level.ERROR)).isEmpty();
assertThat(underTest.getLogs(Level.ERROR)).isEmpty();

assertThat(underTest.logs(LoggerLevel.INFO)).containsOnly("an information");
assertThat(underTest.getLogs(LoggerLevel.INFO)).extracting(LogAndArguments::getRawMsg, LogAndArguments::getFormattedMsg, l -> l.getArgs().map(List::of).orElse(null))
assertThat(underTest.logs(Level.INFO)).containsOnly("an information");
assertThat(underTest.getLogs(Level.INFO)).extracting(LogAndArguments::getRawMsg, LogAndArguments::getFormattedMsg, l -> l.getArgs().map(List::of).orElse(null))
.containsExactly(
tuple("an information", "an information", null));

assertThat(underTest.logs(LoggerLevel.WARN)).containsOnly("warning: 42");
assertThat(underTest.getLogs(LoggerLevel.WARN)).extracting(LogAndArguments::getRawMsg, LogAndArguments::getFormattedMsg, l -> l.getArgs().map(List::of).orElse(null))
assertThat(underTest.logs(Level.WARN)).containsOnly("warning: 42");
assertThat(underTest.getLogs(Level.WARN)).extracting(LogAndArguments::getRawMsg, LogAndArguments::getFormattedMsg, l -> l.getArgs().map(List::of).orElse(null))
.containsExactly(
tuple("warning: {}", "warning: 42", List.of(42)));

underTest.clear();
assertThat(underTest.logs()).isEmpty();
assertThat(underTest.logs(LoggerLevel.INFO)).isEmpty();

underTest.after();
assertThat(underTest.logs(Level.INFO)).isEmpty();
}

@Test
public void use_suppliers() throws Throwable {
public void use_suppliers() {
// when LogTester is used, then info logs are enabled by default
underTest.before();
AtomicBoolean touchedTrace = new AtomicBoolean();
AtomicBoolean touchedDebug = new AtomicBoolean();
Loggers.get("logger1").trace(() -> {
Expand All @@ -100,7 +131,7 @@ public void use_suppliers() throws Throwable {
assertThat(touchedDebug.get()).isFalse();

// change level to DEBUG
underTest.setLevel(LoggerLevel.DEBUG);
underTest.setLevel(Level.DEBUG);
Loggers.get("logger1").trace(() -> {
touchedTrace.set(true);
return "a trace information";
Expand All @@ -117,7 +148,7 @@ public void use_suppliers() throws Throwable {
underTest.clear();

// change level to TRACE
underTest.setLevel(LoggerLevel.TRACE);
underTest.setLevel(Level.TRACE);
Loggers.get("logger1").trace(() -> {
touchedTrace.set(true);
return "a trace information";
Expand Down

0 comments on commit 5d3cf65

Please sign in to comment.