package backends import ( "archive/tar" "archive/zip" "compress/gzip" "os" "path/filepath" "regexp" "strings" "testing" "time" ) // The asset matcher is the one maintenance point when upstream renames release // assets, so pin the current naming for every catalog component. func TestBackends_MatchAssets(t *testing.T) { llama, _ := Find("llama-server") // Verbatim asset list from ggml-org/llama.cpp release b10240. names := []string{ "llama-b10240-bin-android-arm64.tar.gz", "llama-b10240-bin-macos-arm64.tar.gz", "llama-b10240-bin-macos-x64.tar.gz", "llama-b10240-bin-ubuntu-arm64.tar.gz", "llama-b10240-bin-ubuntu-rocm-7.2-x64.tar.gz", "llama-b10240-bin-ubuntu-sycl-fp16-x64.tar.gz", "llama-b10240-bin-ubuntu-vulkan-arm64.tar.gz", "llama-b10240-bin-ubuntu-vulkan-x64.tar.gz", "llama-b10240-bin-ubuntu-x64.tar.gz", "llama-b10240-bin-win-cpu-arm64.zip", "llama-b10240-bin-win-cpu-x64.zip", "llama-b10240-bin-win-cuda-12.4-x64.zip", "llama-b10240-bin-win-cuda-13.3-x64.zip", "llama-b10240-bin-win-hip-radeon-x64.zip", "llama-b10240-bin-win-opencl-adreno-arm64.zip", "llama-b10240-bin-win-sycl-x64.zip", "llama-b10240-bin-win-vulkan-x64.zip", "cudart-llama-bin-win-cuda-12.4-x64.zip", "cudart-llama-bin-win-cuda-13.3-x64.zip", } cases := []struct { variant, goos, want string wantExtra string }{ {"vulkan", osWin, "llama-b10240-bin-win-vulkan-x64.zip", ""}, // The newest CUDA build wins, and the cudart it is paired with must be the // one for the SAME toolkit — matching by list order would take 12.4's. {"cuda", osWin, "llama-b10240-bin-win-cuda-13.3-x64.zip", "cudart-llama-bin-win-cuda-13.3-x64.zip"}, {"rocm", osWin, "llama-b10240-bin-win-hip-radeon-x64.zip", ""}, {"cpu", osWin, "llama-b10240-bin-win-cpu-x64.zip", ""}, {"vulkan", osLinux, "llama-b10240-bin-ubuntu-vulkan-x64.tar.gz", ""}, {"rocm", osLinux, "llama-b10240-bin-ubuntu-rocm-7.2-x64.tar.gz", ""}, {"cpu", osLinux, "llama-b10240-bin-ubuntu-x64.tar.gz", ""}, // The macOS arm64 asset is the Metal build, not a CPU one; only the // Intel x64 asset is unaccelerated. {"metal", osMac, "llama-b10240-bin-macos-arm64.tar.gz", ""}, {"cpu", osMac, "llama-b10240-bin-macos-x64.tar.gz", ""}, } for _, c := range cases { got, extra, err := llama.MatchAssets(c.variant, c.goos, names) if err != nil { t.Fatalf("%s/%s: %v", c.variant, c.goos, err) } if got != c.want { t.Errorf("%s/%s: got %q want %q", c.variant, c.goos, got, c.want) } if c.wantExtra == "" && len(extra) != 0 { t.Errorf("%s/%s: unexpected extras %v", c.variant, c.goos, extra) } if c.wantExtra != "" && (len(extra) != 1 || extra[0] != c.wantExtra) { t.Errorf("%s/%s: extras %v want [%s]", c.variant, c.goos, extra, c.wantExtra) } } if _, _, err := llama.MatchAssets("vulkan", osWin, []string{"nope.zip"}); err == nil { t.Error("expected an error when no asset matches") } // Upstream renamed the Windows ROCm asset (hip-radeon -> rocm-) and // added arm64 CUDA. Verbatim from b10819: with only the old pattern the rocm // variant matched nothing here, which is how a live release went uninstallable // while the b10240 list above still passed. current := []string{ "cudart-llama-bin-win-cuda-12.4-x64.zip", "cudart-llama-bin-win-cuda-13.3-x64.zip", "cudart-llama-bin-win-cuda-13.4-arm64.zip", "llama-b10819-bin-android-arm64.tar.gz", "llama-b10819-bin-macos-arm64.tar.gz", "llama-b10819-bin-macos-x64.tar.gz", "llama-b10819-bin-ubuntu-arm64.tar.gz", "llama-b10819-bin-ubuntu-openvino-2026.3.1-x64.tar.gz", "llama-b10819-bin-ubuntu-rocm-10.0-x64.tar.gz", "llama-b10819-bin-ubuntu-s390x.tar.gz", "llama-b10819-bin-ubuntu-sycl-fp16-x64.tar.gz", "llama-b10819-bin-ubuntu-sycl-fp32-x64.tar.gz", "llama-b10819-bin-ubuntu-vulkan-arm64.tar.gz", "llama-b10819-bin-ubuntu-vulkan-x64.tar.gz", "llama-b10819-bin-ubuntu-x64.tar.gz", "llama-b10819-bin-win-cpu-arm64.zip", "llama-b10819-bin-win-cpu-x64.zip", "llama-b10819-bin-win-cuda-12.4-x64.zip", "llama-b10819-bin-win-cuda-13.3-x64.zip", "llama-b10819-bin-win-cuda-13.4-arm64.zip", "llama-b10819-bin-win-opencl-adreno-arm64.zip", "llama-b10819-bin-win-openvino-2026.3.1-x64.zip", "llama-b10819-bin-win-rocm-10.0-x64.zip", "llama-b10819-bin-win-sycl-x64.zip", "llama-b10819-bin-win-vulkan-x64.zip", "llama-b10819-ui.tar.gz", "llama-b10819-xcframework.zip", } currentCases := []struct { variant, goos, want string wantExtra string }{ {"rocm", osWin, "llama-b10819-bin-win-rocm-10.0-x64.zip", ""}, {"rocm", osLinux, "llama-b10819-bin-ubuntu-rocm-10.0-x64.tar.gz", ""}, {"vulkan", osWin, "llama-b10819-bin-win-vulkan-x64.zip", ""}, {"cuda", osWin, "llama-b10819-bin-win-cuda-13.3-x64.zip", "cudart-llama-bin-win-cuda-13.3-x64.zip"}, {"cpu", osWin, "llama-b10819-bin-win-cpu-x64.zip", ""}, {"metal", osMac, "llama-b10819-bin-macos-arm64.tar.gz", ""}, } for _, c := range currentCases { got, extra, err := llama.MatchAssets(c.variant, c.goos, current) if err != nil { t.Fatalf("b10819 %s/%s: %v", c.variant, c.goos, err) } if got != c.want { t.Errorf("b10819 %s/%s: got %q want %q", c.variant, c.goos, got, c.want) } if c.wantExtra != "" && (len(extra) != 1 || extra[0] != c.wantExtra) { t.Errorf("b10819 %s/%s: extras %v want [%s]", c.variant, c.goos, extra, c.wantExtra) } } // stable-diffusion.cpp: capitalised platform segments, its own cudart zip, // and an upstream ROCm build. Verbatim from release master-809-eb7f35c. sd, _ := Find("sd-server") sdNames := []string{ "cudart-sd-bin-win-cu12-x64.zip", "sd-master-eb7f35c-bin-Darwin-macOS-26.5.2-arm64.zip", "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.14.0.zip", "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip", "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64.zip", "sd-master-eb7f35c-bin-win-cpu-x64.zip", "sd-master-eb7f35c-bin-win-cuda12-x64.zip", "sd-master-eb7f35c-bin-win-rocm-7.14.0-x64.zip", "sd-master-eb7f35c-bin-win-vulkan-x64.zip", } sdCases := []struct { variant, goos, want string wantExtra string }{ {"vulkan", osWin, "sd-master-eb7f35c-bin-win-vulkan-x64.zip", ""}, {"cuda", osWin, "sd-master-eb7f35c-bin-win-cuda12-x64.zip", "cudart-sd-bin-win-cu12-x64.zip"}, {"rocm", osWin, "sd-master-eb7f35c-bin-win-rocm-7.14.0-x64.zip", ""}, {"cpu", osWin, "sd-master-eb7f35c-bin-win-cpu-x64.zip", ""}, {"vulkan", osLinux, "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip", ""}, {"rocm", osLinux, "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.14.0.zip", ""}, {"cpu", osLinux, "sd-master-eb7f35c-bin-Linux-Ubuntu-24.04-x86_64.zip", ""}, // Upstream ships ONE macOS asset and it is a Metal build (the dylib links // Metal.framework), so it is the metal variant and darwin has no cpu build. {"metal", osMac, "sd-master-eb7f35c-bin-Darwin-macOS-26.5.2-arm64.zip", ""}, } for _, c := range sdCases { got, extra, err := sd.MatchAssets(c.variant, c.goos, sdNames) if err != nil { t.Fatalf("sd %s/%s: %v", c.variant, c.goos, err) } if got != c.want { t.Errorf("sd %s/%s: got %q want %q", c.variant, c.goos, got, c.want) } if c.wantExtra == "" && len(extra) != 0 { t.Errorf("sd %s/%s: unexpected extras %v", c.variant, c.goos, extra) } if c.wantExtra != "" && (len(extra) != 1 || extra[0] != c.wantExtra) { t.Errorf("sd %s/%s: extras %v want [%s]", c.variant, c.goos, extra, c.wantExtra) } } // Asking for a build upstream does not publish must fail loudly rather than // fall through to another variant's asset. if _, _, err := sd.MatchAssets("cpu", osMac, sdNames); err == nil { t.Error("sd cpu/darwin should have no published build") } if got := sd.DefaultVariant(nil, osMac); got != "metal" { t.Errorf("sd default variant on darwin = %q want metal", got) } // The 3D backend is tracked from a fork: upstream publishes CLI archives but // no build of the server this backend drives. Verbatim asset names from the // fork's server-v0.1.0 release. t2, _ := Find("trellis2-server") t2Names := []string{ "trellis2-server-v0.1.0-linux-x64-vulkan.tar.gz", "trellis2-server-v0.1.0-win-x64-cuda.zip", "trellis2-server-v0.1.0-win-x64-vulkan.zip", } for _, c := range []struct{ variant, goos, want string }{ {"vulkan", osWin, "trellis2-server-v0.1.0-win-x64-vulkan.zip"}, {"cuda", osWin, "trellis2-server-v0.1.0-win-x64-cuda.zip"}, {"vulkan", osLinux, "trellis2-server-v0.1.0-linux-x64-vulkan.tar.gz"}, } { got, _, err := t2.MatchAssets(c.variant, c.goos, t2Names) if err != nil { t.Fatalf("trellis2 %s/%s: %v", c.variant, c.goos, err) } if got != c.want { t.Errorf("trellis2 %s/%s: got %q want %q", c.variant, c.goos, got, c.want) } } // There is no Linux CUDA build and no macOS build at all, so asking must fail // rather than fall through to another variant's asset. if _, _, err := t2.MatchAssets("cuda", osLinux, t2Names); err == nil { t.Error("trellis2 cuda/linux should have no published build") } if _, _, err := t2.MatchAssets("vulkan", osMac, t2Names); err == nil { t.Error("trellis2 vulkan/darwin should have no published build") } // Real-ESRGAN's assets live on an older tag than its newest release. up, _ := Find("upscaler") if got, _, err := up.MatchAssets("any", osWin, []string{"realesrgan-ncnn-vulkan-20220424-windows.zip"}); err != nil || got != "realesrgan-ncnn-vulkan-20220424-windows.zip" { t.Errorf("upscaler: got %q err %v", got, err) } // yt-dlp's release ships several look-alike assets; the bare exe must win. yt, _ := Find("yt-dlp") got, _, err := yt.MatchAssets("any", osWin, []string{"yt-dlp", "yt-dlp_win.zip", "yt-dlp.exe.sha256", "yt-dlp.exe", "yt-dlp_x86.exe"}) if err != nil || got != "yt-dlp.exe" { t.Errorf("yt-dlp: got %q err %v", got, err) } // audio.cpp: two Metal builds (both macOS assets are accelerated), a separate // cudart zip per toolkit, "-portable" baseline-CPU twins of the cpu and vulkan // builds that must NOT be picked as the default, and a Colab-only Linux CUDA // asset that must stay unmatched. Verbatim from release v0.8.0. ac, _ := Find("audiocpp-server") acNames := []string{ "asr_validation.tar.gz", "audio-v0.8.0-bin-macos-arm64-metal.tar.gz", "audio-v0.8.0-bin-macos-x64-metal.tar.gz", "audio-v0.8.0-bin-ubuntu-x64-cpu-portable.tar.gz", "audio-v0.8.0-bin-ubuntu-x64-cpu.tar.gz", "audio-v0.8.0-bin-ubuntu-x64-cuda12.8-colab.tar.gz", "audio-v0.8.0-bin-ubuntu-x64-vulkan-portable.tar.gz", "audio-v0.8.0-bin-ubuntu-x64-vulkan.tar.gz", "audio-v0.8.0-bin-windows-x64-cpu-portable.zip", "audio-v0.8.0-bin-windows-x64-cpu.zip", "audio-v0.8.0-bin-windows-x64-cuda12.4.zip", "audio-v0.8.0-bin-windows-x64-cuda13.3.zip", "audio-v0.8.0-bin-windows-x64-vulkan.zip", "audio-v0.8.0-cudart-windows-x64-cuda12.4.zip", "audio-v0.8.0-cudart-windows-x64-cuda13.3.zip", "framework.tar.gz", "model_manager.tar.gz", "resources.tar.gz", } acCases := []struct { variant, goos, want string wantExtra string }{ {"vulkan", osWin, "audio-v0.8.0-bin-windows-x64-vulkan.zip", ""}, // Newest toolkit wins, paired with ITS cudart and not 12.4's. {"cuda", osWin, "audio-v0.8.0-bin-windows-x64-cuda13.3.zip", "audio-v0.8.0-cudart-windows-x64-cuda13.3.zip"}, {"cpu", osWin, "audio-v0.8.0-bin-windows-x64-cpu.zip", ""}, {"cpu-portable", osWin, "audio-v0.8.0-bin-windows-x64-cpu-portable.zip", ""}, {"vulkan", osLinux, "audio-v0.8.0-bin-ubuntu-x64-vulkan.tar.gz", ""}, {"cpu", osLinux, "audio-v0.8.0-bin-ubuntu-x64-cpu.tar.gz", ""}, {"cpu-portable", osLinux, "audio-v0.8.0-bin-ubuntu-x64-cpu-portable.tar.gz", ""}, {"metal", osMac, "audio-v0.8.0-bin-macos-arm64-metal.tar.gz", ""}, {"metal-x64", osMac, "audio-v0.8.0-bin-macos-x64-metal.tar.gz", ""}, } for _, c := range acCases { got, extra, err := ac.MatchAssets(c.variant, c.goos, acNames) if err != nil { t.Fatalf("audiocpp %s/%s: %v", c.variant, c.goos, err) } if got != c.want { t.Errorf("audiocpp %s/%s: got %q want %q", c.variant, c.goos, got, c.want) } if c.wantExtra == "" && len(extra) != 0 { t.Errorf("audiocpp %s/%s: unexpected extras %v", c.variant, c.goos, extra) } if c.wantExtra != "" && (len(extra) != 1 || extra[0] != c.wantExtra) { t.Errorf("audiocpp %s/%s: extras %v want [%s]", c.variant, c.goos, extra, c.wantExtra) } } // Upstream publishes no ROCm build, and the Colab CUDA asset is deliberately // not claimed by the cuda variant on Linux: both must fail rather than // silently resolve to some other flavour's binary. if _, _, err := ac.MatchAssets("cuda", osLinux, acNames); err == nil { t.Error("audiocpp cuda/linux should not match the Colab-only asset") } if _, ok := ac.Variant("rocm"); ok { t.Error("audiocpp has no upstream ROCm build; it must not offer the variant") } } // A manual entry is catalogued so the UI can describe the engine, but it has no // installable asset — the manager must refuse rather than start a job that can // only fail at asset matching. func TestBackends_ManualComponentRefusesInstall(t *testing.T) { c, ok := Find("vllm") if !ok { t.Fatal("vllm missing from the catalog") } if !c.Manual || c.Setup == "" { t.Fatalf("vllm must be Manual with setup text, got manual=%v setup=%q", c.Manual, c.Setup) } if len(c.Variants) != 0 { t.Fatalf("a manual component has nothing to download, got %d variants", len(c.Variants)) } m := NewManager(t.TempDir(), nil) if _, err := m.Install("vllm", "", ""); err == nil { t.Fatal("Install(vllm) should be refused") } } func TestBackends_SuggestVariant(t *testing.T) { cases := []struct { gpus []string want string }{ {[]string{"NVIDIA GeForce RTX 4090"}, "cuda"}, {[]string{"AMD Radeon RX 7900 XTX"}, "vulkan"}, {[]string{"Intel(R) Arc(TM) A770"}, "vulkan"}, {[]string{"AMD Radeon Graphics", "NVIDIA RTX 4090"}, "cuda"}, // discrete NVIDIA wins {[]string{"Apple M3 Max"}, "metal"}, {nil, "cpu"}, } for _, c := range cases { if got := SuggestVariant(c.gpus); got != c.want { t.Errorf("SuggestVariant(%v) = %q want %q", c.gpus, got, c.want) } } // A component publishing only an "any" build ignores the GPU suggestion. up, _ := Find("upscaler") if got := up.DefaultVariant([]string{"NVIDIA RTX 4090"}, osWin); got != "any" { t.Errorf("upscaler default variant = %q want any", got) } // An Apple-silicon Mac must land on the Metal build even when the GPU probe // reports nothing, rather than on the Intel CPU asset. llama, _ := Find("llama-server") for _, gpus := range [][]string{nil, {"Apple M3 Max"}} { if got := llama.DefaultVariant(gpus, osMac); got != "metal" { t.Errorf("llama default variant on darwin (gpus=%v) = %q want metal", gpus, got) } } } // A crafted archive must not be able to write outside the install directory. func TestBackends_ExtractRejectsPathEscape(t *testing.T) { dir := t.TempDir() zipPath := filepath.Join(dir, "evil.zip") f, err := os.Create(zipPath) if err != nil { t.Fatal(err) } zw := zip.NewWriter(f) w, err := zw.Create("../escaped.txt") if err != nil { t.Fatal(err) } if _, err := w.Write([]byte("pwned")); err != nil { t.Fatal(err) } zw.Close() f.Close() dest := filepath.Join(dir, "install") if err := os.MkdirAll(dest, 0o755); err != nil { t.Fatal(err) } if err := extract(zipPath, dest); err == nil { t.Fatal("expected extract to reject a ../ entry") } if _, err := os.Stat(filepath.Join(dir, "escaped.txt")); err == nil { t.Fatal("archive escaped the install directory") } } // Installed rows come from per-directory manifests, so a half-extracted or // hand-emptied folder must not be reported as an installed build. func TestBackends_InstalledScan(t *testing.T) { m := NewManager(t.TempDir(), nil) dir := m.InstallDir("llama-server", "b6543", "vulkan") if err := os.MkdirAll(dir, 0o755); err != nil { t.Fatal(err) } exe := filepath.Join(dir, "build", "bin", "llama-server.exe") if err := os.MkdirAll(filepath.Dir(exe), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(exe, []byte("x"), 0o755); err != nil { t.Fatal(err) } if got := m.Installed("llama-server"); len(got) != 0 { t.Fatalf("no manifest yet, got %v", got) } if err := writeManifest(dir, manifest{ Component: "llama-server", Version: "b6543", Variant: "vulkan", Exe: "build/bin/llama-server.exe", InstalledAt: time.Now(), }); err != nil { t.Fatal(err) } got := m.Installed("llama-server") if len(got) != 1 || got[0].Exe != exe { t.Fatalf("got %+v want exe %s", got, exe) } // Size is the whole install, not the executable: llama-server.exe is a stub // in front of the ggml DLLs, so measuring only the exe reported "0 MB". dll := filepath.Join(dir, "build", "bin", "ggml.dll") if err := os.WriteFile(dll, make([]byte, 4096), 0o644); err != nil { t.Fatal(err) } if got := m.Installed("llama-server"); len(got) != 1 || got[0].SizeBytes <= 4096 { t.Fatalf("size should cover the whole install dir, got %+v", got) } // A manifest whose executable vanished is not an install either. if err := os.Remove(exe); err != nil { t.Fatal(err) } if got := m.Installed("llama-server"); len(got) != 0 { t.Fatalf("missing exe should not list, got %v", got) } } // Versioned side-by-side layout: two builds of one component coexist. func TestBackends_InstallDirIsVersioned(t *testing.T) { m := NewManager(filepath.Join(t.TempDir(), "app"), nil) a := m.InstallDir("llama-server", "b1", "vulkan") b := m.InstallDir("llama-server", "b2", "vulkan") c := m.InstallDir("llama-server", "b1", "cuda") if a == b || a == c { t.Fatalf("install dirs collide: %s %s %s", a, b, c) } // Tags with path separators can't escape the component directory. weird := m.InstallDir("llama-server", "../../etc", "vulkan") if !strings.HasPrefix(weird, m.ComponentDir("llama-server")) { t.Fatalf("tag escaped the component dir: %s", weird) } } func TestBackends_PickRelease(t *testing.T) { rels := []Release{ {Tag: "b3", Prerelease: true}, {Tag: "b2"}, {Tag: "b1"}, } if r, _ := pickRelease(rels, "", false, nil); r.Tag != "b2" { t.Errorf("latest skipped prerelease? got %s", r.Tag) } if r, _ := pickRelease(rels, "b1", false, nil); r.Tag != "b1" { t.Errorf("exact tag: got %s", r.Tag) } if _, ok := pickRelease(rels, "b99", false, nil); ok { t.Error("unknown tag should not resolve") } // A source-only newest release (Real-ESRGAN does this) must be skipped in // favour of the newest release that actually ships a usable asset. only := func(tag string) func(Release) bool { return func(r Release) bool { return r.Tag == tag } } if r, _ := pickRelease(rels, "", false, only("b1")); r.Tag != "b1" { t.Errorf("latest should skip releases with no installable asset, got %s", r.Tag) } // Prereleases are still a better answer than nothing installable at all. if r, _ := pickRelease(rels, "", false, only("b3")); r.Tag != "b3" { t.Errorf("should fall back to an installable prerelease, got %s", r.Tag) } // A repo that only ever publishes prereleases (nightly-only forks) opts in, // and then "latest" means the newest release, full stop. if r, _ := pickRelease(rels, "", true, nil); r.Tag != "b3" { t.Errorf("allowPrerelease should take the newest release, got %s", r.Tag) } } func TestBackends_NoReleaseError(t *testing.T) { c := Component{ID: "trellis2-server", Repo: "Radu0120/trellis2.c"} // The case a user hits with a freshly tracked repo: no releases at all. The // old wording was `no release "" found in the last 30 releases`. err := noReleaseError(c, nil, "") if want := "trellis2-server: Radu0120/trellis2.c has no releases yet"; err.Error() != want { t.Errorf("empty listing = %q, want %q", err.Error(), want) } // Releases exist but the requested tag is not among them: name the tag. rels := []Release{{Tag: "server-v0.1.0"}} err = noReleaseError(c, rels, "v0.9") if !strings.Contains(err.Error(), `no release "v0.9" found`) { t.Errorf("explicit tag = %q", err.Error()) } } func TestBackends_ValidateRepo(t *testing.T) { for _, ok := range []string{"ggml-org/llama.cpp", "lemonade-sdk/llamacpp-rocm", "a_b/c-d.e"} { if err := ValidateRepo(ok); err != nil { t.Errorf("%q rejected: %v", ok, err) } } // Everything here would re-target the api.github.com URL the repo is // interpolated into, so each must be refused before it can reach a request. for _, bad := range []string{ "", "noslash", "a/b/c", "../../evil", "a/..", "./x", "a/b?x=1", "a/b#f", "https://evil.example/a/b", "a b/c", "a/b/../..", } { if err := ValidateRepo(bad); err == nil { t.Errorf("%q accepted", bad) } } } func TestBackends_ParseRepo(t *testing.T) { cases := map[string]string{ "ggml-org/llama.cpp": "ggml-org/llama.cpp", " ggml-org/llama.cpp ": "ggml-org/llama.cpp", "https://github.com/ggml-org/llama.cpp": "ggml-org/llama.cpp", "https://github.com/ggml-org/llama.cpp/": "ggml-org/llama.cpp", "https://github.com/ggml-org/llama.cpp.git": "ggml-org/llama.cpp", "https://github.com/ggml-org/llama.cpp/releases": "ggml-org/llama.cpp", } for in, want := range cases { got, err := ParseRepo(in) if err != nil || got != want { t.Errorf("ParseRepo(%q) = %q, %v; want %q", in, got, err, want) } } if _, err := ParseRepo("https://evil.example/a/b"); err == nil { t.Error("a non-github host should not parse into a repo reference") } } // TestBackends_DeriveUnique is the safety net for the whole no-regex feature: // the pattern a user never sees is generated here, and a wrong one downloads and // runs the wrong binary. func TestBackends_DeriveUnique(t *testing.T) { // The real llama.cpp Windows asset list — several flavours, plus two CUDA // toolkit versions that differ ONLY in a version-ish token. llama := []string{ "llama-b6543-bin-win-vulkan-x64.zip", "llama-b6543-bin-win-cpu-x64.zip", "llama-b6543-bin-win-cuda-12.4-x64.zip", "llama-b6543-bin-win-cuda-13.3-x64.zip", "cudart-llama-bin-win-cuda-12.4-x64.zip", "cudart-llama-bin-win-cuda-13.3-x64.zip", } tests := []struct { name string pick string all []string match []string // must match miss []string // must not match }{ { name: "build number wildcarded", pick: "llama-b6543-bin-win-vulkan-x64.zip", all: llama, match: []string{"llama-b6543-bin-win-vulkan-x64.zip", "llama-b7000-bin-win-vulkan-x64.zip"}, miss: []string{"llama-b6543-bin-win-cpu-x64.zip", "llama-b6543-bin-win-cuda-12.4-x64.zip"}, }, { // The toolkit version must survive as a literal or the install becomes // a coin flip between the CUDA 12 and CUDA 13 builds. name: "cuda toolkit pinned, build number free", pick: "llama-b6543-bin-win-cuda-12.4-x64.zip", all: llama, match: []string{"llama-b6543-bin-win-cuda-12.4-x64.zip", "llama-b7000-bin-win-cuda-12.4-x64.zip"}, miss: []string{"llama-b6543-bin-win-cuda-13.3-x64.zip", "llama-b6543-bin-win-vulkan-x64.zip"}, }, { name: "lemonade rocm nightly", pick: "llama-b1247-ubuntu-rocm-gfx110X-x64.zip", all: []string{"llama-b1247-ubuntu-rocm-gfx110X-x64.zip", "llama-b1247-ubuntu-rocm-gfx120X-x64.zip"}, match: []string{"llama-b9999-ubuntu-rocm-gfx110X-x64.zip"}, miss: []string{"llama-b1247-ubuntu-rocm-gfx120X-x64.zip"}, }, { // Nothing identifying at all: fail closed on the literal name rather // than derive "^.*$" and install whatever happens to be first. name: "all-version name falls back to literal", pick: "1.2.3", all: []string{"1.2.3", "1.2.4"}, match: []string{"1.2.3"}, miss: []string{"1.2.4"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { pat := DeriveUnique(tc.pick, tc.all) re, err := regexp.Compile(pat) if err != nil { t.Fatalf("derived an uncompilable pattern %q: %v", pat, err) } // The derivation is only sound if it still identifies its own example // uniquely within the release it came from. hits := 0 for _, n := range tc.all { if re.MatchString(n) { hits++ } } if hits != 1 { t.Errorf("pattern %q matches %d assets of the source release, want 1", pat, hits) } for _, n := range tc.match { if !re.MatchString(n) { t.Errorf("pattern %q does not match %q", pat, n) } } for _, n := range tc.miss { if re.MatchString(n) { t.Errorf("pattern %q wrongly matches %q", pat, n) } } }) } } func TestBackends_SuggestLabel(t *testing.T) { cases := map[string]string{ "llama-b6543-bin-win-vulkan-x64.zip": "llama win vulkan", "llama-b1247-ubuntu-rocm-gfx110X-x64.zip": "llama ubuntu rocm gfx110X", "sd-master-abc1234-bin-win-avx2-x64.zip": "sd master win avx2", } for in, want := range cases { if got := SuggestLabel(in); got != want { t.Errorf("SuggestLabel(%q) = %q, want %q", in, got, want) } } } // TestBackends_SourcesMerge covers the Manager.Sources hook: tracked repos are // visible to every downstream path, but must never shadow a built-in — a // collision would let a user-controlled repo take over a built-in's install // directory and registry row. func TestBackends_SourcesMerge(t *testing.T) { m := NewManager(t.TempDir(), nil) base := len(m.Catalog()) m.Sources = func() []Component { return []Component{ {ID: "custom-me-fork", Name: "My fork", Repo: "me/fork", Kind: "llama", Variants: []Variant{{ID: "win", Patterns: map[string][]string{"windows": {"^x\\.zip$"}}}}}, {ID: "llama-server", Name: "Impostor", Repo: "evil/repo"}, } } cat := m.Catalog() if len(cat) != base+1 { t.Fatalf("catalog has %d components, want %d (the shadowing entry must be dropped)", len(cat), base+1) } c, ok := m.Find("custom-me-fork") if !ok || !c.Custom || c.Repo != "me/fork" { t.Fatalf("tracked source not findable: %+v (ok=%v)", c, ok) } if b, _ := m.Find("llama-server"); b.Repo == "evil/repo" { t.Error("a tracked source shadowed a built-in component") } } func TestBackends_ValidAssetURL(t *testing.T) { if err := validAssetURL("https://github.com/o/r/releases/download/x/y.zip"); err != nil { t.Errorf("github url rejected: %v", err) } for _, bad := range []string{ "http://github.com/o/r/y.zip", "https://evil.example.com/y.zip", "https://github.com.evil.com/y.zip", } { if err := validAssetURL(bad); err == nil { t.Errorf("accepted %s", bad) } } } // writeTarGz builds a tar.gz from regular-file and symlink entries. Symlink // entries are given as "name -> target". func writeTarGz(t *testing.T, path string, entries []string) { t.Helper() f, err := os.Create(path) if err != nil { t.Fatal(err) } defer f.Close() gz := gzip.NewWriter(f) tw := tar.NewWriter(gz) for _, e := range entries { name, target, isLink := strings.Cut(e, " -> ") h := &tar.Header{Name: name, Mode: 0o755, Typeflag: tar.TypeReg, Size: int64(len(name))} if isLink { h = &tar.Header{Name: name, Mode: 0o777, Typeflag: tar.TypeSymlink, Linkname: target} } if err := tw.WriteHeader(h); err != nil { t.Fatal(err) } if !isLink { if _, err := tw.Write([]byte(name)); err != nil { t.Fatal(err) } } } if err := tw.Close(); err != nil { t.Fatal(err) } if err := gz.Close(); err != nil { t.Fatal(err) } } // llama.cpp's Linux bundles ship every library as a SONAME chain of symlinks // and the ELF headers name the middle link, so an extractor that drops links // produces a bundle that cannot load. The links are also listed BEFORE their // target here, as the real tarball does. func TestBackends_ExtractKeepsSonameChain(t *testing.T) { dir := t.TempDir() src := filepath.Join(dir, "bundle.tar.gz") writeTarGz(t, src, []string{ "b1/libllama-common.so -> libllama-common.so.0", "b1/libllama-common.so.0 -> libllama-common.so.0.3.0", "b1/libllama-common.so.0.3.0", "b1/llama-server", }) dest := filepath.Join(dir, "install") if err := extract(src, dest); err != nil { t.Fatal(err) } // Stat follows the link, so this fails on both a missing link and a broken // chain - and passes on the copy fallback used where symlinks need rights. for _, name := range []string{"libllama-common.so", "libllama-common.so.0", "libllama-common.so.0.3.0"} { st, err := os.Stat(filepath.Join(dest, "b1", name)) if err != nil { t.Fatalf("%s: %v", name, err) } if st.Size() == 0 { t.Errorf("%s resolved to an empty file", name) } } } // A symlink is the other half of a path-escape trick: the link itself stays // inside the install directory while its target does not. func TestBackends_ExtractRejectsLinkEscape(t *testing.T) { for _, target := range []string{"../../escaped.txt", "/etc/passwd"} { dir := t.TempDir() src := filepath.Join(dir, "evil.tar.gz") writeTarGz(t, src, []string{"b1/inside.so -> " + target}) dest := filepath.Join(dir, "install") if err := extract(src, dest); err == nil { t.Fatalf("target %q: expected extract to reject the link", target) } if _, err := os.Lstat(filepath.Join(dest, "b1", "inside.so")); err == nil { t.Errorf("target %q: escaping link was created anyway", target) } } }