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
7 changes: 5 additions & 2 deletions docs/e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ JDK, so `java` can only come from what vfox installs:
3. The pinned release installs under the name `PreInstall` gives it: the
requested version plus the build number (`21.0.8-tem` installs as
`21.0.8+9-tem`). `vfox search java <distribution>` (the `Available` hook)
lists that same name as installed, noting JavaFX on `-fx` builds. On
Windows that includes JetBrains Runtime 21.0.5, which ships only as
lists that same name as installed, noting JavaFX on `-fx` builds. For
GraalVM it must also list the feature releases GraalVM ships every month
since 25.1 (`25.4.4.1-graalce`), none of them noted LTS, though Foojay
marks them LTS, as the JDK they build on is; each lasts about a month.
On Windows the list includes JetBrains Runtime 21.0.5, which ships only as
tar.gz there.
4. Before any `vfox use`, a `.sdkmanrc` holding the SDKMAN identifier
without the build number selects the JDK through `ParseLegacyFile`:
Expand Down
9 changes: 8 additions & 1 deletion hooks/available.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ local foojay = require("foojay")
local loongnix = require("loongnix")
local distribution_version_parser = require("distribution_version")

-- is_feature_release reads GraalVM's numbering only: Liberica NIK and Mandrel
-- number every release after themselves (Mandrel 23.1.12 is LTS).
local graalvm = { graal = true, graalce = true }

--- Return all available versions provided by this plugin
--- @param ctx table Empty table used as context, for future extension
--- @return table Descriptions of available versions and accompanying tool descriptions
Expand Down Expand Up @@ -73,7 +77,10 @@ function PLUGIN:Available(ctx)
if not seen[v] then
seen[v] = true
-- check if version exists
local note = jdk.term_of_support == "lts" and "LTS" or ""
-- Foojay marks GraalVM's feature releases LTS, as the JDK they build on is.
local lts = jdk.term_of_support == "lts"
and not (graalvm[short] and distribution_version_parser.is_feature_release(jdk.java_version))
local note = lts and "LTS" or ""
if jdk.javafx_bundled == true then
note = note == "" and "JavaFX" or note .. ", JavaFX"
end
Expand Down
10 changes: 10 additions & 0 deletions lib/distribution_version.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ function distribution_version.convert_version_for_api(version)
return version
end

--- Reports whether a GraalVM version is a feature release. Since 25.1, GraalVM
--- puts one out every month on top of its 25.0 line, numbered in the second
--- component (25.1.3, 25.4.4.1), which the 25.0 line keeps at 0 (25.0.4.1) or
--- leaves out (25).
--- @param version string Foojay java_version
--- @return boolean
function distribution_version.is_feature_release(version)
return version:match("^%d+%.[1-9]") ~= nil
end

function distribution_version.parse_version(arg)
local version_parts = strings.split(arg, "-")
local version
Expand Down
34 changes: 33 additions & 1 deletion tests/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ var DefaultVersions = map[string]string{
// (vfox-java#72).
"jb": "21.0.5",
// GraalVM Community: vfox must leave GRAALVM_HOME alone even for
// GraalVM (vfox-java#27).
// GraalVM (vfox-java#27), and searching must not mark its feature
// releases LTS (vfox-java#95).
"graalce": "25.0.2",
}

Expand Down Expand Up @@ -368,6 +369,16 @@ func PaddedSpecs(distribution, version, installed string) []string {
return specs
}

// GraalVM reports whether the distribution is Oracle GraalVM or GraalVM
// Community, whose searches list GraalVM's feature releases.
func GraalVM(distribution string) bool {
switch Base(distribution) {
case "graal", "graalce":
return true
}
return false
}

