[mike@mwxm4]
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
package main
|
||||
|
||||
// selfupdate_test.go — the parts of selfupdate.go that can be checked without
|
||||
// replacing the running binary: the version arithmetic, the URL it derives, and
|
||||
// what it makes of a Gitea release.
|
||||
//
|
||||
// selfupdate.go itself is a file copied between programs and stays as it is;
|
||||
// the tests live here so the copy keeps working when it lands in the next one.
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// cacheInTemp points os.UserCacheDir at a temporary directory, so the note
|
||||
// selfupdate writes never touches the real one.
|
||||
func cacheInTemp(t *testing.T) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
t.Setenv("HOME", dir) // darwin: ~/Library/Caches
|
||||
t.Setenv("XDG_CACHE_HOME", filepath.Join(dir, "xdg")) // linux
|
||||
}
|
||||
|
||||
// fakeGitea serves one /releases/latest answer, the way Gitea does.
|
||||
func fakeGitea(t *testing.T, body string) *httptest.Server {
|
||||
t.Helper()
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/repos/mike/mgsh/releases/latest" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(body))
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
func testUpdater(srv *httptest.Server, current string) selfUpdater {
|
||||
return selfUpdater{
|
||||
repo: srv.URL + "/mike/mgsh",
|
||||
asset: "mgsh",
|
||||
current: current,
|
||||
every: 0, // no background run from a test
|
||||
quietEnv: "MGSH_NO_UPDATE_CHECK",
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateCompare is the one piece of arithmetic in the file, and the reason
|
||||
// it exists: a plain string comparison puts 4.0.9 after 4.0.10 and would offer
|
||||
// an update backwards forever.
|
||||
func TestUpdateCompare(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
a, b string
|
||||
want int
|
||||
}{
|
||||
{"4.0.10", "4.0.9", 1}, // numerically, not alphabetically
|
||||
{"4.0.9", "4.0.10", -1},
|
||||
{"4.0.64", "4.0.64", 0},
|
||||
{"v4.1.0", "4.0.64", 1}, // a leading v does not count
|
||||
{"4.1", "4.1.0", 0}, // missing places are zeroes
|
||||
{"4.2", "4.1.9", 1},
|
||||
{"4.0.64-rc1", "4.0.64", -1}, // a suffix is not yet the release
|
||||
{"4.0.64", "4.0.64-rc1", 1},
|
||||
{"4.0.64-rc2", "4.0.64-rc1", 1},
|
||||
{"5.0.0", "4.99.99", 1},
|
||||
} {
|
||||
if got := updateCompare(c.a, c.b); got != c.want {
|
||||
t.Errorf("updateCompare(%q, %q) = %d, want %d", c.a, c.b, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateAPIBase: the browser URL of the repo is all the file is configured
|
||||
// with, so everything depends on it becoming the right API root.
|
||||
func TestUpdateAPIBase(t *testing.T) {
|
||||
for _, c := range []struct{ repo, want string }{
|
||||
{"https://git.micw.org/mike/mgsh", "https://git.micw.org/api/v1/repos/mike/mgsh"},
|
||||
{"https://git.micw.org/mike/mgsh/", "https://git.micw.org/api/v1/repos/mike/mgsh"},
|
||||
{"https://git.micw.org/mike/mgsh.git", "https://git.micw.org/api/v1/repos/mike/mgsh"},
|
||||
{"http://localhost:3000/mike/mgsh", "http://localhost:3000/api/v1/repos/mike/mgsh"},
|
||||
} {
|
||||
got, err := selfUpdater{repo: c.repo}.apiBase()
|
||||
if err != nil || got != c.want {
|
||||
t.Errorf("apiBase(%q) = %q, %v, want %q", c.repo, got, err, c.want)
|
||||
}
|
||||
}
|
||||
// anything that is not host/owner/repo has to say so rather than build a
|
||||
// URL that 404s later
|
||||
for _, bad := range []string{"", "git.micw.org/mike/mgsh", "https://git.micw.org/mike", "https://git.micw.org/a/b/c"} {
|
||||
if _, err := (selfUpdater{repo: bad}).apiBase(); err == nil {
|
||||
t.Errorf("apiBase(%q) accepted a malformed repo URL", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfUpdateCheck covers both answers of `update -c`, and that a successful
|
||||
// look leaves the note behind that the daily hint later reads.
|
||||
func TestSelfUpdateCheck(t *testing.T) {
|
||||
cacheInTemp(t)
|
||||
srv := fakeGitea(t, `{"tag_name":"4.1.0","html_url":"https://git.micw.org/mike/mgsh/releases/tag/4.1.0",
|
||||
"assets":[{"name":"mgsh-darwin-arm64","size":9,"browser_download_url":"x"}]}`)
|
||||
|
||||
var out strings.Builder
|
||||
if err := testUpdater(srv, "4.0.64").check(&out); err != nil {
|
||||
t.Fatalf("check: %v", err)
|
||||
}
|
||||
if !strings.Contains(out.String(), "4.1.0 is available") {
|
||||
t.Errorf("check output = %q, want the new version offered", out.String())
|
||||
}
|
||||
|
||||
out.Reset()
|
||||
if err := testUpdater(srv, "4.1.0").check(&out); err != nil {
|
||||
t.Fatalf("check: %v", err)
|
||||
}
|
||||
if !strings.Contains(out.String(), "up to date") {
|
||||
t.Errorf("check output = %q, want 'up to date'", out.String())
|
||||
}
|
||||
|
||||
// the same version must not be offered as an update to itself
|
||||
out.Reset()
|
||||
if err := testUpdater(srv, "4.2.0").check(&out); err != nil {
|
||||
t.Fatalf("check: %v", err)
|
||||
}
|
||||
if strings.Contains(out.String(), "available") {
|
||||
t.Errorf("a newer running version was offered an update: %q", out.String())
|
||||
}
|
||||
|
||||
cache, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
note := filepath.Join(cache, "mgsh", "update.json")
|
||||
b, err := os.ReadFile(note)
|
||||
if err != nil {
|
||||
t.Fatalf("no note written to %s: %v", note, err)
|
||||
}
|
||||
if !strings.Contains(string(b), `"latest":"4.1.0"`) {
|
||||
t.Errorf("note = %s, want the looked-up version in it", b)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfUpdateNoReleases: an empty repository must produce a plain message,
|
||||
// not a nil release that gets compared against the running version.
|
||||
func TestSelfUpdateNoReleases(t *testing.T) {
|
||||
cacheInTemp(t)
|
||||
srv := fakeGitea(t, `{}`)
|
||||
|
||||
err := testUpdater(srv, "4.0.64").check(&strings.Builder{})
|
||||
if err == nil || !strings.Contains(err.Error(), "no releases") {
|
||||
t.Errorf("check against a release-less repo = %v, want a 'no releases' error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSelfUpdateWithoutMatchingAsset: a release built for other platforms must
|
||||
// not be installed, and the message has to name what was looked for — that is
|
||||
// what tells you the release is incomplete rather than the machine unsupported.
|
||||
func TestSelfUpdateWithoutMatchingAsset(t *testing.T) {
|
||||
cacheInTemp(t)
|
||||
srv := fakeGitea(t, `{"tag_name":"4.1.0","assets":[{"name":"mgsh-plan9-mips","size":1,"browser_download_url":"x"}]}`)
|
||||
|
||||
err := testUpdater(srv, "4.0.64").install(&strings.Builder{})
|
||||
if err == nil {
|
||||
t.Fatal("install accepted a release without a binary for this platform")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "mgsh-") || !strings.Contains(err.Error(), "mgsh-plan9-mips") {
|
||||
t.Errorf("error = %v, want the wanted and the available asset names", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateDailyStaysQuiet: the automatic look is for the person sitting
|
||||
// there. Under MGSH_NO_UPDATE_CHECK, and with `every` at zero, it says nothing
|
||||
// and starts nothing.
|
||||
func TestUpdateDailyStaysQuiet(t *testing.T) {
|
||||
cacheInTemp(t)
|
||||
u := selfUpdater{asset: "mgsh", current: "4.0.0", every: 0, quietEnv: "MGSH_NO_UPDATE_CHECK"}
|
||||
if hint := u.daily(); hint != "" {
|
||||
t.Errorf("daily with every=0 = %q, want silence", hint)
|
||||
}
|
||||
|
||||
t.Setenv("MGSH_NO_UPDATE_CHECK", "1")
|
||||
u.every = 1
|
||||
if hint := u.daily(); hint != "" {
|
||||
t.Errorf("daily under MGSH_NO_UPDATE_CHECK = %q, want silence", hint)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user