Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,14 @@ JDK, so `java` can only come from what vfox installs:
(`21.0.80+1-tem`): vfox sorts installed versions as strings, descending,
so `vfox list java` must show it first. `ParseLegacyFile` used to take it
as the first one starting with the release, and, for the padded release,
as the newest release of the major. On Windows the file is written with
CRLF line endings.
as the newest release of the major. Then SDKMAN's identifier of an
early-access build of the pinned major, spelled as `sdk list java` shows
OpenJDK's (`28.0.0.0+ea.20-open`), must select that build:
`java=21.0.0.0+ea.1-tem` selects `21-ea+1-tem`, not the release, which
`ParseLegacyFile` took while it understood only SDKMAN's older
`21.ea.1-tem`. jdk.java.net removes each early-access build within weeks,
so that install is faked: its `bin` holds a stand-in `java` that only
prints the name. 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
Expand Down
12 changes: 10 additions & 2 deletions hooks/parse_legacy_file.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ local installed_version = require("installed_version")
--- reads. SDKMAN calls JetBrains jbr (the plugin's jb) and puts fx in the
--- version of JavaFX builds: 21.0.12.fx-zulu, or 21.0.12-fx+1.1-librca in its
--- semverish identifiers (the plugin's 21.0.12-zulu-fx, 21.0.12+1.1-librca-fx).
--- The semverish ones spell early-access builds 28.0.0+ea.19-open, padded or
--- not (28.0.0.0+ea.19-open), where SDKMAN used to write the plugin's
--- 28.ea.19-open (Foojay's 28-ea+19).
--- Any other spelling, the plugin's own included, is returned unchanged.
local function plugin_spelling(identifier)
local version, distribution = identifier:match("^(.+)%-([^-]+)$")
Expand All @@ -30,11 +33,16 @@ local function plugin_spelling(identifier)
if distribution == "jbr" then
distribution = "jb"
end
local suffix = "-" .. distribution
local release, build = version:match("^(%d[%d.]*)[.-]fx(.*)$")
if release then
return release .. build .. "-" .. distribution .. "-fx"
version, suffix = release .. build, suffix .. "-fx"
end
return version .. "-" .. distribution
local major, ea_build = version:match("^(%d+)[.0]*%+ea%.(%d+)$")
if major then
version = major .. ".ea." .. ea_build
end
return version .. suffix
end

function PLUGIN:ParseLegacyFile(ctx)
Expand Down
23 changes: 23 additions & 0 deletions tests/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,29 @@ func PaddedIdentifier(distribution, version, installed string) string {
return version + buildNumber.FindString(installed) + "-" + sdkmanName(distribution)
}

// EarlyAccess renders the installed name of an early-access build of the
// pinned major (<major>-ea+1, e.g. 21-ea+1-tem, 25-ea+1 for OpenJDK).
// jdk.java.net removes each early-access build within weeks, so the suite
// fakes the install: a java-<version> directory whose bin holds a stand-in
// java that only prints this name.
func EarlyAccess(distribution, version string) string {
return Spec(distribution, Major(version)+"-ea+1")
}

// EarlyAccessIdentifier renders SDKMAN's identifier of that build, spelled
// as `sdk list java` shows OpenJDK's (28.0.0.0+ea.20-open):
// "<major>.0.0.0[-fx]+ea.1-<distribution>" (21.0.0.0+ea.1-tem,
// 25.0.0.0-fx+ea.1-librca). ParseLegacyFile matched only SDKMAN's older
// 21.ea.1-tem to it, so it took the newest release of the major instead
// (vfox-java#86).
func EarlyAccessIdentifier(distribution, version string) string {
version = Major(version) + ".0.0.0"
if JavaFX(distribution) {
version += "-fx"
}
return version + "+ea.1-" + sdkmanName(distribution)
}

// Alias renders the asdf-style spelling "<long-name>-<version>[-fx]" of an
// installed version, which PreUse must rewrite back to installed
// (temurin-21.0.8+9 to 21.0.8+9-tem, openjdk-25.0.2+10 to 25.0.2+10).
Expand Down
17 changes: 17 additions & 0 deletions tests/e2e/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ func TestPaddedIdentifier(t *testing.T) {
}
}

func TestEarlyAccess(t *testing.T) {
for _, c := range []struct{ dist, version, installed, identifier string }{
{"open", "25.0.2", "25-ea+1", "25.0.0.0+ea.1-open"},
{"tem", "21.0.8", "21-ea+1-tem", "21.0.0.0+ea.1-tem"},
{"librca-fx", "25.0.4", "25-ea+1-librca-fx", "25.0.0.0-fx+ea.1-librca"},
{"jb", "21.0.5", "21-ea+1-jb", "21.0.0.0+ea.1-jbr"},
{"graalce", "25.0.2", "25-ea+1-graalce", "25.0.0.0+ea.1-graalce"},
} {
if got := EarlyAccess(c.dist, c.version); got != c.installed {
t.Errorf("EarlyAccess(%s, %s) = %q, want %q", c.dist, c.version, got, c.installed)
}
if got := EarlyAccessIdentifier(c.dist, c.version); got != c.identifier {
t.Errorf("EarlyAccessIdentifier(%s, %s) = %q, want %q", c.dist, c.version, got, c.identifier)
}
}
}

func TestAlias(t *testing.T) {
for _, c := range []struct{ dist, installed, want string }{
{"open", "25.0.2+10", "openjdk-25.0.2+10"},
Expand Down
40 changes: 31 additions & 9 deletions tests/e2e/linux/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribut
// java=25.0.4.fx-librca), the one with a zero padding the release (e.g.
// java=21.0.8.0+9-tem, see common.PaddedIdentifier) 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.
// another release first (see common.Decoy). SDKMAN's identifier of an
// early-access build (e.g. java=21.0.0.0+ea.1-tem) must then select a faked
// install of that build (see common.EarlyAccess), not the release. 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, installed, arch string) {
t.Helper()
const dir = "/tmp/e2e-legacy-sdkmanrc"
// vfox counts a version as installed once its directory holds
// java-<version>; the decoy holds nothing more, so selecting it leaves
// no java on PATH.
Expand All @@ -75,16 +76,37 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distr
common.PaddedIdentifier(distribution, version, installed),
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))
out := legacyJava(ctx, t, ctr, identifier)
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)

// The early-access build's java only prints its name, which the
// release's java, selected instead, would not.
ea := common.EarlyAccess(distribution, version)
eaDir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+ea)
bin := eaDir + "/" + shQuote("java-"+ea) + "/bin"
execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf '%%s' %s > %s/java && chmod +x %s/java",
bin, shQuote("#!/bin/sh\necho "+shQuote(ea)+"\n"), bin, bin))
identifier := common.EarlyAccessIdentifier(distribution, version)
out := legacyJava(ctx, t, ctr, identifier)
require.Containsf(t, out, ea, ".sdkmanrc java=%s must select %s, not %s", identifier, ea, installed)
t.Logf("PASS .sdkmanrc java=%s selects %s, not %s", identifier, ea, installed)
execOK(ctx, t, ctr, "rm --recursive "+eaDir)
}

