1476 lines
44 KiB
Go
1476 lines
44 KiB
Go
package main
|
|
|
|
// Targeted tests filling the coverage gaps left by the focused suites.
|
|
// Each test exists to exercise a specific branch that was uncovered in
|
|
// the `go tool cover` report.
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// ── check.go ────────────────────────────────────────────────────────────
|
|
|
|
func TestCheckFlatpakPackagesPartition(t *testing.T) {
|
|
defer resetMocks()
|
|
|
|
hasCmd = func(name string) bool { return name == "flatpak" }
|
|
probe = func(argv []string, _ time.Duration) (CmdResult, bool) {
|
|
// Only "installed.app" is installed.
|
|
if argv[len(argv)-1] == "installed.app" {
|
|
return CmdResult{ExitCode: 0}, true
|
|
}
|
|
return CmdResult{ExitCode: 1}, true
|
|
}
|
|
|
|
res := checkFlatpakPackages([]string{"a.app", "installed.app", "b.app"})
|
|
if !equalStringSlices(res.alreadyInstalled, []string{"installed.app"}) {
|
|
t.Errorf("alreadyInstalled: want [installed.app], got %v", res.alreadyInstalled)
|
|
}
|
|
if !equalStringSlices(res.toInstall, []string{"a.app", "b.app"}) {
|
|
t.Errorf("toInstall: want [a.app b.app], got %v", res.toInstall)
|
|
}
|
|
}
|
|
|
|
func TestCheckFlatpakPackagesEmpty(t *testing.T) {
|
|
defer resetMocks()
|
|
res := checkFlatpakPackages(nil)
|
|
if len(res.toInstall) != 0 || len(res.alreadyInstalled) != 0 {
|
|
t.Errorf("expected empty results, got %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestCheckAllInParallelCombinations(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "dnf"
|
|
|
|
hasCmd = func(name string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0}, true
|
|
}
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
|
|
// All three on.
|
|
sys, flat, cust := checkAllInParallel(
|
|
true, []string{"git"},
|
|
true, []string{"a.app"},
|
|
true, []*CustomPackage{{Name: "go"}},
|
|
)
|
|
if len(sys.alreadyInstalled) == 0 {
|
|
t.Errorf("expected system pkgs marked installed, got %+v", sys)
|
|
}
|
|
if len(flat.alreadyInstalled) == 0 {
|
|
t.Errorf("expected flatpak pkgs marked installed, got %+v", flat)
|
|
}
|
|
if len(cust.alreadyInstalled) == 0 {
|
|
t.Errorf("expected custom pkgs marked installed, got %+v", cust)
|
|
}
|
|
|
|
// All three off (no goroutines spawned).
|
|
sys2, flat2, cust2 := checkAllInParallel(false, nil, false, nil, false, nil)
|
|
if len(sys2.toInstallRegular) != 0 || len(flat2.toInstall) != 0 || len(cust2.toInstall) != 0 {
|
|
t.Errorf("expected empty results when all flags are off")
|
|
}
|
|
}
|
|
|
|
func TestFmtListOverLimit(t *testing.T) {
|
|
got := fmtList([]string{"a", "b", "c", "d", "e"}, 2)
|
|
if !strings.Contains(got, "a b") || !strings.Contains(got, "+3 more") {
|
|
t.Errorf("expected truncated list with '+3 more', got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestPrintCheckSummaryAllSections(t *testing.T) {
|
|
defer resetMocks()
|
|
captureStdout(t, func() {
|
|
sys := systemCheckResult{
|
|
toInstallRegular: []string{"a", "b"},
|
|
toInstallSpecial: []string{"sp1"},
|
|
alreadyInstalled: []string{"ok1"},
|
|
skipped: []string{"sk1"},
|
|
remapped: []remap{{From: "x", To: []string{"y", "z"}}},
|
|
}
|
|
flat := flatpakCheckResult{
|
|
toInstall: []string{"app1"},
|
|
alreadyInstalled: []string{"app-ok"},
|
|
}
|
|
cust := customCheckResult{
|
|
toInstall: []*CustomPackage{{Name: "go", InstallPath: "/usr/local/go"}},
|
|
alreadyInstalled: []customStatus{{pkg: &CustomPackage{Name: "zig"}, path: "/usr/local/zig"}},
|
|
}
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
hasCmd = func(_ string) bool { return false }
|
|
total := printCheckSummary(sys, flat, cust, "")
|
|
if total != 4 { // 3 system + 1 flatpak + 1 custom — wait recalc
|
|
// 2 regular + 1 special + 1 flatpak + 1 custom = 5
|
|
if total != 5 {
|
|
t.Errorf("expected total 5, got %d", total)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPrintCheckSummaryOnlySystem(t *testing.T) {
|
|
defer resetMocks()
|
|
captureStdout(t, func() {
|
|
sys := systemCheckResult{toInstallRegular: []string{"a"}}
|
|
flat := flatpakCheckResult{}
|
|
cust := customCheckResult{}
|
|
_ = printCheckSummary(sys, flat, cust, "system")
|
|
})
|
|
}
|
|
|
|
func TestPrintCheckSummaryOnlyFlatpak(t *testing.T) {
|
|
defer resetMocks()
|
|
captureStdout(t, func() {
|
|
flat := flatpakCheckResult{toInstall: []string{"a.app"}, alreadyInstalled: []string{"b.app"}}
|
|
_ = printCheckSummary(systemCheckResult{}, flat, customCheckResult{}, "flatpak")
|
|
})
|
|
}
|
|
|
|
// ── custom.go ───────────────────────────────────────────────────────────
|
|
|
|
func TestResolveURLEmpty(t *testing.T) {
|
|
p := &CustomPackage{Name: "x"}
|
|
if p.resolveURL() != "" {
|
|
t.Error("expected empty URL for empty template")
|
|
}
|
|
}
|
|
|
|
func TestResolveSHA256URLEmpty(t *testing.T) {
|
|
p := &CustomPackage{Name: "x"}
|
|
if p.resolveSHA256URL() != "" {
|
|
t.Error("expected empty SHA URL for empty template")
|
|
}
|
|
}
|
|
|
|
func TestResolvedSHA256MapMiss(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
p := &CustomPackage{
|
|
Name: "x",
|
|
SHA256Map: map[string]string{"macos-aarch64": "abc"},
|
|
}
|
|
if got := p.resolvedSHA256(); got != "" {
|
|
t.Errorf("expected empty when map has no entry for current OS/arch, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestPipInstalledNoPython(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(name string) bool { return false }
|
|
if pipInstalled() {
|
|
t.Error("expected pipInstalled false when python3 missing")
|
|
}
|
|
}
|
|
|
|
func TestNpmInstalledOnPath(t *testing.T) {
|
|
defer resetMocks()
|
|
// Pretend "echo" (which definitely exists) is the npm command.
|
|
hasCmd = func(name string) bool { return name == "echo" }
|
|
installed, path := npmInstalled("echo")
|
|
if !installed {
|
|
t.Error("expected installed when hasCmd returns true")
|
|
}
|
|
// path may or may not be set depending on resolved PATH, but
|
|
// LookPath for echo should normally succeed.
|
|
if path == "" {
|
|
t.Log("note: exec.LookPath did not resolve a path; that's OK")
|
|
}
|
|
}
|
|
|
|
func TestNpmInstalledViaPnpmDir(t *testing.T) {
|
|
defer resetMocks()
|
|
tmp := t.TempDir()
|
|
t.Setenv("HOME", tmp)
|
|
binDir := filepath.Join(tmp, ".local/share/pnpm/bin")
|
|
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
binPath := filepath.Join(binDir, "fakebin")
|
|
if err := os.WriteFile(binPath, []byte{}, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hasCmd = func(_ string) bool { return false }
|
|
installed, path := npmInstalled("fakebin")
|
|
if !installed || path != binPath {
|
|
t.Errorf("expected fakebin found in pnpm dir; got installed=%v path=%q", installed, path)
|
|
}
|
|
}
|
|
|
|
func TestNpmInstalledViaNvmDir(t *testing.T) {
|
|
defer resetMocks()
|
|
tmp := t.TempDir()
|
|
t.Setenv("HOME", tmp)
|
|
binDir := filepath.Join(tmp, ".nvm/versions/node/v20.10.0/bin")
|
|
if err := os.MkdirAll(binDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
binPath := filepath.Join(binDir, "fakebin")
|
|
if err := os.WriteFile(binPath, []byte{}, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hasCmd = func(_ string) bool { return false }
|
|
installed, path := npmInstalled("fakebin")
|
|
if !installed || path != binPath {
|
|
t.Errorf("expected fakebin found in nvm dir; got installed=%v path=%q", installed, path)
|
|
}
|
|
}
|
|
|
|
func TestNpmInstalledNotFound(t *testing.T) {
|
|
defer resetMocks()
|
|
tmp := t.TempDir()
|
|
t.Setenv("HOME", tmp)
|
|
hasCmd = func(_ string) bool { return false }
|
|
installed, path := npmInstalled("absolutely-not-here")
|
|
if installed || path != "" {
|
|
t.Errorf("expected not-installed and empty path; got %v %q", installed, path)
|
|
}
|
|
}
|
|
|
|
func TestIsCustomPkgInstalledNpmNames(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(name string) bool {
|
|
return name == "claude" || name == "codex" || name == "copilot" || name == "playwright"
|
|
}
|
|
for _, name := range []string{"claude", "codex", "copilot", "playwright"} {
|
|
ok, _ := isCustomPkgInstalled(&CustomPackage{Name: name})
|
|
if !ok {
|
|
t.Errorf("expected %s detected as installed", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsCustomPkgInstalledNoPath(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
// Unknown package with no install path → returns false, "".
|
|
ok, path := isCustomPkgInstalled(&CustomPackage{Name: "no-such-pkg"})
|
|
if ok || path != "" {
|
|
t.Errorf("expected (false, \"\") for unknown pkg, got (%v, %q)", ok, path)
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveHashError(t *testing.T) {
|
|
defer resetMocks()
|
|
// SHA256 set but file doesn't exist — sha256Of returns an error.
|
|
p := &CustomPackage{Name: "x", SHA256: "deadbeef"}
|
|
if verifyArchive("/no/such/file", p) {
|
|
t.Error("expected verifyArchive false when hash computation fails")
|
|
}
|
|
if !hasErrors() {
|
|
t.Error("expected error logged for hash failure")
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveNoSHANoSig(t *testing.T) {
|
|
defer resetMocks()
|
|
p := &CustomPackage{Name: "x"}
|
|
if !verifyArchive("/no/such/file", p) {
|
|
t.Error("expected verifyArchive true when nothing to verify")
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveMinisignDownloadFail(t *testing.T) {
|
|
defer resetMocks()
|
|
p := &CustomPackage{Name: "x", SHA256URLTemplate: "http://example.com/{version}.sig", Version: "1"}
|
|
download = func(_, _ string) bool { return false }
|
|
if verifyArchive("/tmp/foo", p) {
|
|
t.Error("expected verifyArchive false when signature download fails")
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveMinisignNotInstalled(t *testing.T) {
|
|
defer resetMocks()
|
|
p := &CustomPackage{Name: "x", SHA256URLTemplate: "http://example.com/{version}.sig", Version: "1"}
|
|
download = func(_, _ string) bool { return true }
|
|
hasCmd = func(name string) bool { return name != "minisign" }
|
|
if !verifyArchive("/tmp/foo", p) {
|
|
t.Error("expected verifyArchive true (skip-with-warning) when minisign missing")
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveMinisignFails(t *testing.T) {
|
|
defer resetMocks()
|
|
p := &CustomPackage{Name: "x", SHA256URLTemplate: "http://example.com/{version}.sig", Version: "1", MinisignKey: "key"}
|
|
download = func(_, _ string) bool { return true }
|
|
hasCmd = func(_ string) bool { return true }
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
if verifyArchive("/tmp/foo", p) {
|
|
t.Error("expected verifyArchive false when minisign verification fails")
|
|
}
|
|
}
|
|
|
|
func TestVerifyArchiveMinisignOK(t *testing.T) {
|
|
defer resetMocks()
|
|
p := &CustomPackage{Name: "x", SHA256URLTemplate: "http://example.com/{version}.sig", Version: "1"}
|
|
download = func(_, _ string) bool { return true }
|
|
hasCmd = func(_ string) bool { return true }
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
|
if !verifyArchive("/tmp/foo", p) {
|
|
t.Error("expected verifyArchive true when minisign succeeds")
|
|
}
|
|
}
|
|
|
|
func TestUrlArchOKEmptyTemplate(t *testing.T) {
|
|
defer resetMocks()
|
|
if !urlArchOK(&CustomPackage{Name: "x"}) {
|
|
t.Error("expected urlArchOK true for empty URL")
|
|
}
|
|
}
|
|
|
|
func TestUrlArchOKNoArchToken(t *testing.T) {
|
|
defer resetMocks()
|
|
archName = "x86_64"
|
|
// URL doesn't contain an arch token at all — treated as OK.
|
|
p := &CustomPackage{Name: "x", URLTemplate: "http://example.com/generic.tar.gz"}
|
|
if !urlArchOK(p) {
|
|
t.Error("expected urlArchOK true for arch-agnostic URL")
|
|
}
|
|
}
|
|
|
|
func TestInstallGoWithExistingDir(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(name string) (os.FileInfo, error) {
|
|
if name == "/usr/local/go" {
|
|
return nil, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
var cmds [][]string
|
|
runCmd = func(argv []string, _ CmdOpts) CmdResult {
|
|
cmds = append(cmds, append([]string(nil), argv...))
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
installGo("/tmp/go.tgz")
|
|
// First call should be "rm -rf /usr/local/go".
|
|
if len(cmds) < 1 || cmds[0][0] != "rm" {
|
|
t.Errorf("expected rm -rf as first call, got: %v", cmds)
|
|
}
|
|
}
|
|
|
|
func TestInstallFirecrackerTarFail(t *testing.T) {
|
|
defer resetMocks()
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installFirecracker("/tmp/foo.tgz", "/tmp")
|
|
if !hasErrors() {
|
|
t.Error("expected error logged when tar fails")
|
|
}
|
|
}
|
|
|
|
func TestInstallFirecrackerNoBinaryFound(t *testing.T) {
|
|
defer resetMocks()
|
|
tmp := t.TempDir()
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
|
installFirecracker(filepath.Join(tmp, "x.tgz"), tmp)
|
|
if !hasErrors() {
|
|
t.Error("expected error logged when no firecracker binary in archive")
|
|
}
|
|
}
|
|
|
|
func TestInstallZigWithExistingDir(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
osStat = func(name string) (os.FileInfo, error) {
|
|
if strings.Contains(name, "zig-1.2.3") {
|
|
return nil, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
var cmds [][]string
|
|
runCmd = func(argv []string, _ CmdOpts) CmdResult {
|
|
cmds = append(cmds, append([]string(nil), argv...))
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
pkg := &CustomPackage{Name: "zig", Version: "1.2.3"}
|
|
installZig(pkg, "/tmp/zig.tar.xz")
|
|
if len(cmds) < 1 || cmds[0][0] != "rm" {
|
|
t.Errorf("expected rm -rf as first call, got %v", cmds)
|
|
}
|
|
}
|
|
|
|
func TestInstallNeovimAssetMissing(t *testing.T) {
|
|
defer resetMocks()
|
|
tmp := t.TempDir()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
rel := v.(*ghRelease)
|
|
rel.Assets = []ghAsset{{Name: "wrong-name.tar.gz"}}
|
|
return true
|
|
}
|
|
installNeovim(nil, tmp)
|
|
if !hasErrors() {
|
|
t.Error("expected error when asset name not found")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestGoNoMatchingFile(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
// Provide a release but no archive file with the expected name.
|
|
data := `[{"version":"go1.99.0","files":[]}]`
|
|
return json.Unmarshal([]byte(data), v) == nil
|
|
}
|
|
_, _, ok := resolveLatestGo(nil)
|
|
if ok {
|
|
t.Error("expected resolveLatestGo to fail when no matching archive in release")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestGoFetchFails(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, _ any) bool { return false }
|
|
if _, _, ok := resolveLatestGo(nil); ok {
|
|
t.Error("expected resolveLatestGo to fail on HTTP error")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestGoEmptyArray(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, v any) bool {
|
|
return json.Unmarshal([]byte("[]"), v) == nil
|
|
}
|
|
if _, _, ok := resolveLatestGo(nil); ok {
|
|
t.Error("expected resolveLatestGo to fail on empty array")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestGoSingleObject(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
// Server returns a single object (not array) — fallback path.
|
|
data := `{"version":"go1.50.0","files":[{"filename":"go1.50.0.linux-amd64.tar.gz","kind":"archive","sha256":"abc"}]}`
|
|
return json.Unmarshal([]byte(data), v) == nil
|
|
}
|
|
v, sha, ok := resolveLatestGo(nil)
|
|
if !ok || v != "1.50.0" || sha != "abc" {
|
|
t.Errorf("expected fallback parse to yield 1.50.0/abc, got %q/%q/%v", v, sha, ok)
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestFirecrackerMacOS(t *testing.T) {
|
|
defer resetMocks()
|
|
isMacOS = true
|
|
if _, _, ok := resolveLatestFirecracker(nil); ok {
|
|
t.Error("expected resolveLatestFirecracker false on macOS")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestFirecrackerNoTag(t *testing.T) {
|
|
defer resetMocks()
|
|
isMacOS = false
|
|
fetchJSON = func(_ string, v any) bool {
|
|
rel := v.(*ghRelease)
|
|
rel.TagName = ""
|
|
return true
|
|
}
|
|
if _, _, ok := resolveLatestFirecracker(nil); ok {
|
|
t.Error("expected false when tag missing")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestFirecrackerNoMatchingAsset(t *testing.T) {
|
|
defer resetMocks()
|
|
isMacOS = false
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
rel := v.(*ghRelease)
|
|
rel.TagName = "v1.0.0"
|
|
rel.Assets = []ghAsset{{Name: "wrong"}}
|
|
return true
|
|
}
|
|
if _, _, ok := resolveLatestFirecracker(nil); ok {
|
|
t.Error("expected false when no matching .sha256.txt asset")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestFirecrackerEmptySHA(t *testing.T) {
|
|
defer resetMocks()
|
|
isMacOS = false
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
rel := v.(*ghRelease)
|
|
rel.TagName = "v1.0.0"
|
|
rel.Assets = []ghAsset{{Name: "firecracker-v1.0.0-x86_64.tgz.sha256.txt", BrowserDownloadURL: "http://x"}}
|
|
return true
|
|
}
|
|
fetchText = func(_ string) string { return "" }
|
|
if _, _, ok := resolveLatestFirecracker(nil); ok {
|
|
t.Error("expected false when SHA body is empty")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestZigEmpty(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, v any) bool {
|
|
return json.Unmarshal([]byte(`{"master":{}}`), v) == nil
|
|
}
|
|
if _, _, ok := resolveLatestZig(nil); ok {
|
|
t.Error("expected false when only master version present")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestZigMissingPlatformKey(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
// Has a stable version but missing the current platform key.
|
|
return json.Unmarshal([]byte(`{"0.11.0":{"aarch64-linux":{"shasum":"abc"}}}`), v) == nil
|
|
}
|
|
if _, _, ok := resolveLatestZig(nil); ok {
|
|
t.Error("expected false when platform key missing")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestZigEmptySHA(t *testing.T) {
|
|
defer resetMocks()
|
|
osName = "linux"
|
|
archName = "x86_64"
|
|
fetchJSON = func(_ string, v any) bool {
|
|
return json.Unmarshal([]byte(`{"0.11.0":{"x86_64-linux":{"shasum":""}}}`), v) == nil
|
|
}
|
|
if _, _, ok := resolveLatestZig(nil); ok {
|
|
t.Error("expected false when shasum empty")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestFetchJSONFail(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, _ any) bool { return false }
|
|
if _, _, ok := resolveLatestZig(nil); ok {
|
|
t.Error("expected false on fetch failure")
|
|
}
|
|
}
|
|
|
|
func TestResolveLatestUnknownHint(t *testing.T) {
|
|
defer resetMocks()
|
|
resolveLatest(&CustomPackage{Name: "x"}) // no FetchLatest → no-op
|
|
resolveLatest(&CustomPackage{Name: "x", FetchLatest: "nonexistent"}) // unknown → no-op
|
|
}
|
|
|
|
func TestResolveLatestUpgrades(t *testing.T) {
|
|
defer resetMocks()
|
|
latestResolvers["upgrade-fixture"] = func(_ *CustomPackage) (string, string, bool) {
|
|
return "2.0.0", "FACE", true
|
|
}
|
|
defer delete(latestResolvers, "upgrade-fixture")
|
|
p := &CustomPackage{Name: "x", Version: "1.0.0", FetchLatest: "upgrade-fixture", SHA256URLTemplate: "sig"}
|
|
resolveLatest(p)
|
|
if p.Version != "2.0.0" || p.SHA256 != "face" || p.SHA256URLTemplate != "" {
|
|
t.Errorf("expected upgrade applied with lowercase SHA and cleared template, got %+v", p)
|
|
}
|
|
}
|
|
|
|
// runOneCustomInstall coverage: hit a few branches.
|
|
|
|
func TestRunOneCustomInstallFirecrackerOnMacOS(t *testing.T) {
|
|
defer resetMocks()
|
|
isMacOS = true
|
|
runOneCustomInstall(&CustomPackage{Name: "firecracker"})
|
|
// Should warn-and-skip without error.
|
|
issuesMu.Lock()
|
|
hasWarn := false
|
|
for _, msg := range issues {
|
|
if strings.Contains(msg, "Linux-only") {
|
|
hasWarn = true
|
|
}
|
|
}
|
|
issuesMu.Unlock()
|
|
if !hasWarn {
|
|
t.Error("expected Linux-only warning")
|
|
}
|
|
}
|
|
|
|
func TestRunOneCustomInstallNoURLNoHandler(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
hasCmd = func(_ string) bool { return false }
|
|
runOneCustomInstall(&CustomPackage{Name: "unknownpkg"})
|
|
// Should warn about no URL / no handler.
|
|
if !hasIssueContaining("No URL or install handler") {
|
|
t.Errorf("expected 'no URL' warning, issues: %v", issuesSnapshot())
|
|
}
|
|
}
|
|
|
|
func TestRunOneCustomInstallDownloadFail(t *testing.T) {
|
|
defer resetMocks()
|
|
archName = "x86_64"
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
hasCmd = func(_ string) bool { return false }
|
|
download = func(_, _ string) bool { return false }
|
|
pkg := &CustomPackage{
|
|
Name: "go",
|
|
Version: "1.0.0",
|
|
URLTemplate: "http://example.com/go-{arch}.tar.gz",
|
|
SHA256: "abc",
|
|
}
|
|
runOneCustomInstall(pkg)
|
|
}
|
|
|
|
func TestRunOneCustomInstallArchMismatch(t *testing.T) {
|
|
defer resetMocks()
|
|
archName = "x86_64"
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
hasCmd = func(_ string) bool { return false }
|
|
pkg := &CustomPackage{
|
|
Name: "weird",
|
|
URLTemplate: "http://example.com/weird-aarch64.tar.gz",
|
|
}
|
|
runOneCustomInstall(pkg)
|
|
}
|
|
|
|
func TestRunOneCustomInstallVerifyFail(t *testing.T) {
|
|
defer resetMocks()
|
|
archName = "x86_64"
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
hasCmd = func(_ string) bool { return false }
|
|
download = func(_, dest string) bool {
|
|
// Write empty file so sha256Of works but the digest mismatches.
|
|
return os.WriteFile(dest, []byte("x"), 0o644) == nil
|
|
}
|
|
pkg := &CustomPackage{
|
|
Name: "go",
|
|
Version: "1.0.0",
|
|
URLTemplate: "http://example.com/go-{arch}.tar.gz",
|
|
SHA256: "deadbeef",
|
|
}
|
|
runOneCustomInstall(pkg)
|
|
}
|
|
|
|
func TestInstallNpmToolsBatchNoNVM(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
installNpmToolsBatch([]*CustomPackage{{Name: "claude"}})
|
|
if !hasIssueContaining("NVM is not installed") {
|
|
t.Error("expected error about missing NVM")
|
|
}
|
|
}
|
|
|
|
func TestInstallNpmToolsBatchEmpty(t *testing.T) {
|
|
defer resetMocks()
|
|
installNpmToolsBatch(nil)
|
|
}
|
|
|
|
func TestInstallNpmToolsBatchNonNpmFiltered(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(name string) (os.FileInfo, error) {
|
|
if strings.HasSuffix(name, ".nvm") {
|
|
return nil, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
called := false
|
|
runShell = func(_ string, _ CmdOpts) CmdResult {
|
|
called = true
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
// Only an unknown name — should filter to nothing and short-circuit.
|
|
installNpmToolsBatch([]*CustomPackage{{Name: "not-an-npm-tool"}})
|
|
// ensureNodeLTS still runs, so shell calls *are* expected from that.
|
|
_ = called
|
|
}
|
|
|
|
func TestInstallNpmToolsBatchPlaywrightBrowsers(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "apt-get"
|
|
osStat = func(name string) (os.FileInfo, error) {
|
|
if strings.HasSuffix(name, ".nvm") {
|
|
return nil, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
var cmds []string
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
cmds = append(cmds, cmd)
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
installNpmToolsBatch([]*CustomPackage{{Name: "playwright"}})
|
|
hasBrowserInstall := false
|
|
for _, c := range cmds {
|
|
if strings.Contains(c, "pnpx playwright install --with-deps") {
|
|
hasBrowserInstall = true
|
|
}
|
|
}
|
|
if !hasBrowserInstall {
|
|
t.Errorf("expected pnpx playwright install --with-deps call, got: %v", cmds)
|
|
}
|
|
}
|
|
|
|
func TestInstallCustomPackagesEmpty(t *testing.T) {
|
|
defer resetMocks()
|
|
captureStdout(t, func() {
|
|
installCustomPackages(nil)
|
|
})
|
|
}
|
|
|
|
// ── post.go ─────────────────────────────────────────────────────────────
|
|
|
|
func TestInstallPyenvFailure(t *testing.T) {
|
|
defer resetMocks()
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installPyenv()
|
|
if !hasErrors() {
|
|
t.Error("expected error on pyenv install failure")
|
|
}
|
|
}
|
|
|
|
func TestInstallNVMFetchFail(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, _ any) bool { return false }
|
|
installNVM()
|
|
}
|
|
|
|
func TestInstallNVMNoTag(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, v any) bool {
|
|
v.(*ghRelease).TagName = ""
|
|
return true
|
|
}
|
|
installNVM()
|
|
if !hasIssueContaining("NVM tag_name missing") {
|
|
t.Error("expected NVM tag missing error")
|
|
}
|
|
}
|
|
|
|
func TestInstallNVMShellFail(t *testing.T) {
|
|
defer resetMocks()
|
|
fetchJSON = func(_ string, v any) bool {
|
|
v.(*ghRelease).TagName = "v0.39.0"
|
|
return true
|
|
}
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installNVM()
|
|
if !hasIssueContaining("NVM installation failed") {
|
|
t.Error("expected NVM install failed error")
|
|
}
|
|
}
|
|
|
|
func TestInstallAgyFail(t *testing.T) {
|
|
defer resetMocks()
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installAgy()
|
|
if !hasErrors() {
|
|
t.Error("expected error from installAgy failure")
|
|
}
|
|
}
|
|
|
|
func TestInstallNpmPackageNoNvm(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
installNpmPackage("@scope/pkg")
|
|
if !hasIssueContaining("NVM is not installed") {
|
|
t.Error("expected NVM-missing error")
|
|
}
|
|
}
|
|
|
|
func TestInstallNpmPackageShellFail(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installNpmPackage("@scope/pkg")
|
|
if !hasIssueContaining("installation failed") {
|
|
t.Error("expected install failure error")
|
|
}
|
|
}
|
|
|
|
func TestInstallPlaywrightBrowsersNonApt(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "dnf"
|
|
called := false
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
called = strings.Contains(cmd, "pnpx playwright install")
|
|
if strings.Contains(cmd, "--with-deps") {
|
|
t.Errorf("did not expect --with-deps on non-apt, got: %q", cmd)
|
|
}
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
installPlaywrightBrowsers()
|
|
if !called {
|
|
t.Error("expected pnpx playwright install call")
|
|
}
|
|
}
|
|
|
|
func TestInstallPlaywrightBrowsersFail(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "dnf"
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installPlaywrightBrowsers()
|
|
if !hasErrors() {
|
|
t.Error("expected error on browser install failure")
|
|
}
|
|
}
|
|
|
|
func TestInstallPlaywrightNoNvm(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
installPlaywright()
|
|
if !hasIssueContaining("NVM is not installed") {
|
|
t.Error("expected NVM error")
|
|
}
|
|
}
|
|
|
|
func TestInstallPlaywrightAddFails(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
calls := 0
|
|
runShell = func(_ string, _ CmdOpts) CmdResult {
|
|
calls++
|
|
// ensureNodeLTS issues a series of shell calls; fail the pnpm add step.
|
|
// Easiest: just fail everything that contains "pnpm add -g playwright".
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "pnpm add -g playwright") {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
installPlaywright()
|
|
if !hasIssueContaining("playwright installation failed") {
|
|
t.Errorf("expected playwright install failure, issues: %v", issuesSnapshot())
|
|
}
|
|
}
|
|
|
|
func TestInstallPipPython3Missing(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
installPip()
|
|
if !hasIssueContaining("python3 is not installed") {
|
|
t.Error("expected python3-missing error")
|
|
}
|
|
}
|
|
|
|
func TestInstallPipDecimalFixFails(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "apt-get"
|
|
hasCmd = func(_ string) bool { return true }
|
|
// Decimal probe fails both before and after attempted fix.
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 1}, true
|
|
}
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 0} }
|
|
installPip()
|
|
if !hasIssueContaining("_decimal C extension could not be fixed") {
|
|
t.Error("expected decimal-fix error")
|
|
}
|
|
}
|
|
|
|
func TestInstallPipEnsurepipFailApt(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "apt-get"
|
|
hasCmd = func(_ string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0}, true
|
|
}
|
|
calls := 0
|
|
runCmd = func(argv []string, _ CmdOpts) CmdResult {
|
|
calls++
|
|
// ensurepip is the first runCmd call → fail it.
|
|
if calls == 1 {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
installPip()
|
|
// Should attempt fallback `apt-get install python3-pip`.
|
|
}
|
|
|
|
func TestInstallPipEnsurepipFailUnknownMgr(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "brew"
|
|
hasCmd = func(_ string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0}, true
|
|
}
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installPip()
|
|
if !hasIssueContaining("ensurepip failed") {
|
|
t.Error("expected ensurepip-failure error on unknown pkgmgr")
|
|
}
|
|
}
|
|
|
|
func TestEnsureZshDefaultNoZsh(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
ensureZshDefault()
|
|
if !hasIssueContaining("zsh not installed") {
|
|
t.Error("expected zsh-missing warning")
|
|
}
|
|
}
|
|
|
|
func TestEnsureZshDefaultRHEL(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("/bin/zsh\n")}, true
|
|
}
|
|
isRHELFamily = true
|
|
tmpDir := t.TempDir()
|
|
passwdPath = filepath.Join(tmpDir, "passwd")
|
|
t.Setenv("SUDO_USER", "testuser")
|
|
// Real getpwnam will fail for "testuser" — that's fine, we just want
|
|
// to exercise the RHEL branch up to the user lookup.
|
|
ensureZshDefault()
|
|
}
|
|
|
|
func TestEnsureZshDefaultAlreadyZsh(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("/bin/zsh\n")}, true
|
|
}
|
|
t.Setenv("SUDO_USER", os.Getenv("USER"))
|
|
if u := os.Getenv("USER"); u == "" {
|
|
t.Skip("USER env not set; cannot test")
|
|
}
|
|
tmp := t.TempDir()
|
|
passwdPath = filepath.Join(tmp, "passwd")
|
|
// Write a passwd entry that says the user's shell is already zsh.
|
|
uid := fmt.Sprintf("%d", os.Getuid())
|
|
entry := fmt.Sprintf("%s:x:%s:0::/home/%s:/bin/zsh\n", os.Getenv("USER"), uid, os.Getenv("USER"))
|
|
os.WriteFile(passwdPath, []byte(entry), 0o644)
|
|
ensureZshDefault()
|
|
}
|
|
|
|
func TestEnsureZshDefaultChshFails(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("/bin/zsh\n")}, true
|
|
}
|
|
t.Setenv("SUDO_USER", os.Getenv("USER"))
|
|
if os.Getenv("USER") == "" {
|
|
t.Skip("USER env not set")
|
|
}
|
|
tmp := t.TempDir()
|
|
passwdPath = filepath.Join(tmp, "passwd")
|
|
uid := fmt.Sprintf("%d", os.Getuid())
|
|
entry := fmt.Sprintf("%s:x:%s:0::/home/%s:/bin/bash\n", os.Getenv("USER"), uid, os.Getenv("USER"))
|
|
os.WriteFile(passwdPath, []byte(entry), 0o644)
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
ensureZshDefault()
|
|
if !hasIssueContaining("Failed to set default shell") {
|
|
t.Error("expected chsh-failure error")
|
|
}
|
|
}
|
|
|
|
func TestInvokingUserNoSudo(t *testing.T) {
|
|
defer resetMocks()
|
|
t.Setenv("SUDO_USER", "")
|
|
u := invokingUser()
|
|
if u == "" {
|
|
t.Error("expected invokingUser to fall back to user.Current()")
|
|
}
|
|
}
|
|
|
|
func TestEnsureNodeLTSAlreadyInstalled(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "nvm version lts") {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("v20.10.0\n")}
|
|
}
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
ensureNodeLTS()
|
|
}
|
|
|
|
func TestEnsureNodeLTSInstallFail(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "nvm install --lts") {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("")}
|
|
}
|
|
ensureNodeLTS()
|
|
if !hasIssueContaining("Node.js LTS install via nvm failed") {
|
|
t.Error("expected node install error")
|
|
}
|
|
}
|
|
|
|
func TestEnsureNodeLTSCorepackFail(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "corepack enable") {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("v20\n")}
|
|
}
|
|
ensureNodeLTS()
|
|
if !hasIssueContaining("Failed to enable corepack") {
|
|
t.Error("expected corepack error")
|
|
}
|
|
}
|
|
|
|
func TestEnsureNodeLTSPnpmSetupFail(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "pnpm setup") {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("v20\n")}
|
|
}
|
|
ensureNodeLTS()
|
|
if !hasIssueContaining("pnpm setup failed") {
|
|
t.Error("expected pnpm setup error")
|
|
}
|
|
}
|
|
|
|
func TestEnsureNodeLTSAliasFail(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
runShell = func(cmd string, _ CmdOpts) CmdResult {
|
|
if strings.Contains(cmd, "nvm alias default") {
|
|
return CmdResult{ExitCode: 1}
|
|
}
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("v20\n")}
|
|
}
|
|
ensureNodeLTS()
|
|
if !hasIssueContaining("Setting nvm default to LTS failed") {
|
|
t.Error("expected nvm default error")
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshNoZsh(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
installOhMyZsh()
|
|
if !hasIssueContaining("zsh is not installed") {
|
|
t.Error("expected zsh error")
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshNoGit(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(name string) bool { return name == "zsh" }
|
|
installOhMyZsh()
|
|
if !hasIssueContaining("git is not installed") {
|
|
t.Error("expected git error")
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshInstallerFail(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
runShell = func(_ string, _ CmdOpts) CmdResult { return CmdResult{ExitCode: 1} }
|
|
installOhMyZsh()
|
|
if !hasIssueContaining("oh-my-zsh installer failed") {
|
|
t.Error("expected installer failure")
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshNoZshrc(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
osReadFile = func(_ string) ([]byte, error) { return nil, os.ErrNotExist }
|
|
installOhMyZsh()
|
|
if !hasIssueContaining("~/.zshrc not present") {
|
|
t.Error("expected zshrc-missing warning")
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshAppendTheme(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
osReadFile = func(_ string) ([]byte, error) { return []byte("# config without theme\n"), nil }
|
|
written := ""
|
|
osWriteFile = func(_ string, data []byte, _ os.FileMode) error {
|
|
written = string(data)
|
|
return nil
|
|
}
|
|
installOhMyZsh()
|
|
if !strings.Contains(written, `ZSH_THEME="gnzh"`) {
|
|
t.Errorf("expected theme appended, got: %q", written)
|
|
}
|
|
}
|
|
|
|
func TestInstallOhMyZshWriteFail(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return true }
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
osReadFile = func(_ string) ([]byte, error) { return []byte("ZSH_THEME=\"x\"\n"), nil }
|
|
osWriteFile = func(_ string, _ []byte, _ os.FileMode) error { return errors.New("write fail") }
|
|
installOhMyZsh()
|
|
if !hasIssueContaining("could not write ~/.zshrc") {
|
|
t.Error("expected write-fail error")
|
|
}
|
|
}
|
|
|
|
func TestLatestStablePythonProbeFail(t *testing.T) {
|
|
defer resetMocks()
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{}, false
|
|
}
|
|
if v := latestStablePython("/x"); v != "" {
|
|
t.Errorf("expected empty result on probe failure, got %q", v)
|
|
}
|
|
}
|
|
|
|
func TestLatestStablePythonNonZeroExit(t *testing.T) {
|
|
defer resetMocks()
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 1}, true
|
|
}
|
|
if v := latestStablePython("/x"); v != "" {
|
|
t.Errorf("expected empty result on non-zero exit, got %q", v)
|
|
}
|
|
}
|
|
|
|
func TestLatestStablePythonNoVersions(t *testing.T) {
|
|
defer resetMocks()
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte(" 2.7.18\n system\n")}, true
|
|
}
|
|
// Only python2 in output → no 3.x versions extracted → "".
|
|
if v := latestStablePython("/x"); v != "" {
|
|
t.Errorf("expected empty result when no 3.x versions, got %q", v)
|
|
}
|
|
}
|
|
|
|
func TestEnsurePythonLatestNoDir(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, os.ErrNotExist }
|
|
if wg := ensurePythonLatest(); wg != nil {
|
|
t.Error("expected nil when pyenv dir missing")
|
|
}
|
|
}
|
|
|
|
func TestEnsurePythonLatestNoBin(t *testing.T) {
|
|
defer resetMocks()
|
|
calls := 0
|
|
osStat = func(_ string) (os.FileInfo, error) {
|
|
calls++
|
|
if calls == 1 {
|
|
return nil, nil
|
|
}
|
|
return nil, os.ErrNotExist
|
|
}
|
|
if wg := ensurePythonLatest(); wg != nil {
|
|
t.Error("expected nil when pyenv bin missing")
|
|
}
|
|
}
|
|
|
|
func TestEnsurePythonLatestLatestEmpty(t *testing.T) {
|
|
defer resetMocks()
|
|
osStat = func(_ string) (os.FileInfo, error) { return nil, nil }
|
|
probe = func(_ []string, _ time.Duration) (CmdResult, bool) {
|
|
return CmdResult{ExitCode: 0, Stdout: []byte("")}, true
|
|
}
|
|
if wg := ensurePythonLatest(); wg != nil {
|
|
t.Error("expected nil when no python version found")
|
|
}
|
|
}
|
|
|
|
// ── system.go ───────────────────────────────────────────────────────────
|
|
|
|
func TestPkgInstallBrewCask(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "brew"
|
|
var argv []string
|
|
runCmd = func(a []string, _ CmdOpts) CmdResult {
|
|
argv = a
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
pkgInstall("docker") // docker is a brew cask
|
|
if len(argv) < 3 || argv[2] != "--cask" {
|
|
t.Errorf("expected brew --cask install, got %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestPkgInstallBrewFormula(t *testing.T) {
|
|
defer resetMocks()
|
|
pkgMgr = "brew"
|
|
var argv []string
|
|
runCmd = func(a []string, _ CmdOpts) CmdResult {
|
|
argv = a
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
pkgInstall("git")
|
|
if len(argv) != 3 || argv[2] != "git" {
|
|
t.Errorf("expected brew install git, got %v", argv)
|
|
}
|
|
}
|
|
|
|
// ── pkgmgr.go ───────────────────────────────────────────────────────────
|
|
|
|
func TestEnsureWhichInstalledAlreadyPresent(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(name string) bool { return name == "which" }
|
|
called := false
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult {
|
|
called = true
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
ensureWhichInstalled()
|
|
if called {
|
|
t.Error("expected no runCmd when which is already present")
|
|
}
|
|
}
|
|
|
|
func TestEnsureWhichInstalledViaPacman(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
pkgMgr = "pacman"
|
|
var argv []string
|
|
runCmd = func(a []string, _ CmdOpts) CmdResult {
|
|
argv = a
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
ensureWhichInstalled()
|
|
if len(argv) == 0 || argv[0] != "pacman" {
|
|
t.Errorf("expected pacman install, got %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestEnsureWhichInstalledViaBrew(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
pkgMgr = "brew"
|
|
var argv []string
|
|
runCmd = func(a []string, _ CmdOpts) CmdResult {
|
|
argv = a
|
|
return CmdResult{ExitCode: 0}
|
|
}
|
|
ensureWhichInstalled()
|
|
if len(argv) == 0 || argv[0] != "brew" {
|
|
t.Errorf("expected brew install, got %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestEnsureWhichInstalledFails(t *testing.T) {
|
|
defer resetMocks()
|
|
hasCmd = func(_ string) bool { return false }
|
|
pkgMgr = "dnf"
|
|
runCmd = func(_ []string, _ CmdOpts) CmdResult {
|
|
return CmdResult{ExitCode: 1, Err: errors.New("boom")}
|
|
}
|
|
// Should warn (printed to stderr) but not panic.
|
|
ensureWhichInstalled()
|
|
}
|
|
|
|
// ── parallel.go ─────────────────────────────────────────────────────────
|
|
|
|
func TestTaskOutputSerialPrintf(t *testing.T) {
|
|
defer resetMocks()
|
|
// Serial mode prints to real stdout; just exercise the branch.
|
|
captureStdout(t, func() {
|
|
t := newSerialOutput()
|
|
t.Printf("hello %d\n", 1)
|
|
t.Println("world")
|
|
})
|
|
}
|
|
|
|
func TestParallelDoZeroWorkers(t *testing.T) {
|
|
items := []int{1, 2, 3}
|
|
var sum int64
|
|
parallelDo(items, 0, func(_ int, v int) {
|
|
atomic := int64(v)
|
|
_ = atomic
|
|
sum += int64(v)
|
|
})
|
|
if sum != 6 {
|
|
t.Errorf("expected sum 6 (clamped to 1 worker), got %d", sum)
|
|
}
|
|
}
|
|
|
|
// ── taskctx.go ──────────────────────────────────────────────────────────
|
|
|
|
func TestWithTaskOutputNilFn(t *testing.T) {
|
|
called := false
|
|
withTaskOutput(nil, func() { called = true })
|
|
if !called {
|
|
t.Error("expected fn called even with nil taskOutput")
|
|
}
|
|
}
|
|
|
|
func TestWithTaskOutputNested(t *testing.T) {
|
|
outer := newCapturedOutput("outer")
|
|
inner := newCapturedOutput("inner")
|
|
withTaskOutput(outer, func() {
|
|
if currentTask() != outer {
|
|
t.Error("expected outer task active")
|
|
}
|
|
withTaskOutput(inner, func() {
|
|
if currentTask() != inner {
|
|
t.Error("expected inner task active")
|
|
}
|
|
})
|
|
if currentTask() != outer {
|
|
t.Error("expected outer restored after nested withTaskOutput")
|
|
}
|
|
})
|
|
if currentTask() != nil {
|
|
t.Error("expected no active task after outer returns")
|
|
}
|
|
}
|
|
|
|
// ── exec.go ─────────────────────────────────────────────────────────────
|
|
|
|
func TestWriteToOutEmpty(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
writeToOut(&buf, nil)
|
|
if buf.Len() != 0 {
|
|
t.Error("expected no write for empty input")
|
|
}
|
|
}
|
|
|
|
func TestWriteToOutAddsNewline(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
writeToOut(&buf, []byte("no-newline"))
|
|
if !bytes.HasSuffix(buf.Bytes(), []byte{'\n'}) {
|
|
t.Errorf("expected trailing newline appended, got %q", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestWriteToOutPreservesExistingNewline(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
writeToOut(&buf, []byte("ends-with\n"))
|
|
if bytes.Count(buf.Bytes(), []byte{'\n'}) != 1 {
|
|
t.Errorf("expected exactly one newline, got %d", bytes.Count(buf.Bytes(), []byte{'\n'}))
|
|
}
|
|
}
|
|
|
|
func TestRunCmdRealWithCapture(t *testing.T) {
|
|
defer resetMocks()
|
|
r := runCmdReal([]string{"echo", "cap-mode"}, CmdOpts{Capture: true})
|
|
if !r.OK() {
|
|
t.Fatalf("echo failed: %v", r.Err)
|
|
}
|
|
if !bytes.Contains(r.Stdout, []byte("cap-mode")) {
|
|
t.Errorf("expected captured stdout to contain 'cap-mode', got: %q", r.Stdout)
|
|
}
|
|
}
|
|
|
|
// ── issues.go ───────────────────────────────────────────────────────────
|
|
|
|
func TestWriteRunLogFailure(t *testing.T) {
|
|
defer resetMocks()
|
|
warn("a warning to ensure issues is non-empty")
|
|
osWriteFile = func(_ string, _ []byte, _ os.FileMode) error {
|
|
return errors.New("disk full")
|
|
}
|
|
// Capture stderr to verify the write failure is reported.
|
|
captureStderr(t, func() {
|
|
writeRunLog()
|
|
})
|
|
}
|
|
|
|
// ── main.go ─────────────────────────────────────────────────────────────
|
|
|
|
func TestPromptGitHubTokenFromEnv(t *testing.T) {
|
|
defer func() { githubTokenSet = false }()
|
|
t.Setenv("GITHUB_TOKEN", "ghp_test_token")
|
|
githubTokenSet = false
|
|
captureStdout(t, func() {
|
|
promptGitHubToken()
|
|
})
|
|
if !githubTokenSet {
|
|
t.Error("expected githubTokenSet=true when GITHUB_TOKEN env present")
|
|
}
|
|
}
|
|
|
|
func TestPromptGitHubTokenDecline(t *testing.T) {
|
|
defer func() { githubTokenSet = false }()
|
|
githubTokenSet = false
|
|
os.Unsetenv("GITHUB_TOKEN")
|
|
stdin = strings.NewReader("n\n")
|
|
defer func() { stdin = os.Stdin }()
|
|
captureStdout(t, func() {
|
|
promptGitHubToken()
|
|
})
|
|
if githubTokenSet {
|
|
t.Error("expected githubTokenSet to stay false when user declines")
|
|
}
|
|
}
|
|
|
|
// ── helpers ─────────────────────────────────────────────────────────────
|
|
|
|
func captureStdout(t *testing.T, fn func()) string {
|
|
t.Helper()
|
|
r, w, _ := os.Pipe()
|
|
old := os.Stdout
|
|
os.Stdout = w
|
|
done := make(chan struct{})
|
|
var buf bytes.Buffer
|
|
var mu sync.Mutex
|
|
go func() {
|
|
mu.Lock()
|
|
io.Copy(&buf, r)
|
|
mu.Unlock()
|
|
close(done)
|
|
}()
|
|
fn()
|
|
w.Close()
|
|
os.Stdout = old
|
|
<-done
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return buf.String()
|
|
}
|
|
|
|
func captureStderr(t *testing.T, fn func()) string {
|
|
t.Helper()
|
|
r, w, _ := os.Pipe()
|
|
old := os.Stderr
|
|
os.Stderr = w
|
|
done := make(chan struct{})
|
|
var buf bytes.Buffer
|
|
var mu sync.Mutex
|
|
go func() {
|
|
mu.Lock()
|
|
io.Copy(&buf, r)
|
|
mu.Unlock()
|
|
close(done)
|
|
}()
|
|
fn()
|
|
w.Close()
|
|
os.Stderr = old
|
|
<-done
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return buf.String()
|
|
}
|
|
|
|
func hasIssueContaining(substr string) bool {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
for _, m := range issues {
|
|
if strings.Contains(m, substr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func issuesSnapshot() []string {
|
|
issuesMu.Lock()
|
|
defer issuesMu.Unlock()
|
|
out := make([]string, len(issues))
|
|
copy(out, issues)
|
|
return out
|
|
}
|