initial commit [141.14.140.180,mike]

This commit is contained in:
2026-08-14 09:09:59 +02:00
commit 6a991a25a6
19 changed files with 6691 additions and 0 deletions
+310
View File
@@ -0,0 +1,310 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
)
// Run the asset selection against the real release JSON in t/corpus/, offline.
// Every pick is checked for tokens that contradict the requested platform, so
// a wrong choice fails loudly instead of just looking plausible. This is the
// Go twin of t/select.t and must reach the same verdict.
var platforms = [][2]string{
{"darwin", "arm64"}, {"darwin", "amd64"}, {"linux", "amd64"},
{"linux", "arm64"}, {"windows", "amd64"},
}
// Tokens that must never appear in a pick for the given platform.
var badOS = map[string][]string{
"darwin": {"linux", "windows", "freebsd", "netbsd", "openbsd", "solaris", "aix"},
"linux": {"darwin", "macos", "osx", "apple", "windows", "freebsd", "netbsd", "openbsd", "solaris", "aix"},
"windows": {"linux", "darwin", "macos", "osx", "apple", "freebsd", "netbsd", "openbsd", "solaris", "aix"},
}
var badArch = map[string][]string{
"amd64": {"arm64", "aarch64", "armv7", "armhf", "i686", "i386", "riscv", "riscv64", "ppc64", "ppc64le", "s390x", "sparc64", "mips", "mips64"},
"arm64": {"amd64", "x86_64", "i686", "i386", "riscv", "riscv64", "ppc64", "ppc64le", "s390x", "sparc64", "mips", "mips64"},
}
// Cross-compile targets that are never the host.
var foreign = []string{"android", "androideabi", "ios", "wasi", "wasm", "wasm32", "emscripten"}
var notABinary = regexp.MustCompile(`\.(deb|rpm|pkg|dmg|msi|apk|snap|appimage|asc|sig|sha256)$`)
func corpusDir(t *testing.T) string {
dir := filepath.Join("..", "t", "corpus")
if _, err := os.Stat(dir); err != nil {
t.Skipf("no corpus at %s", dir)
}
return dir
}
func tok(name, word string) bool {
return regexp.MustCompile(`(^|[^a-z0-9])` + regexp.QuoteMeta(word) + `([^a-z0-9]|$)`).MatchString(name)
}
func TestCorpusSelection(t *testing.T) {
dir := corpusDir(t)
bins := map[string]string{}
if body, err := os.ReadFile(filepath.Join(dir, "binaries.json")); err == nil {
json.Unmarshal(body, &bins)
}
files, err := filepath.Glob(filepath.Join(dir, "*__*.json"))
if err != nil || len(files) == 0 {
t.Fatalf("no corpus files in %s", dir)
}
sort.Strings(files)
stats := map[string]int{}
gaps := map[string][]string{}
for _, file := range files {
slug := strings.TrimSuffix(filepath.Base(file), ".json")
repo := strings.Replace(slug, "__", "/", 1)
body, err := os.ReadFile(file)
if err != nil {
t.Fatalf("%s: %v", file, err)
}
var rel Release
if err := json.Unmarshal(body, &rel); err != nil {
t.Fatalf("%s: %v", file, err)
}
if len(rel.Assets) == 0 {
t.Errorf("%s: no assets in release", repo)
continue
}
bin := bins[repo]
if bin == "" {
bin = repo[strings.LastIndex(repo, "/")+1:]
}
for _, p := range platforms {
goos, goarch := p[0], p[1]
hit, err := pickAsset(rel.Assets, bin, goos, goarch, &Spec{})
if err != nil {
t.Errorf("%s %s/%s: %v", repo, goos, goarch, err)
continue
}
if hit == nil {
stats["no match"]++
gaps[repo] = append(gaps[repo], goos+"/"+goarch)
continue
}
name := strings.ToLower(hit.Name)
flag := ""
for _, w := range badOS[goos] {
if tok(name, w) {
flag = "WRONG OS (" + w + ")"
}
}
for _, w := range badArch[goarch] {
if tok(name, w) {
flag = "WRONG ARCH (" + w + ")"
}
}
for _, w := range foreign {
if tok(name, w) {
flag = "FOREIGN TARGET (" + w + ")"
}
}
if flag == "" && goos != "windows" && strings.HasSuffix(name, ".exe") {
flag = "EXE ON UNIX"
}
if flag == "" && notABinary.MatchString(name) {
flag = "NOT A BINARY"
}
if flag == "" && !unpackable(name) {
flag = "UNPACKABLE?"
}
if flag != "" {
t.Errorf("%s %s/%s -> %s [%s]", repo, goos, goarch, hit.Name, flag)
stats[flag]++
continue
}
stats["ok"]++
}
}
var summary []string
for _, k := range sortedKeys(stats) {
summary = append(summary, fmt.Sprintf("%s=%d", k, stats[k]))
}
t.Logf("Totals: %s", strings.Join(summary, ", "))
if len(gaps) > 0 {
var lines []string
for _, repo := range sortedKeys(gaps) {
lines = append(lines, fmt.Sprintf("%s (%s)", repo, strings.Join(gaps[repo], " ")))
}
t.Logf("Unmatched: %s", strings.Join(lines, ", "))
}
}
// The Perl version reaches these totals on the same corpus; a change here is a
// change in behaviour and wants a look, not a blind update.
func TestCorpusTotalsMatchPerl(t *testing.T) {
dir := corpusDir(t)
files, _ := filepath.Glob(filepath.Join(dir, "*__*.json"))
bins := map[string]string{}
if body, err := os.ReadFile(filepath.Join(dir, "binaries.json")); err == nil {
json.Unmarshal(body, &bins)
}
ok, misses := 0, 0
for _, file := range files {
slug := strings.TrimSuffix(filepath.Base(file), ".json")
repo := strings.Replace(slug, "__", "/", 1)
body, _ := os.ReadFile(file)
var rel Release
if json.Unmarshal(body, &rel) != nil {
continue
}
bin := bins[repo]
if bin == "" {
bin = repo[strings.LastIndex(repo, "/")+1:]
}
for _, p := range platforms {
hit, err := pickAsset(rel.Assets, bin, p[0], p[1], &Spec{})
switch {
case err != nil:
t.Fatalf("%s: %v", repo, err)
case hit == nil:
misses++
default:
ok++
}
}
}
if ok != 141 || misses != 9 {
t.Errorf("corpus totals drifted: ok=%d no-match=%d, want ok=141 no-match=9", ok, misses)
}
}
func TestParseRepo(t *testing.T) {
cases := []struct{ in, base, owner, repo string }{
{"https://github.com/sxyazi/yazi", "https://github.com", "sxyazi", "yazi"},
{"https://git.micw.org/mike/dns", "https://git.micw.org", "mike", "dns"},
{"https://git.micw.org/mike/dns.git", "https://git.micw.org", "mike", "dns"},
{"https://git.micw.org/mike/dns/", "https://git.micw.org", "mike", "dns"},
{"git.micw.org/mike/dns", "https://git.micw.org", "mike", "dns"},
{"https://example.org/gitea/mike/dns", "https://example.org/gitea", "mike", "dns"},
{"http://localhost:3000/mike/dns", "http://localhost:3000", "mike", "dns"},
}
for _, c := range cases {
base, owner, repo, err := parseRepo(c.in)
if err != nil {
t.Errorf("%s: %v", c.in, err)
continue
}
if base != c.base || owner != c.owner || repo != c.repo {
t.Errorf("%s -> %s %s %s, want %s %s %s", c.in, base, owner, repo, c.base, c.owner, c.repo)
}
}
for _, bad := range []string{"https://github.com/onlyowner", "https://github.com"} {
if _, _, _, err := parseRepo(bad); err == nil {
t.Errorf("%s: expected an error", bad)
}
}
}
func TestForgeDetection(t *testing.T) {
cases := []struct{ base, forge, api string }{
{"https://github.com", "github", "https://api.github.com"},
{"https://git.micw.org", "gitea", "https://git.micw.org/api/v1"},
{"https://example.org/gitea", "gitea", "https://example.org/gitea/api/v1"},
{"https://notgithub.com", "gitea", "https://notgithub.com/api/v1"},
}
for _, c := range cases {
f, err := detectForge(c.base, "")
if err != nil || f != c.forge {
t.Errorf("%s -> %q (%v), want %q", c.base, f, err, c.forge)
}
if got := apiBase(c.base, f); got != c.api {
t.Errorf("%s -> %s, want %s", c.base, got, c.api)
}
}
if _, err := detectForge("https://x", "gitlab"); err == nil {
t.Error("expected an error for an unknown forge")
}
// GitHub Enterprise needs the override and lands on /api/v3.
if got := apiBase("https://gh.corp.example", "github"); got != "https://gh.corp.example/api/v3" {
t.Errorf("enterprise API base: %s", got)
}
}
func TestParseNames(t *testing.T) {
cases := []struct {
list, fallback string
want []name
}{
{"", "yazi", []name{{"yazi", "yazi"}}},
{"yazi,ya", "x", []name{{"yazi", "yazi"}, {"ya", "ya"}}},
{"yazi:yazi-nightly", "x", []name{{"yazi", "yazi-nightly"}}},
{"a , b", "x", []name{{"a", "a"}, {"b", "b"}}},
}
for _, c := range cases {
got := parseNames(c.list, c.fallback)
if len(got) != len(c.want) {
t.Errorf("%q -> %v, want %v", c.list, got, c.want)
continue
}
for i := range got {
if got[i] != c.want[i] {
t.Errorf("%q -> %v, want %v", c.list, got, c.want)
}
}
}
}
func TestSplitTokens(t *testing.T) {
got := splitTokens(`https://x/y install=~/bin name="a b" pre`)
want := []string{"https://x/y", "install=~/bin", "name=a b", "pre"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Errorf("got %q, want %q", got, want)
}
}
func TestNormalizeTokens(t *testing.T) {
cases := [][2]string{
{"pnpm-win32-x64.zip", "pnpm-windows-x64.zip"},
{"tool-win64.zip", "tool-windows-amd64.zip"},
{"winsomething-x64", "winsomething-x64"}, // not a token, left alone
}
for _, c := range cases {
if got := normalizeTokens(c[0]); got != c[1] {
t.Errorf("%s -> %s, want %s", c[0], got, c[1])
}
}
}
func TestStateFileLayout(t *testing.T) {
t.Setenv("XDG_STATE_HOME", "/tmp/state")
// Same recipe as the Perl version: sanitised path, dot, 8 hex characters.
got := filepath.Base(stateFile("/home/mike/bin/fzf"))
if !regexp.MustCompile(`^home_mike_bin_fzf\.[0-9a-f]{8}\.json$`).MatchString(got) {
t.Errorf("unexpected state file name: %s", got)
}
}
func TestSafeJoin(t *testing.T) {
for _, bad := range []string{"../evil", "/etc/passwd", "a/../../evil"} {
if _, ok := safeJoin("/dest", bad); ok {
t.Errorf("%q should have been rejected", bad)
}
}
if p, ok := safeJoin("/dest", "sub/bin/tool"); !ok || p != filepath.Join("/dest", "sub/bin/tool") {
t.Errorf("safeJoin rejected a normal path: %s %v", p, ok)
}
}