package release import ( "context" "encoding/json" "errors" "os" "os/exec" "path/filepath" "strings" "testing" "time" ) // tinyProgram builds a stripped linux/arm64 Go binary whose main package is // modPath, exactly as the console is built (-s -w), at test time. Nothing is // checked in. func tinyProgram(t *testing.T, modPath string) []byte { t.Helper() if testing.Short() { t.Skip("builds a binary") } dir := t.TempDir() must := func(err error) { t.Helper() if err != nil { t.Fatal(err) } } must(os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module "+modPath+"\n\ngo 1.22\n"), 0o644)) must(os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nfunc main() {}\n"), 0o644)) out := filepath.Join(dir, "bin") cmd := exec.Command("go", "build", "-trimpath", "-ldflags=-s -w", "-o", out, ".") cmd.Dir = dir cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH=arm64", "CGO_ENABLED=0", "GOFLAGS=-buildvcs=false") if b, err := cmd.CombinedOutput(); err != nil { t.Fatalf("build %s: %v\n%s", modPath, err, b) } b, err := os.ReadFile(out) must(err) return b } func TestArtifactProgram(t *testing.T) { if got := ArtifactProgram(tinyProgram(t, "example.com/console")); got != "example.com/console" { t.Fatalf("program = %q", got) } if got := ArtifactProgram([]byte("#!/bin/sh\necho hi\n")); got != "" { t.Fatalf("non-Go program = %q, want empty", got) } } func TestReleaseProgramDisagreement(t *testing.T) { _, err := ReleaseProgram(map[string][]byte{ "linux-arm64": tinyProgram(t, "example.com/console"), "linux-amd64": tinyProgram(t, "example.com/carlos"), }) if err == nil || !strings.Contains(err.Error(), "example.com/console") || !strings.Contains(err.Error(), "example.com/carlos") { t.Fatalf("err = %v, want both programs named", err) } } func shipWithProgram(t *testing.T, s Store, ver string, art []byte, allow bool) error { t.Helper() files := map[string][]byte{adoptPlatform: art} m := Manifest{Schema: 1, App: "console", Version: ver, Kind: "binary", Artifacts: map[string]Artifact{adoptPlatform: {}}} // Ship fills file, sha256, size if err := CheckShipProgram(context.Background(), s, "bab", &m, files, allow); err != nil { return err } return Ship(context.Background(), s, "bab", m, files) } func TestCheckShipProgram(t *testing.T) { s := NewDirStore(t.TempDir()) console := tinyProgram(t, "example.com/console") cli := tinyProgram(t, "example.com/carlos") if err := shipWithProgram(t, s, "v1", console, false); err != nil { t.Fatalf("first release: %v", err) } err := shipWithProgram(t, s, "v2", cli, false) var pm *ProgramMismatchError if !errors.As(err, &pm) || pm.Got != "example.com/carlos" || pm.Want != "example.com/console" { t.Fatalf("wrong program ship = %v, want ProgramMismatchError", err) } if !strings.Contains(err.Error(), "carlos ship --program-change") { t.Fatalf("refusal %q does not name the override", err) } if err := shipWithProgram(t, s, "v3", cli, true); err != nil { t.Fatalf("--program-change ship: %v", err) } m, err := GetManifest(context.Background(), s, "bab", "console", "v3") if err != nil || m.Program != "example.com/carlos" || !m.ProgramChange { t.Fatalf("v3 manifest = %+v, %v; want program recorded and program_change true", m, err) } if err := shipWithProgram(t, s, "v4", []byte("#!/bin/sh\n"), false); err != nil { t.Fatalf("non-Go artifact refused: %v", err) } } // sameProgram is b with one byte appended: different bytes (a different // sha256, so a different release) whose build info still names the same // program. func sameProgram(b []byte) []byte { return append(b[:len(b):len(b)], 0) } // Ruling G1: --program-change is recorded whenever it is given, even when the // newest release already has the same program. Ship A, then B1 with the flag, // then B2 with the flag: B2 must carry the consent, because a box that missed // B1 is still on A and decides by its own history. func TestCheckShipProgramRecordsTheFlagWhenThePredecessorAgrees(t *testing.T) { s := NewDirStore(t.TempDir()) a, b1 := tinyProgram(t, "example.com/console"), tinyProgram(t, "example.com/carlos") b2 := sameProgram(b1) if err := shipWithProgram(t, s, "va", a, false); err != nil { t.Fatal(err) } if err := shipWithProgram(t, s, "vb1", b1, true); err != nil { t.Fatal(err) } if err := shipWithProgram(t, s, "vb2", b2, true); err != nil { t.Fatal(err) } m, err := GetManifest(context.Background(), s, "bab", "console", "vb2") if err != nil || m.Program != "example.com/carlos" || !m.ProgramChange { t.Fatalf("vb2 manifest = %+v, %v; want program_change true though vb1 already names the program", m, err) } // And without the flag a same-program ship records no change. if err := shipWithProgram(t, s, "vb3", sameProgram(b2), false); err != nil { t.Fatal(err) } if m, err := GetManifest(context.Background(), s, "bab", "console", "vb3"); err != nil || m.ProgramChange { t.Fatalf("vb3 manifest = %+v, %v; want no program_change", m, err) } } // Every Go artifact of a release agreeing on one program passes, and the // manifest names it. func TestCheckShipProgramAgreeingArtifactsPass(t *testing.T) { s := NewDirStore(t.TempDir()) arm := tinyProgram(t, "example.com/console") files := map[string][]byte{"linux-arm64": arm, "linux-amd64": sameProgram(arm)} m := Manifest{Schema: 1, App: "console", Version: "v1", Kind: "binary", Artifacts: map[string]Artifact{"linux-arm64": {}, "linux-amd64": {}}} if err := CheckShipProgram(context.Background(), s, "bab", &m, files, false); err != nil { t.Fatalf("agreeing artifacts: %v", err) } if m.Program != "example.com/console" { t.Fatalf("program = %q, want example.com/console", m.Program) } } // A static release is never judged or stamped, whatever its bytes are. func TestCheckShipProgramSkipsStatic(t *testing.T) { s := NewDirStore(t.TempDir()) if err := shipWithProgram(t, s, "v1", tinyProgram(t, "example.com/console"), false); err != nil { t.Fatal(err) } files := map[string][]byte{"site.tar.gz": tinyProgram(t, "example.com/carlos")} m := Manifest{Schema: 1, App: "console", Version: "v2", Kind: "static", Artifacts: map[string]Artifact{"site.tar.gz": {}}} if err := CheckShipProgram(context.Background(), s, "bab", &m, files, false); err != nil { t.Fatalf("static release judged: %v", err) } if m.Program != "" || m.ProgramChange { t.Fatalf("static manifest stamped: program %q change %v", m.Program, m.ProgramChange) } } // putManifest writes a raw manifest for app console under account bab. func putManifest(t *testing.T, s Store, ver string, m Manifest) { t.Helper() raw, err := json.Marshal(m) if err != nil { t.Fatal(err) } if _, err := s.Put(context.Background(), ManifestKey("bab", "console", ver), raw, ""); err != nil { t.Fatal(err) } } // PreviousProgram picks by built_at, not by the order the store lists // versions in. "z-new" was built last, but its manifest object is the older // one (a re-upload, a copy between buckets), so the store's own ranking — // newest object first, ties by version — lists "a-old" first. func TestPreviousProgramOrdersByBuiltAt(t *testing.T) { root := t.TempDir() s := NewDirStore(root) art := map[string]Artifact{adoptPlatform: {File: adoptPlatform, SHA256: strings.Repeat("a", 64), Size: 1}} putManifest(t, s, "z-new", Manifest{Schema: 1, App: "console", Version: "z-new", Kind: "binary", BuiltAt: "2026-06-01T00:00:00Z", Artifacts: art, Program: "example.com/new"}) putManifest(t, s, "a-old", Manifest{Schema: 1, App: "console", Version: "a-old", Kind: "binary", BuiltAt: "2026-01-01T00:00:00Z", Artifacts: art, Program: "example.com/old"}) older, newer := time.Now().Add(-time.Hour), time.Now() for ver, mod := range map[string]time.Time{"z-new": older, "a-old": newer} { p := filepath.Join(root, filepath.FromSlash(ManifestKey("bab", "console", ver))) if err := os.Chtimes(p, mod, mod); err != nil { t.Fatal(err) } } if vers, _, err := VersionCandidates(context.Background(), s, "bab", "console", 20); err != nil || len(vers) != 2 || vers[0] != "a-old" { t.Fatalf("listing = %v, %v; the test needs a-old listed first", vers, err) } got, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err != nil || got != "example.com/new" || ver != "z-new" { t.Fatalf("PreviousProgram = %q %q %v, want example.com/new from z-new", got, ver, err) } } // A manifest that parses but fails validation, and one that names another // app or version than its key, are skipped; the older valid one answers. func TestPreviousProgramSkipsInvalidAndMisnamedManifests(t *testing.T) { s := NewDirStore(t.TempDir()) if err := shipWithProgram(t, s, "v1", tinyProgram(t, "example.com/console"), false); err != nil { t.Fatal(err) } art := map[string]Artifact{adoptPlatform: {File: adoptPlatform, SHA256: strings.Repeat("a", 64), Size: 1}} late := "2999-01-01T00:00:00Z" // Fails validateManifest: schema 2. putManifest(t, s, "v2", Manifest{Schema: 2, App: "console", Version: "v2", Kind: "binary", BuiltAt: late, Artifacts: art, Program: "example.com/invalid"}) // Valid, but declares another app. putManifest(t, s, "v3", Manifest{Schema: 1, App: "other", Version: "v3", Kind: "binary", BuiltAt: late, Artifacts: art, Program: "example.com/other-app"}) // Valid, but declares another version. putManifest(t, s, "v4", Manifest{Schema: 1, App: "console", Version: "v9", Kind: "binary", BuiltAt: late, Artifacts: art, Program: "example.com/other-version"}) got, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err != nil || got != "example.com/console" || ver != "v1" { t.Fatalf("PreviousProgram = %q %q %v, want example.com/console from v1", got, ver, err) } } // A release from an old client has no program and must not switch the check // off for the next ship. func TestPreviousProgramSkipsReleasesWithoutOne(t *testing.T) { s := NewDirStore(t.TempDir()) if err := shipWithProgram(t, s, "v1", tinyProgram(t, "example.com/console"), false); err != nil { t.Fatal(err) } shipRelease(t, s, "bab", "console", "v2", adoptPlatform, []byte("old client, no program field")) got, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err != nil || got != "example.com/console" || ver != "v1" { t.Fatalf("PreviousProgram = %q %q %v, want example.com/console from v1", got, ver, err) } } // failingStore wraps a Store and fails Get for a specific key with a non-NotExist error. type failingStore struct { Store failKey string } func (fs *failingStore) Get(ctx context.Context, key string) ([]byte, string, error) { if key == fs.failKey { return nil, "", errors.New("simulated store failure") } return fs.Store.Get(ctx, key) } // A store error (not just missing manifest) must fail the check closed. func TestPreviousProgramFailsClosedOnStoreError(t *testing.T) { base := NewDirStore(t.TempDir()) console := tinyProgram(t, "example.com/console") // Ship v1 with console program, then v2 normally. if err := shipWithProgram(t, base, "v1", console, false); err != nil { t.Fatalf("ship v1: %v", err) } shipRelease(t, base, "bab", "console", "v2", adoptPlatform, []byte("v2 content")) // Wrap the store to fail on v1's manifest key. manifestKey := ManifestKey("bab", "console", "v1") s := &failingStore{Store: base, failKey: manifestKey} // PreviousProgram must return an error (not silently skip the failure). prog, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err == nil || prog != "" || ver != "" { t.Fatalf("PreviousProgram = %q %q %v, want non-nil error", prog, ver, err) } // CheckShipProgram must propagate the error. files := map[string][]byte{adoptPlatform: console} m := Manifest{Schema: 1, App: "console", Version: "v3", Kind: "binary", Artifacts: map[string]Artifact{adoptPlatform: {}}} checkErr := CheckShipProgram(context.Background(), s, "bab", &m, files, false) if checkErr == nil { t.Fatalf("CheckShipProgram returned nil, want error") } // The error should not be a ProgramMismatchError (which would indicate the check // wrongly disabled itself and allowed the program change). var pm *ProgramMismatchError if errors.As(checkErr, &pm) { t.Fatalf("CheckShipProgram = ProgramMismatchError, want other error: %v", checkErr) } } // An in-flight release with no manifest yet is skipped gracefully. func TestPreviousProgramSkipsAnInFlightRelease(t *testing.T) { s := NewDirStore(t.TempDir()) console := tinyProgram(t, "example.com/console") // Ship v1 with console program. if err := shipWithProgram(t, s, "v1", console, false); err != nil { t.Fatal(err) } // Ship an artifact for v2 without a manifest (in-flight, manifest still being written). // Write the artifact file directly. artKey := ArtifactKey("bab", "console", "v2", adoptPlatform) if _, err := s.Put(context.Background(), artKey, console, ""); err != nil { t.Fatalf("put artifact: %v", err) } // PreviousProgram should skip v2 (no manifest) and return v1. prog, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err != nil || prog != "example.com/console" || ver != "v1" { t.Fatalf("PreviousProgram = %q %q %v, want example.com/console from v1", prog, ver, err) } } // A corrupt (unparseable) manifest is skipped, and an older valid one is found. func TestPreviousProgramSkipsACorruptManifest(t *testing.T) { s := NewDirStore(t.TempDir()) console := tinyProgram(t, "example.com/console") // Ship v1 with console program. if err := shipWithProgram(t, s, "v1", console, false); err != nil { t.Fatal(err) } // Write a corrupt manifest for v2 (not valid JSON). manifestKey := ManifestKey("bab", "console", "v2") if _, err := s.Put(context.Background(), manifestKey, []byte("not json at all"), ""); err != nil { t.Fatalf("put corrupt manifest: %v", err) } // PreviousProgram should skip v2 (corrupt manifest) and return v1. prog, ver, err := PreviousProgram(context.Background(), s, "bab", "console") if err != nil || prog != "example.com/console" || ver != "v1" { t.Fatalf("PreviousProgram = %q %q %v, want example.com/console from v1", prog, ver, err) } }