// legacyJava writes java=<identifier> to a .sdkmanrc and returns what java
// prints next to it. 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.
func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, identifier string) string {
t.Helper()
const dir = "/tmp/e2e-legacy-sdkmanrc"
execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc",
dir, shQuote(identifier), dir))
return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf(
`cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings))
}

// verifyToolchain runs java and javac through the activate hook and checks
Expand Down
58 changes: 40 additions & 18 deletions tests/e2e/windows/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotB
// java=21.0.8.0+9-tem, see common.PaddedIdentifier) 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.
// common.Decoy). SDKMAN's identifier of an early-access build (e.g.
// java=21.0.0.0+ea.1-tem) must then select a faked install of that build
// (see common.EarlyAccess), not the release. 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`
// vfox counts a version as installed once its directory holds
// java-<version>; the decoy holds nothing more, so selecting it leaves
// no java on PATH.
Expand All @@ -79,25 +80,46 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box s
common.PaddedIdentifier(distribution, version, installed),
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...)
out := legacyJava(ctx, t, ctr, box, identifier)
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...)

// The early-access build's java (a java.cmd) only prints its name, which
// the release's java, selected instead, would not.
ea := common.EarlyAccess(distribution, version)
eaDir := fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+ea))
execOK(ctx, t, ctr, fmt.Sprintf(
"$bin = Join-Path %s %s; New-Item -ItemType Directory -Force -Path $bin | Out-Null; "+
"Set-Content -Path (Join-Path $bin 'java.cmd') -Value %s",
eaDir, pwshQuote(`java-`+ea+`\bin`), pwshQuote("@echo "+ea)), box.vars...)
identifier := common.EarlyAccessIdentifier(distribution, version)
out := legacyJava(ctx, t, ctr, box, identifier)
require.Containsf(t, out, ea, ".sdkmanrc java=%s must select %s, not %s", identifier, ea, installed)
t.Logf("PASS .sdkmanrc java=%s selects %s, not %s", identifier, ea, installed)
execOK(ctx, t, ctr, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", eaDir), box.vars...)
}

