From 5242c58f0517b61539f44a53013ae1fca94d09f2 Mon Sep 17 00:00:00 2001 From: zglicz Date: Tue, 3 Dec 2024 14:43:21 +0100 Subject: [PATCH] JS-471 SonarLint can configure persistent bundle path (#4980) Co-authored-by: Victor Diez --- .../javascript/bridge/BridgeServerImpl.java | 8 +- .../plugins/javascript/bridge/Bundle.java | 3 +- .../plugins/javascript/bridge/BundleImpl.java | 10 ++- .../bridge/BridgeServerImplTest.java | 74 ++++++++++++++----- .../javascript/bridge/BundleImplTest.java | 8 ++ .../src/test/resources/package/bin/server.cjs | 24 ++++++ 6 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 sonar-plugin/bridge/src/test/resources/package/bin/server.cjs diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java index 4e00fc2f6cc..2b342d992d6 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java @@ -68,6 +68,7 @@ private enum Status { private static final String MAX_OLD_SPACE_SIZE_PROPERTY = "sonar.javascript.node.maxspace"; private static final String ALLOW_TS_PARSER_JS_FILES = "sonar.javascript.allowTsParserJsFiles"; private static final String DEBUG_MEMORY = "sonar.javascript.node.debugMemory"; + public static final String SONARLINT_BUNDLE_PATH = "sonar.js.internal.bundlePath"; public static final String SONARJS_EXISTING_NODE_PROCESS_PORT = "SONARJS_EXISTING_NODE_PROCESS_PORT"; private static final Gson GSON = new Gson(); @@ -181,7 +182,12 @@ int getTimeoutSeconds() { * @throws IOException */ void deploy(Configuration configuration) throws IOException { - bundle.deploy(temporaryDeployLocation); + var bundlePath = configuration.get(SONARLINT_BUNDLE_PATH); + if (bundlePath.isPresent()) { + bundle.setDeployLocation(Path.of(bundlePath.get())); + } else { + bundle.deploy(temporaryDeployLocation); + } if (configuration.get(NODE_EXECUTABLE_PROPERTY).isPresent() || configuration.getBoolean(SKIP_NODE_PROVISIONING_PROPERTY).orElse(false) || configuration.getBoolean(NODE_FORCE_HOST_PROPERTY).orElse(false)) { diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/Bundle.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/Bundle.java index 60833bd07e8..b6f2a492385 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/Bundle.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/Bundle.java @@ -20,7 +20,8 @@ import java.nio.file.Path; import org.sonar.plugins.javascript.nodejs.BundlePathResolver; -interface Bundle extends BundlePathResolver { +public interface Bundle extends BundlePathResolver { + void setDeployLocation(Path deployLocation); void deploy(Path deployLocation) throws IOException; String startServerScript(); diff --git a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BundleImpl.java b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BundleImpl.java index 42d402e2574..9eb3f97a528 100644 --- a/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BundleImpl.java +++ b/sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BundleImpl.java @@ -34,7 +34,7 @@ public class BundleImpl implements Bundle { // this archive is created in the bridge module private static final String BUNDLE_LOCATION = "/sonarjs-1.0.0.tgz"; - private static final String DEFAULT_STARTUP_SCRIPT = "package/bin/server.cjs"; + public static final String DEFAULT_STARTUP_SCRIPT = "package/bin/server.cjs"; private Path deployLocation; private final String bundleLocation; @@ -46,6 +46,12 @@ public BundleImpl() { this.bundleLocation = bundleLocation; } + @Override + public void setDeployLocation(Path deployLocation) { + LOG.debug("Setting deploy location to {}", deployLocation); + this.deployLocation = deployLocation; + } + @Override public void deploy(Path deployLocation) throws IOException { LOG.debug("Deploying the bridge server into {}", deployLocation); @@ -54,7 +60,7 @@ public void deploy(Path deployLocation) throws IOException { throw new IllegalStateException("The bridge server was not found in the plugin jar"); } BundleUtils.extractFromClasspath(bundle, deployLocation); - this.deployLocation = deployLocation; + setDeployLocation(deployLocation); } @Override diff --git a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java index 8709653c971..80a8be7a40e 100644 --- a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java +++ b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java @@ -16,6 +16,24 @@ */ package org.sonar.plugins.javascript.bridge; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.WARN; +import static org.sonar.plugins.javascript.bridge.AnalysisMode.DEFAULT_LINTER_ID; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_FORCE_HOST_PROPERTY; +import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.SKIP_NODE_PROVISIONING_PROPERTY; + import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -54,24 +72,6 @@ import org.sonar.plugins.javascript.nodejs.ProcessWrapper; import org.sonar.plugins.javascript.nodejs.ProcessWrapperImpl; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; -import static org.slf4j.event.Level.DEBUG; -import static org.slf4j.event.Level.ERROR; -import static org.slf4j.event.Level.INFO; -import static org.slf4j.event.Level.WARN; -import static org.sonar.plugins.javascript.bridge.AnalysisMode.DEFAULT_LINTER_ID; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_EXECUTABLE_PROPERTY; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.NODE_FORCE_HOST_PROPERTY; -import static org.sonar.plugins.javascript.nodejs.NodeCommandBuilderImpl.SKIP_NODE_PROVISIONING_PROPERTY; - class BridgeServerImplTest { private static final String START_SERVER_SCRIPT = "startServer.js"; @@ -827,11 +827,36 @@ void should_not_deploy_runtime_if_node_force_host_is_set() throws Exception { ); } - private BridgeServerImpl createBridgeServer(String startServerScript) { + @Test + void should_start_bridge_from_path() throws IOException { + bridgeServer = createBridgeServer(new BundleImpl()); + var deployLocation = "src/test/resources"; + var settings = new MapSettings().setProperty(BridgeServerImpl.SONARLINT_BUNDLE_PATH, deployLocation); + context.setSettings(settings); + + var config = BridgeServerConfig.fromSensorContext(context); + bridgeServer.startServerLazily(config); + assertThat(logTester.logs(DEBUG)) + .contains("Setting deploy location to " + deployLocation.replace("/", File.separator)); + } + + @Test + void should_fail_on_bad_bridge_path() { + bridgeServer = createBridgeServer(new BundleImpl()); + var deployLocation = "src/test"; + var settings = new MapSettings().setProperty(BridgeServerImpl.SONARLINT_BUNDLE_PATH, deployLocation); + context.setSettings(settings); + + var config = BridgeServerConfig.fromSensorContext(context); + assertThatThrownBy(() -> bridgeServer.startServerLazily(config)) + .isInstanceOf(NodeCommandException.class); + } + + private BridgeServerImpl createBridgeServer(Bundle bundle) { return new BridgeServerImpl( builder(), TEST_TIMEOUT_SECONDS, - new TestBundle(startServerScript), + bundle, emptyRulesBundles, deprecationWarning, tempFolder, @@ -839,6 +864,10 @@ private BridgeServerImpl createBridgeServer(String startServerScript) { ); } + private BridgeServerImpl createBridgeServer(String startServerScript) { + return createBridgeServer(new TestBundle(startServerScript)); + } + /** * Mock used to bypass the embedded node deployment */ @@ -858,6 +887,11 @@ static class TestBundle implements Bundle { this.startServerScript = startServerScript; } + @Override + public void setDeployLocation(Path deployLocation) { + // no-op for unit test + } + @Override public void deploy(Path deployLocation) { // no-op for unit test diff --git a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BundleImplTest.java b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BundleImplTest.java index b5fde630672..85034ca6b22 100644 --- a/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BundleImplTest.java +++ b/sonar-plugin/bridge/src/test/java/org/sonar/plugins/javascript/bridge/BundleImplTest.java @@ -48,4 +48,12 @@ void should_not_fail_when_deployed_twice() throws Exception { bundle.deploy(deployLocation); // no exception expected } + + @Test + void should_save_deploy_location() { + BundleImpl bundle = new BundleImpl(); + bundle.setDeployLocation(deployLocation); + String scriptPath = bundle.startServerScript(); + assertThat(scriptPath).isEqualTo(deployLocation.resolve(BundleImpl.DEFAULT_STARTUP_SCRIPT).toString()); + } } diff --git a/sonar-plugin/bridge/src/test/resources/package/bin/server.cjs b/sonar-plugin/bridge/src/test/resources/package/bin/server.cjs new file mode 100644 index 00000000000..570573e2a74 --- /dev/null +++ b/sonar-plugin/bridge/src/test/resources/package/bin/server.cjs @@ -0,0 +1,24 @@ +#!/usr/bin/env node + +const http = require('http'); +const port = process.argv[2]; +const host = process.argv[3]; + +const requestHandler = (request, response) => { + if (request.url === '/status') { + response.writeHead(200, { 'Content-Type': 'text/plain' }); + response.end('OK!'); + server.close(); + } +}; + +const server = http.createServer(requestHandler); +server.keepAliveTimeout = 100; // this is used so server disconnects faster + +server.listen(port, host, err => { + if (err) { + return console.log('something bad happened', err); + } + + console.log(`server is listening on ${host} ${port}`); +});