package main import ( "archive/tar" "compress/gzip" "context" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "time" ) func TestInstallScriptInstallsUserOwnedBinaryAndPathSymlink(t *testing.T) { skipInstallScriptTestsOnWindows(t) home := t.TempDir() archivePath := filepath.Join(t.TempDir(), "no-mistakes-v1.2.3-darwin-arm64.tar.gz") binaryScript := "#!/bin/sh\nexit 0\n" makeInstallArchive(t, archivePath, binaryScript) fakeBin := makeFakeInstallCommands(t) localBin := filepath.Join(home, ".local", "bin") if err := os.MkdirAll(localBin, 0o755); err != nil { t.Fatal(err) } runInstallScript(t, home, fakeBin, map[string]string{ "FAKE_RELEASE_ARCHIVE": archivePath, }) realBin := filepath.Join(home, ".no-mistakes", "bin", "no-mistakes") assertFileContent(t, realBin, binaryScript) assertSymlinkTarget(t, filepath.Join(localBin, "no-mistakes"), realBin) } func TestInstallScriptReplacesExistingPathEntryWithSymlink(t *testing.T) { skipInstallScriptTestsOnWindows(t) home := t.TempDir() archivePath := filepath.Join(t.TempDir(), "no-mistakes-v1.2.3-darwin-arm64.tar.gz") binaryScript := "#!/bin/sh\nexit 0\n" makeInstallArchive(t, archivePath, binaryScript) fakeBin := makeFakeInstallCommands(t) linkDir := filepath.Join(t.TempDir(), "link-bin") if err := os.MkdirAll(linkDir, 0o755); err != nil { t.Fatal(err) } oldPath := filepath.Join(linkDir, "no-mistakes") if err := os.WriteFile(oldPath, []byte("old-binary"), 0o755); err != nil { t.Fatal(err) } runInstallScript(t, home, fakeBin, map[string]string{ "FAKE_RELEASE_ARCHIVE": archivePath, "NO_MISTAKES_LINK_DIR": linkDir, }) realBin := filepath.Join(home, ".no-mistakes", "bin", "no-mistakes") assertFileContent(t, realBin, binaryScript) assertSymlinkTarget(t, oldPath, realBin) } func TestInstallScriptRestartsDaemonAfterInstall(t *testing.T) { skipInstallScriptTestsOnWindows(t) home := t.TempDir() archivePath := filepath.Join(t.TempDir(), "no-mistakes-v1.2.3-darwin-arm64.tar.gz") callLog := filepath.Join(t.TempDir(), "calls.log") makeInstallArchive(t, archivePath, "#!/bin/sh\nprintf '%s\n' \"$*\" >> \"$NO_MISTAKES_CALL_LOG\"\n") fakeBin := makeFakeInstallCommands(t) localBin := filepath.Join(home, ".local", "bin") if err := os.MkdirAll(localBin, 0o755); err != nil { t.Fatal(err) } runInstallScript(t, home, fakeBin, map[string]string{ "FAKE_RELEASE_ARCHIVE": archivePath, "NO_MISTAKES_CALL_LOG": callLog, }) data, err := os.ReadFile(callLog) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "daemon restart") { t.Fatalf("install.sh should restart the daemon after install, got calls %q", string(data)) } } func TestInstallScriptFailsWhenDaemonRestartFails(t *testing.T) { skipInstallScriptTestsOnWindows(t) home := t.TempDir() archivePath := filepath.Join(t.TempDir(), "no-mistakes-v1.2.3-darwin-arm64.tar.gz") callLog := filepath.Join(t.TempDir(), "calls.log") makeInstallArchive(t, archivePath, "#!/bin/sh\nprintf '%s\n' \"$*\" >> \"$NO_MISTAKES_CALL_LOG\"\nif [ \"$1\" = \"daemon\" ] && [ \"$2\" = \"restart\" ]; then\n exit 23\nfi\n") fakeBin := makeFakeInstallCommands(t) localBin := filepath.Join(home, ".local", "bin") if err := os.MkdirAll(localBin, 0o755); err != nil { t.Fatal(err) } output, err := runInstallScriptCommand(t, home, fakeBin, map[string]string{ "FAKE_RELEASE_ARCHIVE": archivePath, "NO_MISTAKES_CALL_LOG": callLog, }) if err == nil { t.Fatalf("install.sh should fail when daemon restart fails\n%s", output) } data, err := os.ReadFile(callLog) if err != nil { t.Fatal(err) } if !strings.Contains(string(data), "daemon restart") { t.Fatalf("install.sh should still attempt daemon restart, got calls %q", string(data)) } } func TestPowerShellInstallScriptChecksDaemonRestartFailure(t *testing.T) { data, err := os.ReadFile(filepath.Join("docs", "install.ps1")) if err != nil { t.Fatal(err) } text := string(data) if !strings.Contains(text, "$restart = Start-Process -FilePath \"$installDir\\no-mistakes.exe\" -ArgumentList @(") { t.Fatal("install.ps1 should run daemon restart in a way that exposes the exit code") } if !strings.Contains(text, "-Wait -PassThru") { t.Fatal("install.ps1 should wait for daemon restart to finish and inspect the process result") } if !strings.Contains(text, "if ($restart.ExitCode -ne 0)") { t.Fatal("install.ps1 should fail the install when daemon restart returns a non-zero exit code") } if !strings.Contains(text, "throw \"Failed to restart daemon (exit code $($restart.ExitCode))\"") { t.Fatal("install.ps1 should surface the daemon restart exit code") } } func skipInstallScriptTestsOnWindows(t *testing.T) { t.Helper() if runtime.GOOS == "windows" { t.Skip("install.sh is a POSIX installer; Windows uses install.ps1") } } func runInstallScript(t *testing.T, home, fakeBin string, extraEnv map[string]string) { t.Helper() output, err := runInstallScriptCommand(t, home, fakeBin, extraEnv) if err != nil { t.Fatalf("install.sh failed: %v\n%s", err, output) } } func runInstallScriptCommand(t *testing.T, home, fakeBin string, extraEnv map[string]string) ([]byte, error) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "sh", "docs/install.sh") pathValue := strings.Join([]string{fakeBin, filepath.Join(home, ".local", "bin"), os.Getenv("PATH")}, string(os.PathListSeparator)) cmd.Env = append(filteredEnv(os.Environ(), "HOME", "PATH"), []string{ "HOME=" + home, "PATH=" + pathValue, }...) for key, value := range extraEnv { cmd.Env = append(cmd.Env, key+"="+value) } return cmd.CombinedOutput() } func filteredEnv(env []string, excluded ...string) []string { blocked := make(map[string]struct{}, len(excluded)) for _, key := range excluded { blocked[key] = struct{}{} } filtered := make([]string, 0, len(env)) for _, entry := range env { key, _, found := strings.Cut(entry, "=") if !found { filtered = append(filtered, entry) continue } if _, skip := blocked[key]; skip { continue } filtered = append(filtered, entry) } return filtered } func makeInstallArchive(t *testing.T, archivePath, binaryContent string) { t.Helper() file, err := os.Create(archivePath) if err != nil { t.Fatal(err) } defer file.Close() gz := gzip.NewWriter(file) tw := tar.NewWriter(gz) data := []byte(binaryContent) hdr := &tar.Header{Name: "no-mistakes", Mode: 0o755, Size: int64(len(data))} if err := tw.WriteHeader(hdr); err != nil { t.Fatal(err) } if _, err := tw.Write(data); err != nil { t.Fatal(err) } if err := tw.Close(); err != nil { t.Fatal(err) } if err := gz.Close(); err != nil { t.Fatal(err) } } func makeFakeInstallCommands(t *testing.T) string { t.Helper() binDir := t.TempDir() writeExecutable(t, filepath.Join(binDir, "uname"), `#!/bin/sh case "$1" in -s) printf 'Darwin\n' ;; -m) printf 'arm64\n' ;; *) command uname "$@" ;; esac `) writeExecutable(t, filepath.Join(binDir, "curl"), `#!/bin/sh out="" url="" while [ "$#" -gt 0 ]; do case "$1" in -o) out="$2"; shift 2 ;; -*) shift ;; *) url="$1"; shift ;; esac done if [ -n "$out" ]; then cp "$FAKE_RELEASE_ARCHIVE" "$out" exit 0 fi printf '{"tag_name":"v1.2.3"}' `) writeExecutable(t, filepath.Join(binDir, "sudo"), "#!/bin/sh\nexec \"$@\"\n") return binDir } func writeExecutable(t *testing.T, path, content string) { t.Helper() if err := os.WriteFile(path, []byte(content), 0o755); err != nil { t.Fatal(err) } } func assertFileContent(t *testing.T, path, want string) { t.Helper() data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(data) != want { t.Fatalf("file %s = %q, want %q", path, string(data), want) } } func assertSymlinkTarget(t *testing.T, path, wantTarget string) { t.Helper() info, err := os.Lstat(path) if err != nil { t.Fatal(err) } if info.Mode()&os.ModeSymlink == 0 { t.Fatalf("%s is not a symlink", path) } target, err := os.Readlink(path) if err != nil { t.Fatal(err) } if target != wantTarget { t.Fatalf("symlink %s -> %s, want %s", path, target, wantTarget) } }