okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffaufrefactor(cli): invoke daemon storage helpersf6da61d

main
4.2 KiB125 linesraw
1// The readiness handshake makes ownership and confirmation races deterministic.
2// E2E cannot pause Go's apply call between helper capture and authorization.
3package storage
4
5import (
6	"albedo/cli/internal/daemon"
7	"context"
8	"errors"
9	"os"
10	"path/filepath"
11	"strings"
12	"testing"
13)
14
15func TestMaintenanceOwnershipAndRelease(t *testing.T) {
16	for _, ending := range []string{"success", "cancel", "helper killed", "input EOF", "replacement", "vacuum failure", "deletion failure", "no free pages"} {
17		t.Run(ending, func(t *testing.T) {
18			if ending == "deletion failure" && os.Geteuid() == 0 {
19				t.Skip("root can unlink files in a directory without write permission")
20			}
21			home := t.TempDir()
22			directory := filepath.Join(home, "candidates")
23			if err := os.Mkdir(directory, 0700); err != nil {
24				t.Fatal(err)
25			}
26			path := filepath.Join(directory, "candidate")
27			if err := os.WriteFile(path, []byte("data"), 0600); err != nil {
28				t.Fatal(err)
29			}
30			ctx, cancel := context.WithCancel(t.Context())
31			defer cancel()
32			helper, err := testService(home).startMaintenance(ctx, []daemon.StorageFile{{Path: path, Bytes: 4}})
33			if err != nil {
34				t.Fatal(err)
35			}
36			defer helper.close()
37			if other, acquireErr := testService(home).startMaintenance(t.Context(), nil); acquireErr == nil {
38				other.close()
39				t.Fatal("second cleanup acquired ownership")
40			} else if !strings.Contains(acquireErr.Error(), "storage is in use") {
41				t.Fatal(acquireErr)
42			}
43			switch ending {
44			case "success":
45				_, err = helper.apply(ctx, false, daemon.StorageReport{})
46			case "cancel":
47				cancel()
48				_, err = helper.apply(ctx, false, daemon.StorageReport{})
49				if !errors.Is(err, context.Canceled) {
50					t.Fatalf("lost cancellation: %v", err)
51				}
52			case "helper killed":
53				if err = helper.command.Process.Kill(); err != nil {
54					t.Fatal(err)
55				}
56				_, err = helper.apply(ctx, false, daemon.StorageReport{})
57			case "input EOF":
58				if err = helper.input.Close(); err != nil {
59					t.Fatal(err)
60				}
61				err = helper.command.Wait()
62				helper.waited = true
63			case "replacement":
64				replacement := filepath.Join(home, "replacement")
65				if err = os.WriteFile(replacement, []byte("else"), 0600); err != nil {
66					t.Fatal(err)
67				}
68				if err = os.Rename(replacement, path); err != nil {
69					t.Fatal(err)
70				}
71				_, err = helper.apply(ctx, false, daemon.StorageReport{})
72			case "deletion failure":
73				if err = os.Chmod(directory, 0500); err != nil {
74					t.Fatal(err)
75				}
76				defer os.Chmod(directory, 0700)
77				_, err = helper.apply(ctx, false, daemon.StorageReport{})
78			case "vacuum failure":
79				_, err = helper.apply(ctx, true, daemon.StorageReport{Database: 4096, DB: daemon.StorageDatabase{FreePages: 1}})
80			case "no free pages":
81				var result CleanupResult
82				result, err = helper.apply(ctx, true, daemon.StorageReport{Database: 4096})
83				if !result.VacuumSkipped || result.Vacuumed {
84					t.Fatalf("unexpected vacuum result: %+v", result)
85				}
86			}
87			wantFailure := ending == "cancel" || ending == "helper killed" || ending == "replacement" || ending == "vacuum failure" || ending == "deletion failure"
88			if (err != nil) != wantFailure {
89				t.Fatalf("%s: %v", ending, err)
90			}
91			helper.close()
92			deleted := ending == "success" || ending == "vacuum failure" || ending == "no free pages"
93			_, statErr := os.Lstat(path)
94			if os.IsNotExist(statErr) != deleted {
95				t.Fatalf("unexpected candidate state: %v", statErr)
96			}
97			next, err := testService(home).startMaintenance(t.Context(), nil)
98			if err != nil {
99				t.Fatalf("ownership not released: %v", err)
100			}
101			next.close()
102		})
103	}
104}
105
106func TestCleanupRequiresOwnershipBeforeRevalidation(t *testing.T) {
107	home := t.TempDir()
108	service := testService(home)
109	plan, err := service.PlanCleanup(t.Context(), CleanupOptions{Backups: true})
110	if err != nil {
111		t.Fatal(err)
112	}
113	helper, err := testService(home).startMaintenance(t.Context(), nil)
114	if err != nil {
115		t.Fatal(err)
116	}
117	defer helper.close()
118	// Revalidation would fail on this directory. Contention must win first.
119	if err := os.Mkdir(filepath.Join(home, "albedo.sqlite"), 0700); err != nil {
120		t.Fatal(err)
121	}
122	if _, err := service.ApplyCleanup(t.Context(), plan); err == nil || !strings.Contains(err.Error(), "storage is in use") {
123		t.Fatalf("did not acquire before report: %v", err)
124	}
125}