From 9b9908a818c7d64f52ed0f11a24c47fbadc7d39f Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Thu, 19 Dec 2024 16:58:58 +0100 Subject: [PATCH] SCANJLIB-169 Rework the API to support functional errors The bootstrapper now returns a result with a boolean allowing the caller to terminate without throwing an exception. This is used to log auth errors, but it may be also useful for other functional errors. --- .../java/com/sonar/scanner/lib/it/Main.java | 19 ++- .../com/sonar/scanner/lib/it/ProxyTest.java | 4 +- .../com/sonar/scanner/lib/it/SSLTest.java | 20 +-- .../scanner/lib/EnvironmentConfig.java | 2 +- .../lib/ScannerEngineBootstrapResult.java | 41 +++++ .../lib/ScannerEngineBootstrapper.java | 95 ++++++++---- .../scanner/lib/ScannerEngineFacade.java | 1 - .../scanner/lib/internal/FailedBootstrap.java | 41 +++++ .../lib/internal/MessageException.java | 33 ++++ .../lib/internal/SuccessfulBootstrap.java | 47 ++++++ .../facade/forked/JavaRunnerFactory.java | 5 +- .../lib/internal/http/HttpException.java | 50 +++++++ .../lib/internal/http/ScannerHttpClient.java | 58 +++---- .../lib/ScannerEngineBootstrapperTest.java | 141 ++++++++++++------ .../SimulationScannerEngineFacadeTest.java | 8 +- .../internal/http/ScannerHttpClientTest.java | 9 +- lib/src/test/java/testutils/LogTester.java | 7 +- 17 files changed, 449 insertions(+), 132 deletions(-) create mode 100644 lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapResult.java create mode 100644 lib/src/main/java/org/sonarsource/scanner/lib/internal/FailedBootstrap.java create mode 100644 lib/src/main/java/org/sonarsource/scanner/lib/internal/MessageException.java create mode 100644 lib/src/main/java/org/sonarsource/scanner/lib/internal/SuccessfulBootstrap.java create mode 100644 lib/src/main/java/org/sonarsource/scanner/lib/internal/http/HttpException.java diff --git a/its/it-simple-scanner/src/main/java/com/sonar/scanner/lib/it/Main.java b/its/it-simple-scanner/src/main/java/com/sonar/scanner/lib/it/Main.java index 1a6651f1..e376d8a6 100644 --- a/its/it-simple-scanner/src/main/java/com/sonar/scanner/lib/it/Main.java +++ b/its/it-simple-scanner/src/main/java/com/sonar/scanner/lib/it/Main.java @@ -21,11 +21,13 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.sonarsource.scanner.lib.EnvironmentConfig; import org.sonarsource.scanner.lib.ScannerEngineBootstrapper; public class Main { public static void main(String[] args) { + AtomicBoolean success = new AtomicBoolean(false); try { Map props = new HashMap<>(EnvironmentConfig.load()); @@ -36,20 +38,25 @@ public static void main(String[] args) { } } - runProject(props); + success.set(runScanner(props)); } catch (Exception e) { e.printStackTrace(); - System.exit(1); + System.exit(2); } - System.exit(0); + System.exit(success.get() ? 0 : 1); } - private static void runProject(Map props) throws Exception { + private static boolean runScanner(Map props) throws Exception { - try (var scannerEngine = ScannerEngineBootstrapper.create("Simple Scanner", "1.0") + try (var bootstrapResult = ScannerEngineBootstrapper.create("Simple Scanner", "1.0") .addBootstrapProperties(props) .bootstrap()) { - scannerEngine.analyze(props); + if (bootstrapResult.isSuccessful()) { + bootstrapResult.getEngineFacade().analyze(props); + return true; + } else { + return false; + } } } } diff --git a/its/it-tests/src/test/java/com/sonar/scanner/lib/it/ProxyTest.java b/its/it-tests/src/test/java/com/sonar/scanner/lib/it/ProxyTest.java index d803bba9..d7d1267e 100644 --- a/its/it-tests/src/test/java/com/sonar/scanner/lib/it/ProxyTest.java +++ b/its/it-tests/src/test/java/com/sonar/scanner/lib/it/ProxyTest.java @@ -206,8 +206,8 @@ public void simple_analysis_with_proxy_auth() throws Exception { params.put("sonar.scanner.proxyPort", "" + httpProxyPort); BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), ORCHESTRATOR.getServer().getUrl(), params, Map.of()); - assertThat(buildResult.getLastStatus()).isEqualTo(1); - assertThat(buildResult.getLogs()).contains("Error status returned by url", ": 407"); + assertThat(buildResult.getLastStatus()).isNotZero(); + assertThat(buildResult.getLogs()).contains("Failed to query server version: Proxy Authentication Required."); assertThat(seenByProxy).isEmpty(); params.put("sonar.scanner.proxyUser", PROXY_USER); diff --git a/its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java b/its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java index 31b2cdb6..41688275 100644 --- a/its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java +++ b/its/it-tests/src/test/java/com/sonar/scanner/lib/it/SSLTest.java @@ -115,7 +115,7 @@ private static void startSSLTransparentReverseProxy(boolean requireClientAuth) t // Handler Structure HandlerCollection handlers = new HandlerCollection(); - handlers.setHandlers(new Handler[] {proxyHandler(), new DefaultHandler()}); + handlers.setHandlers(new Handler[]{proxyHandler(), new DefaultHandler()}); server.setHandler(handlers); ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig)); @@ -178,7 +178,7 @@ public void simple_analysis_with_server_and_client_certificate() throws Exceptio BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort); assertThat(buildResult.getLastStatus()).isNotZero(); - assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException"); + assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain"); Path clientTruststore = Paths.get(SSLTest.class.getResource(KEYSTORE_CLIENT_WITH_CA_KEYTOOL).toURI()).toAbsolutePath(); assertThat(clientTruststore).exists(); @@ -204,7 +204,7 @@ public void simple_analysis_with_server_and_without_client_certificate_is_failin BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort); assertThat(buildResult.getLastStatus()).isNotZero(); - assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException"); + assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain"); Path clientTruststore = Paths.get(SSLTest.class.getResource(KEYSTORE_CLIENT_WITH_CA_KEYTOOL).toURI()).toAbsolutePath(); assertThat(clientTruststore).exists(); @@ -218,16 +218,10 @@ public void simple_analysis_with_server_and_without_client_certificate_is_failin // Voluntary missing client keystore buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort, params, Map.of()); - assertThat(buildResult.getLastStatus()).isEqualTo(1); + assertThat(buildResult.getLastStatus()).isNotZero(); - // different exception is thrown depending on the JDK version. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8172163 - String failedAnalysis = "(?s).*java\\.lang\\.IllegalStateException: Failed to get server version.*"; assertThat(buildResult.getLogs()) - .matches(p -> p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLException: Broken pipe \\(Write failed\\).*") || - p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLProtocolException: Broken pipe \\(Write failed\\).*") || - p.matches(failedAnalysis + "Caused by: javax\\.net\\.ssl\\.SSLHandshakeException: Received fatal alert: bad_certificate.*") || - p.matches(failedAnalysis + "Caused by: java\\.net\\.SocketException: Broken pipe.*") || - p.matches(failedAnalysis + "Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.*")); + .contains("Failed to query server version: Call to URL [https://localhost:" + httpsPort + "/api/v2/analysis/version] failed: Received fatal alert: bad_certificate"); } private static Path project(String projectName) { @@ -244,7 +238,7 @@ public void simple_analysis_with_server_certificate(String clientTrustStore, Str BuildResult buildResult = scanner.executeSimpleProject(project("js-sample"), "https://localhost:" + httpsPort); assertThat(buildResult.getLastStatus()).isNotZero(); - assertThat(buildResult.getLogs()).contains("javax.net.ssl.SSLHandshakeException"); + assertThat(buildResult.getLogs()).contains("None of the TrustManagers trust this certificate chain"); Path clientTruststore = Paths.get(SSLTest.class.getResource(clientTrustStore).toURI()).toAbsolutePath(); assertThat(clientTruststore).exists(); @@ -265,7 +259,7 @@ public void simple_analysis_with_server_certificate(String clientTrustStore, Str @DataProvider() public static Object[][] variousClientTrustStores() { - return new Object[][] { + return new Object[][]{ {KEYSTORE_CLIENT_WITH_CA_KEYTOOL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, true}, {KEYSTORE_CLIENT_WITH_CA_OPENSSL, CLIENT_WITH_CA_KEYSTORE_PASSWORD, false}, {KEYSTORE_CLIENT_WITH_CERTIFICATE_KEYTOOL, CLIENT_WITH_CERTIFICATE_KEYSTORE_PASSWORD, true}, diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/EnvironmentConfig.java b/lib/src/main/java/org/sonarsource/scanner/lib/EnvironmentConfig.java index e9cc79ac..81b5770b 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/EnvironmentConfig.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/EnvironmentConfig.java @@ -40,7 +40,7 @@ public class EnvironmentConfig { private static final String GENERIC_ENV_PREFIX = "SONAR_SCANNER_"; private static final String SONAR_HOST_URL_ENV_VAR = "SONAR_HOST_URL"; private static final String SONAR_USER_HOME_ENV_VAR = "SONAR_USER_HOME"; - private static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN"; + static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN"; private EnvironmentConfig() { // only static methods diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapResult.java b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapResult.java new file mode 100644 index 00000000..7a1928eb --- /dev/null +++ b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapResult.java @@ -0,0 +1,41 @@ +/* + * SonarScanner Java Library + * 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.lib; + +/** + * Closing this will automatically close the {@link ScannerEngineFacade} that it contains, if any. + */ +public interface ScannerEngineBootstrapResult extends AutoCloseable { + + /** + * Allow to test if the bootstrapping has been successful. If not, the {@link ScannerEngineFacade} should not be used. + * A log message should have been emitted in case of failure. + * + * @return true if the bootstrapping has been successful, false otherwise + */ + boolean isSuccessful(); + + /** + * Get the facade to interact with the engine. Only call this method if {@link #isSuccessful()} returns true. + * + * @return the facade to interact with the engine + */ + ScannerEngineFacade getEngineFacade(); +} diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java index 0ead5192..41c5ac7a 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java @@ -33,7 +33,10 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonarsource.scanner.lib.internal.FailedBootstrap; import org.sonarsource.scanner.lib.internal.InternalProperties; +import org.sonarsource.scanner.lib.internal.MessageException; +import org.sonarsource.scanner.lib.internal.SuccessfulBootstrap; import org.sonarsource.scanner.lib.internal.cache.FileCache; import org.sonarsource.scanner.lib.internal.facade.forked.NewScannerEngineFacade; import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncherFactory; @@ -41,6 +44,7 @@ import org.sonarsource.scanner.lib.internal.facade.inprocess.IsolatedLauncherFactory; import org.sonarsource.scanner.lib.internal.facade.simulation.SimulationScannerEngineFacade; import org.sonarsource.scanner.lib.internal.http.HttpConfig; +import org.sonarsource.scanner.lib.internal.http.HttpException; import org.sonarsource.scanner.lib.internal.http.ScannerHttpClient; import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore; import org.sonarsource.scanner.lib.internal.util.ArchResolver; @@ -111,10 +115,7 @@ public ScannerEngineBootstrapper setBootstrapProperty(String key, String value) return this; } - /** - * Bootstrap the scanner-engine. - */ - public ScannerEngineFacade bootstrap() { + public ScannerEngineBootstrapResult bootstrap() { if (LOG.isDebugEnabled()) { LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory())); } @@ -125,22 +126,59 @@ public ScannerEngineFacade bootstrap() { var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE); var sonarUserHome = resolveSonarUserHome(immutableProperties); var fileCache = FileCache.create(sonarUserHome); - var httpConfig = new HttpConfig(immutableProperties, sonarUserHome); - scannerHttpClient.init(httpConfig); - String serverVersion = null; - if (!isSonarCloud) { - serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties); - } if (isSimulation) { - return new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion); - } else if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) { - var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties); - return new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion); + var serverVersion = immutableProperties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "9.9"); + return new SuccessfulBootstrap(new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion)); + } + + // No HTTP call should be made before this point + try { + var httpConfig = new HttpConfig(immutableProperties, sonarUserHome); + scannerHttpClient.init(httpConfig); + + var serverVersion = !isSonarCloud ? getServerVersion(scannerHttpClient) : null; + if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) { + var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties); + return new SuccessfulBootstrap(new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion)); + } else { + var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache); + var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig); + return new SuccessfulBootstrap(new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion)); + } + } catch (MessageException e) { + return handleException(e); + } + } + + private static ScannerEngineBootstrapResult handleException(MessageException e) { + var message = new StringBuilder(e.getMessage()); + if (e.getCause() instanceof HttpException) { + var httpEx = (HttpException) e.getCause(); + var code = httpEx.getCode(); + if (code == 401 || code == 403) { + var helpMessage = "Please check the property " + ScannerProperties.SONAR_TOKEN + + " or the environment variable " + EnvironmentConfig.TOKEN_ENV_VARIABLE + "."; + message.append(". ").append(helpMessage); + } + if (code == 407) { + var helpMessage = "Please check the properties " + ScannerProperties.SONAR_SCANNER_PROXY_USER + + " and " + ScannerProperties.SONAR_SCANNER_PROXY_PASSWORD + "."; + message.append(". ").append(helpMessage); + } + } + logWithStacktraceOnlyIfDebug(message.toString(), e); + return new FailedBootstrap(); + } + + /** + * For functional errors, the stacktrace is not necessary. It is only useful for debugging. + */ + private static void logWithStacktraceOnlyIfDebug(String message, Throwable t) { + if (LOG.isDebugEnabled()) { + LOG.error(message, t); } else { - var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache); - var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig); - return new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion); + LOG.error(message); } } @@ -195,20 +233,21 @@ private static Path resolveSonarUserHome(Map properties) { return Paths.get(sonarUserHome); } - private static String getServerVersion(ScannerHttpClient scannerHttpClient, boolean isSimulation, Map properties) { - if (isSimulation) { - return properties.getOrDefault(InternalProperties.SCANNER_VERSION_SIMULATION, "5.6"); - } - + private static String getServerVersion(ScannerHttpClient scannerHttpClient) { try { return scannerHttpClient.callRestApi("/analysis/version"); } catch (Exception e) { - try { - return scannerHttpClient.callWebApi("/api/server/version"); - } catch (Exception e2) { - var ex = new IllegalStateException("Failed to get server version", e2); - ex.addSuppressed(e); - throw ex; + if (e instanceof HttpException && ((HttpException) e).getCode() == 404) { + // Fallback to the old endpoint + try { + return scannerHttpClient.callWebApi("/api/server/version"); + } catch (Exception e2) { + var ex = new MessageException("Failed to query server version: " + e2.getMessage(), e2); + ex.addSuppressed(e); + throw ex; + } + } else { + throw new MessageException("Failed to query server version: " + e.getMessage(), e); } } } diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineFacade.java b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineFacade.java index f1b2b95f..03852a8c 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineFacade.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineFacade.java @@ -21,7 +21,6 @@ import java.util.Map; - public interface ScannerEngineFacade extends AutoCloseable { /** diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/FailedBootstrap.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/FailedBootstrap.java new file mode 100644 index 00000000..0fc096b4 --- /dev/null +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/FailedBootstrap.java @@ -0,0 +1,41 @@ +/* + * SonarScanner Java Library + * 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.lib.internal; + +import org.sonarsource.scanner.lib.ScannerEngineBootstrapResult; +import org.sonarsource.scanner.lib.ScannerEngineFacade; + +public class FailedBootstrap implements ScannerEngineBootstrapResult { + + @Override + public boolean isSuccessful() { + return false; + } + + @Override + public ScannerEngineFacade getEngineFacade() { + throw new UnsupportedOperationException("No engine facade available"); + } + + @Override + public void close() { + // No operation + } +} diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/MessageException.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/MessageException.java new file mode 100644 index 00000000..dee34686 --- /dev/null +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/MessageException.java @@ -0,0 +1,33 @@ +/* + * SonarScanner Java Library + * 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.lib.internal; + +/** + * Functional error that should not log a stacktrace by default + */ +public class MessageException extends RuntimeException { + public MessageException(String message) { + super(message); + } + + public MessageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/SuccessfulBootstrap.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/SuccessfulBootstrap.java new file mode 100644 index 00000000..b4f5b5d9 --- /dev/null +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/SuccessfulBootstrap.java @@ -0,0 +1,47 @@ +/* + * SonarScanner Java Library + * 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.lib.internal; + +import org.sonarsource.scanner.lib.ScannerEngineBootstrapResult; +import org.sonarsource.scanner.lib.ScannerEngineFacade; + +public class SuccessfulBootstrap implements ScannerEngineBootstrapResult { + + private final ScannerEngineFacade facade; + + public SuccessfulBootstrap(ScannerEngineFacade facade) { + this.facade = facade; + } + + @Override + public boolean isSuccessful() { + return true; + } + + @Override + public ScannerEngineFacade getEngineFacade() { + return facade; + } + + @Override + public void close() throws Exception { + facade.close(); + } +} diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/facade/forked/JavaRunnerFactory.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/facade/forked/JavaRunnerFactory.java index 5c2dfb27..4f6fe7c5 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/internal/facade/forked/JavaRunnerFactory.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/facade/forked/JavaRunnerFactory.java @@ -42,6 +42,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonarsource.scanner.lib.internal.MessageException; import org.sonarsource.scanner.lib.internal.cache.CachedFile; import org.sonarsource.scanner.lib.internal.cache.FileCache; import org.sonarsource.scanner.lib.internal.cache.HashMismatchException; @@ -162,8 +163,8 @@ private static Optional getJreMetadata(ScannerHttpClient scannerHtt }.getType(); List jres = new Gson().fromJson(response, listType); return jres.stream().findFirst(); - } catch (IOException e) { - throw new IllegalStateException("Failed to get JRE metadata", e); + } catch (Exception e) { + throw new MessageException("Failed to query JRE metadata: " + e.getMessage(), e); } } diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/HttpException.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/HttpException.java new file mode 100644 index 00000000..5cda4477 --- /dev/null +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/HttpException.java @@ -0,0 +1,50 @@ +/* + * SonarScanner Java Library + * 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.lib.internal.http; + +import java.net.URL; +import javax.annotation.Nullable; + +public class HttpException extends RuntimeException { + private final URL requestUrl; + private final int code; + @Nullable + private final String body; + + public HttpException(URL requestUrl, int code, String message, @Nullable String body) { + super(message); + this.requestUrl = requestUrl; + this.code = code; + this.body = body; + } + + public URL getRequestUrl() { + return requestUrl; + } + + public int getCode() { + return code; + } + + @Nullable + public String getBody() { + return body; + } +} diff --git a/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java index b55d6458..972b5b9a 100644 --- a/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java +++ b/lib/src/main/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClient.java @@ -35,6 +35,7 @@ import org.sonarsource.scanner.lib.internal.util.Utils; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; public class ScannerHttpClient { @@ -52,7 +53,7 @@ public void init(HttpConfig httpConfig) { } - public void downloadFromRestApi(String urlPath, Path toFile) throws IOException { + public void downloadFromRestApi(String urlPath, Path toFile) { if (!urlPath.startsWith("/")) { throw new IllegalArgumentException(format(EXCEPTION_MESSAGE_MISSING_SLASH, urlPath)); } @@ -60,7 +61,7 @@ public void downloadFromRestApi(String urlPath, Path toFile) throws IOException downloadFile(url, toFile, true); } - public void downloadFromWebApi(String urlPath, Path toFile) throws IOException { + public void downloadFromWebApi(String urlPath, Path toFile) { if (!urlPath.startsWith("/")) { throw new IllegalArgumentException(format(EXCEPTION_MESSAGE_MISSING_SLASH, urlPath)); } @@ -68,7 +69,7 @@ public void downloadFromWebApi(String urlPath, Path toFile) throws IOException { downloadFile(url, toFile, true); } - public void downloadFromExternalUrl(String url, Path toFile) throws IOException { + public void downloadFromExternalUrl(String url, Path toFile) { downloadFile(url, toFile, false); } @@ -81,16 +82,18 @@ public void downloadFromExternalUrl(String url, Path toFile) throws IOException * @throws IOException if connectivity problem or timeout (network) or IO error (when writing to file) * @throws IllegalStateException if HTTP response code is different than 2xx */ - private void downloadFile(String url, Path toFile, boolean authentication) throws IOException { + private void downloadFile(String url, Path toFile, boolean authentication) { LOG.debug("Download {} to {}", url, toFile.toAbsolutePath()); - try (ResponseBody responseBody = callUrl(url, authentication, "application/octet-stream"); - InputStream in = responseBody.byteStream()) { - Files.copy(in, toFile, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException | RuntimeException e) { - Utils.deleteQuietly(toFile); - throw e; - } + callUrl(url, authentication, "application/octet-stream", responseBody -> { + try (InputStream in = responseBody.byteStream()) { + Files.copy(in, toFile, StandardCopyOption.REPLACE_EXISTING); + return null; + } catch (IOException | RuntimeException e) { + Utils.deleteQuietly(toFile); + throw e; + } + }); } public String callRestApi(String urlPath) throws IOException { @@ -116,10 +119,8 @@ public String callWebApi(String urlPath) throws IOException { * @throws IOException if connectivity problem or timeout (network) * @throws IllegalStateException if HTTP response code is different than 2xx */ - private String callApi(String url) throws IOException { - try (ResponseBody responseBody = callUrl(url, true, null)) { - return responseBody.string(); - } + private String callApi(String url) { + return callUrl(url, true, null, ResponseBody::string); } /** @@ -128,25 +129,28 @@ private String callApi(String url) throws IOException { * @param url the URL to call * @param authentication if true, the request will be authenticated with the token * @param acceptHeader the value of the Accept header - * @throws IllegalStateException if HTTP code is different than 2xx */ - private ResponseBody callUrl(String url, boolean authentication, @Nullable String acceptHeader) { + private G callUrl(String url, boolean authentication, @Nullable String acceptHeader, ResponseHandler responseHandler) { var httpClient = buildHttpClient(authentication); var request = prepareRequest(url, acceptHeader); - Response response; - try { - response = httpClient.newCall(request).execute(); + try (Response response = httpClient.newCall(request).execute()) { + var body = response.body(); + if (!response.isSuccessful()) { + throw new HttpException(response.request().url().url(), response.code(), response.message(), body != null ? body.string() : null); + } + return responseHandler.apply(requireNonNull(body, "Response body is empty")); + } catch (HttpException e) { + throw e; } catch (Exception e) { - throw new IllegalStateException(format("Call to URL [%s] failed", url), e); + throw new IllegalStateException(format("Call to URL [%s] failed: %s", url, e.getMessage()), e); } - if (!response.isSuccessful()) { - response.close(); - throw new IllegalStateException(format("Error status returned by url [%s]: %s", response.request().url(), response.code())); - } - return response.body(); } - private Request prepareRequest(String url, @org.jetbrains.annotations.Nullable String acceptHeader) { + private interface ResponseHandler { + G apply(ResponseBody responseBody) throws IOException; + } + + private Request prepareRequest(String url, @Nullable String acceptHeader) { var requestBuilder = new Request.Builder() .get() .url(url) diff --git a/lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java b/lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java index 02e80139..021f9bca 100644 --- a/lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java +++ b/lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java @@ -19,9 +19,11 @@ */ package org.sonarsource.scanner.lib; +import ch.qos.logback.classic.spi.ThrowableProxyUtil; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -31,26 +33,32 @@ 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.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junitpioneer.jupiter.RestoreSystemProperties; import org.mockito.Mockito; +import org.slf4j.event.Level; import org.sonarsource.scanner.lib.internal.InternalProperties; import org.sonarsource.scanner.lib.internal.cache.FileCache; import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncher; import org.sonarsource.scanner.lib.internal.facade.forked.ScannerEngineLauncherFactory; import org.sonarsource.scanner.lib.internal.facade.inprocess.IsolatedLauncherFactory; import org.sonarsource.scanner.lib.internal.http.HttpConfig; +import org.sonarsource.scanner.lib.internal.http.HttpException; import org.sonarsource.scanner.lib.internal.http.ScannerHttpClient; import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore; import org.sonarsource.scanner.lib.internal.http.ssl.SslConfig; import org.sonarsource.scanner.lib.internal.util.System2; +import testutils.LogTester; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -59,6 +67,9 @@ class ScannerEngineBootstrapperTest { + @RegisterExtension + private LogTester logTester = new LogTester(); + private final ScannerHttpClient scannerHttpClient = mock(ScannerHttpClient.class); private final ScannerEngineLauncherFactory scannerEngineLauncherFactory = mock(ScannerEngineLauncherFactory.class); private final System2 system = mock(System2.class); @@ -85,27 +96,28 @@ public void setUp() { @Test void should_use_new_bootstrapping_with_default_url() throws Exception { - try (var scannerEngineFacade = underTest.bootstrap()) { + try (var bootstrapResult = underTest.bootstrap()) { verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap()); - assertThat(scannerEngineFacade.isSonarCloud()).isTrue(); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isTrue(); } } @Test void should_use_new_bootstrapping_with_sonarcloud_url() throws Exception { - try (var scannerEngineFacade = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "https://sonarcloud.io").bootstrap()) { + try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "https://sonarcloud.io").bootstrap()) { verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap()); - assertThat(scannerEngineFacade.isSonarCloud()).isTrue(); + assertThat(bootstrapResult.isSuccessful()).isTrue(); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isTrue(); } } @Test void should_use_new_bootstrapping_with_sonarqube_10_6() throws Exception { when(scannerHttpClient.callRestApi("/analysis/version")).thenReturn(SQ_VERSION_NEW_BOOTSTRAPPING); - try (var scannerEngineFacade = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) { + try (var bootstrapResult = underTest.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) { verify(scannerEngineLauncherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class), anyMap()); - assertThat(scannerEngineFacade.isSonarCloud()).isFalse(); - assertThat(scannerEngineFacade.getServerVersion()).isEqualTo(SQ_VERSION_NEW_BOOTSTRAPPING); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isFalse(); + assertThat(bootstrapResult.getEngineFacade().getServerVersion()).isEqualTo(SQ_VERSION_NEW_BOOTSTRAPPING); } } @@ -117,14 +129,33 @@ void should_use_old_bootstrapping_with_sonarqube_10_5() throws Exception { ScannerEngineBootstrapper bootstrapper = new ScannerEngineBootstrapper("Gradle", "3.1", system, scannerHttpClient, launcherFactory, scannerEngineLauncherFactory); - when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new IOException("404 Not found")); + when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new HttpException(URI.create("http://myserver").toURL(), 404, "Not Found", null)); when(scannerHttpClient.callWebApi("/api/server/version")).thenReturn("10.5"); - try (var scannerEngineFacade = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) { + try (var bootstrapResult = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://myserver").bootstrap()) { verify(launcherFactory).createLauncher(eq(scannerHttpClient), any(FileCache.class)); - assertThat(scannerEngineFacade.isSonarCloud()).isFalse(); - assertThat(scannerEngineFacade.getServerVersion()).isEqualTo("10.5"); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isFalse(); + assertThat(bootstrapResult.getEngineFacade().getServerVersion()).isEqualTo("10.5"); + } + } + + @Test + void should_show_help_on_proxy_auth_error() throws Exception { + IsolatedLauncherFactory launcherFactory = mock(IsolatedLauncherFactory.class); + when(launcherFactory.createLauncher(eq(scannerHttpClient), any(FileCache.class))) + .thenReturn(mock(IsolatedLauncherFactory.IsolatedLauncherAndClassloader.class)); + + ScannerEngineBootstrapper bootstrapper = new ScannerEngineBootstrapper("Gradle", "3.1", system, scannerHttpClient, + launcherFactory, scannerEngineLauncherFactory); + when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new HttpException(URI.create("http://myserver").toURL(), 407, "Proxy Authentication Required", null)); + + logTester.setLevel(Level.DEBUG); + + try (var result = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://myserver").bootstrap()) { + assertThat(result.isSuccessful()).isFalse(); } + + assertThat(logTester.logs(Level.ERROR)).contains("Failed to query server version: Proxy Authentication Required. Please check the properties sonar.scanner.proxyUser and sonar.scanner.proxyPassword."); } @Test @@ -135,25 +166,51 @@ void should_preserve_both_exceptions_when_checking_version() throws Exception { ScannerEngineBootstrapper bootstrapper = new ScannerEngineBootstrapper("Gradle", "3.1", system, scannerHttpClient, launcherFactory, scannerEngineLauncherFactory); - when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new IOException("404 Not found")); - when(scannerHttpClient.callWebApi("/api/server/version")).thenThrow(new IOException("400 Server Error")); - - assertThatThrownBy(() -> { - try (var ignored = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) { - // Should throw - } - }) - .hasMessage("Failed to get server version") - .hasStackTraceContaining("400 Server Error", "404 Not found"); + when(scannerHttpClient.callRestApi("/analysis/version")).thenThrow(new HttpException(URI.create("http://myserver").toURL(), 404, "Not Found", null)); + when(scannerHttpClient.callWebApi("/api/server/version")).thenThrow(new HttpException(URI.create("http://myserver").toURL(), 400, "Server Error", null)); + + logTester.setLevel(Level.DEBUG); + + try (var result = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://myserver").bootstrap()) { + assertThat(result.isSuccessful()).isFalse(); + } + + var loggedError = logTester.logEvents(Level.ERROR); + assertThat(loggedError).hasSize(1); + assertThat(loggedError.get(0).getFormattedMessage()).contains("Failed to query server version: Server Error"); + assertThat(ThrowableProxyUtil.asString(loggedError.get(0).getThrowableProxy())) + .containsSubsequence( + "Suppressed: org.sonarsource.scanner.lib.internal.http.HttpException: Not Found", + "Caused by: org.sonarsource.scanner.lib.internal.http.HttpException: Server Error"); + } + + + @ParameterizedTest + @ValueSource(ints = {401, 403}) + void should_log_user_friendly_message_when_auth_error(int code) throws Exception { + IsolatedLauncherFactory launcherFactory = mock(IsolatedLauncherFactory.class); + when(launcherFactory.createLauncher(eq(scannerHttpClient), any(FileCache.class))) + .thenReturn(mock(IsolatedLauncherFactory.IsolatedLauncherAndClassloader.class)); + + ScannerEngineBootstrapper bootstrapper = new ScannerEngineBootstrapper("Gradle", "3.1", system, scannerHttpClient, + launcherFactory, scannerEngineLauncherFactory); + when(scannerHttpClient.callRestApi(anyString())).thenThrow(new HttpException(URI.create("http://myserver").toURL(), code, "Unauthorized", null)); + when(scannerHttpClient.callWebApi(anyString())).thenThrow(new HttpException(URI.create("http://myserver").toURL(), code, "Unauthorized", null)); + + try (var result = bootstrapper.setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost").bootstrap()) { + assertThat(result.isSuccessful()).isFalse(); + } + + assertThat(logTester.logs(Level.ERROR)).contains("Failed to query server version: Unauthorized. Please check the property sonar.token or the environment variable SONAR_TOKEN."); } @Test void should_launch_in_simulation_mode() throws Exception { - try (var scannerEngine = underTest + try (var bootstrapResult = underTest .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .bootstrap()) { - scannerEngine.analyze(Map.of("sonar.projectKey", "foo")); + bootstrapResult.getEngineFacade().analyze(Map.of("sonar.projectKey", "foo")); assertThat(readDumpedProps().getProperty("sonar.projectKey")).isEqualTo("foo"); } @@ -167,10 +224,10 @@ private Properties readDumpedProps() throws IOException { @Test void test_app() throws Exception { - try (var scannerEngine = underTest + try (var bootstrapResult = underTest .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).contains( + assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains( entry("sonar.scanner.app", "Gradle"), entry("sonar.scanner.appVersion", "3.1")); } @@ -178,41 +235,41 @@ void test_app() throws Exception { @Test void should_set_sonarcloud_as_host_by_default() throws Exception { - try (var scannerEngine = underTest + try (var bootstrapResult = underTest .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).contains( + assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains( entry("sonar.host.url", "https://sonarcloud.io")); - assertThat(scannerEngine.isSonarCloud()).isTrue(); - assertThrows(UnsupportedOperationException.class, scannerEngine::getServerVersion); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isTrue(); + assertThrows(UnsupportedOperationException.class, bootstrapResult.getEngineFacade()::getServerVersion); } } @Test void should_use_sonarcloud_url_from_property() throws Exception { - try (var scannerEngine = underTest + try (var bootstrapResult = underTest .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .setBootstrapProperty("sonar.scanner.sonarcloudUrl", "https://preprod.sonarcloud.io") .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).contains( + assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains( entry("sonar.host.url", "https://preprod.sonarcloud.io")); - assertThat(scannerEngine.isSonarCloud()).isTrue(); - assertThrows(UnsupportedOperationException.class, scannerEngine::getServerVersion); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isTrue(); + assertThrows(UnsupportedOperationException.class, bootstrapResult.getEngineFacade()::getServerVersion); } } @Test void should_set_sonarqube_api_url_and_remove_trailing_slash() throws Exception { - try (var scannerEngine = underTest + try (var bootstrapResult = underTest .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .setBootstrapProperty(ScannerProperties.HOST_URL, "http://localhost/") .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).contains( + assertThat(bootstrapResult.getEngineFacade().getBootstrapProperties()).contains( entry(ScannerProperties.API_BASE_URL, "http://localhost/api/v2")); - assertThat(scannerEngine.isSonarCloud()).isFalse(); + assertThat(bootstrapResult.getEngineFacade().isSonarCloud()).isFalse(); } } @@ -229,7 +286,7 @@ void should_set_properties() throws Exception { } }) .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).contains( + assertThat(scannerEngine.getEngineFacade().getBootstrapProperties()).contains( entry("sonar.projectKey", "foo"), entry("sonar.host.url", "http://localhost"), entry("sonar.login", "admin"), @@ -243,8 +300,8 @@ void should_set_os_and_arch() throws Exception { .setBootstrapProperty(InternalProperties.SCANNER_DUMP_TO_FILE, dumpFile.toString()) .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).containsEntry("sonar.scanner.os", "linux"); - assertThat(scannerEngine.getBootstrapProperties()).containsKey("sonar.scanner.arch"); + assertThat(scannerEngine.getEngineFacade().getBootstrapProperties()).containsEntry("sonar.scanner.os", "linux"); + assertThat(scannerEngine.getEngineFacade().getBootstrapProperties()).containsKey("sonar.scanner.arch"); } } @@ -258,8 +315,8 @@ void should_not_override_os_and_arch_when_passed() throws Exception { .setBootstrapProperty(ScannerProperties.SCANNER_ARCH, "some-arch") .bootstrap()) { - assertThat(scannerEngine.getBootstrapProperties()).containsEntry("sonar.scanner.os", "some-os"); - assertThat(scannerEngine.getBootstrapProperties()).containsEntry("sonar.scanner.arch", "some-arch"); + assertThat(scannerEngine.getEngineFacade().getBootstrapProperties()).containsEntry("sonar.scanner.os", "some-os"); + assertThat(scannerEngine.getEngineFacade().getBootstrapProperties()).containsEntry("sonar.scanner.arch", "some-arch"); } } diff --git a/lib/src/test/java/org/sonarsource/scanner/lib/internal/facade/simulation/SimulationScannerEngineFacadeTest.java b/lib/src/test/java/org/sonarsource/scanner/lib/internal/facade/simulation/SimulationScannerEngineFacadeTest.java index c3f4e47e..661dce7a 100644 --- a/lib/src/test/java/org/sonarsource/scanner/lib/internal/facade/simulation/SimulationScannerEngineFacadeTest.java +++ b/lib/src/test/java/org/sonarsource/scanner/lib/internal/facade/simulation/SimulationScannerEngineFacadeTest.java @@ -52,7 +52,7 @@ void failIfInvalidFile() { Map props = createProperties(); props.put(InternalProperties.SCANNER_DUMP_TO_FILE, temp.getAbsolutePath()); - assertThatThrownBy(() -> underTest.doAnalyze(props)) + assertThatThrownBy(() -> underTest.analyze(props)) .isInstanceOf(IllegalStateException.class) .hasMessage("Fail to export scanner properties"); } @@ -60,7 +60,7 @@ void failIfInvalidFile() { @Test void testDump() throws IOException { Map props = createProperties(); - underTest.doAnalyze(props); + underTest.analyze(props); assertDump(props); } @@ -68,7 +68,7 @@ void testDump() throws IOException { void error_dump() { Map props = new HashMap<>(); props.put(InternalProperties.SCANNER_DUMP_TO_FILE, "an invalid # file \\//?name*?\""); - assertThatThrownBy(() -> underTest.doAnalyze(props)) + assertThatThrownBy(() -> underTest.analyze(props)) .isInstanceOf(IllegalStateException.class) .hasMessage("Fail to export scanner properties"); } @@ -86,6 +86,6 @@ private void assertDump(Map props) throws IOException { try (FileInputStream fis = new FileInputStream(filename)) { p.load(fis); } - assertThat(p).isEqualTo(props); + assertThat(p).containsAllEntriesOf(props); } } diff --git a/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java b/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java index 9fc3e3ec..707fcc14 100644 --- a/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java +++ b/lib/src/test/java/org/sonarsource/scanner/lib/internal/http/ScannerHttpClientTest.java @@ -40,7 +40,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -104,14 +103,14 @@ void downloadFromWebApi_fails_on_url_validation(@TempDir Path tmpFolder) { } @Test - void should_throw_ISE_if_response_not_successful(@TempDir Path tmpFolder) { + void should_throw_HttpException_if_response_not_successful(@TempDir Path tmpFolder) { var toFile = tmpFolder.resolve("index.txt"); - answer(HELLO_WORLD, 400); + answer(HELLO_WORLD, 403); ScannerHttpClient underTest = create(); assertThatThrownBy(() -> underTest.downloadFromWebApi("/batch/index.txt", toFile)) - .isInstanceOf(IllegalStateException.class) - .hasMessage(format("Error status returned by url [http://%s:%d/batch/index.txt]: 400", "localhost", sonarqube.getPort())); + .isInstanceOf(HttpException.class) + .hasMessage("Forbidden"); } @Test diff --git a/lib/src/test/java/testutils/LogTester.java b/lib/src/test/java/testutils/LogTester.java index c12297f1..1fbd5d41 100644 --- a/lib/src/test/java/testutils/LogTester.java +++ b/lib/src/test/java/testutils/LogTester.java @@ -64,9 +64,14 @@ public List logs() { * a given level */ public List logs(Level level) { + return logEvents(level).stream() + .map(LoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } + + public List logEvents(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) .collect(Collectors.toList()); }