From f5c0e174a8abf4333fbc04f08f0bf570fe7bf418 Mon Sep 17 00:00:00 2001 From: Nicolas Quinquenel Date: Fri, 5 Jan 2024 14:08:40 +0100 Subject: [PATCH] SLCORE-667 Avoid loading DBD analyzers if Python is not present --- .../plugin/commons/DataflowBugDetection.java | 4 +- .../core/plugin/commons/PluginsLoader.java | 2 +- .../core/plugin/commons/SkipReason.java | 10 ++ .../SonarPluginRequirementsChecker.java | 52 +++++--- .../SonarPluginRequirementsCheckerTests.java | 124 ++++++++++++++---- 5 files changed, 148 insertions(+), 44 deletions(-) diff --git a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/DataflowBugDetection.java b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/DataflowBugDetection.java index ff5844c77c..472f9f718d 100644 --- a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/DataflowBugDetection.java +++ b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/DataflowBugDetection.java @@ -21,13 +21,13 @@ import java.util.Set; -class DataflowBugDetection { +public class DataflowBugDetection { private DataflowBugDetection() { // Static stuff only } - private static final Set PLUGIN_ALLOW_LIST = Set.of("dbd", "dbdpythonfrontend"); + public static final Set PLUGIN_ALLOW_LIST = Set.of("dbd", "dbdpythonfrontend"); static Set getPluginAllowList(boolean isDataflowBugDetectionEnabled) { return isDataflowBugDetectionEnabled ? PLUGIN_ALLOW_LIST : Set.of(); diff --git a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoader.java b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoader.java index a8ab23d1f0..0e288a17a6 100644 --- a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoader.java +++ b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/PluginsLoader.java @@ -71,7 +71,7 @@ public Configuration(Set pluginJarLocations, Set enabledLanguage public PluginsLoadResult load(Configuration configuration) { var javaSpecVersion = Objects.requireNonNull(System2.INSTANCE.property("java.specification.version"), "Missing Java property 'java.specification.version'"); var pluginCheckResultByKeys = requirementsChecker.checkRequirements(configuration.pluginJarLocations, configuration.enabledLanguages, Version.create(javaSpecVersion), - configuration.shouldCheckNodeVersion, configuration.nodeCurrentVersion); + configuration.shouldCheckNodeVersion, configuration.nodeCurrentVersion, configuration.enableDataflowBugDetection); var nonSkippedPlugins = getNonSkippedPlugins(pluginCheckResultByKeys); logPlugins(nonSkippedPlugins); diff --git a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/SkipReason.java b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/SkipReason.java index 4234a986ca..6280a9946e 100644 --- a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/SkipReason.java +++ b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/SkipReason.java @@ -29,6 +29,16 @@ public interface SkipReason { + class UnsupportedPlugin implements SkipReason { + + public static final UnsupportedPlugin INSTANCE = new UnsupportedPlugin(); + + private UnsupportedPlugin() { + // Singleton + } + + } + class IncompatiblePluginApi implements SkipReason { public static final IncompatiblePluginApi INSTANCE = new IncompatiblePluginApi(); diff --git a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsChecker.java b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsChecker.java index 5877c390f4..817b06bd34 100644 --- a/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsChecker.java +++ b/plugin-commons/src/main/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsChecker.java @@ -25,20 +25,21 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nullable; import org.sonarsource.sonarlint.core.commons.Language; import org.sonarsource.sonarlint.core.commons.PluginsMinVersions; import org.sonarsource.sonarlint.core.commons.Version; import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger; import org.sonarsource.sonarlint.core.plugin.commons.ApiVersions; +import org.sonarsource.sonarlint.core.plugin.commons.DataflowBugDetection; import org.sonarsource.sonarlint.core.plugin.commons.SkipReason; import org.sonarsource.sonarlint.core.plugin.commons.SkipReason.UnsatisfiedRuntimeRequirement.RuntimeRequirement; import org.sonarsource.sonarlint.core.plugin.commons.loading.SonarPluginManifest.RequiredPlugin; public class SonarPluginRequirementsChecker { - private static final String OLD_SONARTS_PLUGIN_KEY = "typescript"; - private static final SonarLintLogger LOG = SonarLintLogger.get(); + private static final String OLD_SONARTS_PLUGIN_KEY = "typescript"; private final PluginsMinVersions pluginMinVersions; private final Version implementedPluginApiVersion; @@ -56,7 +57,7 @@ public SonarPluginRequirementsChecker() { * Attempt to read JAR manifests, load metadata, and check all requirements to ensure the plugin can be instantiated. */ public Map checkRequirements(Set pluginJarLocations, Set enabledLanguages, Version jreCurrentVersion, - boolean shouldCheckNodeVersion, Optional nodeCurrentVersion) { + boolean shouldCheckNodeVersion, Optional nodeCurrentVersion, boolean enableDataflowBugDetection) { Map resultsByKey = new HashMap<>(); for (Path jarLocation : pluginJarLocations) { @@ -77,7 +78,7 @@ public Map checkRequirements(Set pl // Second pass of checks for (PluginRequirementsCheckResult result : resultsByKey.values()) { if (!result.isSkipped()) { - resultsByKey.put(result.getPlugin().getKey(), checkUnsatisfiedPluginDependency(result, resultsByKey)); + resultsByKey.put(result.getPlugin().getKey(), checkUnsatisfiedPluginDependency(result, resultsByKey, enableDataflowBugDetection)); } } return resultsByKey; @@ -110,12 +111,10 @@ private PluginRequirementsCheckResult checkIfSkippedAndPopulateReason(PluginInfo return new PluginRequirementsCheckResult(plugin, new SkipReason.IncompatiblePluginVersion(pluginMinVersion)); } var jreMinVersion = plugin.getJreMinVersion(); - if (jreMinVersion != null) { - if (!jreCurrentVersion.satisfiesMinRequirement(jreMinVersion)) { - LOG.debug("Plugin '{}' requires JRE {} while current is {}. Skip loading it.", plugin.getName(), jreMinVersion, jreCurrentVersion); - return new PluginRequirementsCheckResult(plugin, - new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, jreCurrentVersion.toString(), jreMinVersion.toString())); - } + if (jreMinVersion != null && !jreCurrentVersion.satisfiesMinRequirement(jreMinVersion)) { + LOG.debug("Plugin '{}' requires JRE {} while current is {}. Skip loading it.", plugin.getName(), jreMinVersion, jreCurrentVersion); + return new PluginRequirementsCheckResult(plugin, + new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, jreCurrentVersion.toString(), jreMinVersion.toString())); } if (shouldCheckNodeVersion) { var nodeMinVersion = plugin.getNodeJsMinVersion(); @@ -130,6 +129,7 @@ private PluginRequirementsCheckResult checkIfSkippedAndPopulateReason(PluginInfo } } } + return new PluginRequirementsCheckResult(plugin, null); } @@ -151,7 +151,7 @@ static boolean isCompatibleWith(PluginInfo plugin, Version implementedApiVersion } private static PluginRequirementsCheckResult checkUnsatisfiedPluginDependency(PluginRequirementsCheckResult currentResult, - Map currentResultsByKey) { + Map currentResultsByKey, boolean enableDataflowBugDetection) { var plugin = currentResult.getPlugin(); for (RequiredPlugin required : plugin.getRequiredPlugins()) { if ("license".equals(required.getKey()) || (Language.JS.getPluginKey().equals(plugin.getKey()) && OLD_SONARTS_PLUGIN_KEY.equals(required.getKey()))) { @@ -168,19 +168,35 @@ private static PluginRequirementsCheckResult checkUnsatisfiedPluginDependency(Pl // If we evaluate A before B, then A might be wrongly included // But I'm not aware of such case in real life. if (depInfo == null || depInfo.isSkipped()) { - LOG.debug("Plugin '{}' dependency on '{}' is unsatisfied. Skip loading it.", currentResult.getPlugin().getName(), required.getKey()); - return new PluginRequirementsCheckResult(currentResult.getPlugin(), new SkipReason.UnsatisfiedDependency(required.getKey())); + return processUnsatisfiedDependency(currentResult.getPlugin(), required.getKey()); } } var basePluginKey = plugin.getBasePlugin(); - if (basePluginKey != null) { - var baseInfo = currentResultsByKey.get(basePluginKey); - if (baseInfo == null || baseInfo.isSkipped()) { - LOG.debug("Plugin '{}' dependency on '{}' is unsatisfied. Skip loading it.", currentResult.getPlugin().getName(), basePluginKey); - return new PluginRequirementsCheckResult(currentResult.getPlugin(), new SkipReason.UnsatisfiedDependency(basePluginKey)); + if (basePluginKey != null && checkForPluginSkipped(currentResultsByKey.get(basePluginKey))) { + return processUnsatisfiedDependency(currentResult.getPlugin(), basePluginKey); + } + if (DataflowBugDetection.PLUGIN_ALLOW_LIST.contains(plugin.getKey())) { + // Workaround for SLCORE-667 + // dbd and dbdpythonfrontend require Python to be working + if (!enableDataflowBugDetection) { + LOG.debug("Plugin '{}' is not supported. Skip loading it.", plugin.getName()); + return new PluginRequirementsCheckResult(plugin, SkipReason.UnsupportedPlugin.INSTANCE); + } + var pythonPluginResult = currentResultsByKey.get(Language.PYTHON.getPluginKey()); + if (checkForPluginSkipped(pythonPluginResult)) { + return processUnsatisfiedDependency(currentResult.getPlugin(), Language.PYTHON.getPluginKey()); } } return currentResult; } + private static boolean checkForPluginSkipped(@Nullable PluginRequirementsCheckResult plugin) { + return plugin == null || plugin.isSkipped(); + } + + private static PluginRequirementsCheckResult processUnsatisfiedDependency(PluginInfo plugin, String pluginKeyDependency) { + LOG.debug("Plugin '{}' dependency on '{}' is unsatisfied. Skip loading it.", plugin.getName(), pluginKeyDependency); + return new PluginRequirementsCheckResult(plugin, new SkipReason.UnsatisfiedDependency(pluginKeyDependency)); + } + } diff --git a/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsCheckerTests.java b/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsCheckerTests.java index 0322614bb9..5a03ce72b4 100644 --- a/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsCheckerTests.java +++ b/plugin-commons/src/test/java/org/sonarsource/sonarlint/core/plugin/commons/loading/SonarPluginRequirementsCheckerTests.java @@ -82,7 +82,7 @@ void prepare() { @Test void load_no_plugins() { - var loadedPlugins = underTest.checkRequirements(Set.of(), NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(Set.of(), NONE, null, true, null, false); assertThat(loadedPlugins).isEmpty(); } @@ -90,7 +90,7 @@ void load_no_plugins() { @Test void load_plugin_fail_if_missing_jar() { Set jars = Set.of(Paths.get("doesntexists.jar")); - var checkRequirements = underTest.checkRequirements(jars, NONE, null, true, null); + var checkRequirements = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(checkRequirements).isEmpty(); assertThat(logTester.logs(Level.ERROR)).contains("Unable to load plugin doesntexists.jar"); @@ -101,7 +101,7 @@ void load_plugin_skip_corrupted_jar(@TempDir Path storage) throws IOException { var fakePlugin = fakePlugin(storage, "sonarjs.jar"); Set jars = Set.of(fakePlugin); - var checkRequirements = underTest.checkRequirements(jars, NONE, null, true, null); + var checkRequirements = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(checkRequirements).isEmpty(); assertThat(logTester.logs(Level.ERROR)).contains("Unable to load plugin " + fakePlugin); @@ -113,7 +113,7 @@ void load_plugin_skip_unsupported_plugins_api_version(@TempDir Path storage) thr withSqApiVersion("99.9"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) @@ -129,7 +129,7 @@ void load_plugin_skip_unsupported_plugins_version(@TempDir Path storage) throws when(pluginMinVersions.getMinimumVersion(FAKE_PLUGIN_KEY)).thenReturn("2.0"); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) @@ -142,7 +142,7 @@ void load_plugin_skip_not_enabled_languages(@TempDir Path storage) throws IOExce var fakePlugin = fakePlugin(storage, "sonarphp.jar", path -> createPluginManifest(path, Language.PHP.getPluginKey(), V1_0)); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.TS), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.TS), null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("php", true, new SkipReason.LanguagesNotEnabled(new HashSet<>(asList(Language.PHP))))); @@ -155,7 +155,7 @@ void load_plugin_skip_not_enabled_languages_multiple(@TempDir Path storage) thro Set jars = Set.of(fakePlugin); doReturn(V1_0).when(pluginMinVersions).getMinimumVersion(Language.C.getPluginKey()); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("cpp", true, new SkipReason.LanguagesNotEnabled(new HashSet<>(asList(Language.C, Language.CPP, Language.OBJC))))); @@ -168,7 +168,7 @@ void load_plugin_load_even_if_only_one_language_enabled(@TempDir Path storage) t Set jars = Set.of(fakePlugin); doReturn(V1_0).when(pluginMinVersions).getMinimumVersion(Language.CPP.getPluginKey()); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.CPP), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.CPP), null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("cpp", false, null)); @@ -180,7 +180,7 @@ void load_plugin_skip_plugins_having_missing_base_plugin(@TempDir Path storage) path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withBasePlugin(Language.JS.getPluginKey()))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("pluginkey", true, new SkipReason.UnsatisfiedDependency("javascript"))); @@ -198,7 +198,7 @@ void load_plugin_skip_plugins_having_skipped_base_plugin(@TempDir Path storage) // Ensure base plugin is skipped because of min version doReturn("2.0").when(pluginMinVersions).getMinimumVersion(Language.JS.getPluginKey()); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly( @@ -218,7 +218,7 @@ void load_plugin_having_base_plugin(@TempDir Path storage) throws IOException { // Ensure base plugin is not skipped doReturn(V1_0).when(pluginMinVersions).getMinimumVersion(Language.JS.getPluginKey()); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null, false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) @@ -233,7 +233,7 @@ void load_plugin_skip_plugins_having_missing_required_plugin(@TempDir Path stora path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withRequiredPlugins("required2:1.0"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) @@ -247,7 +247,7 @@ void load_plugin_ignore_license_plugin_dependency(@TempDir Path storage) throws path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withRequiredPlugins("license:1.0"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -265,7 +265,7 @@ void load_plugin_skip_plugins_having_skipped_required_plugin(@TempDir Path stora // Ensure dep plugin is skipped because of min version doReturn("2.0").when(pluginMinVersions).getMinimumVersion("required2"); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("pluginkey", true, new SkipReason.UnsatisfiedDependency("required2")), @@ -281,7 +281,7 @@ void load_plugin_ignore_dependency_between_sonarjs_and_sonarts(@TempDir Path sto Set jars = Set.of(fakePlugin); doReturn(true).when(pluginMinVersions).isVersionSupported(Language.JS.getPluginKey(), Version.create(V1_0)); - var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.JS), null, true, null, false); assertThat(loadedPlugins.values()).as(logsWithoutStartStop().collect(Collectors.joining("\n"))) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -294,7 +294,7 @@ void load_plugin(@TempDir Path storage) throws IOException { path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withSqApiVersion("7.9"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, null, false); assertThat(loadedPlugins.values()).as(logsWithoutStartStop().collect(Collectors.joining("\n"))) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -306,7 +306,7 @@ void load_plugin_skip_plugins_having_unsatisfied_jre(@TempDir Path storage) thro var fakePlugin = fakePlugin(storage, "fake.jar", path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withJreMinVersion("11"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create("1.8"), true, null); + var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create("1.8"), true, null, false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("pluginkey", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.JRE, "1.8", "11"))); @@ -319,7 +319,7 @@ void load_plugin_having_satisfied_nodejs(@TempDir Path storage) throws IOExcepti path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion("10.1.2"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("10.1.3"))); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("10.1.3")), false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -332,7 +332,7 @@ void load_plugin_having_satisfied_nodejs_nightly(@TempDir Path storage) throws I path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion("15.0.0"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("15.0.0-nightly20200921039c274dde"))); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("15.0.0-nightly20200921039c274dde")), false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -345,7 +345,7 @@ void load_plugin_skip_plugins_having_unsatisfied_nodejs_version(@TempDir Path st path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion("10.1.2"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("10.1.1"))); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.of(Version.create("10.1.1")), false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) @@ -359,7 +359,7 @@ void load_plugin_skip_plugins_having_unsatisfied_nodejs(@TempDir Path storage) t path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withNodejsMinVersion("10.1.2"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.empty()); + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, true, Optional.empty(), false); assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) .containsOnly(tuple("pluginkey", true, new SkipReason.UnsatisfiedRuntimeRequirement(RuntimeRequirement.NODEJS, null, "10.1.2"))); @@ -371,7 +371,7 @@ void load_plugin_having_satisfied_jre(@TempDir Path storage) throws IOException var fakePlugin = fakePlugin(storage, "fake.jar", path -> createPluginManifest(path, FAKE_PLUGIN_KEY, V1_0, withJreMinVersion("1.7"))); Set jars = Set.of(fakePlugin); - var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create("1.8"), true, Optional.empty()); + var loadedPlugins = underTest.checkRequirements(jars, NONE, Version.create("1.8"), true, Optional.empty(), false); assertThat(loadedPlugins.values()) .extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped) @@ -379,7 +379,85 @@ void load_plugin_having_satisfied_jre(@TempDir Path storage) throws IOException } @Test - void test_isCompatibleWith() throws IOException { + void load_plugin_skip_plugins_having_unsatisfied_python_frontend_dbd(@TempDir Path storage) throws IOException { + var fakePlugin = fakePlugin(storage, "fake.jar", + path -> createPluginManifest(path, "dbdpythonfrontend", V1_0)); + Set jars = Set.of(fakePlugin); + + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, false, Optional.empty(), false); + + assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) + .containsOnly(tuple("dbdpythonfrontend", true, SkipReason.UnsupportedPlugin.INSTANCE)); + assertThat(logsWithoutStartStop()).contains("Plugin 'dbdpythonfrontend' is not supported. Skip loading it."); + } + + @Test + void load_plugin_skip_plugins_having_unsatisfied_python_dbd(@TempDir Path storage) throws IOException { + var fakePlugin = fakePlugin(storage, "fake.jar", + path -> createPluginManifest(path, "dbd", V1_0)); + Set jars = Set.of(fakePlugin); + + var loadedPlugins = underTest.checkRequirements(jars, NONE, null, false, Optional.empty(), true); + + assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) + .containsOnly(tuple("dbd", true, new SkipReason.UnsatisfiedDependency(Language.PYTHON.getPluginKey()))); + assertThat(logsWithoutStartStop()).contains("Plugin 'dbd' dependency on 'python' is unsatisfied. Skip loading it."); + } + + @Test + void load_plugin_having_satisfied_python_frontend_dbd(@TempDir Path storage) throws IOException { + var fakePlugin = fakePlugin(storage, "fake.jar", + path -> createPluginManifest(path, "dbdpythonfrontend", V1_0)); + var fakePythonPlugin = fakePlugin(storage, "python.jar", + path -> createPluginManifest(path, Language.PYTHON.getPluginKey(), "1.15")); + Set jars = Set.of(fakePlugin, fakePythonPlugin); + + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.PYTHON), null, false, Optional.empty(), true); + + assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) + .containsOnly( + tuple("python", false, null), + tuple("dbdpythonfrontend", false, null) + ); + } + + @Test + void load_plugin_having_satisfied_python_dbd(@TempDir Path storage) throws IOException { + var fakePlugin = fakePlugin(storage, "fake.jar", + path -> createPluginManifest(path, "dbd", V1_0)); + var fakePythonPlugin = fakePlugin(storage, "python.jar", + path -> createPluginManifest(path, Language.PYTHON.getPluginKey(), "1.15")); + Set jars = Set.of(fakePlugin, fakePythonPlugin); + + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.PYTHON), null, false, Optional.empty(), true); + + assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) + .containsOnly( + tuple("python", false, null), + tuple("dbd", false, null) + ); + } + + @Test + void load_plugin_having_satisfied_python_dbd_but_no_feature_flag(@TempDir Path storage) throws IOException { + var fakePlugin = fakePlugin(storage, "fake.jar", + path -> createPluginManifest(path, "dbd", V1_0)); + var fakePythonPlugin = fakePlugin(storage, "python.jar", + path -> createPluginManifest(path, Language.PYTHON.getPluginKey(), "1.15")); + Set jars = Set.of(fakePlugin, fakePythonPlugin); + + var loadedPlugins = underTest.checkRequirements(jars, Set.of(Language.PYTHON), null, false, Optional.empty(), false); + + assertThat(loadedPlugins.values()).extracting(r -> r.getPlugin().getKey(), PluginRequirementsCheckResult::isSkipped, p -> p.getSkipReason().orElse(null)) + .containsOnly( + tuple("python", false, null), + tuple("dbd", true, SkipReason.UnsupportedPlugin.INSTANCE) + ); + assertThat(logsWithoutStartStop()).contains("Plugin 'dbd' is not supported. Skip loading it."); + } + + @Test + void test_isCompatibleWith() { assertThat(isCompatibleWith(withMinSqVersion("1.1"), Version.create("1.1"))).isTrue(); assertThat(isCompatibleWith(withMinSqVersion("1.1"), Version.create("1.1.0"))).isTrue(); assertThat(isCompatibleWith(withMinSqVersion("1.0"), Version.create("1.0.0"))).isTrue();