diff --git a/its/ruling/src/test/expected/js/angular.js/javascript-S2486.json b/its/ruling/src/test/expected/js/angular.js/javascript-S2486.json
index 49cd72c2cd7..57a05f87564 100644
--- a/its/ruling/src/test/expected/js/angular.js/javascript-S2486.json
+++ b/its/ruling/src/test/expected/js/angular.js/javascript-S2486.json
@@ -9,6 +9,9 @@
"angular.js:test/helpers/support.js": [
16
],
+"angular.js:test/ng/compileSpec.fixture.js": [
+688
+],
"angular.js:test/ng/compileSpec.js": [
688
],
diff --git a/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S1874.json b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S1874.json
index 5ddd21ad7cd..ed966d562ba 100644
--- a/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S1874.json
+++ b/its/ruling/src/test/expected/js/javascript-test-sources/javascript-S1874.json
@@ -94,6 +94,11 @@
"javascript-test-sources:src/ace/src/mouse/touch_handler.js": [
59
],
+"javascript-test-sources:src/ace/src/scrollbar.js": [
+125,
+130,
+223
+],
"javascript-test-sources:src/ace/src/scrollbar_test.js": [
13
],
@@ -101,8 +106,13 @@
270
],
"javascript-test-sources:src/ace/src/test/all_browser.js": [
+11,
116
],
+"javascript-test-sources:src/ace/src/virtual_renderer.js": [
+1159,
+1167
+],
"javascript-test-sources:src/ace/static.js": [
33
],
@@ -129,6 +139,15 @@
123,
124
],
+"javascript-test-sources:src/ecmascript6/Ghost/core/server/models/post.js": [
+329
+],
+"javascript-test-sources:src/ecmascript6/Ghost/core/server/models/tag.js": [
+68
+],
+"javascript-test-sources:src/ecmascript6/Ghost/core/server/models/user.js": [
+184
+],
"javascript-test-sources:src/ecmascript6/ecmascript6-today/7-rest-params/js/rest-spread.js": [
25
],
@@ -141,6 +160,8 @@
],
"javascript-test-sources:src/ecmascript6/router/third_party/brick/brick-1.0.1.byob.js": [
337,
+572,
+575,
877,
1412,
1637,
diff --git a/its/ruling/src/test/expected/js/p5.js/javascript-S1874.json b/its/ruling/src/test/expected/js/p5.js/javascript-S1874.json
index eac6ca179cf..14c38f6c2ae 100644
--- a/its/ruling/src/test/expected/js/p5.js/javascript-S1874.json
+++ b/its/ruling/src/test/expected/js/p5.js/javascript-S1874.json
@@ -2,8 +2,6 @@
"p5.js:docs/yuidoc-p5-theme/assets/js/reference.js": [
2433,
2433,
-2535,
-3895,
4316,
4316,
4503,
diff --git a/its/ruling/src/test/java/org/sonar/javascript/it/JavaScriptRulingTest.java b/its/ruling/src/test/java/org/sonar/javascript/it/JavaScriptRulingTest.java
index 1c39f50ba12..350f209c5e2 100644
--- a/its/ruling/src/test/java/org/sonar/javascript/it/JavaScriptRulingTest.java
+++ b/its/ruling/src/test/java/org/sonar/javascript/it/JavaScriptRulingTest.java
@@ -272,8 +272,7 @@ static void runRulingTest(
.setProperty("sonar.javascript.node.maxspace", "2048")
.setProperty("sonar.javascript.maxFileSize", "4000")
.setProperty("sonar.cpd.exclusions", "**/*")
- .setProperty("sonar.internal.analysis.failFast", "true")
- .setDebugLogs(true);
+ .setProperty("sonar.internal.analysis.failFast", "true");
orchestrator.executeBuild(build);
assertThat(differencesPath).hasContent("");
diff --git a/sonar-plugin/sonar-javascript-plugin/pom.xml b/sonar-plugin/sonar-javascript-plugin/pom.xml
index dcfc79ca6d5..93401193e44 100644
--- a/sonar-plugin/sonar-javascript-plugin/pom.xml
+++ b/sonar-plugin/sonar-javascript-plugin/pom.xml
@@ -164,6 +164,10 @@
win-x64/node.exe.xz
${project.build.directory}/node/win-x64/node.exe.xz
+
+ win-x64/version.txt
+ ${project.build.directory}/node/win-x64/version.txt
+
@@ -188,6 +192,10 @@
linux-x64/node.xz
${project.build.directory}/node/linux-x64/node.xz
+
+ linux-x64/version.txt
+ ${project.build.directory}/node/linux-x64/version.txt
+
@@ -212,6 +220,10 @@
darwin-arm64/node.xz
${project.build.directory}/node/darwin-arm64/node.xz
+
+ darwin-arm64/version.txt
+ ${project.build.directory}/node/darwin-arm64/version.txt
+
@@ -236,14 +248,26 @@
win-x64/node.exe.xz
${project.build.directory}/node/win-x64/node.exe.xz
+
+ win-x64/version.txt
+ ${project.build.directory}/node/win-x64/version.txt
+
linux-x64/node.xz
${project.build.directory}/node/linux-x64/node.xz
+
+ linux-x64/version.txt
+ ${project.build.directory}/node/linux-x64/version.txt
+
darwin-arm64/node.xz
${project.build.directory}/node/darwin-arm64/node.xz
+
+ darwin-arm64/version.txt
+ ${project.build.directory}/node/darwin-arm64/version.txt
+
diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java
index 07fd5a67959..085f0749c67 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/JavaScriptPlugin.java
@@ -39,6 +39,7 @@
import org.sonar.plugins.javascript.bridge.BundleImpl;
import org.sonar.plugins.javascript.bridge.CssRuleSensor;
import org.sonar.plugins.javascript.bridge.EmbeddedNode;
+import org.sonar.plugins.javascript.bridge.Environment;
import org.sonar.plugins.javascript.bridge.HtmlSensor;
import org.sonar.plugins.javascript.bridge.JsTsChecks;
import org.sonar.plugins.javascript.bridge.JsTsSensor;
@@ -152,7 +153,8 @@ public void define(Context context) {
AnalysisProcessor.class,
YamlSensor.class,
HtmlSensor.class,
- EmbeddedNode.class
+ EmbeddedNode.class,
+ Environment.class
);
context.addExtensions(
diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java
index 16917cd0168..b1260151283 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServerImpl.java
@@ -74,8 +74,7 @@ private enum Status {
public static final String SONARJS_EXISTING_NODE_PROCESS_PORT =
"SONARJS_EXISTING_NODE_PROCESS_PORT";
private static final Gson GSON = new Gson();
-
- private static final String DEPLOY_LOCATION = "bridge-bundle";
+ private static final String BRIDGE_DEPLOY_LOCATION = "bridge-bundle";
private final HttpClient client;
private final NodeCommandBuilder nodeCommandBuilder;
@@ -87,7 +86,7 @@ private enum Status {
private Status status = Status.NOT_STARTED;
private final RulesBundles rulesBundles;
private final NodeDeprecationWarning deprecationWarning;
- private final Path deployLocation;
+ private final Path temporaryDeployLocation;
private final Monitoring monitoring;
private final EmbeddedNode embeddedNode;
private static final int HEARTBEAT_INTERVAL_SECONDS = 5;
@@ -134,7 +133,7 @@ public BridgeServerImpl(
this.rulesBundles = rulesBundles;
this.deprecationWarning = deprecationWarning;
this.hostAddress = InetAddress.getLoopbackAddress().getHostAddress();
- this.deployLocation = tempFolder.newDir(DEPLOY_LOCATION).toPath();
+ this.temporaryDeployLocation = tempFolder.newDir(BRIDGE_DEPLOY_LOCATION).toPath();
this.monitoring = monitoring;
this.heartbeatService = Executors.newSingleThreadScheduledExecutor();
this.embeddedNode = embeddedNode;
@@ -163,9 +162,14 @@ int getTimeoutSeconds() {
return timeoutSeconds;
}
+ /**
+ * Extracts the bridge files and node.js runtime (if included)
+ *
+ * @throws IOException
+ */
void deploy() throws IOException {
- bundle.deploy(deployLocation);
- embeddedNode.deployNode(deployLocation);
+ bundle.deploy(temporaryDeployLocation);
+ embeddedNode.deploy();
}
void startServer(SensorContext context, List deployedBundles) throws IOException {
@@ -292,7 +296,7 @@ public void startServerLazily(SensorContext context) throws IOException {
throw new ServerAlreadyFailedException();
}
deploy();
- List deployedBundles = rulesBundles.deploy(deployLocation.resolve("package"));
+ List deployedBundles = rulesBundles.deploy(temporaryDeployLocation.resolve("package"));
rulesBundles
.getUcfgRulesBundle()
.ifPresent(rulesBundle -> PluginInfo.setUcfgPluginVersion(rulesBundle.bundleVersion()));
diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/EmbeddedNode.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/EmbeddedNode.java
index ce7bfd6dd8e..cb20a502abe 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/EmbeddedNode.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/EmbeddedNode.java
@@ -19,13 +19,17 @@
*/
package org.sonar.plugins.javascript.bridge;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
import static org.sonar.plugins.javascript.bridge.EmbeddedNode.Platform.UNSUPPORTED;
import static org.sonarsource.api.sonarlint.SonarLintSide.INSTANCE;
import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
@@ -36,12 +40,20 @@
import org.sonarsource.api.sonarlint.SonarLintSide;
import org.tukaani.xz.XZInputStream;
+/**
+ * Class handling the extraction of the embedded Node.JS runtime
+ */
@ScannerSide
@SonarLintSide(lifespan = INSTANCE)
public class EmbeddedNode {
+ private static final String DEPLOY_LOCATION = Path.of(".sonar", "js", "node-runtime").toString();
+ public static final String VERSION_FILENAME = "version.txt";
private static final Logger LOG = Loggers.get(EmbeddedNode.class);
private Path deployLocation;
+ private final Platform platform;
+ private boolean isAvailable;
+ private Environment env;
enum Platform {
WIN_X64,
@@ -49,19 +61,33 @@ enum Platform {
DARWIN_ARM64,
UNSUPPORTED;
- String pathInJar() {
+ private String pathInJar() {
switch (this) {
case WIN_X64:
- return "/win-x64/node.exe.xz";
+ return "/win-x64/";
case LINUX_X64:
- return "/linux-x64/node.xz";
+ return "/linux-x64/";
case DARWIN_ARM64:
- return "/darwin-arm64/node.xz";
+ return "/darwin-arm64/";
default:
return "";
}
}
+ /**
+ * @return the path of the node compressed node runtime in the JAR
+ */
+ String archivePathInJar() {
+ return pathInJar() + binary() + ".xz";
+ }
+
+ /**
+ * @return the path of the file storing the version of the node runtime in the JAR
+ */
+ String versionPathInJar() {
+ return pathInJar() + VERSION_FILENAME;
+ }
+
/**
* @return the correct binary name depending on the platform: `node` or `node.exe`
*/
@@ -73,61 +99,99 @@ String binary() {
}
}
- static Platform detect() {
- var osName = System.getProperty("os.name");
+ /**
+ * @return The platform where this code is running
+ */
+ static Platform detect(Environment env) {
+ var osName = env.getOsName();
var lowerCaseOsName = osName.toLowerCase(Locale.ROOT);
- if (osName.contains("Windows") && isX64()) {
+ if (osName.contains("Windows") && isX64(env)) {
return WIN_X64;
- } else if (lowerCaseOsName.contains("linux") && isX64()) {
+ } else if (lowerCaseOsName.contains("linux") && isX64(env)) {
return LINUX_X64;
- } else if (lowerCaseOsName.contains("mac os") && (isARM64())) {
+ } else if (lowerCaseOsName.contains("mac os") && isARM64(env)) {
return DARWIN_ARM64;
}
return UNSUPPORTED;
}
- static boolean isX64() {
- var arch = System.getProperty("os.arch");
- return arch.contains("amd64");
+ private static boolean isX64(Environment env) {
+ return env.getOsArch().contains("amd64");
}
- static boolean isARM64() {
- var arch = System.getProperty("os.arch");
- return arch.contains("aarch64");
+ private static boolean isARM64(Environment env) {
+ return env.getOsArch().contains("aarch64");
}
}
- private final Platform platform = Platform.detect();
+ public EmbeddedNode(Environment env) {
+ this.platform = Platform.detect(env);
+ this.deployLocation = getPluginCache(env.getUserHome());
+ this.env = env;
+ }
- private boolean isAvailable;
+ /**
+ * @return a path to `DEPLOY_LOCATION` from the given `baseDir`
+ */
+ private static Path getPluginCache(String baseDir) {
+ return Path.of(baseDir).resolve(DEPLOY_LOCATION);
+ }
public boolean isAvailable() {
return platform != UNSUPPORTED && isAvailable;
}
- public void deployNode(Path deployLocation) throws IOException {
- LOG.debug(
- "Detected os: {} arch: {} platform: {}",
- System.getProperty("os.name"),
- System.getProperty("os.arch"),
- platform
- );
+ /**
+ * Extracts the node runtime from the JAR to the given `deployLocation`.
+ * Skips the operation if the platform is unsupported, already extracted or missing from the JAR (legacy).
+ *
+ * @throws IOException
+ */
+ public void deploy() throws IOException {
+ LOG.debug("Detected os: {} arch: {} platform: {}", env.getOsName(), env.getOsArch(), platform);
if (platform == UNSUPPORTED || isAvailable) {
return;
}
- this.deployLocation = deployLocation;
- var is = getClass().getResourceAsStream(platform.pathInJar());
+ var is = getClass().getResourceAsStream(platform.archivePathInJar());
if (is == null) {
- LOG.debug("Embedded node not found for platform {}", platform.pathInJar());
+ LOG.debug("Embedded node not found for platform {}", platform.archivePathInJar());
return;
}
- var target = deployLocation.resolve(platform.binary() + ".xz");
- LOG.debug("Copy embedded node to {}", target);
- Files.copy(is, target);
- extract(target);
+
+ var targetArchive = deployLocation.resolve(platform.binary() + ".xz");
+ var targetDirectory = targetArchive.getParent();
+ var targetVersion = targetDirectory.resolve(VERSION_FILENAME);
+ // we assume that since the archive exists, the version file must as well
+ var versionIs = getClass().getResourceAsStream(platform.versionPathInJar());
+
+ if (!Files.exists(targetVersion) || isDifferent(versionIs, targetVersion)) {
+ LOG.debug("Copy embedded node to {}", targetArchive);
+ Files.createDirectories(targetDirectory);
+ Files.copy(is, targetArchive, REPLACE_EXISTING);
+ extract(targetArchive);
+ Files.copy(versionIs, deployLocation.resolve(VERSION_FILENAME), REPLACE_EXISTING);
+ } else {
+ LOG.debug("Skipping node deploy. Deployed node has latest version.");
+ }
+
isAvailable = true;
}
+ private static boolean isDifferent(InputStream newVersionIs, Path currentVersionPath)
+ throws IOException {
+ var newVersionString = new String(newVersionIs.readAllBytes(), StandardCharsets.UTF_8);
+ var currentVersionString = Files.readString(currentVersionPath);
+ LOG.debug(
+ "Currently installed Node.JS version: " +
+ currentVersionString +
+ " at " +
+ currentVersionPath +
+ ". Available version in analyzer: " +
+ newVersionString
+ );
+ return !newVersionString.equals(currentVersionString);
+ }
+
/**
* Expects a path to a xz-compressed file ending in `.xz` like `node.xz` and
* extracts it into the same place as `node`.
@@ -140,11 +204,6 @@ public void deployNode(Path deployLocation) throws IOException {
private void extract(Path source) throws IOException {
var sourceAsString = source.toString();
var target = Path.of(sourceAsString.substring(0, sourceAsString.length() - 3));
- if (Files.exists(target)) {
- // TODO drop this skip if it prevents us from upgrading the runtime
- LOG.debug("Skipping decompression. " + target.toString() + " already exists.");
- return;
- }
LOG.debug("Decompressing " + source.toAbsolutePath() + " into " + target);
try (
var is = Files.newInputStream(source);
@@ -158,7 +217,7 @@ private void extract(Path source) throws IOException {
os.write(buf, 0, nextBytes);
}
if (platform != Platform.WIN_X64) {
- Files.setPosixFilePermissions(target, Set.of(OWNER_EXECUTE, OWNER_READ));
+ Files.setPosixFilePermissions(target, Set.of(OWNER_EXECUTE, OWNER_READ, OWNER_WRITE));
}
}
}
diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/Environment.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/Environment.java
new file mode 100644
index 00000000000..ebbc0d5c8a6
--- /dev/null
+++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/Environment.java
@@ -0,0 +1,27 @@
+package org.sonar.plugins.javascript.bridge;
+
+import static org.sonarsource.api.sonarlint.SonarLintSide.INSTANCE;
+
+import org.sonar.api.scanner.ScannerSide;
+import org.sonarsource.api.sonarlint.SonarLintSide;
+
+/**
+ * Class to access host parameters.
+ * This abstraction is necessary to mock it in tests.
+ */
+@ScannerSide
+@SonarLintSide(lifespan = INSTANCE)
+public class Environment {
+
+ public String getUserHome() {
+ return System.getProperty("user.home");
+ }
+
+ public String getOsName() {
+ return System.getProperty("os.name");
+ }
+
+ public String getOsArch() {
+ return System.getProperty("os.arch");
+ }
+}
diff --git a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/nodejs/NodeCommandBuilderImpl.java b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/nodejs/NodeCommandBuilderImpl.java
index eddf482d360..359b29b4d7a 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/nodejs/NodeCommandBuilderImpl.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/nodejs/NodeCommandBuilderImpl.java
@@ -42,6 +42,7 @@
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.plugins.javascript.bridge.EmbeddedNode;
+import org.sonar.plugins.javascript.bridge.Environment;
public class NodeCommandBuilderImpl implements NodeCommandBuilder {
@@ -59,7 +60,7 @@ public class NodeCommandBuilderImpl implements NodeCommandBuilder {
);
private final ProcessWrapper processWrapper;
- private EmbeddedNode embeddedNode = new EmbeddedNode();
+ private EmbeddedNode embeddedNode = new EmbeddedNode(new Environment());
private Version minNodeVersion;
private Configuration configuration;
private List args = new ArrayList<>();
diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java
index 3bf63647dca..51727f17e9b 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/JavaScriptPluginTest.java
@@ -40,7 +40,7 @@
class JavaScriptPluginTest {
- private static final int BASE_EXTENSIONS = 35;
+ private static final int BASE_EXTENSIONS = 36;
private static final int JS_ADDITIONAL_EXTENSIONS = 4;
private static final int TS_ADDITIONAL_EXTENSIONS = 3;
private static final int CSS_ADDITIONAL_EXTENSIONS = 3;
diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java
index ed8e3dc970d..b8ff074fba7 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/BridgeServerImplTest.java
@@ -112,7 +112,7 @@ public void setUp() throws Exception {
}
@AfterEach
- public void tearDown() throws Exception {
+ public void tearDown() {
try {
if (bridgeServer != null) {
bridgeServer.clean();
@@ -156,7 +156,7 @@ void should_throw_if_failed_to_build_node_command() throws Exception {
deprecationWarning,
tempFolder,
monitoring,
- new EmbeddedNode()
+ new EmbeddedNode(createMockEnvironment())
);
bridgeServer.deploy();
List deployedBundles = emptyList();
@@ -660,7 +660,7 @@ void should_use_default_timeout() {
deprecationWarning,
tempFolder,
monitoring,
- new EmbeddedNode()
+ new EmbeddedNode(createMockEnvironment())
);
assertThat(bridgeServer.getTimeoutSeconds()).isEqualTo(300);
}
@@ -723,7 +723,7 @@ public void execute(SensorContext context) {}
deprecationWarning,
tempFolder,
monitoring,
- new EmbeddedNode()
+ new EmbeddedNode(createMockEnvironment())
);
bridgeServer.deploy();
bridgeServer.startServerLazily(context);
@@ -755,7 +755,7 @@ void test_ucfg_bundle_version() throws Exception {
deprecationWarning,
tempFolder,
monitoring,
- new EmbeddedNode()
+ new EmbeddedNode(createMockEnvironment())
);
bridgeServer.startServerLazily(context);
@@ -772,10 +772,18 @@ private BridgeServerImpl createBridgeServer(String startServerScript) {
deprecationWarning,
tempFolder,
monitoring,
- new EmbeddedNode()
+ new EmbeddedNode(createMockEnvironment())
);
}
+ private Environment createMockEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getUserHome()).thenReturn("");
+ when(mockEnvironment.getOsName()).thenReturn("");
+ when(mockEnvironment.getOsArch()).thenReturn("");
+ return mockEnvironment;
+ }
+
static class TestBundle implements Bundle {
final String startServerScript;
diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/EmbeddedNodeTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/EmbeddedNodeTest.java
index 2e6418d37be..353282b398c 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/EmbeddedNodeTest.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/bridge/EmbeddedNodeTest.java
@@ -1,15 +1,111 @@
package org.sonar.plugins.javascript.bridge;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.event.Level;
+import org.sonar.api.testfixtures.log.LogTesterJUnit5;
class EmbeddedNodeTest {
+ @TempDir
+ Path tempDir;
+
+ @RegisterExtension
+ public LogTesterJUnit5 logTester = new LogTesterJUnit5().setLevel(Level.DEBUG);
+
+ private Environment currentEnvironment = new Environment();
+
+ @Test
+ void should_extract_if_deployLocation_contains_a_different_version() throws Exception {
+ var en = new EmbeddedNode(createTestEnvironment());
+ var runtimeFolder = en.binary().getParent();
+ Files.createDirectories(runtimeFolder);
+ Files.write(runtimeFolder.resolve("version.txt"), "a-different-version".getBytes());
+ en.deploy();
+ assertThat(en.binary()).exists();
+ }
+
+ @Test
+ void should_not_extract_if_deployLocation_contains_the_same_version() throws Exception {
+ var en = new EmbeddedNode(createTestEnvironment());
+ var runtimeFolder = en.binary().getParent();
+ Files.createDirectories(runtimeFolder);
+ Files.write(
+ runtimeFolder.resolve("version.txt"),
+ extractCurrentVersion(createTestEnvironment())
+ );
+ en.deploy();
+ assertThat(en.binary()).doesNotExist();
+ }
+
+ @Test
+ void should_extract_if_deployLocation_has_no_version() throws Exception {
+ var en = new EmbeddedNode(createTestEnvironment());
+ en.deploy();
+ assertThat(tempDir.resolve(en.binary())).exists();
+ }
+
+ @Test
+ void should_detect_platform_for_windows_environment() {
+ var platform = EmbeddedNode.Platform.detect(createWindowsEnvironment());
+ assertThat(platform).isEqualTo(EmbeddedNode.Platform.WIN_X64);
+ assertThat(platform.archivePathInJar()).isEqualTo("/win-x64/node.exe.xz");
+ }
+
+ @Test
+ void should_detect_platform_for_mac_os_environment() {
+ var platform = EmbeddedNode.Platform.detect(createMacOSEnvironment());
+ assertThat(platform).isEqualTo(EmbeddedNode.Platform.DARWIN_ARM64);
+ assertThat(platform.archivePathInJar()).isEqualTo("/darwin-arm64/node.xz");
+ }
+
@Test
- void should_detect_platform_macos() {
- if (System.getProperty("os.name").startsWith("Mac")) {
- assertThat(EmbeddedNode.Platform.detect()).isEqualTo(EmbeddedNode.Platform.DARWIN_ARM64);
- }
+ void should_return_unsupported_for_unknown_environment() {
+ var platform = EmbeddedNode.Platform.detect(createUnsupportedEnvironment());
+ assertThat(platform).isEqualTo(EmbeddedNode.Platform.UNSUPPORTED);
+ assertThat(platform.archivePathInJar()).isEqualTo("node.xz");
+ }
+
+ private byte[] extractCurrentVersion(Environment env) throws IOException {
+ return getClass()
+ .getResourceAsStream(EmbeddedNode.Platform.detect(env).versionPathInJar())
+ .readAllBytes();
+ }
+
+ private Environment createTestEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getUserHome()).thenReturn(tempDir.toString());
+ when(mockEnvironment.getOsName()).thenReturn(currentEnvironment.getOsName());
+ when(mockEnvironment.getOsArch()).thenReturn(currentEnvironment.getOsArch());
+ return mockEnvironment;
+ }
+
+ private Environment createMacOSEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getOsName()).thenReturn("mac os x");
+ when(mockEnvironment.getOsArch()).thenReturn("aarch64");
+ return mockEnvironment;
+ }
+
+ private Environment createWindowsEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getOsName()).thenReturn("Windows 99");
+ when(mockEnvironment.getOsArch()).thenReturn("amd64");
+ return mockEnvironment;
+ }
+
+ private Environment createUnsupportedEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getOsName()).thenReturn("");
+ when(mockEnvironment.getOsArch()).thenReturn("");
+ return mockEnvironment;
}
}
diff --git a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/nodejs/NodeCommandTest.java b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/nodejs/NodeCommandTest.java
index 42d52f2a892..97024685978 100644
--- a/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/nodejs/NodeCommandTest.java
+++ b/sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/nodejs/NodeCommandTest.java
@@ -53,6 +53,7 @@
import org.sonar.api.utils.Version;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.plugins.javascript.bridge.EmbeddedNode;
+import org.sonar.plugins.javascript.bridge.Environment;
class NodeCommandTest {
@@ -406,17 +407,16 @@ void test_windows_default_node_not_found() throws Exception {
@Test
void test_embedded_runtime() throws Exception {
- var en = new EmbeddedNode();
- en.deployNode(tempDir);
+ var en = new EmbeddedNode(createTestEnvironment());
+ en.deploy();
NodeCommand nodeCommand = NodeCommand
.builder()
.script(PATH_TO_SCRIPT)
.pathResolver(getPathResolver())
.embeddedNode(en)
.build();
- // TODO for some reason, using mockProcessWrapper to test for the used command does not yield the expected result
- var expectedCommand =
- Paths.get(tempDir.toString(), en.binary().getFileName().toString()) + " " + PATH_TO_SCRIPT;
+ // For some reason, using mockProcessWrapper to test for the used command does not yield the expected result
+ var expectedCommand = Paths.get(en.binary().toString()) + " " + PATH_TO_SCRIPT;
assertThat(nodeCommand.toString()).isEqualTo(expectedCommand);
}
@@ -431,8 +431,8 @@ void test_embedded_runtime_with_forceHost_for_macos() throws Exception {
mapSettings.setProperty(NODE_FORCE_HOST_PROPERTY, true);
Configuration configuration = mapSettings.asConfig();
- var en = new EmbeddedNode();
- en.deployNode(tempDir);
+ var en = new EmbeddedNode(createTestEnvironment());
+ en.deploy();
NodeCommand nodeCommand = NodeCommand
.builder()
.script(PATH_TO_SCRIPT)
@@ -453,4 +453,12 @@ private static BundlePathResolver getPathResolver() {
File file = new File("src/test/resources");
return p -> new File(file.getAbsoluteFile(), p).getAbsolutePath();
}
+
+ private Environment createTestEnvironment() {
+ Environment mockEnvironment = mock(Environment.class);
+ when(mockEnvironment.getUserHome()).thenReturn(tempDir.toString());
+ when(mockEnvironment.getOsName()).thenReturn(new Environment().getOsName());
+ when(mockEnvironment.getOsArch()).thenReturn(new Environment().getOsArch());
+ return mockEnvironment;
+ }
}
diff --git a/tools/fetch-node/node-distros.mjs b/tools/fetch-node/node-distros.mjs
index ea9c66b548b..a94fd05f6c0 100644
--- a/tools/fetch-node/node-distros.mjs
+++ b/tools/fetch-node/node-distros.mjs
@@ -1,4 +1,4 @@
-const NODE_VERSION = 'v20.5.1';
+export const NODE_VERSION = 'v20.5.1';
const NODE_ORG_URL = `https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}`;
const NODE_ARTIFACTORY_URL = `https://repox.jfrog.io/artifactory/nodejs-dist/${NODE_VERSION}/node-${NODE_VERSION}`;
@@ -11,7 +11,7 @@ const NODE_ARTIFACTORY_URL = `https://repox.jfrog.io/artifactory/nodejs-dist/${N
* - `sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/bridge/EmbeddedNode.java`
* - `sonar-plugin/sonar-javascript-plugin/pom.xml`
*/
-export default [
+export const DISTROS = [
{
id: 'win-x64',
url: `${NODE_ORG_URL}-win-x64.zip`,
@@ -31,3 +31,5 @@ export default [
sha: 'a8678ae00425acdf692e943e3f1cea11a4c46281e4257b82886423bd4ef6f2b5',
},
];
+
+export const VERSION_FILENAME = 'version.txt';
diff --git a/tools/fetch-node/scripts/copy-to-plugin.mjs b/tools/fetch-node/scripts/copy-to-plugin.mjs
index 4d26ded9e6f..462bd9f3554 100644
--- a/tools/fetch-node/scripts/copy-to-plugin.mjs
+++ b/tools/fetch-node/scripts/copy-to-plugin.mjs
@@ -21,7 +21,7 @@ import fse from 'fs-extra';
import * as path from 'node:path';
import * as fs from 'node:fs';
import { RUNTIMES_DIR, TARGET_DIR } from './directories.mjs';
-import NODE_DISTROS from '../node-distros.mjs';
+import { DISTROS, NODE_VERSION, VERSION_FILENAME } from '../node-distros.mjs';
/**
* Copies tools/fetch-node/downloads/runtimes
@@ -29,7 +29,7 @@ import NODE_DISTROS from '../node-distros.mjs';
* sonar-plugin/sonar-javascript-plugin/target/node
*/
-for (const distro of NODE_DISTROS) {
+for (const distro of DISTROS) {
const sourceDir = path.join(RUNTIMES_DIR, distro.id);
const filename = fs.readdirSync(sourceDir).filter(filename => filename.endsWith('.xz'))[0];
const targetDir = path.join(TARGET_DIR, distro.id);
@@ -38,4 +38,5 @@ for (const distro of NODE_DISTROS) {
const targetFilename = path.join(targetDir, filename);
console.log(`Copying ${sourceFilename} to ${targetFilename}`);
fse.copySync(sourceFilename, targetFilename);
+ fs.writeFileSync(path.join(targetDir, VERSION_FILENAME), NODE_VERSION);
}
diff --git a/tools/fetch-node/scripts/fetch-node.mjs b/tools/fetch-node/scripts/fetch-node.mjs
index a367442aef3..4d90f196abd 100644
--- a/tools/fetch-node/scripts/fetch-node.mjs
+++ b/tools/fetch-node/scripts/fetch-node.mjs
@@ -26,7 +26,7 @@ import * as path from 'node:path';
import * as stream from 'node:stream';
import * as crypto from 'node:crypto';
import * as os from 'node:os';
-import NODE_DISTROS from '../node-distros.mjs';
+import { DISTROS } from '../node-distros.mjs';
import { DOWNLOAD_DIR, RUNTIMES_DIR } from './directories.mjs';
/**
@@ -34,7 +34,7 @@ import { DOWNLOAD_DIR, RUNTIMES_DIR } from './directories.mjs';
* downloads/runtimes/{distro.id}/node{.exe}
*/
-for (const distro of NODE_DISTROS) {
+for (const distro of DISTROS) {
const filename = getFilenameFromUrl(distro.url);
const archiveFilename = path.join(DOWNLOAD_DIR, filename);
await downloadRuntime(distro, archiveFilename);