diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2024-04-10 16:01:48 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-04-15 20:02:44 +0000 |
commit | 4b060fef8076c324bc0c0cfe4f64804753c91e29 (patch) | |
tree | f02d1dcae8de61b40fbf4b9431c737110c12c1dc | |
parent | 60fee355ef174e184b5c3a477a3c81e95302c754 (diff) | |
download | sonarqube-4b060fef8076c324bc0c0cfe4f64804753c91e29.tar.gz sonarqube-4b060fef8076c324bc0c0cfe4f64804753c91e29.zip |
SONAR-22036 Add a main to the scanner engine
12 files changed, 541 insertions, 25 deletions
diff --git a/build.gradle b/build.gradle index 8a3216ffb65..2ae30f04359 100644 --- a/build.gradle +++ b/build.gradle @@ -461,6 +461,7 @@ subprojects { dependency 'javax.el:javax.el-api:3.0.0' dependency 'org.glassfish:jakarta.el:3.0.4' dependency 'org.kohsuke:github-api:1.318' + dependency 'org.wiremock:wiremock-standalone:3.5.2' // please keep this list alphabetically ordered } diff --git a/sonar-scanner-engine-shaded/build.gradle b/sonar-scanner-engine-shaded/build.gradle index 77df9b346b9..dcb53b4294d 100644 --- a/sonar-scanner-engine-shaded/build.gradle +++ b/sonar-scanner-engine-shaded/build.gradle @@ -9,3 +9,11 @@ apply plugin: 'com.github.johnrengelman.shadow' dependencies { api project(':sonar-scanner-engine') } + +jar { + manifest { + attributes( + 'Main-Class' : "org.sonar.scanner.bootstrap.ScannerMain" + ) + } +} diff --git a/sonar-scanner-engine/build.gradle b/sonar-scanner-engine/build.gradle index de8669b288d..af2349194a5 100644 --- a/sonar-scanner-engine/build.gradle +++ b/sonar-scanner-engine/build.gradle @@ -61,8 +61,9 @@ dependencies { testImplementation 'org.hamcrest:hamcrest-core' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' - api 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures' + testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures' testImplementation project(':plugins:sonar-xoo-plugin') + testImplementation 'org.wiremock:wiremock-standalone' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java new file mode 100644 index 00000000000..8320b9dc0e7 --- /dev/null +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/bootstrap/BootstrapMediumIT.java @@ -0,0 +1,169 @@ +/* + * SonarQube + * Copyright (C) 2009-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.sonar.scanner.mediumtest.bootstrap; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import java.io.ByteArrayInputStream; +import java.nio.file.Path; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.event.Level; +import org.sonar.api.testfixtures.log.LogTesterJUnit5; +import org.sonar.scanner.bootstrap.ScannerMain; +import org.sonarqube.ws.Ce; +import org.sonarqube.ws.Qualityprofiles; +import org.sonarqube.ws.Rules; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.okJson; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static testutils.TestUtils.protobufBody; + +class BootstrapMediumIT { + + public static final String PROJECT_KEY = "my-project"; + public static final String QPROFILE_KEY = "profile123"; + @RegisterExtension + LogTesterJUnit5 logTester = new LogTesterJUnit5(); + + @RegisterExtension + static WireMockExtension sonarqube = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .build(); + + @BeforeEach + void mockBareMinimalServerEndpoints() { + sonarqube.stubFor(get("/api/plugins/installed") + .willReturn(okJson("{\n" + + " \"plugins\": []\n" + + "}"))); + + sonarqube.stubFor(get("/api/qualityprofiles/search.protobuf?project=" + PROJECT_KEY) + .willReturn(aResponse() + .withResponseBody(protobufBody(Qualityprofiles.SearchWsResponse.newBuilder() + .addProfiles(Qualityprofiles.SearchWsResponse.QualityProfile.newBuilder() + .setKey(QPROFILE_KEY) + .setName("My Profile") + .setRulesUpdatedAt("2021-01-01T00:00:00+0000") + .build()) + .build())))); + + sonarqube.stubFor(get("/api/rules/list.protobuf?qprofile=" + QPROFILE_KEY + "&ps=500&p=1") + .willReturn(aResponse() + .withResponseBody(protobufBody(Rules.ListResponse.newBuilder() + .build())))); + + sonarqube.stubFor(get("/api/languages/list") + .willReturn(okJson("{\n" + + " \"languages\": []\n" + + "}"))); + + sonarqube.stubFor(get("/api/metrics/search?ps=500&p=1") + .willReturn(okJson("{\n" + + " \"metrics\": [],\n" + + " \"total\": 0,\n" + + " \"p\": 1,\n" + + " \"ps\": 100" + + "}"))); + + sonarqube.stubFor(post("/api/ce/submit?projectKey=" + PROJECT_KEY) + .willReturn(aResponse() + .withResponseBody(protobufBody(Ce.SubmitResponse.newBuilder() + .build())))); + } + + @Test + void should_fail_if_invalid_json_input() { + var in = new ByteArrayInputStream("}".getBytes()); + var e = assertThrows(IllegalArgumentException.class, () -> ScannerMain.run(in)); + assertThat(e).hasMessage("Failed to parse JSON input"); + + assertThat(logTester.logs()).contains("Starting SonarScanner Engine..."); + } + + @Test + void should_warn_if_null_property_key() { + try { + ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"value\": \"aValueWithoutKey\"}]}".getBytes())); + } catch (Exception ignored) { + } + assertThat(logTester.logs()).contains("Ignoring property with null key: 'aValueWithoutKey'"); + } + + @Test + void should_warn_if_duplicate_property_keys() { + try { + ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"key\": \"aKey\"}, {\"key\": \"aKey\"}]}".getBytes())); + } catch (Exception ignored) { + } + assertThat(logTester.logs()).contains("Duplicated properties with key: 'aKey'"); + } + + @Test + void should_warn_if_null_property() { + try { + ScannerMain.run(new ByteArrayInputStream("{\"scannerProperties\": [{\"key\": \"aKey\", \"value\": \"aValue\"},]}".getBytes())); + } catch (Exception ignored) { + } + assertThat(logTester.logs()).contains("Ignoring null property"); + } + + /** + * For now this test is just checking that the scanner completes successfully, with no input files, and mocking server responses to the bare minimum. + */ + @Test + void should_complete_successfully(@TempDir Path baseDir) { + + ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": [" + + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"}," + + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"}," + + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"}" + + "]}").getBytes())); + + assertThat(logTester.logs()).contains("SonarScanner Engine completed successfully"); + } + + @Test + void should_enable_verbose(@TempDir Path baseDir) { + + ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": [" + + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"}," + + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"}," + + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"}" + + "]}").getBytes())); + + assertThat(logTester.logs(Level.DEBUG)).isEmpty(); + + ScannerMain.run(new ByteArrayInputStream(("{\"scannerProperties\": [" + + "{\"key\": \"sonar.host.url\", \"value\": \"" + sonarqube.baseUrl() + "\"}," + + "{\"key\": \"sonar.projectKey\", \"value\": \"" + PROJECT_KEY + "\"}," + + "{\"key\": \"sonar.projectBaseDir\", \"value\": \"" + baseDir + "\"}," + + "{\"key\": \"sonar.verbose\", \"value\": \"true\"}" + + "]}").getBytes())); + + assertThat(logTester.logs(Level.DEBUG)).isNotEmpty(); + } +} diff --git a/sonar-scanner-engine/src/it/java/testutils/TestUtils.java b/sonar-scanner-engine/src/it/java/testutils/TestUtils.java new file mode 100644 index 00000000000..9240d366256 --- /dev/null +++ b/sonar-scanner-engine/src/it/java/testutils/TestUtils.java @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-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 com.github.tomakehurst.wiremock.http.Body; +import com.github.tomakehurst.wiremock.http.ContentTypeHeader; +import com.google.protobuf.Message; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class TestUtils { + + public static Body protobufBody(Message message) { + var baos = new ByteArrayOutputStream(); + try { + message.writeTo(baos); + return Body.ofBinaryOrText(baos.toByteArray(), ContentTypeHeader.absent()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java index a7606c4c98b..8ff58ff479f 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/bootstrapper/LoggingConfiguration.java @@ -74,13 +74,16 @@ public final class LoggingConfiguration { } public LoggingConfiguration setVerbose(Map<String, String> props) { + verbose = isVerboseEnabled(props); + return setVerbose(verbose); + } + + public static boolean isVerboseEnabled(Map<String, String> props) { String logLevel = props.get("sonar.log.level"); String deprecatedProfilingLevel = props.get("sonar.log.profilingLevel"); - verbose = "true".equals(props.get("sonar.verbose")) || + return "true".equals(props.get("sonar.verbose")) || "DEBUG".equals(logLevel) || "TRACE".equals(logLevel) || "BASIC".equals(deprecatedProfilingLevel) || "FULL".equals(deprecatedProfilingLevel); - - return setVerbose(verbose); } public LoggingConfiguration setRootLevel(String level) { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java new file mode 100644 index 00000000000..bbad390077a --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoder.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-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.sonar.scanner.bootstrap; + +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.encoder.EncoderBase; +import org.apache.commons.text.StringEscapeUtils; + +import static ch.qos.logback.core.CoreConstants.COMMA_CHAR; +import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR; +import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET; + +public class ScannerLogbackEncoder extends EncoderBase<ILoggingEvent> { + + private static final byte[] EMPTY_BYTES = new byte[0]; + private static final char OPEN_OBJ = '{'; + private static final char CLOSE_OBJ = '}'; + private static final char VALUE_SEPARATOR = COMMA_CHAR; + private static final char QUOTE = DOUBLE_QUOTE_CHAR; + private static final String QUOTE_COL = "\":"; + + private final ThrowableProxyConverter tpc = new ThrowableProxyConverter(); + + @Override + public byte[] headerBytes() { + return EMPTY_BYTES; + } + + @Override + public byte[] encode(ILoggingEvent event) { + StringBuilder sb = new StringBuilder(); + sb.append(OPEN_OBJ); + var level = event.getLevel(); + if (level != null) { + appenderMember(sb, "level", level.levelStr); + sb.append(VALUE_SEPARATOR); + } + + appenderMember(sb, "message", StringEscapeUtils.escapeJson(event.getFormattedMessage())); + + IThrowableProxy tp = event.getThrowableProxy(); + String stackTrace = null; + if (tp != null) { + sb.append(VALUE_SEPARATOR); + stackTrace = tpc.convert(event); + appenderMember(sb, "stacktrace", StringEscapeUtils.escapeJson(stackTrace)); + } + + sb.append(CLOSE_OBJ); + sb.append(CoreConstants.JSON_LINE_SEPARATOR); + return sb.toString().getBytes(UTF_8_CHARSET); + } + + private static void appenderMember(StringBuilder sb, String key, String value) { + sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE); + } + + @Override + public byte[] footerBytes() { + return EMPTY_BYTES; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java new file mode 100644 index 00000000000..405f586d654 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-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.sonar.scanner.bootstrap; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.CheckForNull; +import org.jetbrains.annotations.NotNull; +import org.slf4j.LoggerFactory; +import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.batch.bootstrapper.LoggingConfiguration; + +import static org.sonar.batch.bootstrapper.LoggingConfiguration.LEVEL_ROOT_DEFAULT; +import static org.sonar.batch.bootstrapper.LoggingConfiguration.LEVEL_ROOT_VERBOSE; + +public class ScannerMain { + + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ScannerMain.class); + + private static final String SCANNER_APP_KEY = "sonar.scanner.app"; + private static final String SCANNER_APP_VERSION_KEY = "sonar.scanner.appVersion"; + + public static void main(String... args) { + try { + run(System.in); + } catch (Exception e) { + LOG.error("Error during SonarScanner Engine execution", e); + System.exit(1); + } + } + + public static void run(InputStream in) { + LOG.info("Starting SonarScanner Engine..."); + + var properties = parseInputProperties(in); + + configureLogLevel(properties); + + runScannerEngine(properties); + + LOG.info("SonarScanner Engine completed successfully"); + } + + private static @NotNull Map<String, String> parseInputProperties(InputStream in) { + Map<String, String> properties = new HashMap<>(); + var input = parseJsonInput(in); + if (input != null && input.scannerProperties != null) { + input.scannerProperties.forEach(prop -> { + if (prop == null) { + LOG.warn("Ignoring null property"); + } else if (prop.key == null) { + LOG.warn("Ignoring property with null key: '{}'", prop.value); + } else { + if (properties.containsKey(prop.key)) { + LOG.warn("Duplicated properties with key: '{}'", prop.key); + } + properties.put(prop.key, prop.value); + } + }); + } + return properties; + } + + @CheckForNull + private static Input parseJsonInput(InputStream in) { + try (var reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + return new Gson().fromJson(reader, Input.class); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to parse JSON input", e); + } + } + + private static void runScannerEngine(Map<String, String> properties) { + var scannerAppKey = properties.get(SCANNER_APP_KEY); + var scannerAppVersion = properties.get(SCANNER_APP_VERSION_KEY); + var env = new EnvironmentInformation(scannerAppKey, scannerAppVersion); + SpringGlobalContainer.create(properties, List.of(env)).execute(); + } + + private static void configureLogLevel(Map<String, String> properties) { + var verbose = LoggingConfiguration.isVerboseEnabled(properties); + var rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(Level.toLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT)); + } + + private static class Input { + @SerializedName("scannerProperties") + private List<ScannerProperty> scannerProperties; + } + + private static class ScannerProperty { + @SerializedName("key") + private String key; + + @SerializedName("value") + private String value; + } + +} diff --git a/sonar-scanner-engine/src/main/resources/logback-shared.xml b/sonar-scanner-engine/src/main/resources/logback-shared.xml new file mode 100644 index 00000000000..83d49028733 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/logback-shared.xml @@ -0,0 +1,16 @@ +<!-- This file is shared between the old bootstrapping entry point and new bootstrapping --> +<included> + <!-- BeanUtils generate too many DEBUG logs when sonar.verbose is set --> + <logger name="org.apache.commons.beanutils.converters" level="WARN"/> + + <!-- FileSnapshot generate too many DEBUG logs when sonar.verbose is set --> + <logger name="org.eclipse.jgit.internal.storage.file" level="INFO"/> + + <!-- Spring generates too many DEBUG logs when sonar.verbose is set --> + <logger name="org.springframework" level="INFO"/> + + <!-- AbstractApplicationContext generate too verbose warning if warn is Enabled --> + <logger name="org.springframework.context.annotation.AnnotationConfigApplicationContext" level="ERROR"/> + + <logger name="org.sonar.core.platform.PriorityBeanFactory" level="INFO"/> +</included> diff --git a/sonar-scanner-engine/src/main/resources/logback.xml b/sonar-scanner-engine/src/main/resources/logback.xml new file mode 100644 index 00000000000..4080ae54558 --- /dev/null +++ b/sonar-scanner-engine/src/main/resources/logback.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!DOCTYPE configuration> +<!-- This logback configuration is used when the scanner engine is bootstrapped using the SonarScannerCli class. --> + +<configuration> + <import class="ch.qos.logback.core.ConsoleAppender"/> + <include resource="logback-shared.xml"/> + + <appender name="STDOUT" class="ConsoleAppender"> + <encoder class="org.sonar.scanner.bootstrap.ScannerLogbackEncoder"/> + </appender> + + <root level="info"> + <appender-ref ref="STDOUT"/> + </root> +</configuration>
\ No newline at end of file diff --git a/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml b/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml index 52120f96acf..65b12b2ed24 100644 --- a/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml +++ b/sonar-scanner-engine/src/main/resources/org/sonar/batch/bootstrapper/logback.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8" ?> <configuration debug="false"> + <include resource="logback-shared.xml"/> <!-- @@ -18,27 +19,6 @@ </encoder> </appender> - <!-- BeanUtils generate too many DEBUG logs when sonar.verbose is set --> - <logger name="org.apache.commons.beanutils.converters" level="WARN"/> - - <!-- FileSnapshot generate too many DEBUG logs when sonar.verbose is set --> - <logger name="org.eclipse.jgit.internal.storage.file" level="INFO"/> - - <!-- Spring generates too many DEBUG logs when sonar.verbose is set --> - <logger name="org.springframework" level="INFO"/> - - <!-- AbstractApplicationContext generate too verbose warning if warn is Enabled --> - <logger name="org.springframework.context.annotation.AnnotationConfigApplicationContext" level="ERROR"/> - - <logger name="org.sonar.core.platform.PriorityBeanFactory" level="INFO"/> - - <!-- sonar.showSql --> - <!-- see also org.sonar.db.MyBatis#configureLogback() --> - <logger name="org.mybatis" level="${SQL_LOGGER_LEVEL:-WARN}"/> - <logger name="org.apache.ibatis" level="${SQL_LOGGER_LEVEL:-WARN}"/> - <logger name="java.sql" level="${SQL_LOGGER_LEVEL:-WARN}"/> - <logger name="java.sql.ResultSet" level="WARN"/> - <root level="${ROOT_LOGGER_LEVEL}"> <!-- sonar.verbose --> <appender-ref ref="STDOUT"/> diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java new file mode 100644 index 00000000000..e63985da428 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerLogbackEncoderTest.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-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.sonar.scanner.bootstrap; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.ThrowableProxy; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ScannerLogbackEncoderTest { + + ScannerLogbackEncoder underTest = new ScannerLogbackEncoder(); + + @Test + void no_headers_and_footers() { + assertThat(underTest.headerBytes()).isEmpty(); + assertThat(underTest.footerBytes()).isEmpty(); + } + + @Test + void should_encode_when_no_level_and_no_stacktrace() { + var logEvent = mock(ILoggingEvent.class); + when(logEvent.getLevel()).thenReturn(null); + when(logEvent.getFormattedMessage()).thenReturn("message"); + + var bytes = underTest.encode(logEvent); + + assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"message\":\"message\"}\n"); + } + + @Test + void should_encode_when_no_stacktrace() { + var logEvent = mock(ILoggingEvent.class); + when(logEvent.getLevel()).thenReturn(Level.DEBUG); + when(logEvent.getFormattedMessage()).thenReturn("message"); + + var bytes = underTest.encode(logEvent); + + assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"level\":\"DEBUG\",\"message\":\"message\"}\n"); + } + + @Test + void should_encode_with_stacktrace() { + var logEvent = mock(ILoggingEvent.class); + when(logEvent.getLevel()).thenReturn(Level.DEBUG); + when(logEvent.getFormattedMessage()).thenReturn("message"); + when(logEvent.getThrowableProxy()).thenReturn(new ThrowableProxy(new IllegalArgumentException("foo"))); + + var bytes = underTest.encode(logEvent); + + assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo("{\"level\":\"DEBUG\",\"message\":\"message\",\"stacktrace\":\"java.lang.IllegalArgumentException: foo\\n\"}\n"); + } + +}
\ No newline at end of file |