diff --git a/docs/e2e.md b/docs/e2e.md index a07d687..1f058ee 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -79,8 +79,13 @@ JDK, so `java` can only come from what vfox installs: 4. Before any `vfox use`, a `.sdkmanrc` holding the SDKMAN identifier without the build number selects the JDK through `ParseLegacyFile`: `java=21.0.8-tem`, `java=21.0.5-jbr` (SDKMAN's name for JetBrains) or - `java=25.0.4.fx-librca` (its spelling of JavaFX builds). On Windows the - file is written with CRLF line endings. + `java=25.0.4.fx-librca` (its spelling of JavaFX builds). So does one + holding the installed version (`java=21.0.8+9-tem`). Both run next to an + empty install of another release whose version starts with the pinned + one (`21.0.80+1-tem`): vfox sorts installed versions as strings, + descending, so `vfox list java` must show it first, and + `ParseLegacyFile` used to take it as the first one starting with the + release. On Windows the file is written with CRLF line endings. 5. After `vfox use --global`, the JDK on `PATH` reports the requested `java.version`, the runner's `os.arch` (`amd64` / `aarch64`, which also catches an x64 JDK emulated on Windows ARM64) and the distribution's diff --git a/hooks/parse_legacy_file.lua b/hooks/parse_legacy_file.lua index a12856d..a9132b2 100644 --- a/hooks/parse_legacy_file.lua +++ b/hooks/parse_legacy_file.lua @@ -15,6 +15,7 @@ local strings = require("vfox.strings") local distribution_version_parser = require("distribution_version") +local installed_version = require("installed_version") --- Rewrites an SDKMAN identifier in the plugin's spelling, which parse_version --- reads. SDKMAN calls JetBrains jbr (the plugin's jb) and puts fx in the @@ -64,52 +65,30 @@ function PLUGIN:ParseLegacyFile(ctx) return {} end - local versionArr = strings.split(fullVersion, "+") - local version = versionArr[1] - local build = versionArr[2] - + local version = strings.split(fullVersion, "+")[1] local majorVersion = strings.split(version, ".")[1] --- Installed versions end in - (OpenJDK's in nothing), then -fx for --- JavaFX builds: 21.0.8+9-tem, 21.0.12+7-zulu-fx, 25.0.2+10. local suffix = (vendor == "open" and "" or "-" .. vendor) .. (distributionVersion.javafx_bundled and "-fx" or "") - --- Whether installed version v is of this distribution and JavaFX flavour, - --- with a Java version starting with prefix. - local function matches(v, prefix) - local java_version = v:sub(1, #v - #suffix) - return java_version .. suffix == v - and (vendor ~= "open" or not strings.contains(java_version, "-")) - and strings.has_prefix(java_version, prefix) - end - --- Get the list of versions of the current plugin installed --- (a plain call: vfox 1.x rejects the extra ctx argument of ctx:...). - local targetVersion - local versions = ctx.getInstalledVersions() - for _, v in ipairs(versions) do - --- It may be exactly the same, try it first. - if v == javaVersion then - targetVersion = v - break - end - - if matches(v, version) then - targetVersion = v - break - end - end - - if not targetVersion then - for _, v in ipairs(versions) do - if matches(v, majorVersion .. ".") then - targetVersion = v - break - end - end + --- vfox sorts it as strings, descending (21.0.8+9-tem, 21.0.12+8-tem, + --- 21.0.1+12-tem), so the first match is not the one to take. + local installed = {} + for _, v in ipairs(ctx.getInstalledVersions()) do + installed[v] = true end + --- The version itself (21.0.5+63130-jb), else the newest build of the + --- release, as SDKMAN's build numbers count its own rebuilds + --- (21.0.12+1.1-tem), else the newest installed release of the major + --- (21.0.12+8-tem for 21.0.3-tem). + local name = fullVersion .. suffix return { - version = targetVersion, + version = installed[name] and name + or installed_version.find(installed, version, suffix) + or installed_version.newest(installed, majorVersion .. ".", "^[%d.+]*$", suffix), } end diff --git a/hooks/pre_use.lua b/hooks/pre_use.lua index ff360ef..440df66 100644 --- a/hooks/pre_use.lua +++ b/hooks/pre_use.lua @@ -13,63 +13,7 @@ -- limitations under the License. local distribution_version_parser = require("distribution_version") - ---- Compares the numbers in two version strings one by one, missing ones as 0. -local function compare(a, b) - local x, y = {}, {} - for number in a:gmatch("%d+") do - table.insert(x, tonumber(number)) - end - for number in b:gmatch("%d+") do - table.insert(y, tonumber(number)) - end - for i = 1, math.max(#x, #y) do - if (x[i] or 0) ~= (y[i] or 0) then - return (x[i] or 0) > (y[i] or 0) and 1 or -1 - end - end - return 0 -end - ---- Whether Java version a is newer than b: the release decides, then the ---- build (21.0.12.1+1 > 21.0.8+9 > 21.0.8+7 > 21+35). -local function newer(a, b) - local a_release, a_build = a:match("^([^+]*)(.*)$") - local b_release, b_build = b:match("^([^+]*)(.*)$") - local order = compare(a_release, b_release) - return order > 0 or order == 0 and compare(a_build, b_build) > 0 -end - ---- Returns the installed version that version .. suffix names. vfox uses the ---- returned version as is, while installed names carry the build number ---- PreInstall found, which the input may leave out. So, like PreInstall, take ---- the newest build of a release (21.0.8-tem selects 21.0.8+9-tem) and the ---- newest release of a bare major (21-tem selects 21.0.12.1+1-tem), unless the ---- name itself is installed. Without a match, return the name for vfox to ---- report as not installed. -local function installed_version(installed_sdks, version, suffix) - local name = version .. suffix - if installed_sdks[name] then - return name - end - -- A bare major takes any release of it (21.0.12.1+1 for 21) but no EA build - -- (26-ea+13 for 26); other versions take only a build number (not - -- 21.0.12.1+1 for 21.0.12, nor 21.0.12+8 for 21.0.1). - local more = version:match("^%d+$") and "^[.+][%d.+]*$" or "^%+%d+$" - local best, best_version - for candidate in pairs(installed_sdks) do - local java_version = candidate:sub(1, #candidate - #suffix) - if - java_version .. suffix == candidate - and java_version:sub(1, #version) == version - and java_version:sub(#version + 1):find(more) - and (not best or newer(java_version, best_version)) - then - best, best_version = candidate, java_version - end - end - return best or name -end +local installed_version = require("installed_version") function PLUGIN:PreUse(ctx) local input_version = ctx.version @@ -91,8 +35,11 @@ function PLUGIN:PreUse(ctx) if distribution_version.distribution.short_name ~= "open" then suffix = "-" .. distribution_version.distribution.short_name .. suffix end + -- vfox uses the returned version as is, so return the installed one it + -- names or, without a match, the name for vfox to report as not installed. + local name = distribution_version.version .. suffix return { - version = installed_version(ctx.installedSdks or {}, distribution_version.version, suffix), + version = installed_version.find(ctx.installedSdks or {}, distribution_version.version, suffix) or name, } else return { diff --git a/lib/installed_version.lua b/lib/installed_version.lua new file mode 100644 index 0000000..05d048e --- /dev/null +++ b/lib/installed_version.lua @@ -0,0 +1,82 @@ +-- Copyright 2026 Han Li and contributors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +--- Finds installed versions, which PreInstall names after the Java version, +--- then - (nothing for OpenJDK), then -fx for JavaFX builds: +--- 21.0.8+9-tem, 25.0.4+9-librca-fx, 25.0.2+10. installed is a table keyed by +--- those names. +local installed_version = {} + +--- Compares the numbers in two version strings one by one, missing ones as 0. +local function compare(a, b) + local x, y = {}, {} + for number in a:gmatch("%d+") do + table.insert(x, tonumber(number)) + end + for number in b:gmatch("%d+") do + table.insert(y, tonumber(number)) + end + for i = 1, math.max(#x, #y) do + if (x[i] or 0) ~= (y[i] or 0) then + return (x[i] or 0) > (y[i] or 0) and 1 or -1 + end + end + return 0 +end + +--- Whether Java version a is newer than b: the release decides, then the +--- build (21.0.12.1+1 > 21.0.8+9 > 21.0.8+7 > 21+35). +local function newer(a, b) + local a_release, a_build = a:match("^([^+]*)(.*)$") + local b_release, b_build = b:match("^([^+]*)(.*)$") + local order = compare(a_release, b_release) + return order > 0 or order == 0 and compare(a_build, b_build) > 0 +end + +--- Returns the newest installed version ending in suffix whose Java version is +--- prefix followed by a rest that the pattern rest matches, or nil. +function installed_version.newest(installed, prefix, rest, suffix) + local best, best_version + for candidate in pairs(installed) do + local java_version = candidate:sub(1, #candidate - #suffix) + if + java_version .. suffix == candidate + and java_version:sub(1, #prefix) == prefix + and java_version:sub(#prefix + 1):find(rest) + and (not best or newer(java_version, best_version)) + then + best, best_version = candidate, java_version + end + end + return best +end + +--- Returns the installed version that version .. suffix names, or nil. The +--- input may leave out the build number PreInstall found, so, like PreInstall, +--- take the newest build of a release (21.0.8-tem selects 21.0.8+9-tem) and +--- the newest release of a bare major (21-tem selects 21.0.12.1+1-tem), unless +--- the name itself is installed. +function installed_version.find(installed, version, suffix) + local name = version .. suffix + if installed[name] then + return name + end + -- A bare major takes any release of it (21.0.12.1+1 for 21) but no EA build + -- (26-ea+13 for 26); other versions take only a build number (not + -- 21.0.12.1+1 for 21.0.12, nor 21.0.12+8 for 21.0.1). + local rest = version:match("^%d+$") and "^[.+][%d.+]*$" or "^%+%d+$" + return installed_version.newest(installed, version, rest, suffix) +end + +return installed_version diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index a82dd5a..26f46ca 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -259,6 +259,15 @@ func MissingBuildSpec(distribution, version string) string { return Spec(distribution, version+"+99") } +// Decoy renders the installed name of another release whose version starts +// with the pinned one (0+1, e.g. 21.0.80+1-tem). vfox lists the +// installed versions sorted as strings, descending, so it comes before the +// pinned JDK (21.0.8+9-tem), and ParseLegacyFile, which took the first one +// starting with the release, selected it (vfox-java#79). +func Decoy(distribution, version string) string { + return Spec(distribution, version+"0+1") +} + // Major returns the leading version component: "21.0.8" -> "21". func Major(version string) string { return strings.SplitN(version, ".", 2)[0] @@ -406,6 +415,22 @@ func InstalledVersion(output string) string { return "" } +// listedVersion matches one `vfox list java` entry: "-> ", followed +// by "<— current" for the version in use. +var listedVersion = regexp.MustCompile(`^-> (\S+)`) + +// ListedVersions extracts the installed versions from `vfox list java` +// output, in the order vfox lists them. +func ListedVersions(output string) []string { + var versions []string + for _, line := range strings.Split(StripANSI(output), "\n") { + if m := listedVersion.FindStringSubmatch(strings.TrimRight(line, "\r")); m != nil { + versions = append(versions, m[1]) + } + } + return versions +} + // unpackedTarball matches the "Unpacking ..." line vfox prints // before extracting a .tar.gz. var unpackedTarball = regexp.MustCompile(`(?m)^Unpacking .*\.tar\.gz\.\.\.\r?$`) diff --git a/tests/e2e/common/common_test.go b/tests/e2e/common/common_test.go index f79af10..c4c390f 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -184,11 +184,11 @@ func TestRetryAttemptsDefault(t *testing.T) { } func TestSpecs(t *testing.T) { - for _, c := range []struct{ dist, version, spec, missing, missingBuild, legacy string }{ - {"open", "25.0.2", "25.0.2", "25.0.99", "25.0.2+99", "25.0.2-open"}, - {"tem", "21.0.8", "21.0.8-tem", "21.0.99-tem", "21.0.8+99-tem", "21.0.8-tem"}, - {"librca-fx", "25.0.4", "25.0.4-librca-fx", "25.0.99-librca-fx", "25.0.4+99-librca-fx", "25.0.4.fx-librca"}, - {"jb", "21.0.5", "21.0.5-jb", "21.0.99-jb", "21.0.5+99-jb", "21.0.5-jbr"}, + for _, c := range []struct{ dist, version, spec, missing, missingBuild, legacy, decoy string }{ + {"open", "25.0.2", "25.0.2", "25.0.99", "25.0.2+99", "25.0.2-open", "25.0.20+1"}, + {"tem", "21.0.8", "21.0.8-tem", "21.0.99-tem", "21.0.8+99-tem", "21.0.8-tem", "21.0.80+1-tem"}, + {"librca-fx", "25.0.4", "25.0.4-librca-fx", "25.0.99-librca-fx", "25.0.4+99-librca-fx", "25.0.4.fx-librca", "25.0.40+1-librca-fx"}, + {"jb", "21.0.5", "21.0.5-jb", "21.0.99-jb", "21.0.5+99-jb", "21.0.5-jbr", "21.0.50+1-jb"}, } { if got := Spec(c.dist, c.version); got != c.spec { t.Errorf("Spec(%s, %s) = %q, want %q", c.dist, c.version, got, c.spec) @@ -202,6 +202,9 @@ func TestSpecs(t *testing.T) { if got := LegacyIdentifier(c.dist, c.version); got != c.legacy { t.Errorf("LegacyIdentifier(%s, %s) = %q, want %q", c.dist, c.version, got, c.legacy) } + if got := Decoy(c.dist, c.version); got != c.decoy { + t.Errorf("Decoy(%s, %s) = %q, want %q", c.dist, c.version, got, c.decoy) + } } } @@ -294,6 +297,17 @@ func TestInstalledVersion(t *testing.T) { } } +func TestListedVersions(t *testing.T) { + out := "-> 21.0.80+1-tem\r\n" + + "-> 21.0.8+9-tem \x1b[92m<— current\x1b[0m\r\n" + if got := ListedVersions(out); len(got) != 2 || got[0] != "21.0.80+1-tem" || got[1] != "21.0.8+9-tem" { + t.Errorf("ListedVersions = %q, want [21.0.80+1-tem 21.0.8+9-tem]", got) + } + if got := ListedVersions("no available version\n"); len(got) != 0 { + t.Errorf("ListedVersions = %q, want none", got) + } +} + func TestFromTarball(t *testing.T) { tarball := "Preinstalling java@21.0.5+79248-jb...\r\n" + "Unpacking C:\\Users\\John Doe\\.vfox\\cache\\java\\v-21.0.5+79248-jb\\jbrsdk_jcef-21.0.5-windows-x64-b792.48.tar.gz...\r\n" diff --git a/tests/e2e/linux/e2e_test.go b/tests/e2e/linux/e2e_test.go index d845495..df449c2 100644 --- a/tests/e2e/linux/e2e_test.go +++ b/tests/e2e/linux/e2e_test.go @@ -171,7 +171,7 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c installed := installJava(ctx, t, ctr, common.Spec(c.Distribution, version)) verifySearch(ctx, t, ctr, c.Distribution, installed) // Before any `vfox use`, so java can only come from the legacy file. - verifyLegacyFile(ctx, t, ctr, c.Distribution, version, arch) + verifyLegacyFile(ctx, t, ctr, c.Distribution, version, installed, arch) useJava(ctx, t, ctr, installed) verifyToolchain(ctx, t, ctr, c.Distribution, version, installed, arch) diff --git a/tests/e2e/linux/verify.go b/tests/e2e/linux/verify.go index 1b4e2b4..cc2f504 100644 --- a/tests/e2e/linux/verify.go +++ b/tests/e2e/linux/verify.go @@ -53,20 +53,33 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribut // verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a // .sdkmanrc (PLUGIN.legacyFilenames) holding the SDKMAN identifier without // the build number (e.g. java=21.0.8-tem, java=21.0.5-jbr, -// java=25.0.4.fx-librca). It runs before any `vfox use`, so java can only +// java=25.0.4.fx-librca) or the installed version itself (e.g. +// java=21.0.8+9-tem), while vfox lists an empty install of another release +// first (see common.Decoy). It runs before any `vfox use`, so java can only // come from the legacy file. -func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distribution, version, arch string) { +func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distribution, version, installed, arch string) { t.Helper() const dir = "/tmp/e2e-legacy-sdkmanrc" - identifier := common.LegacyIdentifier(distribution, version) - execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc", - dir, shQuote(identifier), dir)) - // The probe activates inside its own exec, a fresh shell session, so no - // `vfox env` state cached by an earlier call can answer for the file. - out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf( - `cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings)) - common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier) - t.Logf("PASS .sdkmanrc java=%s selects java %s", identifier, version) + // vfox counts a version as installed once its directory holds + // java-; the decoy holds nothing more, so selecting it leaves + // no java on PATH. + decoy := common.Decoy(distribution, version) + decoyDir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+decoy) + execOK(ctx, t, ctr, "mkdir --parents "+decoyDir+"/"+shQuote("java-"+decoy)) + listed := common.ListedVersions(execOK(ctx, t, ctr, "vfox list java")) + require.Equalf(t, []string{decoy, installed}, listed, "vfox list java must list the decoy %s first", decoy) + + for _, identifier := range []string{common.LegacyIdentifier(distribution, version), installed} { + execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc", + dir, shQuote(identifier), dir)) + // The probe activates inside its own exec, a fresh shell session, so no + // `vfox env` state cached by an earlier call can answer for the file. + out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf( + `cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings)) + common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier) + t.Logf("PASS .sdkmanrc java=%s selects java %s, not %s", identifier, version, decoy) + } + execOK(ctx, t, ctr, "rm --recursive "+decoyDir) } // verifyToolchain runs java and javac through the activate hook and checks diff --git a/tests/e2e/windows/e2e_test.go b/tests/e2e/windows/e2e_test.go index 8fdee18..b0799b1 100644 --- a/tests/e2e/windows/e2e_test.go +++ b/tests/e2e/windows/e2e_test.go @@ -248,7 +248,7 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c installed := installJava(ctx, t, ctr, box, common.Spec(c.Distribution, version)) verifySearch(ctx, t, ctr, box, c.Distribution, installed) // Before any `vfox use`, so java can only come from the legacy file. - verifyLegacyFile(ctx, t, ctr, box, c.Distribution, version, arch) + verifyLegacyFile(ctx, t, ctr, box, c.Distribution, version, installed, arch) useJava(ctx, t, ctr, box, activation, installed) verifyToolchain(ctx, t, ctr, box, activation, c.Distribution, version, installed, arch) diff --git a/tests/e2e/windows/verify.go b/tests/e2e/windows/verify.go index 2483108..0509754 100644 --- a/tests/e2e/windows/verify.go +++ b/tests/e2e/windows/verify.go @@ -55,29 +55,44 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotB // verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a // .sdkmanrc (PLUGIN.legacyFilenames) holding the SDKMAN identifier without // the build number (e.g. java=21.0.8-tem, java=21.0.5-jbr, -// java=25.0.4.fx-librca), written by Set-Content with Windows line endings. -// It runs before any `vfox use`, so java can only come from the legacy file. -func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, distribution, version, arch string) { +// java=25.0.4.fx-librca) or the installed version itself (e.g. +// java=21.0.8+9-tem), written by Set-Content with Windows line endings, +// while vfox lists an empty install of another release first (see +// common.Decoy). It runs before any `vfox use`, so java can only come from +// the legacy file. +func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, distribution, version, installed, arch string) { t.Helper() const dir = `C:\e2e-legacy-sdkmanrc` - identifier := common.LegacyIdentifier(distribution, version) - execOK(ctx, t, ctr, fmt.Sprintf( - "New-Item -ItemType Directory -Force -Path %s | Out-Null; Set-Content -Path %s -Value %s", - pwshQuote(dir), pwshQuote(dir+`\.sdkmanrc`), pwshQuote("java="+identifier)), box.vars...) - // The probe activates its own PowerShell process. Activation pins - // __VFOX_PID, which scopes the `vfox env` state cache, but Windows may - // reuse a just-exited PID and legacy files are not among the cache's - // tracked inputs, so the cache file is removed before the export. - script := fmt.Sprintf( - "Invoke-Expression -Command (vfox activate pwsh | Out-String); "+ - "Set-Location -LiteralPath %s; "+ - "if ($env:__VFOX_CURTMPPATH) { Remove-Item -LiteralPath (Join-Path $env:__VFOX_CURTMPPATH 'env-state.json') -Force -ErrorAction SilentlyContinue }; "+ - "$__vfoxExport = vfox env -s pwsh | Out-String; "+ - "if ($__vfoxExport.Trim()) { Invoke-Expression -Command $__vfoxExport }; "+ - "java %s", pwshQuote(dir), showSettings) - out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...) - common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier) - t.Logf("PASS .sdkmanrc java=%s selects java %s", identifier, version) + // vfox counts a version as installed once its directory holds + // java-; the decoy holds nothing more, so selecting it leaves + // no java on PATH. + decoy := common.Decoy(distribution, version) + decoyDir := fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+decoy)) + execOK(ctx, t, ctr, fmt.Sprintf("New-Item -ItemType Directory -Force -Path (Join-Path %s %s) | Out-Null", + decoyDir, pwshQuote("java-"+decoy)), box.vars...) + listed := common.ListedVersions(execOK(ctx, t, ctr, "vfox list java", box.vars...)) + require.Equalf(t, []string{decoy, installed}, listed, "vfox list java must list the decoy %s first", decoy) + + for _, identifier := range []string{common.LegacyIdentifier(distribution, version), installed} { + execOK(ctx, t, ctr, fmt.Sprintf( + "New-Item -ItemType Directory -Force -Path %s | Out-Null; Set-Content -Path %s -Value %s", + pwshQuote(dir), pwshQuote(dir+`\.sdkmanrc`), pwshQuote("java="+identifier)), box.vars...) + // The probe activates its own PowerShell process. Activation pins + // __VFOX_PID, which scopes the `vfox env` state cache, but Windows may + // reuse a just-exited PID and legacy files are not among the cache's + // tracked inputs, so the cache file is removed before the export. + script := fmt.Sprintf( + "Invoke-Expression -Command (vfox activate pwsh | Out-String); "+ + "Set-Location -LiteralPath %s; "+ + "if ($env:__VFOX_CURTMPPATH) { Remove-Item -LiteralPath (Join-Path $env:__VFOX_CURTMPPATH 'env-state.json') -Force -ErrorAction SilentlyContinue }; "+ + "$__vfoxExport = vfox env -s pwsh | Out-String; "+ + "if ($__vfoxExport.Trim()) { Invoke-Expression -Command $__vfoxExport }; "+ + "java %s", pwshQuote(dir), showSettings) + out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...) + common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier) + t.Logf("PASS .sdkmanrc java=%s selects java %s, not %s", identifier, version, decoy) + } + execOK(ctx, t, ctr, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", decoyDir), box.vars...) } // verifyToolchain runs java and javac through the activate + env hooks and diff --git a/tests/parse_legacy_file_test.lua b/tests/parse_legacy_file_test.lua index 60e5029..8daf8c3 100644 --- a/tests/parse_legacy_file_test.lua +++ b/tests/parse_legacy_file_test.lua @@ -21,6 +21,11 @@ local function parse(content, installed) local file = assert(io.open(path, "wb")) assert(file:write(content)) assert(file:close()) + -- vfox lists the installed versions sorted as strings, descending. + installed = { unpack(installed) } + table.sort(installed, function(a, b) + return a > b + end) local ok, result = pcall(function() return PLUGIN:ParseLegacyFile({ filepath = path, @@ -48,6 +53,17 @@ local cases = { { "java=21.0.5+79248-jb\n", { "21.0.5+79248-jb" }, "21.0.5+79248-jb" }, -- Without the exact release, the newest installed one of the major. { "java=21.0.1-tem\n", { "21.0.8+9-tem" }, "21.0.8+9-tem" }, + -- Neither the first version listed nor one whose release merely starts with + -- the requested one (vfox-java#79): the version itself, else the newest + -- build of the release, else the newest release of the major. + { "java=21.0.1-tem\n", { "21.0.1+12-tem", "21.0.12+8-tem" }, "21.0.1+12-tem" }, + { "java=21.0.1+12-tem\n", { "21.0.1+12-tem", "21.0.12+8-tem" }, "21.0.1+12-tem" }, + { "java=21.0.5+63130-jb\n", { "21.0.5+63130-jb", "21.0.5+79248-jb" }, "21.0.5+63130-jb" }, + { "java=21.0.5-jb\n", { "21.0.5+63130-jb", "21.0.5+79248-jb" }, "21.0.5+79248-jb" }, + { "java=21-tem\n", { "21.0.8+9-tem", "21.0.12+8-tem" }, "21.0.12+8-tem" }, + { "java=21.0.3-tem\n", { "21.0.8+9-tem", "21.0.12+8-tem" }, "21.0.12+8-tem" }, + -- SDKMAN's build numbers count its own rebuilds, so they select the release. + { "java=21.0.8+1.1-tem\n", { "21.0.8+9-tem", "21.0.12+8-tem" }, "21.0.8+9-tem" }, -- Windows line endings, as Set-Content writes them. { "java=21.0.8-tem\r\nmaven=3.9.9\r\n", { "21.0.8+9-tem" }, "21.0.8+9-tem" }, -- SDKMAN's JetBrains is jbr and its JavaFX builds are 21.0.12.fx-zulu or, diff --git a/tests/support.lua b/tests/support.lua index fedee23..ab79f24 100644 --- a/tests/support.lua +++ b/tests/support.lua @@ -35,8 +35,5 @@ package.preload["vfox.strings"] = function() trim_prefix = function(value, prefix) return has_prefix(value, prefix) and value:sub(#prefix + 1) or value end, - contains = function(value, substring) - return value:find(substring, 1, true) ~= nil - end, } end