package podman import ( "context" "fmt" "io" "strings" "testing" "time" ) type storageExecutor struct { calls [][]string response func([]string) (string, error) } func (e *storageExecutor) Run(_ context.Context, args []string, stdout, _ io.Writer) error { e.calls = append(e.calls, append([]string(nil), args...)) output, err := e.response(args) _, _ = io.WriteString(stdout, output) return err } func TestStorageInfoUsesEngineGraphroot(t *testing.T) { executor := &storageExecutor{response: func(args []string) (string, error) { if strings.Join(args, " ") != "info --format json" { return "", fmt.Errorf("unexpected %v", args) } return `{"store":{"graphRootAllocated":100,"graphRootUsed":40}}`, nil }} info, err := StorageInfoFor(context.Background(), executor) if err != nil || info.Free() != 60 { t.Fatalf("info=%#v err=%v", info, err) } } func TestPruneLuciBuildCacheOnlyRemovesOldDanglingUnreferencedImages(t *testing.T) { images := `[{ "Id":"old","Size":70,"Created":1,"Containers":0,"Names":[":"],"Labels":{"io.luci.build":"cache-v1"} },{ "Id":"active","Size":70,"Created":1,"Containers":1,"Names":[":"],"Labels":{"io.luci.build":"cache-v1"} },{ "Id":"tagged","Size":70,"Created":1,"Containers":0,"Names":["luci-build-current"],"Labels":{"io.luci.build":"cache-v1"} },{ "Id":"foreign","Size":70,"Created":1,"Containers":0,"Names":[":"],"Labels":{"other":"value"} }]` executor := &storageExecutor{response: func(args []string) (string, error) { if args[0] == "image" && args[1] == "ls" { return images, nil } if args[0] == "image" && args[1] == "rm" { if len(args) != 3 || args[2] != "old" || contains(args, "--force") { return "", fmt.Errorf("unsafe remove %v", args) } return "old", nil } return "", fmt.Errorf("unexpected %v", args) }} report, err := PruneLuciBuildCache(context.Background(), executor, 80, time.Hour, map[string]bool{"active": true}, time.Unix(7200, 0)) if err != nil || len(report.Removed) != 1 || report.Removed[0] != "old" || report.Bytes != 0 { t.Fatalf("report=%#v err=%v", report, err) } for _, call := range executor.calls { if len(call) > 1 && call[0] == "image" && call[1] == "rm" && call[2] != "old" { t.Fatalf("removed protected image: %v", call) } } } func TestPruneFailsClosedWhenSafeCandidatesCannotMeetBudget(t *testing.T) { executor := &storageExecutor{response: func(args []string) (string, error) { if args[0] == "image" && args[1] == "ls" { return `[{"Id":"active","Size":101,"Created":1,"Containers":0,"Names":[":"],"Labels":{"io.luci.build":"cache-v1"}}]`, nil } return "", fmt.Errorf("unexpected mutation %v", args) }} _, err := PruneLuciBuildCache(context.Background(), executor, 100, time.Hour, map[string]bool{"active": true}, time.Unix(2, 0)) if err == nil || !strings.Contains(err.Error(), "above budget") { t.Fatalf("err=%v", err) } }