diff options
11 files changed, 114 insertions, 304 deletions
| @@ -123,6 +123,11 @@                  <artifactId>gson</artifactId>                  <version>${gson.version}</version>              </dependency> +            <dependency> +                <groupId>org.sonarsource.javascript</groupId> +                <artifactId>nodejs-utils</artifactId> +                <version>5.1.1.7506</version> +            </dependency>              <!-- Test dependencies -->              <dependency> diff --git a/sonar-css-plugin/pom.xml b/sonar-css-plugin/pom.xml index 4a6a213..a1645e5 100644 --- a/sonar-css-plugin/pom.xml +++ b/sonar-css-plugin/pom.xml @@ -37,6 +37,10 @@              <artifactId>sonar-analyzer-commons</artifactId>          </dependency>          <dependency> +            <groupId>org.sonarsource.javascript</groupId> +            <artifactId>nodejs-utils</artifactId> +        </dependency> +        <dependency>              <groupId>junit</groupId>              <artifactId>junit</artifactId>          </dependency> @@ -127,8 +131,8 @@                          <configuration>                              <rules>                                  <requireFilesSize> -                                    <minsize>6500000</minsize> -                                    <maxsize>7000000</maxsize> +                                    <minsize>6800000</minsize> +                                    <maxsize>8000000</maxsize>                                      <files>                                          <file>${project.build.directory}/${project.build.finalName}.jar</file>                                      </files> @@ -167,6 +171,16 @@              </plugin>              <plugin> +                <artifactId>maven-surefire-plugin</artifactId> +                <configuration> +                    <environmentVariables> +                        <!-- modify the path to include node installed with frontend plugin --> +                        <PATH>target${file.separator}node${path.separator}${env.PATH}</PATH> +                    </environmentVariables> +                </configuration> +            </plugin> + +            <plugin>                  <artifactId>maven-assembly-plugin</artifactId>                  <version>3.0.0</version>                  <configuration> diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java index 6f78be1..9e78205 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/CssPlugin.java @@ -37,7 +37,7 @@ public class CssPlugin implements Plugin {    public static final String STYLELINT_REPORT_PATHS = "sonar.css.stylelint.reportPaths";    public static final String STYLELINT_REPORT_PATHS_DEFAULT_VALUE = ""; -  public static final String NODE_EXECUTABLE = "sonar.css.node"; +  public static final String FORMER_NODE_EXECUTABLE = "sonar.css.node";    private static final String CSS_CATEGORY = "CSS";    private static final String LINTER_SUBCATEGORY = "Popular Rule Engines"; @@ -66,14 +66,6 @@ public class CssPlugin implements Plugin {          .category(CSS_CATEGORY)          .onQualifiers(Qualifiers.PROJECT)          .multiValues(true) -        .build(), - -      PropertyDefinition.builder(NODE_EXECUTABLE) -        .name("Node.js executable") -        .description("Path to the Node.js executable that will be used to run the analysis of CSS files. When not set, expects 'node' to be in the path.") -        .subCategory(GENERAL_SUBCATEGORY) -        .category(CSS_CATEGORY) -        .hidden()          .build()      ); 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 2b268cd..82e6306 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 @@ -31,7 +31,6 @@ import java.util.Collections;  import java.util.regex.Matcher;  import java.util.regex.Pattern;  import javax.annotation.Nullable; -import org.apache.commons.io.IOUtils;  import org.sonar.api.batch.fs.FileSystem;  import org.sonar.api.batch.fs.InputFile;  import org.sonar.api.batch.rule.CheckFactory; @@ -47,33 +46,36 @@ import org.sonar.css.plugin.CssRules.StylelintConfig;  import org.sonar.css.plugin.StylelintReport.Issue;  import org.sonar.css.plugin.StylelintReport.IssuesPerFile;  import org.sonar.css.plugin.bundle.BundleHandler; +import org.sonarsource.nodejs.NodeCommand; +import org.sonarsource.nodejs.NodeCommandException;  public class CssRuleSensor implements Sensor {    private static final Logger LOG = Loggers.get(CssRuleSensor.class); -  private static final int MIN_NODE_VERSION = 6; -  private static final String WARNING_PREFIX = "CSS files were not analyzed. ";    private final BundleHandler bundleHandler;    private final CssRules cssRules;    private final LinterCommandProvider linterCommandProvider;    @Nullable    private final AnalysisWarningsWrapper analysisWarnings; -  private final ExternalProcessStreamConsumer externalProcessStreamConsumer = new ExternalProcessStreamConsumer(); -  public CssRuleSensor(BundleHandler bundleHandler, -                       CheckFactory checkFactory, -                       LinterCommandProvider linterCommandProvider, -                       @Nullable AnalysisWarningsWrapper analysisWarnings) { +  public CssRuleSensor( +    BundleHandler bundleHandler, +    CheckFactory checkFactory, +    LinterCommandProvider linterCommandProvider, +    @Nullable AnalysisWarningsWrapper analysisWarnings +  ) {      this.bundleHandler = bundleHandler;      this.linterCommandProvider = linterCommandProvider;      this.cssRules = new CssRules(checkFactory);      this.analysisWarnings = analysisWarnings;    } -  public CssRuleSensor(BundleHandler bundleHandler, -                       CheckFactory checkFactory, -                       LinterCommandProvider linterCommandProvider) { +  public CssRuleSensor( +    BundleHandler bundleHandler, +    CheckFactory checkFactory, +    LinterCommandProvider linterCommandProvider +  ) {      this(bundleHandler, checkFactory, linterCommandProvider, null);    } @@ -86,39 +88,38 @@ public class CssRuleSensor implements Sensor {    @Override    public void execute(SensorContext context) { +    // fixme add log and UI warn when old property is provided +      if (cssRules.isEmpty()) {        LOG.warn("No rules are activated in CSS Quality Profile");        return;      } -    if (!checkCompatibleNodeVersion(context)) { -      return; -    } -      File deployDestination = context.fileSystem().workDir(); -    bundleHandler.deployBundle(deployDestination); -    String[] commandParts = linterCommandProvider.commandParts(deployDestination, context);      try { -      ProcessBuilder processBuilder = new ProcessBuilder(commandParts); +      bundleHandler.deployBundle(deployDestination);        createLinterConfig(deployDestination); -      Process process = processBuilder.start();        StringBuilder output = new StringBuilder(); -      externalProcessStreamConsumer.consumeStream(process.getInputStream(), output::append); -      externalProcessStreamConsumer.consumeStream(process.getErrorStream(), LOG::error); -      if (isSuccessful(process)) { + +      NodeCommand nodeCommand = linterCommandProvider.nodeCommand(deployDestination, context, output::append, LOG::error); +      LOG.debug("Starting process: " + nodeCommand.toString()); +      nodeCommand.start(); + +      if (isSuccessful(nodeCommand.waitFor())) {          saveIssues(context, output.toString());        } +    } catch (NodeCommandException e) { +      LOG.error(e.getMessage() + " No CSS files will be analyzed.", e); +      if (analysisWarnings != null) { +        analysisWarnings.addUnique("CSS files were not analyzed. " + e.getMessage()); +      }      } catch (Exception e) { -      LOG.error("Failed to run external linting process " + String.join(" ", commandParts), e); -    } finally { -      externalProcessStreamConsumer.shutdownNow(); +      LOG.error("Failed to run external linting process", e);      }    } -  private boolean isSuccessful(Process process) throws InterruptedException { -    int exitValue = process.waitFor(); -    externalProcessStreamConsumer.await(); +  private boolean isSuccessful(int exitValue) {      // exit codes 0 and 2 are expected. 0 - means no issues were found, 2 - means that at least one "error-level" rule found issue      // see https://github.com/stylelint/stylelint/blob/master/docs/user-guide/cli.md#exit-codes      boolean isSuccessful = exitValue == 0 || exitValue == 2; @@ -128,48 +129,6 @@ public class CssRuleSensor implements Sensor {      return isSuccessful;    } -  private boolean checkCompatibleNodeVersion(SensorContext context) { -    String nodeExecutable = linterCommandProvider.nodeExecutable(context.config()); -    LOG.debug("Checking node version"); -    String messageSuffix = "No CSS files will be analyzed."; - -    String version; -    try { -      Process process = Runtime.getRuntime().exec(nodeExecutable + " -v"); -      version = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8).trim(); -    } catch (Exception e) { -      LOG.error("Failed to get Node.js version. " + messageSuffix, e); -      if (analysisWarnings != null) { -        analysisWarnings.addUnique(WARNING_PREFIX + "Node.js version could not be detected using command: " + nodeExecutable + " -v"); -      } -      return false; -    } - -    Pattern versionPattern = Pattern.compile("v?(\\d+)\\.\\d+\\.\\d+"); -    Matcher versionMatcher = versionPattern.matcher(version); -    if (versionMatcher.matches()) { -      int major = Integer.parseInt(versionMatcher.group(1)); -      if (major < MIN_NODE_VERSION) { -        String message = String.format("Only Node.js v%s or later is supported, got %s.", MIN_NODE_VERSION, version); -        LOG.error(message + ' ' + messageSuffix); -        if (analysisWarnings != null) { -          analysisWarnings.addUnique(WARNING_PREFIX + message); -        } -        return false; -      } -    } else { -      String message = String.format("Failed to parse Node.js version, got '%s'.", version); -      LOG.error(message + ' ' + messageSuffix); -      if (analysisWarnings != null) { -        analysisWarnings.addUnique(WARNING_PREFIX + message); -      } -      return false; -    } - -    LOG.debug(String.format("Using Node.js %s", version)); -    return true; -  } -    private void createLinterConfig(File deployDestination) throws IOException {      String configPath = linterCommandProvider.configPath(deployDestination);      StylelintConfig config = cssRules.getConfig(); diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java deleted file mode 100644 index ef843c4..0000000 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/ExternalProcessStreamConsumer.java +++ /dev/null @@ -1,68 +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; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -class ExternalProcessStreamConsumer { - -  private static final Logger LOG = Loggers.get(ExternalProcessStreamConsumer.class); -  private ExecutorService executorService; - -  ExternalProcessStreamConsumer() { -    executorService = Executors.newCachedThreadPool(r -> { -      Thread thread = new Thread(r); -      thread.setName("nodejs-stream-consumer"); -      thread.setDaemon(true); -      return thread; -    }); -  } - -  void consumeStream(InputStream inputStream, Consumer<String> consumer) { -    executorService.submit(() -> { -      try (BufferedReader errorReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { -        errorReader.lines().forEach(consumer); -      } catch (IOException e) { -        LOG.error("Error while reading stream", e); -      } -    }); -  } - -  void await() throws InterruptedException { -    executorService.shutdown(); -    if (!executorService.awaitTermination(5, TimeUnit.MINUTES)) { -      LOG.error("External process stream consumer timed out"); -    } -  } - -  void shutdownNow() { -    executorService.shutdownNow(); -  } -} diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java index 133e9a4..74a343c 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/LinterCommandProvider.java @@ -20,14 +20,14 @@  package org.sonar.css.plugin;  import java.io.File; +import java.util.function.Consumer;  import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.config.Configuration; +import org.sonarsource.nodejs.NodeCommand;  public interface LinterCommandProvider { -  String[] commandParts(File deployDestination, SensorContext context); +  NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error);    String configPath(File deployDestination); -  String nodeExecutable(Configuration configuration);  } diff --git a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java index 0265c20..90d3ae8 100644 --- a/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java +++ b/sonar-css-plugin/src/main/java/org/sonar/css/plugin/StylelintCommandProvider.java @@ -21,38 +21,43 @@ package org.sonar.css.plugin;  import java.io.File;  import java.nio.file.Paths; -import java.util.Optional; +import java.util.function.Consumer;  import org.sonar.api.batch.ScannerSide;  import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.config.Configuration; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; +import org.sonarsource.nodejs.NodeCommand; +import org.sonarsource.nodejs.NodeCommandException;  @ScannerSide  public class StylelintCommandProvider implements LinterCommandProvider { -  private static final Logger LOG = Loggers.get(StylelintCommandProvider.class); -    private static final String CONFIG_PATH = "css-bundle/stylelintconfig.json"; -  private static final String NODE_EXECUTABLE_DEFAULT = "node"; - -  private String nodeExecutable = null;    @Override -  public String[] commandParts(File deployDestination, SensorContext context) { +  public NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error) {      String projectBaseDir = context.fileSystem().baseDir().getAbsolutePath();      String[] suffixes = context.config().getStringArray(CssPlugin.FILE_SUFFIXES_KEY);      String filesGlob = "**" + File.separator + "*{" + String.join(",", suffixes) + "}";      String filesToAnalyze = Paths.get(projectBaseDir, "TOREPLACE").toString();      filesToAnalyze = filesToAnalyze.replace("TOREPLACE", filesGlob); -    return new String[]{ -      nodeExecutable(context.config()), +    String[] args = {        new File(deployDestination, "css-bundle/node_modules/stylelint/bin/stylelint").getAbsolutePath(),        filesToAnalyze,        "--config", new File(deployDestination, CONFIG_PATH).getAbsolutePath(),        "-f", "json"      }; + +    try { +      return NodeCommand.builder() +        .outputConsumer(output) +        .errorConsumer(error) +        .minNodeVersion(6) +        .configuration(context.config()) +        .nodeJsArgs(args) +        .build(); +    } catch (IllegalArgumentException e) { +      throw new NodeCommandException(e.getMessage(), e); +    }    }    @Override @@ -60,27 +65,4 @@ public class StylelintCommandProvider implements LinterCommandProvider {      return new File(deployDestination, CONFIG_PATH).getAbsolutePath();    } -  @Override -  public String nodeExecutable(Configuration configuration) { -    if (nodeExecutable == null) { -      nodeExecutable = retrieveNodeExecutableFromConfig(configuration); -    } - -    return nodeExecutable; -  } - -  private static String retrieveNodeExecutableFromConfig(Configuration configuration) { -    Optional<String> nodeExecutableOptional = configuration.get(CssPlugin.NODE_EXECUTABLE); -    if (nodeExecutableOptional.isPresent()) { -      String nodeExecutable = nodeExecutableOptional.get(); -      File file = new File(nodeExecutable); -      if (file.exists()) { -        return nodeExecutable; -      } - -      LOG.warn("Provided node executable file does not exist: " + file + ". Fallback to using 'node' from path."); -    } - -    return NODE_EXECUTABLE_DEFAULT; -  }  } diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java index b8c5cd4..792f065 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssPluginTest.java @@ -36,7 +36,7 @@ public class CssPluginTest {      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); -    assertThat(context.getExtensions()).hasSize(11); +    assertThat(context.getExtensions()).hasSize(10);    }    @Test @@ -45,7 +45,7 @@ public class CssPluginTest {      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); -    assertThat(context.getExtensions()).hasSize(12); +    assertThat(context.getExtensions()).hasSize(11);    }    @Test @@ -54,7 +54,7 @@ public class CssPluginTest {      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); -    assertThat(context.getExtensions()).hasSize(13); +    assertThat(context.getExtensions()).hasSize(12);    }    @Test @@ -63,6 +63,6 @@ public class CssPluginTest {      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); -    assertThat(context.getExtensions()).hasSize(12); +    assertThat(context.getExtensions()).hasSize(11);    }  } 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 df3d034..f7f0d31 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,6 +27,7 @@ import java.nio.file.Files;  import java.nio.file.Path;  import java.nio.file.Paths;  import java.util.concurrent.TimeUnit; +import java.util.function.Consumer;  import javax.annotation.Nullable;  import org.awaitility.Awaitility;  import org.junit.Before; @@ -42,16 +43,16 @@ import org.sonar.api.batch.sensor.SensorContext;  import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;  import org.sonar.api.batch.sensor.internal.SensorContextTester;  import org.sonar.api.batch.sensor.issue.Issue; -import org.sonar.api.config.Configuration;  import org.sonar.api.utils.log.LogTester;  import org.sonar.api.utils.log.LoggerLevel;  import org.sonar.css.plugin.bundle.BundleHandler;  import org.sonar.css.plugin.bundle.CssBundleHandler; +import org.sonarsource.nodejs.NodeCommand; +import org.sonarsource.nodejs.NodeCommandException;  import static org.assertj.core.api.Assertions.assertThat;  import static org.awaitility.Awaitility.await;  import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.matches;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.verifyZeroInteractions; @@ -107,37 +108,12 @@ public class CssRuleSensorTest {    @Test    public void test_invalid_node() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); +    InvalidCommandProvider commandProvider = new InvalidCommandProvider();      CssRuleSensor sensor = createCssRuleSensor(commandProvider);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse Node.js version, got 'Invalid version'. No CSS files will be analyzed."); -    verifyZeroInteractions(analysisWarnings); -  } - -  @Test -  public void test_no_node() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable = TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); -    sensor.execute(context); - -    assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to get Node.js version. No CSS files will be analyzed."); -    verifyZeroInteractions(analysisWarnings); -  } - -  @Test -  public void test_old_node() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/oldNodeVersion.js"); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); -    sensor.execute(context); - -    assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Only Node.js v6 or later is supported, got 3.2.1. No CSS files will be analyzed."); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Some problem happened. No CSS files will be analyzed.");      verifyZeroInteractions(analysisWarnings);    } @@ -157,39 +133,14 @@ public class CssRuleSensorTest {    }    @Test -  public void test_invalid_node_with_analysisWarnings() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); -    sensor.execute(context); - -    assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse Node.js version, got 'Invalid version'. No CSS files will be analyzed."); -    verify(analysisWarnings).addUnique(eq("CSS files were not analyzed. Failed to parse Node.js version, got 'Invalid version'.")); -  } - -  @Test -  public void test_no_node_with_analysisWarnings() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable = TestLinterCommandProvider.resourceScript("/executables/invalidNodeVersion.js"); +  public void test_invalid_node_command_with_analysisWarnings() { +    InvalidCommandProvider commandProvider = new InvalidCommandProvider();      CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings);      sensor.execute(context);      assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to get Node.js version. No CSS files will be analyzed."); -    verify(analysisWarnings).addUnique(matches("CSS files were not analyzed. Node.js version could not be detected using command: .* -v")); -  } - -  @Test -  public void test_old_node_with_analysisWarnings() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    commandProvider.nodeExecutable += " " + TestLinterCommandProvider.resourceScript("/executables/oldNodeVersion.js"); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); -    sensor.execute(context); - -    assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Only Node.js v6 or later is supported, got 3.2.1. No CSS files will be analyzed."); -    verify(analysisWarnings).addUnique(eq("CSS files were not analyzed. Only Node.js v6 or later is supported, got 3.2.1.")); +    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Some problem happened. No CSS files will be analyzed."); +    verify(analysisWarnings).addUnique(eq("CSS files were not analyzed. Some problem happened."));    }    @Test @@ -267,11 +218,11 @@ public class CssRuleSensorTest {      return inputFile;    } -  private CssRuleSensor createCssRuleSensor(TestLinterCommandProvider commandProvider) { +  private CssRuleSensor createCssRuleSensor(LinterCommandProvider commandProvider) {      return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider);    } -  private CssRuleSensor createCssRuleSensor(TestLinterCommandProvider commandProvider, @Nullable AnalysisWarningsWrapper analysisWarnings) { +  private CssRuleSensor createCssRuleSensor(LinterCommandProvider commandProvider, @Nullable AnalysisWarningsWrapper analysisWarnings) {      return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider, analysisWarnings);    } @@ -281,20 +232,8 @@ public class CssRuleSensorTest {    private static class TestLinterCommandProvider implements LinterCommandProvider { -    String nodeExecutable = findNodeExecutable(); -      private String[] elements; -    private static String findNodeExecutable() { -      try { -        String nodeFromMavenPlugin = "target/node/node"; -        Runtime.getRuntime().exec(nodeFromMavenPlugin); -        return nodeFromMavenPlugin; -      } catch (IOException e) { -        return "node"; -      } -    } -      private static String resourceScript(String script) {        try {          return new File(TestLinterCommandProvider.class.getResource(script).toURI()).getAbsolutePath(); @@ -304,31 +243,45 @@ public class CssRuleSensorTest {      }      TestLinterCommandProvider nodeScript(String script, String args) { -      this.elements = new String[]{ nodeExecutable, resourceScript(script), args}; +      this.elements = new String[]{ resourceScript(script), args};        return this;      }      @Override -    public String[] commandParts(File deployDestination, SensorContext context) { -      return elements; +    public NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error) { +      return NodeCommand.builder() +        .outputConsumer(output) +        .errorConsumer(error) +        .minNodeVersion(6) +        .configuration(context.config()) +        .nodeJsArgs(elements) +        .build();      }      @Override      public String configPath(File deployDestination) {        return new File(deployDestination, "testconfig.json").getAbsolutePath();      } +  } + +  private static class InvalidCommandProvider implements LinterCommandProvider { + +    @Override +    public NodeCommand nodeCommand(File deployDestination, SensorContext context, Consumer<String> output, Consumer<String> error) { +      throw new NodeCommandException("Some problem happened."); +    }      @Override -    public String nodeExecutable(Configuration configuration) { -      return nodeExecutable; +    public String configPath(File deployDestination) { +      return new File(deployDestination, "testconfig.json").getAbsolutePath();      }    }    private static class TestBundleHandler implements BundleHandler { -      @Override      public void deployBundle(File deployDestination) {        // do nothing      }    } +  } diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintCommandProviderTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintCommandProviderTest.java index 1ae9a1d..9251fe0 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintCommandProviderTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintCommandProviderTest.java @@ -20,13 +20,13 @@  package org.sonar.css.plugin;  import java.io.File; +import java.util.function.Consumer;  import org.junit.Rule;  import org.junit.Test;  import org.junit.rules.TemporaryFolder;  import org.sonar.api.batch.sensor.internal.SensorContextTester; -import org.sonar.api.config.internal.MapSettings;  import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; +import org.sonarsource.nodejs.NodeCommand;  import static org.assertj.core.api.Assertions.assertThat; @@ -39,48 +39,22 @@ public class StylelintCommandProviderTest {    public final LogTester logTester = new LogTester();    @Test -  public void test() throws Exception { +  public void test() {      StylelintCommandProvider stylelintCommandProvider = new StylelintCommandProvider();      File deployDestination = new File("deploy_destination");      File baseDir = new File("src/test/resources").getAbsoluteFile();      SensorContextTester context = SensorContextTester.create(baseDir);      context.settings().setProperty(CssPlugin.FILE_SUFFIXES_KEY, ".foo,.bar"); -    assertThat(stylelintCommandProvider.commandParts(deployDestination, context)).containsExactly( -      "node", +    Consumer<String> noop = a -> {}; +    NodeCommand nodeCommand = stylelintCommandProvider.nodeCommand(deployDestination, context, noop, noop); +    assertThat(nodeCommand.toString()).endsWith( +      String.join(" ",        new File(deployDestination, "css-bundle/node_modules/stylelint/bin/stylelint").getAbsolutePath(),        baseDir.getAbsolutePath() + File.separator + "**" + File.separator + "*{.foo,.bar}",        "--config",        new File(deployDestination, "css-bundle/stylelintconfig.json").getAbsolutePath(),        "-f", -      "json" +      "json")      );    } - -  @Test -  public void test_node_executable_wo_settings() throws Exception { -    StylelintCommandProvider stylelintCommandProvider = new StylelintCommandProvider(); -    MapSettings settings = new MapSettings(); -    assertThat(stylelintCommandProvider.nodeExecutable(settings.asConfig())).isEqualTo("node"); -    assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); -  } - -  @Test -  public void test_node_executable_custom() throws Exception { -    StylelintCommandProvider stylelintCommandProvider = new StylelintCommandProvider(); -    MapSettings settings = new MapSettings(); -    File customNode = temporaryFolder.newFile("custom-node.exe"); -    settings.setProperty(CssPlugin.NODE_EXECUTABLE, customNode.getAbsolutePath()); -    assertThat(stylelintCommandProvider.nodeExecutable(settings.asConfig())).isEqualTo(customNode.getAbsolutePath()); -    assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); -  } - -  @Test -  public void test_node_executable_custom_invalid() throws Exception { -    StylelintCommandProvider stylelintCommandProvider = new StylelintCommandProvider(); - -    MapSettings settings = new MapSettings(); -    settings.setProperty(CssPlugin.NODE_EXECUTABLE, "mynode"); -    assertThat(stylelintCommandProvider.nodeExecutable(settings.asConfig())).isEqualTo("node"); -    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Provided node executable file does not exist: mynode. Fallback to using 'node' from path."); -  }  } diff --git a/sonar-css-plugin/src/test/resources/executables/invalidNodeVersion.js b/sonar-css-plugin/src/test/resources/executables/invalidNodeVersion.js deleted file mode 100644 index 772b9f3..0000000 --- a/sonar-css-plugin/src/test/resources/executables/invalidNodeVersion.js +++ /dev/null @@ -1 +0,0 @@ -console.log("Invalid version"); | 
