diff options
| author | Elena Vilchik | 2019-12-27 11:49:52 +0100 |
|---|---|---|
| committer | GitHub | 2019-12-27 11:49:52 +0100 |
| commit | 0c7fadc03cca985bfc5fbae3a29f29cc71866bac (patch) | |
| tree | a68977c79532e0064f75c2657c2d3e76cbc49ccc /sonar-css-plugin | |
| parent | e65fb9a64ef2b81c3741a63d6d4a046b37659628 (diff) | |
| download | sonar-css-0c7fadc03cca985bfc5fbae3a29f29cc71866bac.tar.bz2 | |
Avoid error level logs when on project without CSS files (#231)
Diffstat (limited to 'sonar-css-plugin')
9 files changed, 237 insertions, 240 deletions
diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java index 3b32501..db961df 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java @@ -37,7 +37,6 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; import org.sonar.api.SonarProduct; import org.sonar.api.batch.fs.FilePredicate; -import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.rule.CheckFactory; @@ -51,12 +50,10 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.css.plugin.CssRules.StylelintConfig; -import org.sonar.css.plugin.server.AnalyzerBridgeServer.Issue; -import org.sonar.css.plugin.server.AnalyzerBridgeServer.Request; import org.sonar.css.plugin.server.CssAnalyzerBridgeServer; -import org.sonar.css.plugin.server.exception.ServerAlreadyFailedException; +import org.sonar.css.plugin.server.CssAnalyzerBridgeServer.Issue; +import org.sonar.css.plugin.server.CssAnalyzerBridgeServer.Request; import org.sonarsource.analyzer.commons.ProgressReport; -import org.sonarsource.nodejs.NodeCommandException; public class CssRuleSensor implements Sensor { @@ -89,34 +86,34 @@ public class CssRuleSensor implements Sensor { public void execute(SensorContext context) { reportOldNodeProperty(context); - boolean failFast = context.config().getBoolean("sonar.internal.analysis.failFast").orElse(false); + List<InputFile> inputFiles = getInputFiles(context); + if (inputFiles.isEmpty()) { + LOG.info("No CSS, PHP or HTML files are found in the project. CSS analysis is skipped."); + return; + } + + File configFile = null; + boolean serverRunning = false; try { - List<InputFile> inputFiles = getInputFiles(context); - if (inputFiles.isEmpty()) { - LOG.info("No CSS, PHP or HTML files are found in the project. CSS analysis is skipped."); - } else { - cssAnalyzerBridgeServer.startServerLazily(context); - File configFile = createLinterConfig(context); - analyzeFiles(context, inputFiles, configFile); - } - } catch (CancellationException e) { - // do not propagate the exception - LOG.info(e.toString()); - } catch (ServerAlreadyFailedException e) { - LOG.debug("Skipping start of css-bundle server due to the failure during first analysis"); - LOG.debug("Skipping execution of CSS rules due to the problems with css-bundle server"); - } catch (NodeCommandException e) { - LOG.error(e.getMessage(), e); - reportAnalysisWarning("CSS rules were not executed. " + e.getMessage()); - if (failFast) { - throw new IllegalStateException("Analysis failed (\"sonar.internal.analysis.failFast\"=true)", e); - } + serverRunning = cssAnalyzerBridgeServer.startServerLazily(context); + configFile = createLinterConfig(context); } catch (Exception e) { - LOG.error("Failure during analysis, " + cssAnalyzerBridgeServer.getCommandInfo(), e); - if (failFast) { - throw new IllegalStateException("Analysis failed (\"sonar.internal.analysis.failFast\"=true)", e); - } + // we can end up here in the following cases: problem during bundle unpacking, or config file creation, or socket creation + String msg = "Failure during CSS analysis preparation, " + cssAnalyzerBridgeServer.getCommandInfo(); + logErrorOrWarn(context, msg, e); + throwFailFast(context, e); + } + + if (serverRunning && configFile != null) { + analyzeFiles(context, inputFiles, configFile); + } + } + + public static void throwFailFast(SensorContext context, Exception e) { + boolean failFast = context.config().getBoolean("sonar.internal.analysis.failFast").orElse(false); + if (failFast) { + throw new IllegalStateException("Analysis failed (\"sonar.internal.analysis.failFast\"=true)", e); } } @@ -128,34 +125,58 @@ public class CssRuleSensor implements Sensor { } } - void analyzeFiles(SensorContext context, List<InputFile> inputFiles, File configFile) throws InterruptedException, IOException { + private void analyzeFiles(SensorContext context, List<InputFile> inputFiles, File configFile) { ProgressReport progressReport = new ProgressReport("Analysis progress", TimeUnit.SECONDS.toMillis(10)); boolean success = false; + try { progressReport.start(inputFiles.stream().map(InputFile::toString).collect(Collectors.toList())); for (InputFile inputFile : inputFiles) { - if (context.isCancelled()) { - throw new CancellationException("Analysis interrupted because the SensorContext is in cancelled state"); - } - if (cssAnalyzerBridgeServer.isAlive()) { - try { - analyzeFile(context, inputFile, configFile); - } catch (IOException | RuntimeException e) { - throw new IOException("Failure during analysis of " + inputFile.uri() + ": " + e.getMessage(), e); - } - progressReport.nextFile(); - } else { - throw new IllegalStateException("css-bundle server is not answering"); - } + analyzeFileWithContextCheck(inputFile, context, configFile); + progressReport.nextFile(); } success = true; + + } catch (CancellationException e) { + // do not propagate the exception + LOG.info(e.toString()); + + } catch (Exception e) { + // we can end up here in the following cases: file analysis request sending, or response parsing, server is not answering + // or some unpredicted state + String msg = "Failure during CSS analysis, " + cssAnalyzerBridgeServer.getCommandInfo(); + logErrorOrWarn(context, msg, e); + throwFailFast(context, e); } finally { - if (success) { - progressReport.stop(); - } else { - progressReport.cancel(); - } + finishProgressReport(progressReport, success); + } + } + + private static void finishProgressReport(ProgressReport progressReport, boolean success) { + if (success) { + progressReport.stop(); + } else { + progressReport.cancel(); + } + try { progressReport.join(); + } catch (InterruptedException e) { + LOG.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + } + } + + void analyzeFileWithContextCheck(InputFile inputFile, SensorContext context, File configFile) throws IOException { + if (context.isCancelled()) { + throw new CancellationException("Analysis interrupted because the SensorContext is in cancelled state"); + } + if (!cssAnalyzerBridgeServer.isAlive()) { + throw new IllegalStateException("css-bundle server is not answering"); + } + try { + analyzeFile(context, inputFile, configFile); + } catch (IOException | RuntimeException e) { + throw new IllegalStateException("Failure during analysis of " + inputFile.uri(), e); } } @@ -187,9 +208,9 @@ public class CssRuleSensor implements Sensor { if (ruleKey == null) { if ("CssSyntaxError".equals(issue.rule)) { String errorMessage = issue.text.replace("(CssSyntaxError)", "").trim(); - LOG.error("Failed to parse {}, line {}, {}", inputFile.uri(), issue.line, errorMessage); + logErrorOrDebug(inputFile, "Failed to parse {}, line {}, {}", inputFile.uri(), issue.line, errorMessage); } else { - LOG.error("Unknown stylelint rule or rule not enabled: '" + issue.rule + "'"); + logErrorOrDebug(inputFile,"Unknown stylelint rule or rule not enabled: '" + issue.rule + "'"); } } else { @@ -206,16 +227,39 @@ public class CssRuleSensor implements Sensor { } } + private static void logErrorOrDebug(InputFile file, String msg, Object ... arguments) { + if (CssLanguage.KEY.equals(file.language())) { + LOG.error(msg, arguments); + } else { + LOG.debug(msg, arguments); + } + } + + private static void logErrorOrWarn(SensorContext context, String msg, Throwable e) { + if (hasCssFiles(context)) { + LOG.error(msg, e); + } else { + LOG.warn(msg); + } + } + private static List<InputFile> getInputFiles(SensorContext context) { FileSystem fileSystem = context.fileSystem(); - FilePredicates predicates = context.fileSystem().predicates(); - FilePredicate mainFilePredicate = predicates.and( + FilePredicate mainFilePredicate = fileSystem.predicates().and( fileSystem.predicates().hasType(InputFile.Type.MAIN), fileSystem.predicates().hasLanguages(CssLanguage.KEY, "php", "web")); return StreamSupport.stream(fileSystem.inputFiles(mainFilePredicate).spliterator(), false) .collect(Collectors.toList()); } + public static boolean hasCssFiles(SensorContext context) { + FileSystem fileSystem = context.fileSystem(); + FilePredicate mainFilePredicate = fileSystem.predicates().and( + fileSystem.predicates().hasType(InputFile.Type.MAIN), + fileSystem.predicates().hasLanguages(CssLanguage.KEY)); + return fileSystem.inputFiles(mainFilePredicate).iterator().hasNext(); + } + private File createLinterConfig(SensorContext context) throws IOException { StylelintConfig config = cssRules.getConfig(); final GsonBuilder gsonBuilder = new GsonBuilder(); diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/AnalyzerBridgeServer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/AnalyzerBridgeServer.java deleted file mode 100644 index 56481ec..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/AnalyzerBridgeServer.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.css.plugin.server; - -import java.io.IOException; -import javax.annotation.Nullable; -import org.sonar.api.Startable; -import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.scanner.ScannerSide; -import org.sonarsource.api.sonarlint.SonarLintSide; - -import static org.sonarsource.api.sonarlint.SonarLintSide.MULTIPLE_ANALYSES; - -@ScannerSide -@SonarLintSide(lifespan = MULTIPLE_ANALYSES) -public interface AnalyzerBridgeServer extends Startable { - - void startServerLazily(SensorContext context) throws IOException; - - Issue[] analyze(Request request) throws IOException; - - String getCommandInfo(); - - boolean isAlive(); - - class Request { - public final String filePath; - /** - * The fileContent is sent only in the SonarLint context or when the encoding - * of the file is not utf-8. Otherwise, for performance reason, it's more efficient to - * not have the fileContent and let the server getting it using filePath. - */ - @Nullable - public final String fileContent; - public final String configFile; - - public Request(String filePath, @Nullable String fileContent, String configFile) { - this.filePath = filePath; - this.fileContent = fileContent; - this.configFile = configFile; - } - } - - class Issue { - public final Integer line; - public final String rule; - public final String text; - - public Issue(Integer line, String rule, String text) { - this.line = line; - this.rule = rule; - this.text = text; - } - } - -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java index 05760f8..271d8f8 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java @@ -24,22 +24,31 @@ import com.google.gson.JsonSyntaxException; import java.io.File; import java.io.IOException; import java.time.Duration; +import javax.annotation.Nullable; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okhttp3.Response; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.api.scanner.ScannerSide; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; import org.sonar.css.plugin.server.bundle.Bundle; -import org.sonar.css.plugin.server.exception.ServerAlreadyFailedException; +import org.sonarsource.api.sonarlint.SonarLintSide; import org.sonarsource.nodejs.NodeCommand; import org.sonarsource.nodejs.NodeCommandBuilder; import org.sonarsource.nodejs.NodeCommandException; -public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { +import static org.sonar.css.plugin.CssRuleSensor.hasCssFiles; +import static org.sonar.css.plugin.CssRuleSensor.throwFailFast; +import static org.sonarsource.api.sonarlint.SonarLintSide.MULTIPLE_ANALYSES; + +@ScannerSide +@SonarLintSide(lifespan = MULTIPLE_ANALYSES) +public class CssAnalyzerBridgeServer { private static final Logger LOG = Loggers.get(CssAnalyzerBridgeServer.class); private static final Profiler PROFILER = Profiler.createIfDebug(LOG); @@ -53,28 +62,29 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { private final NodeCommandBuilder nodeCommandBuilder; final int timeoutSeconds; private final Bundle bundle; + private final AnalysisWarnings analysisWarnings; private int port; private NodeCommand nodeCommand; private boolean failedToStart; // Used by pico container for dependency injection @SuppressWarnings("unused") - public CssAnalyzerBridgeServer(Bundle bundle) { - this(NodeCommand.builder(), DEFAULT_TIMEOUT_SECONDS, bundle); + public CssAnalyzerBridgeServer(Bundle bundle, @Nullable AnalysisWarnings analysisWarnings) { + this(NodeCommand.builder(), DEFAULT_TIMEOUT_SECONDS, bundle, analysisWarnings); } - protected CssAnalyzerBridgeServer(NodeCommandBuilder nodeCommandBuilder, int timeoutSeconds, Bundle bundle) { + protected CssAnalyzerBridgeServer(NodeCommandBuilder nodeCommandBuilder, int timeoutSeconds, Bundle bundle, @Nullable AnalysisWarnings analysisWarnings) { this.nodeCommandBuilder = nodeCommandBuilder; this.timeoutSeconds = timeoutSeconds; this.bundle = bundle; + this.analysisWarnings = analysisWarnings; this.client = new OkHttpClient.Builder() .callTimeout(Duration.ofSeconds(timeoutSeconds)) .readTimeout(Duration.ofSeconds(timeoutSeconds)) .build(); } - // for testing purposes - public void deploy(File deployLocation) throws IOException { + public void deploy(File deployLocation) { bundle.deploy(deployLocation.toPath()); } @@ -121,27 +131,45 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { nodeCommand = nodeCommandBuilder.build(); } - @Override - public void startServerLazily(SensorContext context) throws IOException { + /** + * @return true when server is up and running normally, false otherwise + */ + public boolean startServerLazily(SensorContext context) throws IOException { // required for SonarLint context to avoid restarting already failed server if (failedToStart) { - throw new ServerAlreadyFailedException(); + LOG.debug("Skipping start of css-bundle server due to the failure during first analysis"); + LOG.debug("Skipping execution of CSS rules due to the problems with css-bundle server"); + return false; } try { if (isAlive()) { LOG.debug("css-bundle server is up, no need to start."); - return; + return true; } deploy(context.fileSystem().workDir()); startServer(context); + return true; } catch (NodeCommandException e) { failedToStart = true; - throw e; + processNodeCommandException(e, context); + return false; } } - @Override + // happens for example when NodeJS is not available, or version is too old + private void processNodeCommandException(NodeCommandException e, SensorContext context) { + String message = "CSS rules were not executed. " + e.getMessage(); + if (hasCssFiles(context)) { + LOG.error(message, e); + reportAnalysisWarning(message); + } else { + // error logs are often blocking (esp. in Azure), so we log at warning level if there is no CSS files in the project + LOG.warn(message); + } + throwFailFast(context, e); + } + public Issue[] analyze(Request request) throws IOException { String json = GSON.toJson(request); return parseResponse(request(json)); @@ -164,8 +192,8 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { return GSON.fromJson(result, Issue[].class); } catch (JsonSyntaxException e) { String msg = "Failed to parse response: \n-----\n" + result + "\n-----\n"; - LOG.error(msg, e); - throw new IllegalStateException("Failed to parse response", e); + LOG.debug(msg); + throw new IllegalStateException("Failed to parse response (check DEBUG logs for the response content)", e); } } @@ -183,12 +211,11 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { // in this case response.body() is never null (according to docs) return "OK!".equals(body); } catch (IOException e) { - LOG.error("Error requesting server status. Server is probably dead.", e); + LOG.warn("Error requesting server status. Server is probably dead.", e); return false; } } - @Override public String getCommandInfo() { if (nodeCommand == null) { return "Node.js command to start css-bundle server was not built yet."; @@ -197,12 +224,10 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { } } - @Override public void start() { // Server is started lazily by the sensor } - @Override public void stop() { clean(); } @@ -229,4 +254,39 @@ public class CssAnalyzerBridgeServer implements AnalyzerBridgeServer { this.port = port; } + private void reportAnalysisWarning(String message) { + if (analysisWarnings != null) { + analysisWarnings.addUnique(message); + } + } + + public static class Request { + public final String filePath; + /** + * The fileContent is sent only in the SonarLint context or when the encoding + * of the file is not utf-8. Otherwise, for performance reason, it's more efficient to + * not have the fileContent and let the server getting it using filePath. + */ + @Nullable + public final String fileContent; + public final String configFile; + + public Request(String filePath, @Nullable String fileContent, String configFile) { + this.filePath = filePath; + this.fileContent = fileContent; + this.configFile = configFile; + } + } + + public static class Issue { + public final Integer line; + public final String rule; + public final String text; + + public Issue(Integer line, String rule, String text) { + this.line = line; + this.rule = rule; + this.text = text; + } + } } diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java index 513131f..ef82a2b 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java @@ -19,12 +19,11 @@ */ package org.sonar.css.plugin.server.bundle; -import java.io.IOException; import java.nio.file.Path; public interface Bundle { - void deploy(Path deployLocation) throws IOException; + void deploy(Path deployLocation); /** * should be called after deploy(Path deployLocation) diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/ServerAlreadyFailedException.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/ServerAlreadyFailedException.java deleted file mode 100644 index 0865aa4..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/ServerAlreadyFailedException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.css.plugin.server.exception; - -/** - * This exception is required to inform sensor about analyzer bridge server start up failure in SonarLint - * It is required to not try to start it again - */ -public class ServerAlreadyFailedException extends RuntimeException { -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/package-info.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/package-info.java deleted file mode 100644 index 4f5b753..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SonarCSS - * Copyright (C) 2018-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -@javax.annotation.ParametersAreNonnullByDefault -package org.sonar.css.plugin.server.exception; diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java index 0b37c92..429722d 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java @@ -27,7 +27,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -149,28 +148,13 @@ public class CssRuleSensorTest { } @Test - public void bridge_server_fail_to_start() { - CssAnalyzerBridgeServer badServer = createCssAnalyzerBridgeServer("throw.js"); - sensor = new CssRuleSensor(CHECK_FACTORY, badServer, analysisWarnings); - addInputFile("file.css"); - sensor.execute(context); - assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to start server (1s timeout)"); - - assertThat(logTester.logs(LoggerLevel.DEBUG)) - .doesNotContain("Skipping start of css-bundle server due to the failure during first analysis"); - sensor.execute(context); - assertThat(logTester.logs(LoggerLevel.DEBUG)) - .contains("Skipping start of css-bundle server due to the failure during first analysis"); - } - - @Test public void should_log_when_bridge_server_receives_invalid_response() { addInputFile("invalid-json-response.css"); - addInputFile("file.css"); sensor.execute(context); - assertThat(String.join("\n", logTester.logs(LoggerLevel.ERROR))) + assertThat(String.join("\n", logTester.logs(LoggerLevel.DEBUG))) .contains("Failed to parse response"); - assertThat(context.allIssues()).hasSize(1); + assertThat(String.join("\n", logTester.logs(LoggerLevel.ERROR))) + .contains("Failure during CSS analysis"); } @Test @@ -183,6 +167,32 @@ public class CssRuleSensorTest { assertThatThrownBy(() -> sensor.execute(context)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Analysis failed"); + assertThat(logTester.logs(LoggerLevel.ERROR)).contains("CSS rules were not executed. Failed to start server (1s timeout)"); + } + + @Test + public void should_fail_fast_when_server_fail_to_start_no_css() { + context.settings().setProperty("sonar.internal.analysis.failFast", "true"); + CssAnalyzerBridgeServer badServer = createCssAnalyzerBridgeServer("throw.js"); + sensor = new CssRuleSensor(CHECK_FACTORY, badServer, analysisWarnings); + addInputFile("file.web"); + + assertThatThrownBy(() -> sensor.execute(context)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Analysis failed"); + assertThat(logTester.logs(LoggerLevel.WARN)).contains("CSS rules were not executed. Failed to start server (1s timeout)"); + } + + @Test + public void should_not_fail_fast_when_server_fail_to_start_without_property() { + context.settings().setProperty("sonar.internal.analysis.failFast", "false"); + CssAnalyzerBridgeServer badServer = createCssAnalyzerBridgeServer("throw.js"); + sensor = new CssRuleSensor(CHECK_FACTORY, badServer, analysisWarnings); + addInputFile("file.web"); + + sensor.execute(context); + // log as Warning level is there is no CSS files + assertThat(logTester.logs(LoggerLevel.WARN)).contains("CSS rules were not executed. Failed to start server (1s timeout)"); } @Test @@ -207,13 +217,13 @@ public class CssRuleSensorTest { } @Test - public void analysis_stop_when_server_is_not_anymore_alive() throws IOException, InterruptedException { + public void analysis_stop_when_server_is_not_anymore_alive() { File configFile = new File("config.json"); DefaultInputFile inputFile = addInputFile("dir/file.css"); sensor.execute(context); cssAnalyzerBridgeServer.setPort(43); - assertThatThrownBy(() -> sensor.analyzeFiles(context, Collections.singletonList(inputFile), configFile)) + assertThatThrownBy(() -> sensor.analyzeFileWithContextCheck(inputFile, context, configFile)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("css-bundle server is not answering"); } @@ -247,9 +257,11 @@ public class CssRuleSensorTest { @Test public void test_syntax_error() { InputFile inputFile = addInputFile("syntax-error.css"); + InputFile inputFileNotCss = addInputFile("syntax-error.web"); sensor.execute(context); assertThat(context.allIssues()).isEmpty(); assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse " + inputFile.uri() + ", line 2, Missed semicolon"); + assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Failed to parse " + inputFileNotCss.uri() + ", line 2, Missed semicolon"); } @Test diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java index 99b14ff..3d42261 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java @@ -28,12 +28,12 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.utils.internal.JUnitTempFolder; import org.sonar.api.utils.log.LogTester; +import org.sonar.css.plugin.server.CssAnalyzerBridgeServer.Issue; +import org.sonar.css.plugin.server.CssAnalyzerBridgeServer.Request; import org.sonar.css.plugin.server.bundle.Bundle; -import org.sonar.css.plugin.server.AnalyzerBridgeServer.Issue; -import org.sonar.css.plugin.server.AnalyzerBridgeServer.Request; -import org.sonar.css.plugin.server.exception.ServerAlreadyFailedException; import org.sonarsource.nodejs.NodeCommand; import org.sonarsource.nodejs.NodeCommandBuilder; import org.sonarsource.nodejs.NodeCommandException; @@ -41,6 +41,7 @@ import org.sonarsource.nodejs.NodeCommandException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.sonar.api.utils.log.LoggerLevel.DEBUG; import static org.sonar.api.utils.log.LoggerLevel.ERROR; import static org.sonar.api.utils.log.LoggerLevel.INFO; @@ -79,7 +80,7 @@ public class CssAnalyzerBridgeServerTest { @Test public void default_timeout() { - CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(mock(Bundle.class)); + CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(mock(Bundle.class), null); assertThat(server.timeoutSeconds).isEqualTo(60); } @@ -102,7 +103,11 @@ public class CssAnalyzerBridgeServerTest { } @Test - public void should_throw_if_failed_to_build_node_command() throws Exception { + public void should_return_false_if_failed_to_build_node_command() throws Exception { + context.fileSystem().add(new TestInputFileBuilder("moduleKey", "file.css") + .setLanguage("css") + .build()); + NodeCommandBuilder nodeCommandBuilder = mock(NodeCommandBuilder.class, invocation -> { if (NodeCommandBuilder.class.equals(invocation.getMethod().getReturnType())) { return invocation.getMock(); @@ -111,12 +116,11 @@ public class CssAnalyzerBridgeServerTest { } }); - cssAnalyzerBridgeServer = new CssAnalyzerBridgeServer(nodeCommandBuilder, TEST_TIMEOUT_SECONDS, new TestBundle(START_SERVER_SCRIPT)); - - thrown.expect(NodeCommandException.class); - thrown.expectMessage("msg"); - - cssAnalyzerBridgeServer.startServerLazily(context); + AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); + cssAnalyzerBridgeServer = new CssAnalyzerBridgeServer(nodeCommandBuilder, TEST_TIMEOUT_SECONDS, new TestBundle(START_SERVER_SCRIPT), analysisWarnings); + assertThat(cssAnalyzerBridgeServer.startServerLazily(context)).isFalse(); + assertThat(logTester.logs(ERROR)).contains("CSS rules were not executed. msg"); + verify(analysisWarnings).addUnique("CSS rules were not executed. msg"); } @Test @@ -150,11 +154,8 @@ public class CssAnalyzerBridgeServerTest { @Test public void should_throw_if_failed_to_start() throws Exception { cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer("throw.js"); - - thrown.expect(NodeCommandException.class); - thrown.expectMessage("Failed to start server (" + TEST_TIMEOUT_SECONDS + "s timeout)"); - - cssAnalyzerBridgeServer.startServerLazily(context); + assertThat(cssAnalyzerBridgeServer.startServerLazily(context)).isFalse(); + assertThat(logTester.logs(WARN)).contains("CSS rules were not executed. Failed to start server (" + TEST_TIMEOUT_SECONDS + "s timeout)"); } @Test @@ -200,15 +201,16 @@ public class CssAnalyzerBridgeServerTest { } @Test - public void should_throw_special_exception_when_failed_already() throws Exception { + public void should_return_false_and_log_when_failed_already() throws Exception { cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer("throw.js"); - String failedToStartExceptionMessage = "Failed to start server (" + TEST_TIMEOUT_SECONDS + "s timeout)"; - assertThatThrownBy(() -> cssAnalyzerBridgeServer.startServerLazily(context)) - .isInstanceOf(NodeCommandException.class) - .hasMessage(failedToStartExceptionMessage); + String failedToStartExceptionMessage = "CSS rules were not executed. Failed to start server (" + TEST_TIMEOUT_SECONDS + "s timeout)"; + + assertThat(cssAnalyzerBridgeServer.startServerLazily(context)).isFalse(); + assertThat(logTester.logs(WARN)).contains(failedToStartExceptionMessage); - assertThatThrownBy(() -> cssAnalyzerBridgeServer.startServerLazily(context)) - .isInstanceOf(ServerAlreadyFailedException.class); + assertThat(cssAnalyzerBridgeServer.startServerLazily(context)).isFalse(); + assertThat(logTester.logs(DEBUG)).contains("Skipping start of css-bundle server due to the failure during first analysis", + "Skipping execution of CSS rules due to the problems with css-bundle server"); } @Test @@ -226,7 +228,7 @@ public class CssAnalyzerBridgeServerTest { public static CssAnalyzerBridgeServer createCssAnalyzerBridgeServer(String startServerScript) { - CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(NodeCommand.builder(), TEST_TIMEOUT_SECONDS, new TestBundle(startServerScript)); + CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(NodeCommand.builder(), TEST_TIMEOUT_SECONDS, new TestBundle(startServerScript), null); server.start(); return server; } diff --git a/sonar-css-plugin/src/test/resources/mock-start-server/startServer.js b/sonar-css-plugin/src/test/resources/mock-start-server/startServer.js index f7f40c5..87298a9 100644 --- a/sonar-css-plugin/src/test/resources/mock-start-server/startServer.js +++ b/sonar-css-plugin/src/test/resources/mock-start-server/startServer.js @@ -38,6 +38,7 @@ const requestHandler = (request, response) => { response.end(JSON.stringify([])); break; case "syntax-error.css": + case "syntax-error.web": response.end(JSON.stringify([ {line: 2, rule: "CssSyntaxError", text: "Missed semicolon (CssSyntaxError)"} ])); |
