From c2d900ec74e03e9c782cfe7b7ce06ff62cda6601 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 29 Aug 2026 18:58:06 +0900 Subject: pkg: move from internal Closes #43. Signed-off-by: Ophestra --- pkg/clean_test.go | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 pkg/clean_test.go (limited to 'pkg/clean_test.go') diff --git a/pkg/clean_test.go b/pkg/clean_test.go new file mode 100644 index 00000000..e31d52aa --- /dev/null +++ b/pkg/clean_test.go @@ -0,0 +1,293 @@ +package pkg_test + +import ( + "bytes" + "crypto/sha512" + "io/fs" + "log" + "os" + "slices" + "strings" + "testing" + "unique" + + "hakurei.app/message" + "hakurei.app/pkg" +) + +// formatHandles returns a user-facing string representing h. +func formatHandles[T pkg.ID | pkg.Checksum](handles ...unique.Handle[T]) string { + var buf strings.Builder + for _, h := range handles { + buf.WriteString(pkg.Encode(pkg.Checksum(h.Value()))) + buf.WriteString(", ") + } + return strings.TrimSuffix(buf.String(), ", ") +} + +func TestClean(t *testing.T) { + t.Parallel() + ic := pkg.NewIR() + + testCases := []struct { + name string + a []pkg.Artifact + keep []pkg.Artifact + inputs bool + want expectsFS + + wantIdents []unique.Handle[pkg.ID] + wantChecksums []unique.Handle[pkg.Checksum] + }{ + {"simple", []pkg.Artifact{ + pkg.NewFile("file", nil), + }, nil, false, expectsFS{ + ".": {Mode: fs.ModeDir | 0700}, + + "checksum": {Mode: fs.ModeDir | 0700}, + "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400}, + + "identifier": {Mode: fs.ModeDir | 0700}, + "identifier/h6qx7m0vsyhO6oLEJgY2rKa1c6RZuIZ4QXxZSh__NZJ9VU0MIj5bFB-KuaPcsfpE": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")}, + + "lock": {Mode: 0644}, + "variant": {Mode: 0400}, + "status": {Mode: fs.ModeDir | 0700}, + "substitute": {Mode: fs.ModeDir | 0700}, + "fault": {Mode: fs.ModeDir | 0700}, + "work": {Mode: fs.ModeDir | 0700}, + }, nil, nil}, + + {"keep", []pkg.Artifact{ + pkg.NewFile("removed-file", []byte("removed file")), + }, []pkg.Artifact{ + pkg.NewFile("file", []byte("\xfd")), + }, false, expectsFS{ + ".": {Mode: fs.ModeDir | 0700}, + + "checksum": {Mode: fs.ModeDir | 0700}, + "checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk": {Mode: 0400, Data: []byte("\xfd")}, + + "identifier": {Mode: fs.ModeDir | 0700}, + "identifier/tkrOYhQfTRaArk4nNYNjTmHQUmuJMVIoDeAKIQBm3I4uuMimBKXKMIY2gGZqwQg4": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk")}, + + "lock": {Mode: 0644}, + "variant": {Mode: 0400}, + "status": {Mode: fs.ModeDir | 0700}, + "substitute": {Mode: fs.ModeDir | 0700}, + "fault": {Mode: fs.ModeDir | 0700}, + "work": {Mode: fs.ModeDir | 0700}, + }, []unique.Handle[pkg.ID]{ + ic.Ident(pkg.NewFile("removed-file", []byte("removed file"))), + }, []unique.Handle[pkg.Checksum]{ + unique.Make(sha512.Sum384([]byte("removed file"))), + }}, + + {"inputs anchored substitute", []pkg.Artifact{ + &stubArtifactF{ + kind: pkg.KindExec, + params: []byte("destroyed"), + deps: []pkg.Artifact{ + pkg.NewFile("destroyed-input", []byte("destroyed")), + }, + cure: func(f *pkg.FContext) error { + p := f.GetWorkDir() + if err := os.MkdirAll(p.String(), 0755); err != nil { + return err + } + return os.WriteFile(p.Append("result").String(), nil, 0444) + }, + }, + }, []pkg.Artifact{ + &stubArtifactF{ + kind: pkg.KindExec, + params: []byte("kept"), + deps: []pkg.Artifact{ + pkg.NewFile("kept-input", []byte("kept")), + }, + cure: func(f *pkg.FContext) error { + p := f.GetWorkDir() + if err := os.MkdirAll(p.String(), 0755); err != nil { + return err + } + return os.WriteFile(p.Append("result").String(), nil, 0444) + }, + }, + }, true, expectsFS{ + ".": {Mode: fs.ModeDir | 0700}, + + "checksum": {Mode: fs.ModeDir | 0700}, + "checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")}, + "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500}, + "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444}, + + "identifier": {Mode: fs.ModeDir | 0700}, + "identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")}, + "identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, + + "lock": {Mode: 0644}, + "variant": {Mode: 0400}, + "status": {Mode: fs.ModeDir | 0700}, + + "substitute": {Mode: fs.ModeDir | 0700}, + "substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, + "substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, + + "fault": {Mode: fs.ModeDir | 0700}, + "work": {Mode: fs.ModeDir | 0700}, + }, []unique.Handle[pkg.ID]{ + ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))), + ic.Ident(&stubArtifactF{ + kind: pkg.KindExec, + params: []byte("destroyed"), + deps: []pkg.Artifact{ + pkg.NewFile("destroyed-input", []byte("destroyed")), + }, + }), + }, []unique.Handle[pkg.Checksum]{ + unique.Make(sha512.Sum384([]byte("destroyed"))), + }}, + + {"inputs", []pkg.Artifact{ + &stubArtifactF{ + kind: pkg.KindExec, + params: []byte("destroyed"), + deps: []pkg.Artifact{ + pkg.NewFile("destroyed-input", []byte("destroyed")), + }, + cure: func(f *pkg.FContext) error { + if w, err := f.GetStatusWriter(); err != nil { + return err + } else if _, err = w.Write([]byte("destroyed")); err != nil { + return err + } + + p := f.GetWorkDir() + if err := os.MkdirAll(p.String(), 0755); err != nil { + return err + } + return os.WriteFile(p.Append("result").String(), nil, 0444) + }, + }, + }, []pkg.Artifact{ + &stubArtifactF{ + kind: pkg.KindExec, + params: []byte("kept"), + deps: []pkg.Artifact{ + pkg.NewFile("kept-input", []byte("kept")), + }, + cure: func(f *pkg.FContext) error { + if w, err := f.GetStatusWriter(); err != nil { + return err + } else if _, err = w.Write([]byte("kept")); err != nil { + return err + } + + p := f.GetWorkDir() + if err := os.MkdirAll(p.String(), 0755); err != nil { + return err + } + return os.WriteFile(p.Append("result").String(), []byte{0}, 0444) + }, + }, + }, true, expectsFS{ + ".": {Mode: fs.ModeDir | 0700}, + + "checksum": {Mode: fs.ModeDir | 0700}, + "checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W": {Mode: fs.ModeDir | 0500}, + "checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W/result": {Mode: 0444, Data: []byte("\x00")}, + "checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")}, + + "identifier": {Mode: fs.ModeDir | 0700}, + "identifier/KpxTUsUfGbsX6C6HU2j9cd9He3_tt2o2m2MgH4m0zial8lmoqSWY-Gc3KrS0k-UK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")}, + "identifier/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")}, + + "lock": {Mode: 0644}, + "variant": {Mode: 0400}, + + "status": {Mode: fs.ModeDir | 0700}, + "status/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: 0400, Data: []byte(statusHeader + "kept")}, + "status/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: 0400, Data: []byte(statusHeader + "kept")}, + + "substitute": {Mode: fs.ModeDir | 0700}, + "substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")}, + + "fault": {Mode: fs.ModeDir | 0700}, + "work": {Mode: fs.ModeDir | 0700}, + }, []unique.Handle[pkg.ID]{ + ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))), + ic.Ident(&stubArtifactF{ + kind: pkg.KindExec, + params: []byte("destroyed"), + deps: []pkg.Artifact{ + pkg.NewFile("destroyed-input", []byte("destroyed")), + }, + }), + }, []unique.Handle[pkg.Checksum]{ + unique.Make(expectsFS{ + ".": {Mode: fs.ModeDir | 0500}, + "result": {Mode: 0444}, + }.hash()), + unique.Make(sha512.Sum384([]byte("destroyed"))), + }}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + base := makeBase(t) + msg := message.New(log.New(os.Stderr, "clean: ", 0)) + msg.SwapVerbose(testing.Verbose()) + c, err := pkg.Open(t.Context(), msg, base, nil) + if err != nil { + t.Fatal(err) + } + t.Cleanup(c.Close) + + all := pkg.Collect(slices.Concat(tc.a, tc.keep)) + if _, _, err = c.Cure(&all); !pkg.IsCollected(err) { + t.Fatal(err) + } + + var ( + idents []unique.Handle[pkg.ID] + checksums []unique.Handle[pkg.Checksum] + ) + idents, checksums, err = c.Clean(false, tc.inputs, tc.keep...) + if err != nil { + t.Fatalf("Clean: error = %v", err) + } + var buf [2]pkg.Checksum + + slices.SortFunc(idents, func(a, b unique.Handle[pkg.ID]) int { + buf[0], buf[1] = a.Value(), b.Value() + return bytes.Compare(buf[0][:], buf[1][:]) + }) + slices.SortFunc(checksums, func(a, b unique.Handle[pkg.Checksum]) int { + buf[0], buf[1] = a.Value(), b.Value() + return bytes.Compare(buf[0][:], buf[1][:]) + }) + + if !slices.Equal(idents, tc.wantIdents) { + t.Errorf( + "Clean: idents = %s, want %s", + formatHandles(idents...), formatHandles(tc.wantIdents...), + ) + } + if !slices.Equal(checksums, tc.wantChecksums) { + t.Errorf( + "Clean: checksums = %s, want %s", + formatHandles(checksums...), formatHandles(tc.wantChecksums...), + ) + } + + want := tc.want.hash() + var checksum pkg.Checksum + if err = pkg.SumDir(&checksum, base); err != nil { + t.Fatalf("SumDir: error = %v", err) + } else if checksum != want { + t.Error(expectsFrom(base.String())) + } + }) + } +} -- cgit v1.3.1