diff options
| author | Elena Vilchik | 2019-12-18 17:10:10 +0100 | 
|---|---|---|
| committer | Alban Auzeill | 2019-12-18 17:10:10 +0100 | 
| commit | c8f0071c4f5336dfe0efc5d3c218ab49f2401264 (patch) | |
| tree | 254cd5ed9531d7c62bab4f8ec082e085795ecb8f /sonar-css-plugin/src/test | |
| parent | 13fe08e87c8a70ffe6e248b774ef826bbe1f779d (diff) | |
| download | sonar-css-c8f0071c4f5336dfe0efc5d3c218ab49f2401264.tar.bz2 | |
Rely on NodeJS API of Stylelint to execute CSS rules (#221)
Diffstat (limited to 'sonar-css-plugin/src/test')
23 files changed, 604 insertions, 419 deletions
| diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java deleted file mode 100644 index fc93670..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/AnalysisWarningsWrapperTest.java +++ /dev/null @@ -1,39 +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 org.junit.Test; -import org.sonar.api.notifications.AnalysisWarnings; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class AnalysisWarningsWrapperTest { -  @Test -  public void delegate_to_analysisWarnings() { -    AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); - -    AnalysisWarningsWrapper wrapper = new AnalysisWarningsWrapper(analysisWarnings); - -    String warning = "some warning"; -    wrapper.addUnique(warning); -    verify(analysisWarnings).addUnique(warning); -  } -} 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 a2e420e..b0f1b28 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 @@ -33,16 +33,7 @@ public class CssPluginTest {    @Test    public void count_extensions() { -    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 7), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY); -    Plugin.Context context = new Plugin.Context(runtime); -    Plugin underTest = new CssPlugin(); -    underTest.define(context); -    assertThat(context.getExtensions()).hasSize(10); -  } - -  @Test -  public void count_extensions_7_2() { -    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(7, 2), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY); +    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(7, 9), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY);      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); @@ -50,17 +41,8 @@ public class CssPluginTest {    }    @Test -  public void count_extensions_7_4() { -    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(7, 4), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY); -    Plugin.Context context = new Plugin.Context(runtime); -    Plugin underTest = new CssPlugin(); -    underTest.define(context); -    assertThat(context.getExtensions()).hasSize(12); -  } - -  @Test -  public void count_extensions_7_4_sonarlint() { -    SonarRuntime runtime = SonarRuntimeImpl.forSonarLint(Version.create(7, 4)); +  public void count_extensions_sonarlint() { +    SonarRuntime runtime = SonarRuntimeImpl.forSonarLint(Version.create(7, 9));      Plugin.Context context = new Plugin.Context(runtime);      Plugin underTest = new CssPlugin();      underTest.define(context); 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 a0a41f8..0dd35d8 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 @@ -21,41 +21,40 @@ package org.sonar.css.plugin;  import java.io.File;  import java.io.IOException; +import java.net.URI;  import java.net.URISyntaxException;  import java.nio.charset.StandardCharsets;  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 java.util.Collections; +import org.junit.After;  import org.junit.Before;  import org.junit.Rule;  import org.junit.Test;  import org.junit.rules.ExpectedException;  import org.junit.rules.TemporaryFolder; +import org.sonar.api.batch.fs.InputFile;  import org.sonar.api.batch.fs.InputFile.Type;  import org.sonar.api.batch.fs.internal.DefaultInputFile;  import org.sonar.api.batch.fs.internal.TestInputFileBuilder;  import org.sonar.api.batch.rule.CheckFactory; -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.notifications.AnalysisWarnings;  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 org.sonar.css.plugin.server.CssAnalyzerBridgeServer;  import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; +import static org.assertj.core.api.Assertions.assertThatThrownBy;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions;  import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.css.plugin.server.CssAnalyzerBridgeServerTest.createCssAnalyzerBridgeServer;  public class CssRuleSensorTest { @@ -66,235 +65,212 @@ public class CssRuleSensorTest {    public TemporaryFolder tmpDir = new TemporaryFolder();    @Rule -  public ExpectedException thrown= ExpectedException.none(); +  public ExpectedException thrown = ExpectedException.none(); -  private static CheckFactory checkFactory = new CheckFactory(new TestActiveRules("S4647", "S4656")); +  private static final CheckFactory CHECK_FACTORY = new CheckFactory(new TestActiveRules("S4647", "S4656", "S4658"));    private static final File BASE_DIR = new File("src/test/resources").getAbsoluteFile();    private SensorContextTester context = SensorContextTester.create(BASE_DIR); -  private DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css"); -  private AnalysisWarningsWrapper analysisWarnings = mock(AnalysisWarningsWrapper.class); +  private AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); +  private CssAnalyzerBridgeServer cssAnalyzerBridgeServer; +  private CssRuleSensor sensor;    @Before    public void setUp() {      context.fileSystem().setWorkDir(tmpDir.getRoot().toPath()); -    Awaitility.setDefaultTimeout(5, TimeUnit.MINUTES); +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    sensor = new CssRuleSensor(CHECK_FACTORY, cssAnalyzerBridgeServer, analysisWarnings); +  } + +  @After +  public void tearDown() throws Exception { +    if (cssAnalyzerBridgeServer != null) { +      cssAnalyzerBridgeServer.stop(); +    }    }    @Test    public void test_descriptor() { -    CssRuleSensor sensor = new CssRuleSensor(new CssBundleHandler(), checkFactory, new StylelintCommandProvider(), analysisWarnings);      DefaultSensorDescriptor sensorDescriptor = new DefaultSensorDescriptor();      sensor.describe(sensorDescriptor);      assertThat(sensorDescriptor.name()).isEqualTo("SonarCSS Rules");      assertThat(sensorDescriptor.languages()).isEmpty(); +    assertThat(sensorDescriptor.configurationPredicate()).isNull(); +    assertThat(sensorDescriptor.ruleRepositories()).containsOnly("css");    }    @Test    public void test_execute() throws IOException { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); +    addInputFile("file.css"); +    addInputFile("file-with-rule-id-message.css");      sensor.execute(context); -    assertThat(context.allIssues()).hasSize(1); -    Issue issue = context.allIssues().iterator().next(); -    assertThat(issue.primaryLocation().message()).isEqualTo("some message"); - -    Path configPath = Paths.get(context.fileSystem().workDir().getAbsolutePath(), "testconfig.json"); -    assertThat(Files.readAllLines(configPath)).containsOnly("{\"rules\":{\"color-no-invalid-hex\":true,\"declaration-block-no-duplicate-properties\":[true,{\"ignore\":[\"consecutive-duplicates-with-different-values\"]}]}}"); +    assertThat(context.allIssues()).hasSize(2); +    assertThat(context.allIssues()).extracting("primaryLocation.message") +      .containsOnly("some message", "Unexpected empty block"); + +    assertThat(String.join("\n", logTester.logs(LoggerLevel.DEBUG))) +      .matches("(?s).*Analyzing \\S*file\\.css.*") +      .matches("(?s).*Found 1 issue\\(s\\).*"); + +    Path configPath = Paths.get(context.fileSystem().workDir().getAbsolutePath(), "css-bundle", "stylelintconfig.json"); +    assertThat(Files.readAllLines(configPath)).containsOnly( +      "{\"rules\":{" + +        "\"block-no-empty\":true," + +        "\"color-no-invalid-hex\":true," + +        "\"declaration-block-no-duplicate-properties\":[true,{\"ignore\":[\"consecutive-duplicates-with-different-values\"]}]" + +      "}}");      verifyZeroInteractions(analysisWarnings);    }    @Test -  public void test_old_property_is_provided() { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); -    context.settings().setProperty(CssPlugin.FORMER_NODE_EXECUTABLE, "foo"); -    sensor.execute(context); +  public void test_non_css_files() { +    DefaultInputFile fileCss = addInputFile("file.css"); +    DefaultInputFile fileHtml = addInputFile("file.web"); +    DefaultInputFile filePhp = addInputFile("file.php"); +    addInputFile("file.js"); -    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Property 'sonar.css.node' is ignored, 'sonar.nodejs.executable' should be used instead"); -    verify(analysisWarnings).addUnique(eq("Property 'sonar.css.node' is ignored, 'sonar.nodejs.executable' should be used instead")); +    sensor.execute(context); -    assertThat(context.allIssues()).hasSize(1); +    assertThat(context.allIssues()).hasSize(3); +    assertThat(context.allIssues()) +      .extracting("primaryLocation.component") +      .containsOnly(fileCss, fileHtml, filePhp);    }    @Test -  public void test_invalid_node() { -    InvalidCommandProvider commandProvider = new InvalidCommandProvider(); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); +  public void test_no_file_to_analyze() throws IOException {      sensor.execute(context); -      assertThat(context.allIssues()).hasSize(0); -    assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Some problem happened. No CSS files will be analyzed."); -    verifyZeroInteractions(analysisWarnings); +    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty(); +    assertThat(logTester.logs(LoggerLevel.INFO)).contains("No CSS, PHP or HTML files are found in the project. CSS analysis is skipped.");    }    @Test -  public void test_execute_with_analysisWarnings() throws IOException { -    TestLinterCommandProvider commandProvider = getCommandProvider(); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +  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(context.allIssues()).hasSize(1); -    Issue issue = context.allIssues().iterator().next(); -    assertThat(issue.primaryLocation().message()).isEqualTo("some message"); - -    Path configPath = Paths.get(context.fileSystem().workDir().getAbsolutePath(), "testconfig.json"); -    assertThat(Files.readAllLines(configPath)).containsOnly("{\"rules\":{\"color-no-invalid-hex\":true,\"declaration-block-no-duplicate-properties\":[true,{\"ignore\":[\"consecutive-duplicates-with-different-values\"]}]}}"); -    verifyZeroInteractions(analysisWarnings); +    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 test_invalid_node_command_with_analysisWarnings() { -    InvalidCommandProvider commandProvider = new InvalidCommandProvider(); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider, analysisWarnings); +  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))) +      .contains("Failed to parse response"); +    assertThat(context.allIssues()).hasSize(1); +  } -    assertThat(context.allIssues()).hasSize(0); -    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 +  public void should_fail_fast_when_server_fail_to_start() { +    context.settings().setProperty("sonar.internal.analysis.failFast", "true"); +    CssAnalyzerBridgeServer badServer = createCssAnalyzerBridgeServer("throw.js"); +    sensor = new CssRuleSensor(CHECK_FACTORY, badServer, analysisWarnings); +    addInputFile("file.css"); + +    assertThatThrownBy(() -> sensor.execute(context)) +      .isInstanceOf(IllegalStateException.class) +      .hasMessageContaining("Analysis failed");    }    @Test -  public void test_error() { -    TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); -    sensor.execute(context); +  public void should_fail_fast_when_bridge_server_receives_invalid_response() { +    context.settings().setProperty("sonar.internal.analysis.failFast", "true"); +    addInputFile("invalid-json-response.css"); + +    assertThatThrownBy(() -> sensor.execute(context)) +      .isInstanceOf(IllegalStateException.class) +      .hasMessageContaining("Analysis failed"); +  } -    assertThat(logTester.logs(LoggerLevel.ERROR)).anyMatch(s -> s.startsWith("Failed to run external linting process")); +  @Test +  public void should_not_analyze_files_with_not_file_uri() throws URISyntaxException, IOException { +    InputFile httpFile = mock(InputFile.class); +    when(httpFile.filename()).thenReturn("file.css"); +    when(httpFile.uri()).thenReturn(new URI("http://lost-on-earth.com/file.css")); +    sensor.analyzeFile(context, httpFile, new File("config.json")); +    assertThat(String.join("\n", logTester.logs(LoggerLevel.DEBUG))) +      .matches("(?s).*Skipping \\S*file.css as it has not 'file' scheme.*") +      .doesNotMatch("(?s).*\nAnalyzing \\S*file.css.*");    }    @Test -  public void test_not_execute_rules_if_nothing_enabled() { -    TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = new CssRuleSensor(new TestBundleHandler(), new CheckFactory(new TestActiveRules()), commandProvider, analysisWarnings); +  public void analysis_stop_when_server_is_not_anymore_alive() throws IOException, InterruptedException { +    File configFile = new File("config.json"); +    DefaultInputFile inputFile = addInputFile("dir/file.css");      sensor.execute(context); +    cssAnalyzerBridgeServer.setPort(43); -    assertThat(logTester.logs(LoggerLevel.WARN)).contains("No rules are activated in CSS Quality Profile"); +    assertThatThrownBy(() -> sensor.analyzeFiles(context, Collections.singletonList(inputFile), configFile)) +      .isInstanceOf(IllegalStateException.class) +      .hasMessageContaining("css-bundle server is not answering");    }    @Test -  public void test_stylelint_throws() { -    TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockThrow.js", inputFile.absolutePath()); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); +  public void should_stop_execution_when_sensor_context_is_cancelled() throws IOException { +    addInputFile("file.css"); +    context.setCancelled(true);      sensor.execute(context); - -    await().until(() -> logTester.logs(LoggerLevel.ERROR) -      .contains("throw new Error('houps!');")); +    assertThat(context.allIssues()).isEmpty(); +    assertThat(logTester.logs(LoggerLevel.INFO)) +      .contains("java.util.concurrent.CancellationException: Analysis interrupted because the SensorContext is in cancelled state");    }    @Test -  public void test_stylelint_exitvalue() { -    TestLinterCommandProvider commandProvider = new TestLinterCommandProvider().nodeScript("/executables/mockExit.js", "1"); -    CssRuleSensor sensor = createCssRuleSensor(commandProvider); +  public void test_old_property_is_provided() { +    context.settings().setProperty(CssPlugin.FORMER_NODE_EXECUTABLE, "foo"); +    addInputFile("file.css");      sensor.execute(context); -    await().until(() -> logTester.logs(LoggerLevel.ERROR) -      .contains("Analysis didn't terminate normally, please verify ERROR and WARN logs above. Exit code 1")); +    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Property 'sonar.css.node' is ignored, 'sonar.nodejs.executable' should be used instead"); +    verify(analysisWarnings).addUnique(eq("Property 'sonar.css.node' is ignored, 'sonar.nodejs.executable' should be used instead")); + +    assertThat(context.allIssues()).hasSize(1); + +    sensor = new CssRuleSensor(CHECK_FACTORY, cssAnalyzerBridgeServer, null); +    sensor.execute(context); +    verifyNoMoreInteractions(analysisWarnings);    }    @Test    public void test_syntax_error() { -    SensorContextTester context = SensorContextTester.create(BASE_DIR); -    context.fileSystem().setWorkDir(tmpDir.getRoot().toPath()); -    DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css"); -    TestLinterCommandProvider rulesExecution = new TestLinterCommandProvider().nodeScript("/executables/mockSyntaxError.js", inputFile.absolutePath()); -    CssRuleSensor sensor = createCssRuleSensor(rulesExecution); +    InputFile inputFile = addInputFile("syntax-error.css");      sensor.execute(context); - +    assertThat(context.allIssues()).isEmpty();      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Failed to parse " + inputFile.uri() + ", line 2, Missed semicolon");    }    @Test    public void test_unknown_rule() { -    SensorContextTester context = SensorContextTester.create(BASE_DIR); -    context.fileSystem().setWorkDir(tmpDir.getRoot().toPath()); -    DefaultInputFile inputFile = createInputFile(context, "some css content\n on 2 lines", "dir/file.css"); -    TestLinterCommandProvider rulesExecution = new TestLinterCommandProvider().nodeScript("/executables/mockUnknownRule.js", inputFile.absolutePath()); -    CssRuleSensor sensor = createCssRuleSensor(rulesExecution); +    addInputFile("unknown-rule.css");      sensor.execute(context); +    assertThat(context.allIssues()).isEmpty();      assertThat(logTester.logs(LoggerLevel.ERROR)).contains("Unknown stylelint rule or rule not enabled: 'unknown-rule-key'");    } -  private static DefaultInputFile createInputFile(SensorContextTester sensorContext, String content, String relativePath) { +  private DefaultInputFile addInputFile(String relativePath) {      DefaultInputFile inputFile = new TestInputFileBuilder("moduleKey", relativePath) -      .setModuleBaseDir(sensorContext.fileSystem().baseDirPath()) +      .setModuleBaseDir(context.fileSystem().baseDirPath())        .setType(Type.MAIN) -      .setLanguage(CssLanguage.KEY) +      .setLanguage(relativePath.split("\\.")[1])        .setCharset(StandardCharsets.UTF_8) -      .setContents(content) +      .setContents("some css content\n on 2 lines")        .build(); -    sensorContext.fileSystem().add(inputFile); +    context.fileSystem().add(inputFile);      return inputFile;    } -  private CssRuleSensor createCssRuleSensor(LinterCommandProvider commandProvider) { -    return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider); -  } - -  private CssRuleSensor createCssRuleSensor(LinterCommandProvider commandProvider, @Nullable AnalysisWarningsWrapper analysisWarnings) { -    return new CssRuleSensor(new TestBundleHandler(), checkFactory, commandProvider, analysisWarnings); -  } - -  private TestLinterCommandProvider getCommandProvider() { -    return new TestLinterCommandProvider().nodeScript("/executables/mockStylelint.js", inputFile.absolutePath()); -  } - -  private static class TestLinterCommandProvider implements LinterCommandProvider { - -    private String[] elements; - -    private static String resourceScript(String script) { -      try { -        return new File(TestLinterCommandProvider.class.getResource(script).toURI()).getAbsolutePath(); -      } catch (URISyntaxException e) { -        throw new IllegalStateException(e); -      } -    } - -    TestLinterCommandProvider nodeScript(String script, String args) { -      this.elements = new String[]{ resourceScript(script), args}; -      return this; -    } - -    @Override -    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 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/CssRulesDefinitionTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java index 8521561..1522c99 100644 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/CssRulesDefinitionTest.java @@ -28,7 +28,7 @@ public class CssRulesDefinitionTest {    @Test    public void test_with_external_rules() { -    CssRulesDefinition rulesDefinition = new CssRulesDefinition(true); +    CssRulesDefinition rulesDefinition = new CssRulesDefinition();      RulesDefinition.Context context = new RulesDefinition.Context();      rulesDefinition.define(context); @@ -46,19 +46,4 @@ public class CssRulesDefinitionTest {      assertThat(mainRepository.isExternal()).isEqualTo(false);      assertThat(mainRepository.rules()).hasSize(CssRules.getRuleClasses().size());    } - -  @Test -  public void test_no_external_rules() throws Exception { -    CssRulesDefinition rulesDefinition = new CssRulesDefinition(false); -    RulesDefinition.Context context = new RulesDefinition.Context(); -    rulesDefinition.define(context); - -    assertThat(context.repositories()).hasSize(1); - -    RulesDefinition.Repository repository = context.repository("css"); -    assertThat(repository.name()).isEqualTo("SonarAnalyzer"); -    assertThat(repository.language()).isEqualTo("css"); -    assertThat(repository.isExternal()).isEqualTo(false); -    assertThat(repository.rules()).hasSize(CssRules.getRuleClasses().size()); -  }  } 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 deleted file mode 100644 index f2d2fed..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/StylelintCommandProviderTest.java +++ /dev/null @@ -1,63 +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.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.utils.log.LogTester; -import org.sonarsource.nodejs.NodeCommand; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StylelintCommandProviderTest { - -  @Rule -  public TemporaryFolder temporaryFolder = new TemporaryFolder(); - -  @Rule -  public final LogTester logTester = new LogTester(); - -  @Test -  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") -      .setProperty("sonar.javascript.file.suffixes", ".js") -      .setProperty("sonar.php.file.suffixes", ".php") -      .setProperty("sonar.java.file.suffixes", ".java"); -    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,.php}", -      "--config", -      new File(deployDestination, "css-bundle/stylelintconfig.json").getAbsolutePath(), -      "-f", -      "json") -    ); -  } -} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java new file mode 100644 index 0000000..dadb16c --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssAnalyzerBundleTest.java @@ -0,0 +1,85 @@ +/* + * 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.bundle; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.internal.JUnitTempFolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class CssAnalyzerBundleTest { + +  @Rule +  public JUnitTempFolder tempFolder = new JUnitTempFolder(); + +  @Test +  public void default_css_bundle_location() throws Exception { +    CssAnalyzerBundle bundle = new CssAnalyzerBundle(); +    assertThat(bundle.bundleLocation).isEqualTo("/css-bundle.zip"); +  } + +  @Test +  public void almost_empty_css_bundle() throws Exception { +    Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); +    Path deployLocation = tempFolder.newDir().toPath(); +    String expectedStartServer = deployLocation.resolve(Paths.get("css-bundle", "bin", "server")).toString(); +    bundle.deploy(deployLocation); +    String script = bundle.startServerScript(); +    assertThat(script).isEqualTo(expectedStartServer); +    File scriptFile = new File(script); +    assertThat(scriptFile).exists(); +    String content = new String(Files.readAllBytes(scriptFile.toPath()), StandardCharsets.UTF_8); +    assertThat(content).startsWith("#!/usr/bin/env node"); +  } + +  @Test +  public void missing_bundle() throws Exception { +    Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-bundle-path.zip"); +    assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) +      .isInstanceOf(IllegalStateException.class) +      .hasMessage("css-bundle not found in /bundle/invalid-bundle-path.zip"); +  } + +  @Test +  public void invalid_bundle_zip() throws Exception { +    Bundle bundle = new CssAnalyzerBundle("/bundle/invalid-zip-file.zip"); +    assertThatThrownBy(() -> bundle.deploy(tempFolder.newDir().toPath())) +      .isInstanceOf(IllegalStateException.class) +      .hasMessage("Failed to deploy css-bundle (with classpath '/bundle/invalid-zip-file.zip')"); +  } + +  @Test +  public void should_not_fail_when_deployed_twice() throws Exception { +    Bundle bundle = new CssAnalyzerBundle("/bundle/test-css-bundle.zip"); +    Path deployLocation = tempFolder.newDir().toPath(); +    assertThatCode(() -> { +      bundle.deploy(deployLocation); +      bundle.deploy(deployLocation); +    }).doesNotThrowAnyException(); +  } +} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssBundleHandlerTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssBundleHandlerTest.java deleted file mode 100644 index f76c912..0000000 --- a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/bundle/CssBundleHandlerTest.java +++ /dev/null @@ -1,55 +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.bundle; - -import java.io.File; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CssBundleHandlerTest { - -  @Rule -  public TemporaryFolder temporaryFolder = new TemporaryFolder(); - -  @Rule -  public ExpectedException expectedException = ExpectedException.none(); - -  private File DEPLOY_DESTINATION; - -  @Before -  public void setUp() throws Exception { -    DEPLOY_DESTINATION = temporaryFolder.newFolder("deployDestination"); -  } - -  @Test -  public void test() { -    CssBundleHandler bundleHandler = new CssBundleHandler(); -    bundleHandler.bundleLocation = "/bundle/test-bundle.zip"; -    bundleHandler.deployBundle(DEPLOY_DESTINATION); - -    assertThat(new File(DEPLOY_DESTINATION, "test-bundle.js").exists()).isTrue(); -  } - -} 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 new file mode 100644 index 0000000..302b6bb --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/CssAnalyzerBridgeServerTest.java @@ -0,0 +1,254 @@ +/* + * 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.nio.file.Path; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.ExpectedException; +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.utils.internal.JUnitTempFolder; +import org.sonar.api.utils.log.LogTester; +import org.sonar.css.plugin.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; + +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.sonar.api.utils.log.LoggerLevel.DEBUG; +import static org.sonar.api.utils.log.LoggerLevel.INFO; +import static org.sonar.api.utils.log.LoggerLevel.WARN; + +public class CssAnalyzerBridgeServerTest { + +  private static final String START_SERVER_SCRIPT = "startServer.js"; +  private static final String CONFIG_FILE = "config.json"; +  private static final int TEST_TIMEOUT_SECONDS = 1; + +  @org.junit.Rule +  public LogTester logTester = new LogTester(); + +  @org.junit.Rule +  public final ExpectedException thrown = ExpectedException.none(); + +  @org.junit.Rule +  public JUnitTempFolder tempFolder = new JUnitTempFolder(); + +  private SensorContextTester context; +  private CssAnalyzerBridgeServer cssAnalyzerBridgeServer; + +  @Before +  public void setUp() throws Exception { +    context = SensorContextTester.create(tempFolder.newDir()); +    context.fileSystem().setWorkDir(tempFolder.newDir().toPath()); +  } + +  @After +  public void tearDown() throws Exception { +    if (cssAnalyzerBridgeServer != null) { +      cssAnalyzerBridgeServer.clean(); +    } +  } + +  @Test +  public void default_timeout() { +    CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(mock(Bundle.class)); +    assertThat(server.timeoutSeconds).isEqualTo(60); +  } + +  @Test +  public void issue_constructor() { +    Issue issue = new Issue(2, "r", "t"); +    assertThat(issue.line).isEqualTo(2); +    assertThat(issue.rule).isEqualTo("r"); +    assertThat(issue.text).isEqualTo("t"); +  } + +  @Test +  public void should_throw_when_not_existing_start_script() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer("NOT_EXISTING.js"); + +    thrown.expect(NodeCommandException.class); +    thrown.expectMessage("Node.js script to start css-bundle server doesn't exist"); + +    cssAnalyzerBridgeServer.startServer(context); +  } + +  @Test +  public void should_throw_if_failed_to_build_node_command() throws Exception { +    NodeCommandBuilder nodeCommandBuilder = mock(NodeCommandBuilder.class, invocation -> { +      if (NodeCommandBuilder.class.equals(invocation.getMethod().getReturnType())) { +        return invocation.getMock(); +      } else { +        throw new NodeCommandException("msg"); +      } +    }); + +    cssAnalyzerBridgeServer = new CssAnalyzerBridgeServer(nodeCommandBuilder, TEST_TIMEOUT_SECONDS, new TestBundle(START_SERVER_SCRIPT)); + +    thrown.expect(NodeCommandException.class); +    thrown.expectMessage("msg"); + +    cssAnalyzerBridgeServer.startServerLazily(context); +  } + +  @Test +  public void should_forward_process_streams() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    cssAnalyzerBridgeServer.startServerLazily(context); + +    assertThat(logTester.logs(DEBUG)).contains("testing debug log"); +    assertThat(logTester.logs(WARN)).contains("testing warn log"); +    assertThat(logTester.logs(INFO)).contains("testing info log"); +  } + +  @Test +  public void should_get_answer_from_server() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    cssAnalyzerBridgeServer.startServerLazily(context); + +    Request request = new Request("/absolute/path/file.css", CONFIG_FILE); +    Issue[] issues = cssAnalyzerBridgeServer.analyze(request); +    assertThat(issues).hasSize(1); +    assertThat(issues[0].line).isEqualTo(2); +    assertThat(issues[0].rule).isEqualTo("block-no-empty"); +    assertThat(issues[0].text).isEqualTo("Unexpected empty block"); + +    request = new Request("/absolute/path/empty.css", CONFIG_FILE); +    issues = cssAnalyzerBridgeServer.analyze(request); +    assertThat(issues).isEmpty(); +  } + +  @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); +  } + +  @Test +  public void should_return_command_info() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    assertThat(cssAnalyzerBridgeServer.getCommandInfo()).isEqualTo("Node.js command to start css-bundle server was not built yet."); + +    cssAnalyzerBridgeServer.startServerLazily(context); +    assertThat(cssAnalyzerBridgeServer.getCommandInfo()).contains("Node.js command to start css-bundle was: ", "node", START_SERVER_SCRIPT); +    assertThat(cssAnalyzerBridgeServer.getCommandInfo()).doesNotContain("--max-old-space-size"); +  } + +  @Test +  public void should_set_max_old_space_size() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    context.setSettings(new MapSettings().setProperty("sonar.css.node.maxspace", 2048)); +    cssAnalyzerBridgeServer.startServerLazily(context); +    assertThat(cssAnalyzerBridgeServer.getCommandInfo()).contains("--max-old-space-size=2048"); +  } + +  @Test +  public void test_isAlive() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    assertThat(cssAnalyzerBridgeServer.isAlive()).isFalse(); +    cssAnalyzerBridgeServer.startServerLazily(context); +    assertThat(cssAnalyzerBridgeServer.isAlive()).isTrue(); +    cssAnalyzerBridgeServer.stop(); +    assertThat(cssAnalyzerBridgeServer.isAlive()).isFalse(); +  } + +  @Test +  public void test_lazy_start() throws Exception { +    String alreadyStarted = "css-bundle server is up, no need to start."; +    String starting = "Starting Node.js process to start css-bundle server at port"; +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(); +    cssAnalyzerBridgeServer.startServerLazily(context); +    assertThat(logTester.logs(DEBUG).stream().anyMatch(s -> s.startsWith(starting))).isTrue(); +    assertThat(logTester.logs(DEBUG)).doesNotContain(alreadyStarted); +    logTester.clear(); +    cssAnalyzerBridgeServer.startServerLazily(context); +    assertThat(logTester.logs(DEBUG).stream().noneMatch(s -> s.startsWith(starting))).isTrue(); +    assertThat(logTester.logs(DEBUG)).contains(alreadyStarted); +  } + +  @Test +  public void should_throw_special_exception_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); + +    assertThatThrownBy(() -> cssAnalyzerBridgeServer.startServerLazily(context)) +      .isInstanceOf(ServerAlreadyFailedException.class); +  } + +  @Test +  public void should_fail_if_bad_json_response() throws Exception { +    cssAnalyzerBridgeServer = createCssAnalyzerBridgeServer(START_SERVER_SCRIPT); +    cssAnalyzerBridgeServer.deploy(context.fileSystem().workDir()); +    cssAnalyzerBridgeServer.startServerLazily(context); + +    DefaultInputFile inputFile = TestInputFileBuilder.create("foo", "invalid-json-response.css") +      .build(); +    Request request = new Request(inputFile.absolutePath(), CONFIG_FILE); +    assertThatThrownBy(() -> cssAnalyzerBridgeServer.analyze(request)).isInstanceOf(IllegalStateException.class); +    assertThat(context.allIssues()).isEmpty(); +  } + + +  public static CssAnalyzerBridgeServer createCssAnalyzerBridgeServer(String startServerScript) { +    CssAnalyzerBridgeServer server = new CssAnalyzerBridgeServer(NodeCommand.builder(), TEST_TIMEOUT_SECONDS, new TestBundle(startServerScript)); +    server.start(); +    return server; +  } + +  public static CssAnalyzerBridgeServer createCssAnalyzerBridgeServer() { +    return createCssAnalyzerBridgeServer(START_SERVER_SCRIPT); +  } + +  static class TestBundle implements Bundle { + +    final String startServerScript; + +    TestBundle(String startServerScript) { +      this.startServerScript = startServerScript; +    } + +    @Override +    public void deploy(Path deployLocation) { +      // no-op for unit test +    } + +    @Override +    public String startServerScript() { +      return "src/test/resources/mock-start-server/" + startServerScript; +    } +  } +} diff --git a/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/NetUtilsTest.java b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/NetUtilsTest.java new file mode 100644 index 0000000..0aee145 --- /dev/null +++ b/sonar-css-plugin/src/test/java/org/sonar/css/plugin/server/NetUtilsTest.java @@ -0,0 +1,55 @@ +/* + * 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 java.util.concurrent.TimeUnit; +import org.awaitility.Awaitility; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class NetUtilsTest { + +  @Test +  public void findOpenPort_should_not_return_zero() throws IOException { +    assertThat(NetUtils.findOpenPort()) +      .isGreaterThan(0) +      .isLessThanOrEqualTo(65535); +  } + +  @Test +  public void waitServerToStart_can_be_interrupted() throws InterruptedException { +    // try to connect to a port that does not exists +    Thread worker = new Thread(() -> NetUtils.waitServerToStart("localhost", 8, 1000)); +    worker.start(); +    Awaitility.setDefaultTimeout(1, TimeUnit.SECONDS); +    // wait for the worker thread to start and to be blocked on Thread.sleep(20); +    await().until(() -> worker.getState() == Thread.State.TIMED_WAITING); + +    long start = System.currentTimeMillis(); +    worker.interrupt(); +    worker.join(); +    long timeToInterrupt = System.currentTimeMillis() - start; +    assertThat(timeToInterrupt).isLessThan(20); +  } + +} diff --git a/sonar-css-plugin/src/test/resources/.DS_Store b/sonar-css-plugin/src/test/resources/.DS_StoreBinary files differ deleted file mode 100644 index 63066fe..0000000 --- a/sonar-css-plugin/src/test/resources/.DS_Store +++ /dev/null diff --git a/sonar-css-plugin/src/test/resources/bundle/.DS_Store b/sonar-css-plugin/src/test/resources/bundle/.DS_StoreBinary files differ deleted file mode 100644 index 5008ddf..0000000 --- a/sonar-css-plugin/src/test/resources/bundle/.DS_Store +++ /dev/null diff --git a/sonar-css-plugin/src/test/resources/bundle/invalid-zip-file.zip b/sonar-css-plugin/src/test/resources/bundle/invalid-zip-file.zip new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/sonar-css-plugin/src/test/resources/bundle/invalid-zip-file.zip diff --git a/sonar-css-plugin/src/test/resources/bundle/test-bundle.zip b/sonar-css-plugin/src/test/resources/bundle/test-bundle.zipBinary files differ deleted file mode 100644 index 446e89f..0000000 --- a/sonar-css-plugin/src/test/resources/bundle/test-bundle.zip +++ /dev/null diff --git a/sonar-css-plugin/src/test/resources/bundle/test-css-bundle.zip b/sonar-css-plugin/src/test/resources/bundle/test-css-bundle.zipBinary files differ new file mode 100644 index 0000000..3a0a924 --- /dev/null +++ b/sonar-css-plugin/src/test/resources/bundle/test-css-bundle.zip diff --git a/sonar-css-plugin/src/test/resources/executables/mockError.js b/sonar-css-plugin/src/test/resources/executables/mockError.js deleted file mode 100644 index 23849a4..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockError.js +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -console.log("Incorrect json might appear if exception thrown during analysis") diff --git a/sonar-css-plugin/src/test/resources/executables/mockExit.js b/sonar-css-plugin/src/test/resources/executables/mockExit.js deleted file mode 100644 index b18a959..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockExit.js +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -console.log("[]"); - -process.exit(process.argv[2]); diff --git a/sonar-css-plugin/src/test/resources/executables/mockStylelint.js b/sonar-css-plugin/src/test/resources/executables/mockStylelint.js deleted file mode 100644 index 70959dc..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockStylelint.js +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node -var testFile = process.argv[2]; - -var result = [ -  { -    source: testFile, - -    warnings: [ -      { -        text: "some message (color-no-invalid-hex)", -        line: 2, -        rule: "color-no-invalid-hex" -      } -    ] -  } -]; - -var json = JSON.stringify(result); -console.log(json); diff --git a/sonar-css-plugin/src/test/resources/executables/mockSyntaxError.js b/sonar-css-plugin/src/test/resources/executables/mockSyntaxError.js deleted file mode 100644 index cd32af2..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockSyntaxError.js +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node -var testFile = process.argv[2]; - -var result = [ -  { -    source: testFile, - -    warnings: [ -      { -        text: "Missed semicolon (CssSyntaxError)", -        line: 2, -        rule: "CssSyntaxError" -      } -    ] -  } -]; - -var json = JSON.stringify(result); -console.log(json); diff --git a/sonar-css-plugin/src/test/resources/executables/mockThrow.js b/sonar-css-plugin/src/test/resources/executables/mockThrow.js deleted file mode 100644 index ca88c27..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockThrow.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -throw new Error('houps!'); diff --git a/sonar-css-plugin/src/test/resources/executables/mockUnknownRule.js b/sonar-css-plugin/src/test/resources/executables/mockUnknownRule.js deleted file mode 100644 index 844b38b..0000000 --- a/sonar-css-plugin/src/test/resources/executables/mockUnknownRule.js +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env node -var testFile = process.argv[2]; - -var result = [ -  { -    source: testFile, - -    warnings: [ -      { -        text: "some message", -        line: 2, -        rule: "unknown-rule-key" -      } -    ] -  } -]; - -var json = JSON.stringify(result); -console.log(json); diff --git a/sonar-css-plugin/src/test/resources/executables/oldNodeVersion.js b/sonar-css-plugin/src/test/resources/executables/oldNodeVersion.js deleted file mode 100644 index 817b98a..0000000 --- a/sonar-css-plugin/src/test/resources/executables/oldNodeVersion.js +++ /dev/null @@ -1 +0,0 @@ -console.log("3.2.1"); 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 new file mode 100644 index 0000000..c8b8d38 --- /dev/null +++ b/sonar-css-plugin/src/test/resources/mock-start-server/startServer.js @@ -0,0 +1,70 @@ +#!/usr/bin/env node + +const http = require('http'); +const port = process.argv[2]; + +console.log(`DEBUG testing debug log`) +console.log(`WARN testing warn log`) +console.log(`testing info log`) + +const requestHandler = (request, response) => { +  let data = []; +  request.on('data', chunk => { +    data.push(chunk); +  }); +  request.on('end', () => { +    let fileName = null; +    if (data.length > 0) { +      const analysisRequest = JSON.parse(data.join()); +      fileName = analysisRequest.filePath.replace(/.*[\/\\]/g,""); +    } +    if (request.url === '/status') { +      response.writeHead(200, { 'Content-Type': 'text/plain' }); +      response.end('OK!'); +    } else { +      switch (fileName) { +        case "file.css": +        case "file.web": +        case "file.php": +        case "file.js": // to test that we will not save this issue even if it's provided by response +          response.end(JSON.stringify([ +            {line: 2, rule: "block-no-empty", text: "Unexpected empty block"} +          ])); +          break; +        case "file-with-rule-id-message.css": +          response.end(JSON.stringify([ +            {line: 2, rule: "color-no-invalid-hex", text: "some message (color-no-invalid-hex)"} +          ])); +          break; +        case "empty.css": +          response.end(JSON.stringify([])); +          break; +        case "syntax-error.css": +          response.end(JSON.stringify([ +            {line: 2, rule: "CssSyntaxError", text: "Missed semicolon (CssSyntaxError)"} +          ])); +          break; +        case "unknown-rule.css": +          response.end(JSON.stringify([ +            {line: 2, rule: "unknown-rule-key", text: "some message"} +          ])); +          break; +        case "invalid-json-response.css": +          response.end("["); +          break; +        default: +          throw "Unexpected fileName: " + fileName; +      } +    } +  }); +}; + +const server = http.createServer(requestHandler); + +server.listen(port, (err) => { +  if (err) { +    return console.log('something bad happened', err) +  } + +  console.log(`server is listening on ${port}`) +}); diff --git a/sonar-css-plugin/src/test/resources/mock-start-server/throw.js b/sonar-css-plugin/src/test/resources/mock-start-server/throw.js new file mode 100644 index 0000000..028dbc8 --- /dev/null +++ b/sonar-css-plugin/src/test/resources/mock-start-server/throw.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +throw "Something wrong happened" | 