// legacyJava writes java=<identifier> to a .sdkmanrc and returns what java
// prints next to it. 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.
func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, identifier string) string {
t.Helper()
const dir = `C:\e2e-legacy-sdkmanrc`
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...)
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)
return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...)
}

// verifyToolchain runs java and javac through the activate + env hooks and
Expand Down
14 changes: 14 additions & 0 deletions tests/parse_legacy_file_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ local cases = {
{ "java=25.0.1.0+8-open\n", { "25+36" }, "25+36" },
{ "java=26.0.1-oracle\n", { "26-oracle" }, "26-oracle" },
{ "java=26.0.2-oracle\n", { "26-oracle", "26.0.1-oracle" }, "26.0.1-oracle" },
-- SDKMAN's semverish identifiers write early-access builds 28.0.0+ea.19,
-- padded or not, where it used to write 28.ea.19 (vfox-java#86).
{ "java=28.ea.19-open\n", { "28-ea+19" }, "28-ea+19" },
{ "java=28.0.0+ea.19-open\n", { "28-ea+19" }, "28-ea+19" },
{ "java=28.0.0.0+ea.19-open\n", { "28-ea+18", "28-ea+19", "27+36" }, "28-ea+19" },
{ "java=26.0.0+ea.13-graal\n", { "26-ea+2-graal", "26-ea+13-graal" }, "26-ea+13-graal" },
{ "java=28.0.0.0+ea.19-jbr\n", { "28-ea+19-jb" }, "28-ea+19-jb" },
{ "java=28.0.0.0-fx+ea.19-zulu\n", { "28-ea+19-zulu", "28-ea+19-zulu-fx" }, "28-ea+19-zulu-fx" },
-- A build not installed takes the newest one installed, as 28.ea.20 does.
{ "java=28.ea.20-open\n", { "28-ea+18", "28-ea+19" }, "28-ea+19" },
{ "java=28.0.0.0+ea.20-open\n", { "28-ea+18", "28-ea+19" }, "28-ea+19" },
-- Never a release for an early-access build, nor the other way round.
{ "java=28.0.0.0+ea.19-open\n", { "28+36", "28.0.1+3" }, nil },
{ "java=28.0.0+36-open\n", { "28-ea+19" }, nil },
-- No java line at all selects nothing.
{ "maven=3.9.9\n", { "21.0.8+9-tem" }, nil },
}
Expand Down
Loading