// Vendor returns the java.vendor property the distribution's builds report,
// when the suite knows it.
func Vendor(distribution string) (string, bool) {
Expand Down Expand Up @@ -506,6 +517,27 @@ func CheckInstalled(t *testing.T, output, spec string) string {
return installed
}

// featureReleaseEntry matches the `vfox search` entry of a GraalVM feature
// release. GraalVM ships one every month since 25.1, numbered with the
// feature release train as the minor version (25.4.4.1-graalce), which its
// other releases keep at zero (25.0.2-graalce).
var featureReleaseEntry = regexp.MustCompile(`^\d+\.[1-9]`)

// FeatureReleaseEntries returns the entries of GraalVM feature releases in
// a GraalVM `vfox search`, without the leading " - ". Foojay marks them LTS,
// as the JDK they build on is, and the Available hook passed that on, though
// each is superseded within a month (vfox-java#95).
func FeatureReleaseEntries(output string) []string {
var entries []string
for _, line := range strings.Split(StripANSI(output), "\n") {
entry, ok := strings.CutPrefix(strings.TrimRight(line, "\r"), " - ")
if ok && featureReleaseEntry.MatchString(entry) {
entries = append(entries, entry)
}
}
return entries
}

// SearchEntry finds the `vfox search` entry listing exactly version
// (" - <version>[ (<note>)][ (installed)]") and returns it without the
// leading " - ".
Expand Down
16 changes: 16 additions & 0 deletions tests/e2e/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ func TestDistributionFacts(t *testing.T) {
if !JavaFX("librca-fx") || JavaFX("tem") || Base("librca-fx") != "librca" {
t.Error("JavaFX/Base mishandle the -fx suffix")
}
if !GraalVM("graal") || !GraalVM("graalce") || GraalVM("mandrel") || GraalVM("nik") {
t.Error("GraalVM must hold for graal and graalce alone")
}
if SearchArgs("open") != "" || SearchArgs("librca-fx") != " librca" {
t.Errorf("SearchArgs = %q, %q", SearchArgs("open"), SearchArgs("librca-fx"))
}
Expand Down Expand Up @@ -379,6 +382,19 @@ func TestCheckInstalled(t *testing.T) {
}
}

func TestFeatureReleaseEntries(t *testing.T) {
out := "Available versions:\r\n" +
" - 25.4.4.1-graalce (LTS)\r\n" +
" - 25.1.3-graalce\r\n" +
" - 25.0.2-graalce (LTS) (installed)\r\n" +
" - 25-graalce (LTS)\r\n" +
" - 24.0.2-graalce\r\n"
want := []string{"25.4.4.1-graalce (LTS)", "25.1.3-graalce"}
if got := FeatureReleaseEntries(out); !slices.Equal(got, want) {
t.Errorf("FeatureReleaseEntries = %q, want %q", got, want)
}
}

func TestSearchEntry(t *testing.T) {
out := "Available versions:\n" +
" - 25.0.4+9-librca (LTS)\n" +
Expand Down
14 changes: 13 additions & 1 deletion tests/e2e/linux/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ const userGraalVMHome = "/opt/graalvm-for-native-image"

// verifySearch checks `vfox search java` (the plugin's Available hook) lists
// the installed version under the name PreInstall gave it, marked
// installed, with the JavaFX note for -fx builds.
// installed, with the JavaFX note for -fx builds. For GraalVM it must also
// list feature releases (e.g. 25.4.4.1-graalce), none of them marked LTS
// (see common.FeatureReleaseEntries).
func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribution, installed string) {
t.Helper()
cmd := "vfox search java" + common.SearchArgs(distribution)
Expand All @@ -52,6 +54,16 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribut
require.Containsf(t, entry, "JavaFX", "%s must note JavaFX on %s", cmd, installed)
}
t.Logf("PASS %s lists %s", cmd, entry)
if common.GraalVM(distribution) {
// Foojay lists every feature release since 25.1.3 on each platform
// the suite runs GraalVM on, so the check cannot pass vacuously.
features := common.FeatureReleaseEntries(out)
require.NotEmptyf(t, features, "%s lists no feature release\n--- output ---\n%s", cmd, out)
for _, feature := range features {
require.NotContainsf(t, feature, "LTS", "%s must not mark the feature release %s LTS", cmd, feature)
}
t.Logf("PASS %s marks no feature release LTS: %s", cmd, strings.Join(features, ", "))
}
}

// verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a
Expand Down
12 changes: 12 additions & 0 deletions tests/e2e/windows/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const userGraalVMHome = `C:\graalvm-for-native-image`
// the installed version under the name PreInstall gave it, marked
// installed, with the JavaFX note for -fx builds. That includes releases
// shipped only as tar.gz, such as JetBrains Runtime 21.0.5 (vfox-java#72).
// For GraalVM it must also list feature releases (e.g. 25.4.4.1-graalce),
// none of them marked LTS (see common.FeatureReleaseEntries).
func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, distribution, installed string) {
t.Helper()
cmd := "vfox search java" + common.SearchArgs(distribution)
Expand All @@ -54,6 +56,16 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotB
require.Containsf(t, entry, "JavaFX", "%s must note JavaFX on %s", cmd, installed)
}
t.Logf("PASS %s lists %s", cmd, entry)
if common.GraalVM(distribution) {
// Foojay lists every feature release since 25.1.3 on each platform
// the suite runs GraalVM on, so the check cannot pass vacuously.
features := common.FeatureReleaseEntries(out)
require.NotEmptyf(t, features, "%s lists no feature release\n--- output ---\n%s", cmd, out)
for _, feature := range features {
require.NotContainsf(t, feature, "LTS", "%s must not mark the feature release %s LTS", cmd, feature)
}
t.Logf("PASS %s marks no feature release LTS: %s", cmd, strings.Join(features, ", "))
}
}

// verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a
Expand Down
31 changes: 30 additions & 1 deletion tests/pre_install_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ local function pkg(java_version, fx, archive_type)
}
end

local function lts(jdk)
jdk.term_of_support = "lts"
return jdk
end

-- Foojay answers observed on 2026-10-17 for linux/amd64 tar.gz packages, keyed
-- by "<distribution>[ <version>][ <latest>]", and for windows/amd64 zip and
-- tar.gz packages, keyed by "windows <distribution>[ <version>][ <latest>]".
Expand All @@ -51,6 +56,11 @@ local foojay = {
-- Searching asks for no version; abridged.
["openjdk"] = { pkg("25.0.2+10"), pkg("21.0.2+13") },
["temurin"] = { pkg("25.0.2+10"), pkg("21.0.8+9") },
-- Observed on 2026-10-18: Foojay marks GraalVM's feature releases LTS, as
-- 25.0 is, and Mandrel 23.1 LTS, as the JDK 21 it builds on is; abridged.
["graalvm"] = { lts(pkg("25.4.4.1")), lts(pkg("25.1.3")), lts(pkg("25.0.4.1")), lts(pkg("25")), pkg("24.0.2") },
["graalvm_community"] = { lts(pkg("25.4.4.1")), lts(pkg("25.0.2")) },
["mandrel"] = { lts(pkg("23.1.12")) },
-- JetBrains Runtime ships Windows zips for its newer builds only, and Foojay
-- lists a version's zip and tar.gz in either order; abridged.
["windows jetbrains 21"] = { pkg("21+2121") },
Expand Down Expand Up @@ -178,7 +188,7 @@ assert(#requests == 1 and not requests[1]:find("latest=", 1, true), "Available:
local function listed(query)
local versions = {}
for _, item in ipairs(PLUGIN:Available({ args = { query } })) do
versions[item.version] = true
versions[item.version] = item.note
end
return versions
end
Expand All @@ -188,6 +198,25 @@ end
local all = listed("all")
assert(all["25.0.2+10-tem"] and all["21.0.8+9-tem"], "Available all: Temurin versions lost their suffix")

-- Searching drops the LTS note from GraalVM's feature releases, each
-- superseded within a month, whether it lists one distribution or all.
-- GraalVM's other releases keep it, and so do Mandrel's, which Mandrel numbers
-- after itself.
for version, note in pairs({
["25.4.4.1-graal"] = "",
["25.1.3-graal"] = "",
["25.0.4.1-graal"] = "LTS",
["25-graal"] = "LTS",
["25.4.4.1-graalce"] = "",
["25.0.2-graalce"] = "LTS",
["23.1.12-mandrel"] = "LTS",
}) do
for _, query in ipairs({ version:match("%-(%a+)$"), "all" }) do
local got = listed(query)[version]
assert(got == note, ("Available %s: %s noted %q, want %q"):format(query, version, tostring(got), note))
end
end

-- Windows asks for zip and tar.gz packages in one lookup: installing takes a
-- version's zip where there is one, and searching lists the releases that ship
-- only as tar.gz too, newest first and each once.
Expand Down
Loading