From ac37ecfc2fa0898f942b78aa2581c26fb2aca0c2 Mon Sep 17 00:00:00 2001 From: Jonathan Breedlove Date: Fri, 24 Jan 2025 12:58:59 -0800 Subject: [PATCH] Support for full semantic versioning in LSP artifacts (#329) --- plugin/.classpath | 2 +- plugin/.settings/org.eclipse.jdt.core.prefs | 1 + plugin/META-INF/MANIFEST.MF | 1 + plugin/pom.xml | 8 +- .../amazonq/lsp/manager/LspConstants.java | 15 +++- .../lsp/manager/fetcher/ArtifactUtils.java | 7 +- .../lsp/manager/fetcher/RemoteLspFetcher.java | 42 ++++----- .../fetcher/VersionManifestFetcher.java | 2 +- .../amazonq/lsp/manager/model/Manifest.java | 2 +- ...sion.java => ManifestArtifactVersion.java} | 2 +- .../eclipse/amazonq/util/UpdateUtils.java | 14 +-- .../configuration/DefaultPluginStoreTest.java | 1 + .../manager/fetcher/ArtifactUtilsTest.java | 19 +++-- .../manager/fetcher/RemoteLspFetcherTest.java | 85 ++++++++++++++----- 14 files changed, 137 insertions(+), 64 deletions(-) rename plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/{ArtifactVersion.java => ManifestArtifactVersion.java} (94%) diff --git a/plugin/.classpath b/plugin/.classpath index e6cee444..d541b7d8 100644 --- a/plugin/.classpath +++ b/plugin/.classpath @@ -32,4 +32,4 @@ - + \ No newline at end of file diff --git a/plugin/.settings/org.eclipse.jdt.core.prefs b/plugin/.settings/org.eclipse.jdt.core.prefs index d4540a53..d089a9b7 100644 --- a/plugin/.settings/org.eclipse.jdt.core.prefs +++ b/plugin/.settings/org.eclipse.jdt.core.prefs @@ -5,6 +5,7 @@ org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.release=enabled org.eclipse.jdt.core.compiler.source=17 diff --git a/plugin/META-INF/MANIFEST.MF b/plugin/META-INF/MANIFEST.MF index 88843d2f..f2f5db3d 100644 --- a/plugin/META-INF/MANIFEST.MF +++ b/plugin/META-INF/MANIFEST.MF @@ -52,6 +52,7 @@ Bundle-Classpath: target/classes/, target/dependency/jackson-databind-2.17.2.jar, target/dependency/jakarta.inject-api-2.0.1.jar, target/dependency/json-utils-2.28.26.jar, + target/dependency/maven-artifact-3.9.9.jar, target/dependency/metrics-spi-2.28.26.jar, target/dependency/netty-nio-client-2.28.26.jar, target/dependency/nimbus-jose-jwt-9.41.2.jar, diff --git a/plugin/pom.xml b/plugin/pom.xml index 368e926b..f6c73679 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -91,6 +91,11 @@ nimbus-jose-jwt 9.41.2 + + org.apache.maven + maven-artifact + 3.9.9 + org.junit.jupiter junit-jupiter @@ -134,7 +139,7 @@ ${project.build.directory}/dependency - software.amazon.awssdk,com.fasterxml.jackson,com.nimbusds,jakarta.inject,commons-codec,org.apache.httpcomponents,org.reactivestreams + software.amazon.awssdk,com.fasterxml.jackson,com.nimbusds,jakarta.inject,commons-codec,org.apache.httpcomponents,org.reactivestreams,org.apache.maven @@ -207,7 +212,6 @@ - @{argLine} -javaagent:${org.mockito:mockito-core:jar} ${project.build.testSourceDirectory} diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/LspConstants.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/LspConstants.java index 364988e5..a679fa33 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/LspConstants.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/LspConstants.java @@ -5,7 +5,10 @@ import java.nio.file.Paths; -import org.eclipse.osgi.service.resolver.VersionRange; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; + +import software.aws.toolkits.eclipse.amazonq.exception.AmazonQPluginException; public final class LspConstants { private LspConstants() { @@ -25,5 +28,13 @@ private LspConstants() { public static final String LSP_SUBDIRECTORY = "lsp"; public static final String AMAZONQ_LSP_SUBDIRECTORY = Paths.get(LSP_SUBDIRECTORY, "AmazonQ").toString(); - public static final VersionRange LSP_SUPPORTED_VERSION_RANGE = new VersionRange("[3.0.0, 3.0.10)"); + public static final VersionRange LSP_SUPPORTED_VERSION_RANGE = createVersionRange(); + + private static VersionRange createVersionRange() { + try { + return VersionRange.createFromVersionSpec("[3.0.0, 3.0.10)"); + } catch (InvalidVersionSpecificationException e) { + throw new AmazonQPluginException("Failed to parse LSP supported version range", e); + } + } } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtils.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtils.java index 63daa474..5ddfe42b 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtils.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtils.java @@ -16,7 +16,8 @@ import java.util.zip.ZipFile; import org.apache.commons.codec.digest.DigestUtils; -import org.osgi.framework.Version; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import software.aws.toolkits.eclipse.amazonq.exception.AmazonQPluginException; import software.aws.toolkits.eclipse.amazonq.plugin.Activator; @@ -60,8 +61,8 @@ public static void copyDirectory(final Path source, final Path target) throws IO }); } - public static Version parseVersion(final String versionString) { - return new Version(versionString); + public static ArtifactVersion parseVersion(final String versionString) { + return new DefaultArtifactVersion(versionString); } public static boolean validateHash(final Path file, final List hashes, final boolean strict) { diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcher.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcher.java index c3f04441..5a02a5f5 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcher.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcher.java @@ -22,14 +22,15 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.osgi.framework.Version; -import org.osgi.framework.VersionRange; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; import software.aws.toolkits.eclipse.amazonq.exception.AmazonQPluginException; import software.aws.toolkits.eclipse.amazonq.exception.LspError; import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspConstants; import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspFetchResult; -import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.ArtifactVersion; +import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.ManifestArtifactVersion; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Content; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Manifest; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Target; @@ -187,7 +188,7 @@ private void logMessageWithLicense(final String message, final String attributio } } - private Optional resolveVersion(final Manifest manifestFile, final PluginPlatform platform, + private Optional resolveVersion(final Manifest manifestFile, final PluginPlatform platform, final PluginArchitecture architecture, final Instant start) { if (manifestFile == null) { String failureReason = "No valid manifest version data was received. An error could have caused this. Please check logs."; @@ -201,8 +202,8 @@ private Optional resolveVersion(final Manifest manifestFile, fi .filter(version -> version.targets().stream() .anyMatch(target -> hasRequiredTargetContent(target, platform, architecture))) .max(Comparator.comparing( - artifactVersion -> Version.parseVersion(artifactVersion.serverVersion()) - )); + artifactVersion -> new DefaultArtifactVersion(artifactVersion.serverVersion()) + )); } private boolean hasRequiredTargetContent(final Target target, final PluginPlatform platform, final PluginArchitecture architecture) { @@ -215,11 +216,11 @@ private boolean isCompatibleTarget(final Target target, final PluginPlatform pla return target.platform().equalsIgnoreCase(platform.getValue()) && target.arch().equalsIgnoreCase(architecture.getValue()); } - private boolean isCompatibleVersion(final ArtifactVersion version) { - return versionRange.includes(ArtifactUtils.parseVersion(version.serverVersion())) && !version.isDelisted(); + private boolean isCompatibleVersion(final ManifestArtifactVersion version) { + return versionRange.containsVersion(ArtifactUtils.parseVersion(version.serverVersion())) && !version.isDelisted(); } - private Optional resolveTarget(final Optional targetVersion, final PluginPlatform platform, + private Optional resolveTarget(final Optional targetVersion, final PluginPlatform platform, final PluginArchitecture architecture) { return targetVersion.flatMap(version -> version.targets().stream() .filter(target -> isCompatibleTarget(target, platform, architecture)) @@ -320,9 +321,9 @@ private Path getFallback(final String expectedLspVersion, final PluginPlatform p // filter to get sorted list of compatible lsp versions that have a valid cache var sortedCachedLspVersions = compatibleLspVersions.stream() .filter(artifactVersion -> isValidCachedVersion(artifactVersion, expectedServerVersion, cachedVersions)) - .sorted(Comparator.comparing(x -> Version.parseVersion(x.serverVersion()), Comparator.reverseOrder())) + .sorted((v1, v2) -> new DefaultArtifactVersion(v2.serverVersion()) + .compareTo(new DefaultArtifactVersion(v1.serverVersion()))) .collect(Collectors.toList()); - var fallbackDir = sortedCachedLspVersions.stream() .map(x -> getValidLocalCacheDirectory(x, platform, architecture, destinationFolder)) .filter(Objects::nonNull).findFirst().orElse(null); @@ -333,7 +334,7 @@ private Path getFallback(final String expectedLspVersion, final PluginPlatform p * Validate the local cache directory of the given lsp version(matches expected hash) * If valid return cache directory, else return null */ - private Path getValidLocalCacheDirectory(final ArtifactVersion artifactVersion, final PluginPlatform platform, + private Path getValidLocalCacheDirectory(final ManifestArtifactVersion artifactVersion, final PluginPlatform platform, final PluginArchitecture architecture, final Path destinationFolder) { var target = resolveTarget(Optional.of(artifactVersion), platform, architecture); if (!target.isPresent() || target.get().contents() == null || target.get().contents().isEmpty()) { @@ -345,14 +346,14 @@ private Path getValidLocalCacheDirectory(final ArtifactVersion artifactVersion, return hasValidCache ? cacheDir : null; } - private boolean isValidCachedVersion(final ArtifactVersion lspVersion, final Version expectedServerVersion, - final List cachedVersions) { + private boolean isValidCachedVersion(final ManifestArtifactVersion lspVersion, final ArtifactVersion expectedServerVersion, + final List cachedVersions) { var serverVersion = ArtifactUtils.parseVersion(lspVersion.serverVersion()); return cachedVersions.contains(serverVersion) && (serverVersion.compareTo(expectedServerVersion) <= 0); } - private List getCachedVersions(final Path destinationFolder) { + private List getCachedVersions(final Path destinationFolder) { try { return Files.list(destinationFolder) .filter(Files::isDirectory) @@ -366,7 +367,7 @@ private List getCachedVersions(final Path destinationFolder) { } } - private Version getVersionedName(final String filename) { + private ArtifactVersion getVersionedName(final String filename) { try { return ArtifactUtils.parseVersion(filename); } catch (Exception e) { @@ -374,7 +375,7 @@ private Version getVersionedName(final String filename) { } } - private List getCompatibleArtifactVersions() { + private List getCompatibleArtifactVersions() { return manifest.versions().stream() .filter(version -> isCompatibleVersion(version)) .toList(); @@ -385,7 +386,8 @@ private void deleteDelistedVersions(final Path destinationFolder) { var cachedVersions = getCachedVersions(destinationFolder); // delete de-listed versions in the toolkit compatible version range - var delistedVersions = cachedVersions.stream().filter(x -> !compatibleVersions.contains(x) && versionRange.includes(x)).collect(Collectors.toList()); + var delistedVersions = cachedVersions.stream() + .filter(x -> !compatibleVersions.contains(x) && versionRange.containsVersion(x)).collect(Collectors.toList()); if (delistedVersions.size() > 0) { Activator.getLogger().info(String.format("Cleaning up %s cached de-listed versions for Amazon Q Language Server", delistedVersions.size())); } @@ -398,7 +400,7 @@ private void deleteExtraVersions(final Path destinationFolder) { var cachedVersions = getCachedVersions(destinationFolder); // delete extra versions in the compatible toolkit version range except highest 2 versions var extraVersions = cachedVersions.stream() - .filter(x -> versionRange.includes(x)) + .filter(x -> versionRange.containsVersion(x)) .sorted(Comparator.reverseOrder()) .skip(2) .collect(Collectors.toList()); @@ -410,7 +412,7 @@ private void deleteExtraVersions(final Path destinationFolder) { }); } - private void deleteCachedVersion(final Path destinationFolder, final Version version) { + private void deleteCachedVersion(final Path destinationFolder, final ArtifactVersion version) { var versionPath = destinationFolder.resolve(version.toString()); ArtifactUtils.deleteDirectory(versionPath); } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java index 90a1ddc3..fc546669 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/VersionManifestFetcher.java @@ -145,7 +145,7 @@ private Optional validateManifest(final String content) { try { var manifest = OBJECT_MAPPER.readValue(content, Manifest.class); var version = ArtifactUtils.parseVersion(manifest.manifestSchemaVersion()); - if (version.getMajor() == LspConstants.MANIFEST_MAJOR_VERSION) { + if (version.getMajorVersion() == LspConstants.MANIFEST_MAJOR_VERSION) { return Optional.of(manifest); } } catch (Exception e) { diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/Manifest.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/Manifest.java index 4943eb5f..cafb08d7 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/Manifest.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/Manifest.java @@ -14,5 +14,5 @@ public record Manifest( String artifactId, String artifactDescription, Boolean isManifestDeprecated, - @JsonSetter(nulls = Nulls.AS_EMPTY) List versions) { + @JsonSetter(nulls = Nulls.AS_EMPTY) List versions) { } diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ArtifactVersion.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ManifestArtifactVersion.java similarity index 94% rename from plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ArtifactVersion.java rename to plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ManifestArtifactVersion.java index 7593d6cf..55f5e8e5 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ArtifactVersion.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/lsp/manager/model/ManifestArtifactVersion.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; -public record ArtifactVersion( +public record ManifestArtifactVersion( @JsonProperty(required = true) String serverVersion, boolean isDelisted, Runtime runtime, diff --git a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/UpdateUtils.java b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/UpdateUtils.java index 5083e282..1f7b9cee 100644 --- a/plugin/src/software/aws/toolkits/eclipse/amazonq/util/UpdateUtils.java +++ b/plugin/src/software/aws/toolkits/eclipse/amazonq/util/UpdateUtils.java @@ -13,9 +13,9 @@ import java.net.http.HttpResponse; import java.time.Duration; +import org.apache.maven.artifact.versioning.ArtifactVersion; import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup; import org.eclipse.swt.widgets.Display; -import org.osgi.framework.Version; import org.tukaani.xz.XZInputStream; @@ -26,9 +26,9 @@ public final class UpdateUtils { private static final String REQUEST_URL = "https://amazonq.eclipsetoolkit.amazonwebservices.com/artifacts.xml.xz"; - private static Version mostRecentNotificationVersion; - private static Version remoteVersion; - private static Version localVersion; + private static ArtifactVersion mostRecentNotificationVersion; + private static ArtifactVersion remoteVersion; + private static ArtifactVersion localVersion; private static final UpdateUtils INSTANCE = new UpdateUtils(); public static UpdateUtils getInstance() { @@ -36,7 +36,7 @@ public static UpdateUtils getInstance() { } private UpdateUtils() { - mostRecentNotificationVersion = Activator.getPluginStore().getObject(Constants.DO_NOT_SHOW_UPDATE_KEY, Version.class); + mostRecentNotificationVersion = Activator.getPluginStore().getObject(Constants.DO_NOT_SHOW_UPDATE_KEY, ArtifactVersion.class); String localString = PluginClientMetadata.getInstance().getPluginVersion(); localVersion = ArtifactUtils.parseVersion(localString.substring(0, localString.lastIndexOf("."))); } @@ -62,7 +62,7 @@ public void checkForUpdate() { } } - private Version fetchRemoteArtifactVersion(final String repositoryUrl) { + private ArtifactVersion fetchRemoteArtifactVersion(final String repositoryUrl) { HttpClient connection = HttpClientFactory.getInstance(); try { HttpRequest request = HttpRequest.newBuilder() @@ -117,7 +117,7 @@ private void showNotification() { }); } - private static boolean remoteVersionIsGreater(final Version remote, final Version local) { + private static boolean remoteVersionIsGreater(final ArtifactVersion remote, final ArtifactVersion local) { return remote.compareTo(local) > 0; } } diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/configuration/DefaultPluginStoreTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/configuration/DefaultPluginStoreTest.java index 7b525567..13e8ec7e 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/configuration/DefaultPluginStoreTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/configuration/DefaultPluginStoreTest.java @@ -69,6 +69,7 @@ final void testPutAndGetKeySuccess(final String key) throws Exception { assertEquals(value, pluginStore.get(key)); verifyNoInteractions(mockLogger); } + @Test void testPutOverridingValue() throws BackingStoreException { String key = "testKey"; diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtilsTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtilsTest.java index 4d48ed8c..202898db 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtilsTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/ArtifactUtilsTest.java @@ -3,9 +3,9 @@ package software.aws.toolkits.eclipse.amazonq.lsp.manager.fetcher; +import org.apache.maven.artifact.versioning.ArtifactVersion; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.osgi.framework.Version; import java.io.IOException; import java.nio.file.FileSystem; @@ -40,10 +40,19 @@ void testExtractFile(@TempDir final Path tempDir) throws IOException { @Test void testParseVersion() { - Version version = ArtifactUtils.parseVersion("1.2.3"); - assertEquals(1, version.getMajor()); - assertEquals(2, version.getMinor()); - assertEquals(3, version.getMicro()); + ArtifactVersion version = ArtifactUtils.parseVersion("1.2.3"); + assertEquals(1, version.getMajorVersion()); + assertEquals(2, version.getMinorVersion()); + assertEquals(3, version.getIncrementalVersion()); + } + + @Test + void testParseVersionWithQualifier() { + ArtifactVersion version = ArtifactUtils.parseVersion("1.2.3-rc.1"); + assertEquals(1, version.getMajorVersion()); + assertEquals(2, version.getMinorVersion()); + assertEquals(3, version.getIncrementalVersion()); + assertEquals("rc.1", version.getQualifier()); } @Test diff --git a/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcherTest.java b/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcherTest.java index b777f699..54ac9f3a 100644 --- a/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcherTest.java +++ b/plugin/tst/software/aws/toolkits/eclipse/amazonq/lsp/manager/fetcher/RemoteLspFetcherTest.java @@ -33,7 +33,8 @@ import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import org.eclipse.osgi.service.resolver.VersionRange; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -52,7 +53,7 @@ import software.aws.toolkits.eclipse.amazonq.extensions.implementation.ActivatorStaticMockExtension; import software.aws.toolkits.eclipse.amazonq.extensions.implementation.ArtifactUtilsStaticMockExtension; import software.aws.toolkits.eclipse.amazonq.lsp.manager.LspFetchResult; -import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.ArtifactVersion; +import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.ManifestArtifactVersion; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Content; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Manifest; import software.aws.toolkits.eclipse.amazonq.lsp.manager.model.Target; @@ -64,11 +65,19 @@ import software.aws.toolkits.telemetry.TelemetryDefinitions.LanguageServerLocation; public final class RemoteLspFetcherTest { - private static VersionRange versionRange = new VersionRange("[1.0.0, 2.0.0]"); - private final String sampleVersion = String.format("%s.7.0", versionRange.getLeft().getMajor()); + private static VersionRange versionRange; + static { + try { + versionRange = VersionRange.createFromVersionSpec("[1.0.0, 2.0.0]"); + } catch (InvalidVersionSpecificationException e) { + throw new AmazonQPluginException("Failed to parse LSP supported version range", e); + } + } + + private final String sampleVersion = "1.7.0"; private LspFetcher lspFetcher; private Manifest sampleManifest; - private final ArtifactVersion sampleLspVersion; + private final ManifestArtifactVersion sampleLspVersion; private HttpClient httpClient; @RegisterExtension @@ -218,7 +227,7 @@ void fetchWhenFromRemote() throws IOException, InterruptedException { } @Test - void fetchFromFallbackWhenRemoteReturnsHttpErrorAndNoFAllBackVersionFound() + void fetchFromFallbackWhenRemoteReturnsHttpErrorAndNoFallBackVersionFound() throws IOException, InterruptedException { var zipPath = Paths.get(tempDir.toString(), "remote", "servers.zip"); setupZipTargetContent(zipPath, sampleLspVersion); @@ -245,10 +254,10 @@ void fetchFromFallbackWhenRemoteReturnsHttpError() throws IOException, Interrupt setupZipTargetContent(remoteZipPath, sampleLspVersion); ArtifactUtils.deleteFile(remoteZipPath); - String testFallbackVersion = String.format("%s.2.5", versionRange.getLeft().getMajor()); + String testFallbackVersion = "1.0.2"; var zipPath = Paths.get(tempDir.toString(), testFallbackVersion, "servers.zip"); var unzippedPath = Paths.get(tempDir.toString(), testFallbackVersion, "servers"); - ArtifactVersion testFallbackSampleLspVersion = createLspVersion(testFallbackVersion); + ManifestArtifactVersion testFallbackSampleLspVersion = createLspVersion(testFallbackVersion); setupZipTargetContent(zipPath, testFallbackSampleLspVersion); @@ -267,14 +276,48 @@ void fetchFromFallbackWhenRemoteReturnsHttpError() throws IOException, Interrupt @Test void fetchWhenMultipleVersionsChooseLatest() throws IOException, InterruptedException { - var oneAdditionalVersion = String.format("%s.8.3", versionRange.getLeft().getMajor()); + var oneAdditionalVersion = "1.8.3-rc.1"; var oneAdditionalLspVersion = createLspVersion(oneAdditionalVersion); - sampleManifest = createManifest(List.of(sampleLspVersion, oneAdditionalLspVersion)); + var secondAdditionalVersion = "1.8.3"; + var secondAdditionalLspVersion = createLspVersion(secondAdditionalVersion); + sampleManifest = createManifest(List.of(sampleLspVersion, secondAdditionalLspVersion)); + + var zipPath = Paths.get(tempDir.toString(), "remote", "servers.zip"); + var unzippedPath = Paths.get(tempDir.toString(), "remote", "servers"); + + setupZipTargetContent(zipPath, secondAdditionalLspVersion); + + var mockResponse = createMockHttpResponse(zipPath, HttpURLConnection.HTTP_OK); + when(httpClient.send(any(HttpRequest.class), ArgumentMatchers.>any())) + .thenReturn(mockResponse); + + lspFetcher = createFetcher(); + var result = lspFetcher.fetch(PluginPlatform.MAC, PluginArchitecture.ARM_64, tempDir, Instant.now()); + + var expectedAssetDirectory = Paths.get(tempDir.toString(), secondAdditionalVersion); + assertEquals(expectedAssetDirectory.toString(), result.assetDirectory()); + assertEquals(LanguageServerLocation.REMOTE, result.location()); + assertEquals(secondAdditionalVersion, result.version()); + + assertTrue(zipContentsMatchUnzipped(zipPath, unzippedPath)); + } + + @Test + void fetchWhenMultipleLabelVersionsChooseLatest() throws IOException, InterruptedException { + var oneAdditionalVersion = "1.8.3-beta.1"; + var oneAdditionalLspVersion = createLspVersion(oneAdditionalVersion); + var secondAdditionalVersion = "1.8.3-rc.1"; + var secondAdditionalLspVersion = createLspVersion(secondAdditionalVersion); + var thirdAdditionalVersion = "1.8.3-rc.2"; + var thirdAdditionalLspVersion = createLspVersion(thirdAdditionalVersion); + + sampleManifest = createManifest(List.of(sampleLspVersion, oneAdditionalLspVersion, + secondAdditionalLspVersion, thirdAdditionalLspVersion)); var zipPath = Paths.get(tempDir.toString(), "remote", "servers.zip"); var unzippedPath = Paths.get(tempDir.toString(), "remote", "servers"); - setupZipTargetContent(zipPath, oneAdditionalLspVersion); + setupZipTargetContent(zipPath, thirdAdditionalLspVersion); var mockResponse = createMockHttpResponse(zipPath, HttpURLConnection.HTTP_OK); when(httpClient.send(any(HttpRequest.class), ArgumentMatchers.>any())) @@ -283,10 +326,10 @@ void fetchWhenMultipleVersionsChooseLatest() throws IOException, InterruptedExce lspFetcher = createFetcher(); var result = lspFetcher.fetch(PluginPlatform.MAC, PluginArchitecture.ARM_64, tempDir, Instant.now()); - var expectedAssetDirectory = Paths.get(tempDir.toString(), oneAdditionalVersion); + var expectedAssetDirectory = Paths.get(tempDir.toString(), thirdAdditionalVersion); assertEquals(expectedAssetDirectory.toString(), result.assetDirectory()); assertEquals(LanguageServerLocation.REMOTE, result.location()); - assertEquals(oneAdditionalVersion, result.version()); + assertEquals(thirdAdditionalVersion, result.version()); assertTrue(zipContentsMatchUnzipped(zipPath, unzippedPath)); } @@ -314,7 +357,7 @@ private void assertInstallResult(final LspFetchResult result, final LanguageServ assertEquals(expectedLocation, result.location()); } - private void setupFileTargetContent(final String filename, final ArtifactVersion lspVersion, final String hash) + private void setupFileTargetContent(final String filename, final ManifestArtifactVersion lspVersion, final String hash) throws IOException, FileNotFoundException { var sampleContentPath = Paths.get(tempDir.toString(), lspVersion.serverVersion(), filename); @@ -325,7 +368,7 @@ private void setupFileTargetContent(final String filename, final ArtifactVersion lspVersion.targets().get(0).contents().add(content); } - private void setupZipTargetContent(final Path zipPath, final ArtifactVersion lspVersion) + private void setupZipTargetContent(final Path zipPath, final ManifestArtifactVersion lspVersion) throws IOException, FileNotFoundException { var unzippedPath = zipPath.getParent().resolve(ArtifactUtils.getFilenameWithoutExtension(zipPath)); @@ -392,9 +435,9 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr } private static Stream incompatibleManifestVersions() { - return Stream.of(Arguments.of(String.format("%s.0.2", versionRange.getLeft().getMajor() - 1)), - Arguments.of(String.format("%s.0.2", versionRange.getRight().getMajor() + 1)), - Arguments.of(String.format("%s.0.2", versionRange.getRight().getMajor() + 2))); + return Stream.of(Arguments.of("0.0.2"), + Arguments.of("2.0.2"), + Arguments.of("3.0.2")); } private void assertExceptionThrownWithMessage(final Exception exception, final String expectedMessage) { @@ -402,14 +445,14 @@ private void assertExceptionThrownWithMessage(final Exception exception, final S assertTrue(exception.getMessage().contains(expectedMessage)); } - private ArtifactVersion createLspVersion(final String version) { + private ManifestArtifactVersion createLspVersion(final String version) { var content = new ArrayList(); var target = new Target(PluginPlatform.MAC.getValue(), PluginArchitecture.ARM_64.getValue(), content); var targets = List.of(target); - return new ArtifactVersion(version, false, null, null, null, null, targets); + return new ManifestArtifactVersion(version, false, null, null, null, null, targets); } - private Manifest createManifest(final List lspVersions) { + private Manifest createManifest(final List lspVersions) { return new Manifest(null, null, null, false, lspVersions); }