feat(resolve): add POSIX/ANYOS/ANYARCH matching and test coverage

The resolver now handles:
- ANYOS assets match any query OS
- posix_2017/posix_2024 assets match any non-Windows OS
- ANYARCH assets match any query architecture (ranked below specific)

14 tests covering: exact match, version constraints, arch fallback
(Rosetta 2, Windows ARM64, micro-arch), format preference, libc
filtering, base-over-variant preference, POSIX/ANYOS/ANYARCH fallback,
Survey catalog, and no-match.
This commit is contained in:
AJ ONeal
2026-03-10 15:06:19 -06:00
parent 7e134ead87
commit f779e240fd
2 changed files with 274 additions and 5 deletions

View File

@@ -93,16 +93,16 @@ func Best(dists []Dist, q Query) *Match {
continue
}
// OS filter.
if d.OS != string(q.OS) {
// OS filter: exact match, POSIX fallback, or ANYOS.
if !osMatches(q.OS, d.OS) {
continue
}
// Arch filter (including compat arches).
// Empty arch in the dist means "universal/platform-agnostic" — accept it
// but rank it lower than an exact match.
// Empty arch or ANYARCH means "universal/platform-agnostic" —
// accept it but rank it lower than an exact match.
aRank, archOK := archRank[d.Arch]
if !archOK && d.Arch == "" {
if !archOK && (d.Arch == "" || d.Arch == string(buildmeta.ArchAny)) {
// Universal binary — rank after all specific arches.
aRank = len(compatArches)
archOK = true
@@ -241,6 +241,25 @@ func (c *candidate) betterThan(other *candidate) bool {
return c.formatRank < other.formatRank
}
// osMatches checks whether a dist's OS is acceptable for the query.
// Matches exact OS, ANYOS (universal), and POSIX compatibility levels
// (posix_2017 matches any non-Windows OS).
func osMatches(want buildmeta.OS, have string) bool {
if have == string(want) {
return true
}
if have == string(buildmeta.OSAny) {
return true
}
// POSIX assets run on any non-Windows system.
if want != buildmeta.OSWindows {
if have == string(buildmeta.OSPosix2017) || have == string(buildmeta.OSPosix2024) {
return true
}
}
return false
}
// libcMatches checks whether a dist's libc is acceptable for the query.
func libcMatches(os buildmeta.OS, want buildmeta.Libc, have string) bool {
// Darwin and Windows don't use libc tagging — accept anything.

View File

@@ -0,0 +1,250 @@
package resolve
import (
"testing"
"github.com/webinstall/webi-installers/internal/buildmeta"
)
// bat-style dists: standard goreleaser output.
var batDists = []Dist{
{Version: "0.26.1", Channel: "stable", OS: "darwin", Arch: "aarch64", Format: ".tar.gz", Filename: "bat-v0.26.1-aarch64-apple-darwin.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "darwin", Arch: "x86_64", Format: ".tar.gz", Filename: "bat-v0.26.1-x86_64-apple-darwin.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "linux", Arch: "aarch64", Libc: "gnu", Format: ".tar.gz", Filename: "bat-v0.26.1-aarch64-unknown-linux-gnu.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "linux", Arch: "aarch64", Libc: "musl", Format: ".tar.gz", Filename: "bat-v0.26.1-aarch64-unknown-linux-musl.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "linux", Arch: "x86_64", Libc: "gnu", Format: ".tar.gz", Filename: "bat-v0.26.1-x86_64-unknown-linux-gnu.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "linux", Arch: "x86_64", Libc: "musl", Format: ".tar.gz", Filename: "bat-v0.26.1-x86_64-unknown-linux-musl.tar.gz"},
{Version: "0.26.1", Channel: "stable", OS: "windows", Arch: "aarch64", Format: ".zip", Filename: "bat-v0.26.1-aarch64-pc-windows-msvc.zip"},
{Version: "0.26.1", Channel: "stable", OS: "windows", Arch: "x86_64", Libc: "gnu", Format: ".zip", Filename: "bat-v0.26.1-x86_64-pc-windows-gnu.zip"},
{Version: "0.26.1", Channel: "stable", OS: "windows", Arch: "x86_64", Libc: "msvc", Format: ".zip", Filename: "bat-v0.26.1-x86_64-pc-windows-msvc.zip"},
// Older version.
{Version: "0.25.0", Channel: "stable", OS: "darwin", Arch: "aarch64", Format: ".tar.gz", Filename: "bat-v0.25.0-aarch64-apple-darwin.tar.gz"},
{Version: "0.25.0", Channel: "stable", OS: "linux", Arch: "x86_64", Libc: "gnu", Format: ".tar.gz", Filename: "bat-v0.25.0-x86_64-unknown-linux-gnu.tar.gz"},
}
func TestBestExactMatch(t *testing.T) {
m := Best(batDists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match")
}
if m.Version != "0.26.1" {
t.Errorf("Version = %q, want 0.26.1", m.Version)
}
if m.Filename != "bat-v0.26.1-x86_64-unknown-linux-gnu.tar.gz" {
t.Errorf("Filename = %q", m.Filename)
}
}
func TestBestVersionConstraint(t *testing.T) {
m := Best(batDists, Query{
OS: buildmeta.OSDarwin,
Arch: buildmeta.ArchARM64,
Formats: []string{".tar.gz"},
Version: "0.25",
})
if m == nil {
t.Fatal("expected match")
}
if m.Version != "0.25.0" {
t.Errorf("Version = %q, want 0.25.0", m.Version)
}
}
func TestBestArchFallback(t *testing.T) {
// macOS ARM64 should fall back to x86_64 via Rosetta 2
// when no ARM64 build exists.
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "darwin", Arch: "x86_64", Format: ".tar.gz", Filename: "tool-darwin-amd64.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSDarwin,
Arch: buildmeta.ArchARM64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match via Rosetta 2 fallback")
}
if m.Arch != "x86_64" {
t.Errorf("Arch = %q, want x86_64", m.Arch)
}
}
func TestBestPrefersNativeOverCompat(t *testing.T) {
// When both native and compat builds exist, prefer native.
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "darwin", Arch: "x86_64", Format: ".tar.gz", Filename: "tool-darwin-amd64.tar.gz"},
{Version: "1.0.0", Channel: "stable", OS: "darwin", Arch: "aarch64", Format: ".tar.gz", Filename: "tool-darwin-arm64.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSDarwin,
Arch: buildmeta.ArchARM64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match")
}
if m.Arch != "aarch64" {
t.Errorf("Arch = %q, want aarch64 (native)", m.Arch)
}
}
func TestBestFormatPreference(t *testing.T) {
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".zip", Filename: "tool.zip"},
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Filename: "tool.tar.gz"},
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".tar.xz", Filename: "tool.tar.xz"},
}
m := Best(dists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.xz", ".tar.gz", ".zip"},
})
if m == nil {
t.Fatal("expected match")
}
if m.Format != ".tar.xz" {
t.Errorf("Format = %q, want .tar.xz", m.Format)
}
}
func TestBestNoMatch(t *testing.T) {
m := Best(batDists, Query{
OS: buildmeta.OSFreeBSD,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.gz"},
})
if m != nil {
t.Errorf("expected nil, got %+v", m)
}
}
func TestBestLibcMusl(t *testing.T) {
m := Best(batDists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64,
Libc: buildmeta.LibcMusl,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match")
}
if m.Libc != "musl" {
t.Errorf("Libc = %q, want musl", m.Libc)
}
}
func TestBestPrefersBaseOverVariant(t *testing.T) {
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Filename: "tool.tar.gz"},
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Filename: "tool-rocm.tar.gz", Variants: []string{"rocm"}},
}
m := Best(dists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match")
}
if m.Filename != "tool.tar.gz" {
t.Errorf("got variant build %q, want base", m.Filename)
}
}
func TestBestPosixFallback(t *testing.T) {
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "posix_2017", Format: ".tar.gz", Filename: "script.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match via POSIX fallback")
}
if m.OS != "posix_2017" {
t.Errorf("OS = %q, want posix_2017", m.OS)
}
}
func TestBestAnyOS(t *testing.T) {
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "ANYOS", Format: ".tar.gz", Filename: "tool.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSWindows,
Arch: buildmeta.ArchAMD64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match via ANYOS")
}
}
func TestBestAnyArch(t *testing.T) {
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "ANYARCH", Format: ".tar.gz", Filename: "tool.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchARM64,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match via ANYARCH")
}
}
func TestBestWindowsArchFallback(t *testing.T) {
// Windows ARM64 should fall back to x86_64 via emulation.
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "windows", Arch: "x86_64", Format: ".zip", Filename: "tool-win64.zip"},
}
m := Best(dists, Query{
OS: buildmeta.OSWindows,
Arch: buildmeta.ArchARM64,
Formats: []string{".zip"},
})
if m == nil {
t.Fatal("expected match via Windows ARM64 emulation")
}
if m.Arch != "x86_64" {
t.Errorf("Arch = %q, want x86_64", m.Arch)
}
}
func TestBestMicroArchFallback(t *testing.T) {
// amd64v3 query should fall back to amd64 baseline.
dists := []Dist{
{Version: "1.0.0", Channel: "stable", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Filename: "tool-amd64.tar.gz"},
}
m := Best(dists, Query{
OS: buildmeta.OSLinux,
Arch: buildmeta.ArchAMD64v3,
Formats: []string{".tar.gz"},
})
if m == nil {
t.Fatal("expected match via micro-arch fallback")
}
if m.Arch != "x86_64" {
t.Errorf("Arch = %q, want x86_64 (baseline)", m.Arch)
}
}
func TestSurvey(t *testing.T) {
cat := Survey(batDists)
if cat.Stable != "0.26.1" {
t.Errorf("Stable = %q, want 0.26.1", cat.Stable)
}
if cat.Latest != "0.26.1" {
t.Errorf("Latest = %q, want 0.26.1", cat.Latest)
}
if len(cat.OSes) != 3 {
t.Errorf("OSes = %v, want 3", cat.OSes)
}
}