aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--internal/pkg/clean_test.go22
-rw-r--r--internal/pkg/pkg.go23
-rw-r--r--internal/pkg/pkg_test.go126
3 files changed, 122 insertions, 49 deletions
diff --git a/internal/pkg/clean_test.go b/internal/pkg/clean_test.go
index 1b93185c..9b71a777 100644
--- a/internal/pkg/clean_test.go
+++ b/internal/pkg/clean_test.go
@@ -6,13 +6,11 @@ import (
"io/fs"
"log"
"os"
- "path/filepath"
"slices"
"strings"
"testing"
"unique"
- "hakurei.app/check"
"hakurei.app/internal/pkg"
"hakurei.app/message"
)
@@ -236,25 +234,7 @@ func TestClean(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
- base := check.MustAbs(t.TempDir())
- if err := os.Chmod(base.String(), 0700); err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- t.Error(err)
- return nil
- }
- if !d.IsDir() {
- return nil
- }
- return os.Chmod(path, 0700)
- }); err != nil {
- t.Fatal(err)
- }
- })
-
+ base := makeBase(t)
msg := message.New(log.New(os.Stderr, "clean: ", 0))
msg.SwapVerbose(testing.Verbose())
c, err := pkg.Open(t.Context(), msg, 0, 0, 0, base)
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go
index 262ff667..d98b3753 100644
--- a/internal/pkg/pkg.go
+++ b/internal/pkg/pkg.go
@@ -1107,19 +1107,28 @@ func (c *Cache) Scrub(checks int) error {
got := p.Get().(*Checksum)
defer p.Put(got)
- if _, err := os.Stat(c.base.Append(
+ var ok bool
+ for _, name := range [...]string{
dirIdentifier,
- ent.Name(),
- ).String()); err != nil {
- if !errors.Is(err, os.ErrNotExist) {
- addErr(dir.Append(ent.Name()), err)
+ dirSubstitute,
+ } {
+ if _, err := os.Stat(c.base.Append(
+ name,
+ ent.Name(),
+ ).String()); err != nil {
+ if !errors.Is(err, os.ErrNotExist) {
+ addErr(dir.Append(ent.Name()), err)
+ }
+ continue
}
+ ok = true
+ }
+ if !ok {
seMu.Lock()
se.DanglingStatus = append(se.DanglingStatus, *want)
seMu.Unlock()
- return false
}
- return true
+ return ok
}}
}
wg.Wait()
diff --git a/internal/pkg/pkg_test.go b/internal/pkg/pkg_test.go
index a4fb8361..35f858a5 100644
--- a/internal/pkg/pkg_test.go
+++ b/internal/pkg/pkg_test.go
@@ -61,6 +61,11 @@ var (
//
//go:linkname irArtifact hakurei.app/internal/pkg.irArtifact
irArtifact map[pkg.Kind]pkg.IRReadFunc
+
+ // statusHeader is the header written to all status files in dirStatus.
+ //
+ //go:linkname statusHeader hakurei.app/internal/pkg.statusHeader
+ statusHeader string
)
// newRContext returns the address of a new [pkg.RContext] unsafely created for
@@ -434,6 +439,31 @@ const (
checkDestroySubstitutes = 1 << (iota + 32)
)
+// makeBase returns a [pkg.Cache] base directory created for tb.
+func makeBase(tb testing.TB) (base *check.Absolute) {
+ tb.Helper()
+
+ base = check.MustAbs(tb.TempDir())
+ if err := os.Chmod(base.String(), 0700); err != nil {
+ tb.Fatal(err)
+ }
+ tb.Cleanup(func() {
+ if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ tb.Error(err)
+ return nil
+ }
+ if !d.IsDir() {
+ return nil
+ }
+ return os.Chmod(path, 0700)
+ }); err != nil {
+ tb.Fatal(err)
+ }
+ })
+ return
+}
+
// checkWithCache runs a slice of cacheTestCase.
func checkWithCache(t *testing.T, testCases []cacheTestCase) {
t.Helper()
@@ -443,25 +473,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
t.Helper()
t.Parallel()
- base := check.MustAbs(t.TempDir())
- if err := os.Chmod(base.String(), 0700); err != nil {
- t.Fatal(err)
- }
- t.Cleanup(func() {
- if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- t.Error(err)
- return nil
- }
- if !d.IsDir() {
- return nil
- }
- return os.Chmod(path, 0700)
- }); err != nil {
- t.Fatal(err)
- }
- })
-
+ base := makeBase(t)
msg := message.New(log.New(os.Stderr, "cache: ", 0))
msg.SwapVerbose(testing.Verbose())
@@ -536,8 +548,9 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
// destroy empty status directory
if err := syscall.Rmdir(base.Append("status").String()); err != nil {
- t.Error(expectsFrom(base.Append("status").String()))
- t.Fatal(err)
+ if !errors.Is(err, syscall.ENOTEMPTY) {
+ t.Fatal(err)
+ }
}
// destroy empty fault directory
@@ -1406,6 +1419,77 @@ func TestCache(t *testing.T) {
"substitute": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700},
}},
+
+ {"status substitute clean", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ destroyed := &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)
+ },
+ }
+ substituted := new(*destroyed)
+ substituted.deps = []pkg.Artifact{
+ pkg.NewFile("destroyed-input-0", []byte("destroyed")),
+ }
+ substituted.cure = func(*pkg.FContext) error {
+ panic("substitutable cure reached")
+ }
+
+ cureMany(t, c, []cureStep{
+ {"destroyed", destroyed, base.Append(
+ "identifier",
+ pkg.Encode(c.Ident(destroyed).Value()),
+ ), expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: 0444},
+ }, nil},
+
+ {"substituted", substituted, base.Append(
+ "identifier",
+ pkg.Encode(c.Ident(substituted).Value()),
+ ), expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+ "result": {Mode: 0444},
+ }, nil},
+ })
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
+ "checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
+ "checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV": {Mode: 0400, Data: []byte("destroyed")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/0Jmc-vGnNDlXTD3jxy-4DGxHW-2-2LtLZ9SXaJtDIqu4uyHfDwDbghNBQ2aYRpab": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+ "identifier/L-A8SK4ZX631eyealbJVH08u5pAVEf2NMk8RmLlxl7BVADkU4hNjWD6pi5H7uL1F": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
+ "identifier/VbVV2dFVosCriHnG9t5vwfW4lbHvzOkV2eYwpGpPJZOgNBc0g1wZAhbdKEweLqmW": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+ "identifier/aSMwvPAwdsIF9e1spuLyRNEc8aTFA4HRVasoNqGjxdm1laSMs2h2teGsoSzLp_pR": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
+
+ "status": {Mode: fs.ModeDir | 0700},
+ "status/0Jmc-vGnNDlXTD3jxy-4DGxHW-2-2LtLZ9SXaJtDIqu4uyHfDwDbghNBQ2aYRpab": {Mode: fs.ModeSymlink | 0777, Data: []byte("dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM")},
+ "status/VbVV2dFVosCriHnG9t5vwfW4lbHvzOkV2eYwpGpPJZOgNBc0g1wZAhbdKEweLqmW": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
+ "status/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+ "substitute/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
+
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
}
checkWithCache(t, testCases)
}