aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-css-plugin
diff options
context:
space:
mode:
authorElena Vilchik2019-12-27 11:49:52 +0100
committerGitHub2019-12-27 11:49:52 +0100
commit0c7fadc03cca985bfc5fbae3a29f29cc71866bac (patch)
treea68977c79532e0064f75c2657c2d3e76cbc49ccc /sonar-css-plugin
parente65fb9a64ef2b81c3741a63d6d4a046b37659628 (diff)
downloadsonar-css-0c7fadc03cca985bfc5fbae3a29f29cc71866bac.tar.bz2
Avoid error level logs when on project without CSS files (#231)
Diffstat (limited to 'sonar-css-plugin')
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssRuleSensor.java150
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/AnalyzerBridgeServer.java73
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServer.java98
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/bundle/Bundle.java3
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/ServerAlreadyFailedException.java27
-rw-r--r--sonar-css-plugin/src/main/java/org/sonar/css/plugin/server/exception/package-info.java21
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRuleSensorTest.java54
-rw-r--r--sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java50
-rw-r--r--sonar-css-plugin/src/test/resources/mock-start-server/startServer.js1
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)"}
]));