aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkg/clean_test.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
committerOphestra <cat@gensokyo.uk>2026-08-29 18:58:06 +0900
commitc2d900ec74e03e9c782cfe7b7ce06ff62cda6601 (patch)
treefb13320bfdc62d810aa59769e8809dc2f707919e /pkg/clean_test.go
parentbd4f29909e0750a4660eaf48e3169777a265b0ab (diff)
pkg: move from internalHEADstagingmasterdevelop
Closes #43. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'pkg/clean_test.go')
-rw-r--r--pkg/clean_test.go293
1 files changed, 293 insertions, 0 deletions
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()))
+ }
+ })
+ }
+}