//go:build linux package host import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" "testing" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/seeds" ) func publishSeedsFloor(t *testing.T, s release.Store, rel string) { t.Helper() if err := seeds.PublishFloor(context.Background(), s, seeds.Floor{Min: seeds.RequiredProtocol, MinRelease: rel}); err != nil { t.Fatal(err) } } func TestSyncLocalSeedsFloorArmsAndOnlyRises(t *testing.T) { ctx := context.Background() dd := t.TempDir() s := release.NewDirStore(t.TempDir()) if f, err := syncLocalSeedsFloor(ctx, s, dd); f != nil || err != nil { t.Fatalf("nothing published: %+v, %v", f, err) } if seedsFloorArmed(dd) { t.Fatal("armed with nothing published") } publishSeedsFloor(t, s, "v0.27.0") f, err := syncLocalSeedsFloor(ctx, s, dd) if err != nil || f == nil || f.MinRelease != "v0.27.0" || !seedsFloorArmed(dd) { t.Fatalf("after publish: %+v, %v", f, err) } if line, ok := readBeneathLine(dd, seeds.FloorMinReleaseFile); !ok || line != "v0.27.0" { t.Fatalf("min-release file = %q, %v", line, ok) } // Once armed, armed: an object that disappears leaves the files. if err := s.Delete(ctx, seeds.FloorKey); err != nil { t.Fatal(err) } if f, _ := syncLocalSeedsFloor(ctx, s, dd); f == nil || f.MinRelease != "v0.27.0" { t.Fatalf("after the object went: %+v", f) } } func TestSeedsFloorFilesWriteOrder(t *testing.T) { dd := t.TempDir() // A crash after the min-release file and before the JSON: the Go // readers see no floor, and the units' line is AHEAD of every verdict. if err := os.MkdirAll(filepath.Join(dd, ".moves"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dd, seeds.FloorMinReleaseFile), []byte("v0.27.0\n"), 0o644); err != nil { t.Fatal(err) } if f, err := readLocalSeedsFloor(dd); f != nil || err != nil { t.Fatalf("readers trust only the JSON: %+v, %v", f, err) } // And the writer writes the line first, the JSON last. var order []string writeHook = func(name string) { order = append(order, name) } defer func() { writeHook = nil }() if err := withFloorLock(dd, true, func(d *movesDir) error { return writeSeedsFloorFiles(d, seeds.Floor{Min: 1, MinRelease: "v0.28.0"}) }); err != nil { t.Fatal(err) } if len(order) != 2 || order[0] != filepath.Base(seeds.FloorMinReleaseFile) || order[1] != filepath.Base(seeds.FloorFile) { t.Fatalf("write order = %q", order) } } func TestJudgeSeedsPrev(t *testing.T) { opt, dd := t.TempDir(), t.TempDir() verdict := seedsPrevAllowedPath(opt) // No carlos.prev: no verdict. if err := judgeSeedsPrev(opt, dd); err != nil { t.Fatal(err) } if _, err := os.Stat(verdict); !os.IsNotExist(err) { t.Fatal("a verdict with no carlos.prev") } if err := os.WriteFile(systemPrevPath(opt), []byte("#!/bin/sh\n"), 0o755); err != nil { t.Fatal(err) } if err := writePrevVersion(opt, "v0.27.2"); err != nil { t.Fatal(err) } // No seeds floor: no verdict (the units do not read one then). if err := judgeSeedsPrev(opt, dd); err != nil { t.Fatal(err) } if _, err := os.Stat(verdict); !os.IsNotExist(err) { t.Fatal("a verdict with no seeds floor") } s := release.NewDirStore(t.TempDir()) publishSeedsFloor(t, s, "v0.27.0") if _, err := syncLocalSeedsFloor(context.Background(), s, dd); err != nil { t.Fatal(err) } if err := judgeSeedsPrev(opt, dd); err != nil { t.Fatal(err) } if line, ok := readLine(verdict); !ok || line != "v0.27.0" { t.Fatalf("verdict = %q, %v; want the floor's min_release", line, ok) } // carlos.prev below the floor: the verdict goes. if err := writePrevVersion(opt, "v0.26.9"); err != nil { t.Fatal(err) } if err := judgeSeedsPrev(opt, dd); err != nil { t.Fatal(err) } if _, err := os.Stat(verdict); !os.IsNotExist(err) { t.Fatal("a verdict for a carlos.prev below the floor") } } func TestWithSeedsFields(t *testing.T) { dd, opt := t.TempDir(), t.TempDir() f := withSeedsFields(edgesFile{Box: "kass-1"}, dd, opt) if f.SeedsProtocol != seeds.ProtocolVersion || f.SeedsFloorMinRelease != "" || f.PrevSeedsAllowedFor != "" { t.Fatalf("unarmed = %+v", f) } s := release.NewDirStore(t.TempDir()) publishSeedsFloor(t, s, "v0.27.0") if _, err := syncLocalSeedsFloor(context.Background(), s, dd); err != nil { t.Fatal(err) } os.WriteFile(seedsPrevAllowedPath(opt), []byte("v0.27.0\n"), 0o644) f = withSeedsFields(f, dd, opt) if f.SeedsFloorMinRelease != "v0.27.0" || f.PrevSeedsAllowedFor != "v0.27.0" { t.Fatalf("armed = %+v", f) } body, _ := json.Marshal(f) if want := fmt.Sprintf(`"seeds_protocol":%d`, seeds.ProtocolVersion); !strings.Contains(string(body), want) { t.Fatalf("seeds_protocol must be on the wire, even at 0: want %s in %s", want, body) } }