mesh/merge-gate head 6147a2045467 merged into trunk 7aacbc162014 — pass: builds mesh-sdk (+41 dependent(s)) → ace, g14, novox, shanks; no bus step; 9 wait(…
mesh/repo-check head 6147a2045467 merged into trunk 7aacbc162014 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery delivered
mesh/delivery-group group fix/1839-build-floor delivered: every member is delivered
mesh/check failed: integration: meshbuildnode/cmd/mesh-build-node.TestAFixtureBundleBuiltTwiceHasOneDigest: build_integration_test.go:71: no toolchain…
buildstep.Floor refuses a step, naming the node, what is free and the floor (20 GiB, build-agent's), while the work or cache filesystem is under it. buildstep.Workspaces holds a workspace while a step runs on it, removes it when its caller releases it, and sweeps one left unheld past ADR 0509's two-hour bound.
114 lines
4.1 KiB
Go
114 lines
4.1 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// 1839/AC1: a filesystem under the floor refuses the step, naming the node, what is free and the floor.
|
|
func TestAFilesystemUnderTheFloorRefusesNamingNodeFreeAndFloor(t *testing.T) {
|
|
dir := t.TempDir()
|
|
f := Floor{Node: "ace", Bytes: 20 << 30, Free: func(string) (uint64, error) { return 4<<30 + 5, nil }}
|
|
err := f.Check(dir)
|
|
var ref *Refusal
|
|
if !errors.As(err, &ref) {
|
|
t.Fatalf("Check = %v, want a *Refusal", err)
|
|
}
|
|
if want := "refused: ace has 4 GiB free under the floor of 20 GiB (on " + dir + ")"; err.Error() != want {
|
|
t.Errorf("Check = %q, want %q", err, want)
|
|
}
|
|
}
|
|
|
|
// 1839/AC1: at or above the floor, and with no floor set, nothing is refused.
|
|
func TestAtTheFloorOrWithNoFloorNothingIsRefused(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := (Floor{Node: "ace", Bytes: 20 << 30, Free: func(string) (uint64, error) { return 20 << 30, nil }}).Check(dir); err != nil {
|
|
t.Errorf("at the floor: %v", err)
|
|
}
|
|
if err := (Floor{Node: "ace", Free: func(string) (uint64, error) { return 0, nil }}).Check(dir); err != nil {
|
|
t.Errorf("no floor: %v", err)
|
|
}
|
|
}
|
|
|
|
// 1839/AC1: a path not made yet is read at its nearest folder that is; a free space that cannot be read is a refusal.
|
|
func TestAPathNotMadeIsReadAtItsParentAndAnUnreadableOneRefuses(t *testing.T) {
|
|
dir := t.TempDir()
|
|
var read string
|
|
f := Floor{Node: "ace", Bytes: 1, Free: func(p string) (uint64, error) { read = p; return 2, nil }}
|
|
if err := f.Check(filepath.Join(dir, "not", "made")); err != nil || read != dir {
|
|
t.Errorf("Check = %v, read %q, want nil and %q", err, read, dir)
|
|
}
|
|
f.Free = func(string) (uint64, error) { return 0, errors.New("statfs: boom") }
|
|
var ref *Refusal
|
|
if err := f.Check(dir); !errors.As(err, &ref) {
|
|
t.Errorf("Check = %v, want a refusal", err)
|
|
}
|
|
}
|
|
|
|
func makeWorkspace(t *testing.T, w *Workspaces, id string, age time.Duration) string {
|
|
t.Helper()
|
|
p := filepath.Join(w.Dir, id)
|
|
if err := os.MkdirAll(filepath.Join(p, "tree"), 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
at := time.Now().Add(-age)
|
|
if err := os.Chtimes(p, at, at); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p
|
|
}
|
|
|
|
func gone(p string) bool { _, err := os.Stat(p); return os.IsNotExist(err) }
|
|
|
|
// 1839/AC2: a released workspace is gone at once; one already gone is no error; one a running step holds is refused
|
|
// and kept; an id that is not a workspace's is refused.
|
|
func TestReleaseRemovesAWorkspaceAtOnce(t *testing.T) {
|
|
w := &Workspaces{Dir: t.TempDir()}
|
|
p := makeWorkspace(t, w, "abc1", 0)
|
|
if err := w.Release("abc1"); err != nil || !gone(p) {
|
|
t.Fatalf("Release = %v, gone %v", err, gone(p))
|
|
}
|
|
if err := w.Release("abc1"); err != nil {
|
|
t.Errorf("a second release = %v, want nil", err)
|
|
}
|
|
held := makeWorkspace(t, w, "abc2", 0)
|
|
done := w.Hold("abc2")
|
|
if err := w.Release("abc2"); err == nil || gone(held) {
|
|
t.Errorf("Release of a held workspace = %v, gone %v; want refused and kept", err, gone(held))
|
|
}
|
|
done()
|
|
if err := w.Release("abc2"); err != nil || !gone(held) {
|
|
t.Errorf("Release after the hold ended = %v, gone %v", err, gone(held))
|
|
}
|
|
if err := w.Release("../etc"); err == nil {
|
|
t.Errorf("Release of ../etc answered nil")
|
|
}
|
|
}
|
|
|
|
// 1839/AC3: the sweep removes a workspace no step holds whose last use is past the bound, and keeps a held one and a
|
|
// recent one; a hold ending counts as a use.
|
|
func TestSweepRemovesUnheldWorkspacesPastTheBound(t *testing.T) {
|
|
w := &Workspaces{Dir: t.TempDir()}
|
|
old := makeWorkspace(t, w, "old1", 3*time.Hour)
|
|
heldOld := makeWorkspace(t, w, "held1", 3*time.Hour)
|
|
recent := makeWorkspace(t, w, "new1", time.Minute)
|
|
if err := os.WriteFile(filepath.Join(w.Dir, "not-a-workspace.txt"), nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
done := w.Hold("held1")
|
|
removed := w.Sweep(time.Now(), 2*time.Hour)
|
|
if !gone(old) || gone(heldOld) || gone(recent) {
|
|
t.Errorf("after the sweep: old gone %v, held kept %v, recent kept %v", gone(old), !gone(heldOld), !gone(recent))
|
|
}
|
|
if len(removed) != 1 || removed[0] != "old1" {
|
|
t.Errorf("Sweep answered %v, want [old1]", removed)
|
|
}
|
|
done()
|
|
if w.Sweep(time.Now(), 2*time.Hour); gone(heldOld) {
|
|
t.Errorf("a workspace whose hold just ended was swept: its end is a use")
|
|
}
|
|
}
|