diff options
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/archive.go | 85 | ||||
| -rw-r--r-- | pkg/archive_test.go | 63 | ||||
| -rw-r--r-- | pkg/clean_test.go | 12 | ||||
| -rw-r--r-- | pkg/exec.go | 7 | ||||
| -rw-r--r-- | pkg/exec_test.go | 112 | ||||
| -rw-r--r-- | pkg/internal/tester/expected/expected.go (renamed from pkg/internal/testtool/expected/expected.go) | 0 | ||||
| -rw-r--r-- | pkg/internal/tester/expected/sum_amd64.go | 11 | ||||
| -rw-r--r-- | pkg/internal/tester/expected/sum_arm64.go | 11 | ||||
| -rw-r--r-- | pkg/internal/tester/expected/sum_riscv64.go | 11 | ||||
| -rw-r--r-- | pkg/internal/tester/main.go (renamed from pkg/internal/testtool/main.go) | 31 | ||||
| -rw-r--r-- | pkg/internal/testtool/expected/sum_amd64.go | 11 | ||||
| -rw-r--r-- | pkg/internal/testtool/expected/sum_arm64.go | 11 | ||||
| -rw-r--r-- | pkg/internal/testtool/expected/sum_riscv64.go | 11 | ||||
| -rw-r--r-- | pkg/ir_test.go | 18 | ||||
| -rw-r--r-- | pkg/pkg.go | 34 | ||||
| -rw-r--r-- | pkg/pkg_test.go | 55 | ||||
| -rw-r--r-- | pkg/tar.go | 57 | ||||
| -rw-r--r-- | pkg/tar_test.go | 46 |
18 files changed, 399 insertions, 187 deletions
diff --git a/pkg/archive.go b/pkg/archive.go index ea4017d9..ba7c61b9 100644 --- a/pkg/archive.go +++ b/pkg/archive.go @@ -311,31 +311,28 @@ func (a archiveArtifact) Inputs() []Artifact { // IsExclusive returns false: [Reader] is fully sequential. func (archiveArtifact) IsExclusive() bool { return false } -// Cure cures the [Artifact], producing a directory located at work. -func (a archiveArtifact) Cure(t *TContext) (err error) { - var r io.ReadCloser - if r, err = t.Open(a.f); err != nil { - return - } - - defer func() { - closeErr := r.Close() - if err == nil { - err = closeErr - } - }() +// Revision satisfies [RevisionArtifact] for status behaviour. +func (archiveArtifact) Revision() uint64 { return 0 } +// Extract reads an archive stream from r and writes its contents to dir. If +// writeStatus is non-nil, it is called with a user-facing message after +// each successfully extracted entry. +func Extract( + r io.Reader, + dir string, + writeStatus func(s string) error, +) (err error) { type dirTargetPerm struct { path string mode fs.FileMode } var madeDirectories []dirTargetPerm - if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil { + if err = os.MkdirAll(dir, 0700); err != nil { return } var root *os.Root - if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil { + if root, err = os.OpenRoot(dir); err != nil { return } defer func() { @@ -348,6 +345,7 @@ func (a archiveArtifact) Cure(t *TContext) (err error) { var header *ArchiveHeader ar := NewReader(r) for header, err = ar.Next(); err == nil; header, err = ar.Next() { + var m string if header.Mode.IsRegular() { var f *os.File if f, err = root.OpenFile( @@ -363,18 +361,33 @@ func (a archiveArtifact) Cure(t *TContext) (err error) { } else if err = f.Close(); err != nil { return } + + m = fmt.Sprintf( + "%s %s (%d bytes)", + header.Mode, + header.Path, + header.Size, + ) } else if header.Mode&fs.ModeSymlink != 0 { var p []byte if p, err = io.ReadAll(ar); err != nil { return } + linkname := unsafe.String(unsafe.SliceData(p), len(p)) if err = root.Symlink( - unsafe.String(unsafe.SliceData(p), len(p)), + linkname, header.Path, ); err != nil { return } + + m = fmt.Sprintf( + "%s %s linkname %s", + header.Mode, + header.Path, + linkname, + ) } else if header.Mode.IsDir() { if header.Path == "." { continue @@ -387,9 +400,21 @@ func (a archiveArtifact) Cure(t *TContext) (err error) { if err = root.Mkdir(header.Path, 0700); err != nil { return } + + m = fmt.Sprintf( + "%s %s", + header.Mode, + header.Path, + ) } else { return InvalidFileModeError(header.Mode) } + + if writeStatus != nil && m != "" { + if err = writeStatus(m); err != nil { + return + } + } } if errors.Is(err, io.EOF) { err = nil @@ -406,6 +431,34 @@ func (a archiveArtifact) Cure(t *TContext) (err error) { return } +// Cure cures the [Artifact], producing a directory located at work. +func (a archiveArtifact) Cure(t *TContext) (err error) { + var status io.Writer + if status, err = t.GetStatusWriter(); err != nil { + return + } + + var r io.ReadCloser + if r, err = t.Open(a.f); err != nil { + return + } + + msg := t.GetMessage() + err = Extract(r, t.GetWorkDir().String(), func(s string) (err error) { + msg.Verbose(s) + if _, err = io.WriteString(status, s); err != nil { + return + } + _, err = status.Write([]byte{'\n'}) + return + }) + + if closeErr := r.Close(); err == nil { + err = closeErr + } + return +} + // CuresExempt exempts the cheap [KindArchive] implementation often found at // the end of a [FileArtifact] pipeline. func (archiveArtifact) CuresExempt() {} diff --git a/pkg/archive_test.go b/pkg/archive_test.go index 44ea56a7..0171d914 100644 --- a/pkg/archive_test.go +++ b/pkg/archive_test.go @@ -2,9 +2,12 @@ package pkg_test import ( "bytes" + "crypto/sha512" + "errors" "io" "io/fs" "maps" + "net/http" "reflect" "testing" "testing/fstest" @@ -176,33 +179,63 @@ func TestArchiveArtifact(t *testing.T) { want := maps.Clone(archiveTestdata) want["."].Mode = fs.ModeDir | 0500 + var buf bytes.Buffer + if err := pkg.Write(archiveTestdata, ".", &buf); err != nil { + t.Fatal(err) + } + wantChecksum := sha512.Sum384(buf.Bytes()) + wantChecksumEncode := pkg.Encode(wantChecksum) + + var transport http.Transport + client := http.Client{Transport: &transport} + transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{ + "testdata": {Data: buf.Bytes(), Mode: 0400}, + })) + checkWithCache(t, []cacheTestCase{ {"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - var buf bytes.Buffer - if err := pkg.Write(archiveTestdata, ".", &buf); err != nil { - t.Fatal(err) - } - cureMany(t, c, []cureStep{ {"sample", pkg.NewArchive( pkg.NewFile("", buf.Bytes()), ), ignorePathname, expectsFS(want), pkg.WNew, nil}, + + {"close error passthrough", pkg.NewArchive( + pkg.NewHTTPGet( + &client, + "file:///testdata", + pkg.Checksum{}, + ), + ), nil, nil, pkg.WNew, errors.Join(&pkg.ChecksumMismatchError{ + Got: wantChecksum, + })}, }) + + dropFaults(t, base) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, - "checksum": {Mode: fs.ModeDir | 0700}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")}, - "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")}, + "checksum": {Mode: fs.ModeDir | 0700}, + "checksum/" + wantChecksumEncode: {Mode: fs.ModeDir | 0500}, + "checksum/" + wantChecksumEncode + "/block": {Mode: fs.ModeDir | 0700}, + "checksum/" + wantChecksumEncode + "/block/uevent": {Mode: 0600}, + "checksum/" + wantChecksumEncode + "/devices": {Mode: fs.ModeDir | 0700}, + "checksum/" + wantChecksumEncode + "/devices/empty": {Mode: fs.ModeDir | 0700}, + "checksum/" + wantChecksumEncode + "/devices/sub": {Mode: fs.ModeDir | 0700}, + "checksum/" + wantChecksumEncode + "/devices/sub/uevent": {Mode: 0600, Data: []byte("add")}, + "checksum/" + wantChecksumEncode + "/devices/uevent": {Mode: 0600, Data: []byte("add")}, "identifier": {Mode: fs.ModeDir | 0700}, - "identifier/-zXKfphyWM2Ko7VMVURaEMSuixNokFSe0xFbnwR2RtRtvMrcPGWalV0LIn45PXTY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")}, + "identifier/iyvWY0irMoW4AVRnG_du07d_nAEUtbHdeiaAYRdp-vf3wstM9u67IokOsPfU9eyM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantChecksumEncode)}, + + "status": {Mode: fs.ModeDir | 0711}, + "status/iyvWY0irMoW4AVRnG_du07d_nAEUtbHdeiaAYRdp-vf3wstM9u67IokOsPfU9eyM": {Mode: 0444, Data: []byte(statusHeader + `drwx------ block +-rw------- block/uevent (0 bytes) +drwx------ devices +drwx------ devices/empty +drwx------ devices/sub +-rw------- devices/sub/uevent (3 bytes) +-rw------- devices/uevent (3 bytes) +`)}, "substitute": {Mode: fs.ModeDir | 0700}, "work": {Mode: fs.ModeDir | 0700}, diff --git a/pkg/clean_test.go b/pkg/clean_test.go index e31d52aa..3b5d0b3b 100644 --- a/pkg/clean_test.go +++ b/pkg/clean_test.go @@ -52,7 +52,7 @@ func TestClean(t *testing.T) { "lock": {Mode: 0644}, "variant": {Mode: 0400}, - "status": {Mode: fs.ModeDir | 0700}, + "status": {Mode: fs.ModeDir | 0711}, "substitute": {Mode: fs.ModeDir | 0700}, "fault": {Mode: fs.ModeDir | 0700}, "work": {Mode: fs.ModeDir | 0700}, @@ -73,7 +73,7 @@ func TestClean(t *testing.T) { "lock": {Mode: 0644}, "variant": {Mode: 0400}, - "status": {Mode: fs.ModeDir | 0700}, + "status": {Mode: fs.ModeDir | 0711}, "substitute": {Mode: fs.ModeDir | 0700}, "fault": {Mode: fs.ModeDir | 0700}, "work": {Mode: fs.ModeDir | 0700}, @@ -127,7 +127,7 @@ func TestClean(t *testing.T) { "lock": {Mode: 0644}, "variant": {Mode: 0400}, - "status": {Mode: fs.ModeDir | 0700}, + "status": {Mode: fs.ModeDir | 0711}, "substitute": {Mode: fs.ModeDir | 0700}, "substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, @@ -205,9 +205,9 @@ func TestClean(t *testing.T) { "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")}, + "status": {Mode: fs.ModeDir | 0711}, + "status/xxQ43VMIS1WHMSEFnUdBNd6pulbtgiTorP_mtJpEsZENYhGBiDEL6Y2D8yqn0Sra": {Mode: 0444, Data: []byte(statusHeader + "kept")}, + "status/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: 0444, Data: []byte(statusHeader + "kept")}, "substitute": {Mode: fs.ModeDir | 0700}, "substitute/td1tf1kb3z8iUFg2k1xlHeTTEz3xR4e77WSWdv2JhjmZyY6iuvodhdT8BblDOi0E": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")}, diff --git a/pkg/exec.go b/pkg/exec.go index b40ae84b..fe099ac9 100644 --- a/pkg/exec.go +++ b/pkg/exec.go @@ -601,7 +601,12 @@ func (a *execArtifact) makeContainer( fhs.AbsTmp, std.BindWritable|std.BindEnsure, ) - z.Proc(fhs.AbsProc).Dev(fhs.AbsDev, true) + z. + Proc(fhs.AbsProc). + DevWritable(fhs.AbsDev, true). + Tmpfs(fhs.AbsDevShm, 0, 01777). + Bind(fhs.AbsDevKvm, fhs.AbsDevKvm, std.BindOptional|std.BindDevice). + Remount(fhs.AbsDev, syscall.MS_RDONLY) return } diff --git a/pkg/exec_test.go b/pkg/exec_test.go index 051af551..a24c1e5e 100644 --- a/pkg/exec_test.go +++ b/pkg/exec_test.go @@ -23,18 +23,18 @@ import ( "hakurei.app/internal/stub" "hakurei.app/pkg" - "hakurei.app/pkg/internal/testtool/expected" + "hakurei.app/pkg/internal/tester/expected" ) -// testtoolBin is the container test tool binary made available to the +// testHelperBin is the container test helper binary made available to the // execArtifact for testing its curing environment. // -//go:generate env CGO_ENABLED=0 go build -tags testtool -o internal/testtool ./internal/testtool -//go:embed internal/testtool/testtool -var testtoolBin []byte +//go:generate env CGO_ENABLED=0 go build -tags tester -o internal/tester ./internal/tester +//go:embed internal/tester/tester +var testHelperBin []byte func init() { - pathname, err := filepath.Abs("internal/testtool/testtool") + pathname, err := filepath.Abs("internal/tester/tester") if err != nil { panic(err) } @@ -153,15 +153,15 @@ func TestExec(t *testing.T) { checkWithCache(t, []cacheTestCase{ {"offline", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( "exec-offline", "", new(wantOffline.hash()), 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.MustPath("/file", false, newStubFile( pkg.KindHTTPGet, @@ -176,15 +176,15 @@ func TestExec(t *testing.T) { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantOffline, pkg.WNew, nil}, {"substitution", pkg.NewExec( "exec-offline", "", new(wantOffline.hash()), 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.MustPath("/file", false, newStubFile( pkg.KindHTTPGet, @@ -192,7 +192,7 @@ func TestExec(t *testing.T) { nil, nil, nil, )), - // substitution miss fails in testtool due to differing idents + // substitution miss fails in tester due to differing idents pkg.MustPath("/.hakurei", false, &stubArtifact{ kind: pkg.KindTar, params: []byte("empty directory (substituted)"), @@ -200,15 +200,15 @@ func TestExec(t *testing.T) { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantOffline, pkg.WSubstitute, nil}, {"error passthrough", pkg.NewExec( "", "", nil, 0, false, true, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.MustPath("/proc/nonexistent", false, failingArtifact), ), nil, nil, pkg.WNew, pkg.InputError{ @@ -219,8 +219,8 @@ func TestExec(t *testing.T) { "", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.ExecPath{}, ), nil, nil, pkg.WNew, pkg.ErrInvalidPaths}, @@ -231,8 +231,8 @@ func TestExec(t *testing.T) { "", "", nil, 0, false, false, pkg.AbsWork, nil, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, ) var exitError *exec.ExitError if _, _, err := c.Cure(initFailureArtifact); !errors.As(err, &exitError) || @@ -256,13 +256,13 @@ func TestExec(t *testing.T) { if !bytes.HasPrefix(faultStatus, []byte( "hakurei.app/pkg ", )) || !bytes.Contains(faultStatus, []byte( - "\ninit: fork/exec /opt/bin/testtool: no such file or directory\n", + "\ninit: fork/exec /opt/bin/tester: no such file or directory\n", )) { t.Errorf("unexpected status:\n%s", string(faultStatus)) } destroyStatus(t, base, 2, 1) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -286,7 +286,7 @@ func TestExec(t *testing.T) { }}, {"net", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() wantNet := expectsFS{ ".": {Mode: fs.ModeDir | 0500}, @@ -298,8 +298,8 @@ func TestExec(t *testing.T) { "exec-net", "", new(wantNet.hash()), 0, true, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool", "net"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester", "net"}, pkg.MustPath("/file", false, newStubFile( pkg.KindHTTPGet, @@ -314,12 +314,12 @@ func TestExec(t *testing.T) { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantNet, pkg.WNew, nil}, }) destroyStatus(t, base, 2, 0) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -341,15 +341,15 @@ func TestExec(t *testing.T) { }}, {"overlay root", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( "exec-overlay-root", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.MustPath("/", true, &stubArtifact{ kind: pkg.KindTar, @@ -358,12 +358,12 @@ func TestExec(t *testing.T) { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantOffline, pkg.WNew, nil}, }) destroyStatus(t, base, 2, 0) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -383,15 +383,15 @@ func TestExec(t *testing.T) { }}, {"overlay work", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( "exec-overlay-work", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, - check.MustAbs("/work/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/work/bin/tester"), + []string{"tester"}, pkg.MustPath("/", true, &stubArtifact{ kind: pkg.KindTar, @@ -405,12 +405,12 @@ func TestExec(t *testing.T) { cure: func(t *pkg.TContext) error { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, - }), pkg.Path(pkg.AbsWork, false /* ignored */, testtool), + }), pkg.Path(pkg.AbsWork, false /* ignored */, testHelper), ), ignorePathname, wantOffline, pkg.WNew, nil}, }) destroyStatus(t, base, 2, 0) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -430,15 +430,15 @@ func TestExec(t *testing.T) { }}, {"multiple layers", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( "exec-multiple-layers", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool", "layers"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester", "layers"}, pkg.MustPath("/", true, &stubArtifact{ kind: pkg.KindTar, @@ -474,12 +474,12 @@ func TestExec(t *testing.T) { return os.WriteFile(work.Append("check").String(), []byte("layers"), 0400) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantOffline, pkg.WNew, nil}, }) destroyStatus(t, base, 2, 0) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -504,15 +504,15 @@ func TestExec(t *testing.T) { }}, {"overlay layer promotion", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { - testtool, testtoolDestroy := newTesttool() + testHelper, destroyTestHelper := newTestHelper() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( "exec-layer-promotion", "", nil, 0, false, true, pkg.AbsWork, []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool", "promote"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester", "promote"}, pkg.MustPath("/", true, &stubArtifact{ kind: pkg.KindTar, @@ -527,12 +527,12 @@ func TestExec(t *testing.T) { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), - pkg.MustPath("/opt", false, testtool), + pkg.MustPath("/opt", false, testHelper), ), ignorePathname, wantOffline, pkg.WNew, nil}, }) destroyStatus(t, base, 2, 0) - testtoolDestroy(t, base, c) + destroyTestHelper(t, base, c) }, expectsFS{ ".": {Mode: fs.ModeDir | 0700}, @@ -621,14 +621,14 @@ func TestExec(t *testing.T) { }) } -// newTesttool returns an [Artifact] that cures into testtoolBin. The returned +// newTestHelper returns an [Artifact] that cures into testHelperBin. The returned // function must be called at the end of the test but not deferred. -func newTesttool() ( - testtool pkg.Artifact, - testtoolDestroy func(t *testing.T, base *check.Absolute, c *pkg.Cache), +func newTestHelper() ( + testHelper pkg.Artifact, + destroyTestHelper func(t *testing.T, base *check.Absolute, c *pkg.Cache), ) { - // testtoolBin is built during go:generate and is not deterministic - testtool = overrideIdent{pkg.ID{0xfe, 0xff}, &stubArtifact{ + // testHelperBin is built during go:generate and is not deterministic + testHelper = overrideIdent{pkg.ID{0xfe, 0xff}, &stubArtifact{ kind: pkg.KindTar, cure: func(t *pkg.TContext) error { work := t.GetWorkDir() @@ -661,10 +661,10 @@ func newTesttool() ( return os.WriteFile(t.GetWorkDir().Append( "bin", - "testtool", - ).String(), testtoolBin, 0500) + "tester", + ).String(), testHelperBin, 0500) }, }} - testtoolDestroy = newDestroyArtifactFunc(testtool) + destroyTestHelper = newDestroyArtifactFunc(testHelper) return } diff --git a/pkg/internal/testtool/expected/expected.go b/pkg/internal/tester/expected/expected.go index c0e8e4a2..c0e8e4a2 100644 --- a/pkg/internal/testtool/expected/expected.go +++ b/pkg/internal/tester/expected/expected.go diff --git a/pkg/internal/tester/expected/sum_amd64.go b/pkg/internal/tester/expected/sum_amd64.go new file mode 100644 index 00000000..c3ef87b6 --- /dev/null +++ b/pkg/internal/tester/expected/sum_amd64.go @@ -0,0 +1,11 @@ +package expected + +const ( + Offline = "8cvRbO_908FqbZvom0pp2MdLHiZMlllsBBHtwJMDOPeIm_Y55AvL8nkB5-ZvKQP7" + OfflineS = "R4nw81-Egce4yJgi4eoK0nDzPNkvCffaPUn8KPP1q1sdQ-dxjGc6YaYtBViUZTz4" + OvlRoot = "0W_DBfLbYbYiXkCL9BIwq1BBL_CAOQw1Al75FmU-w1pYWxc-s1CpaUl2VGiK1wU6" + Layers = "X0e6BdvAC_-MOguDI8f_9OrLyv9r32nV4zKRkLMr2Xye0-Nlc3Rew9Cycm0FlNfb" + Net = "B4VXPvG7mhh96PWmjwwma8Fy4HliP9WFEEW5vtq2lQrPEulZT_lSixX7Iowqpe_J" + Promote = "T_WzFNqwS4hZvNXsnDKOQkc7iwl38CY0GUsCGVnAhiJkU7iLARbeNDtAcA7C-FX1" + Work = "HknOKpTaBKOA5hwAP4eiqwzOQrw9JPQpJVhf-mpd2mIpQc6dfP7xqDALV2h4z0Ce" +) diff --git a/pkg/internal/tester/expected/sum_arm64.go b/pkg/internal/tester/expected/sum_arm64.go new file mode 100644 index 00000000..3b0d3a5b --- /dev/null +++ b/pkg/internal/tester/expected/sum_arm64.go @@ -0,0 +1,11 @@ +package expected + +const ( + Offline = "f2G7Tr0wDhFL8LuJcgggob3C6jjVUvR1Vrs_xIQVaFt7qaoZJwtWIZ3-KgI0BjAE" + OfflineS = "6ShIakYroFkApzv_spmRfdhndwnnUVZFD2fgBRVA8n6GiSuYoeqF_37Loun6DneC" + OvlRoot = "RDokIPUWpf6kGWaaOtowwNGkeBFOIyQBjUY3sQbJ_KDnioKTQTKn2kijDdQ9L6Fp" + Layers = "dKXuQIWrySwPm5CNlR86XCe6FLrppcAJ6UxQKP75RCIjuK0nXA8ee6FMh4VNmKQS" + Net = "vxarc7CMN1BFQe7jOf4iDKNpz2DVT1Om1KdrVCAZyQd_7mr3loB0GjSnbE-mpjyC" + Promote = "oypLdVJZmZ1c9Tmfk-8N-3EBzgO99MymPS9H5nsCBPtm9CV9woEj8CBtlKk3b7gH" + Work = "JlGKRqXYGu6cREq_D4FIAHRT4zqu7uGIUtppWXDdEzRpQKflh7Y4sNHda1ecxRzj" +) diff --git a/pkg/internal/tester/expected/sum_riscv64.go b/pkg/internal/tester/expected/sum_riscv64.go new file mode 100644 index 00000000..77c58120 --- /dev/null +++ b/pkg/internal/tester/expected/sum_riscv64.go @@ -0,0 +1,11 @@ +package expected + +const ( + Offline = "Xa9l5ZZx7lt0B0YZeHihzWa6P6bGQT3rYn2kaftMDqZ7L6bpE9eCG9_e1dWlz-pn" + OfflineS = "6_GnlF6L7phCGuzeG91SAXCgxYPMJqv3UsZVvvoTdCvTtl8nCKnYxTlxAjdTNYY_" + OvlRoot = "2VZwLevuQ_ZmwFbAS-eZ4jGjcc71VqQYMkbZgzsV175oL3hGMto6jK-kFp6tcOF9" + Layers = "QukD3gaFOC-1wSm4XvdLtkZllw5APdTSX4mcWT88XEDNGMhvcblgaOr6Q1a20D7W" + Net = "XJME3Hjy2iZ2kGepJXtUOhETEMD6Li9ccLwi7y_4vQ7Kur0KGpXrfM8tyFnfLkFg" + Promote = "l71HASLptywY1pYL_pJBD8qoBkuUJCUobA6mSLUIsajew1IYkscZckBsqPu_czYn" + Work = "ZTJD0WoYxSB15h_VPjXHMuLhlL2bMgyGrJgltHRd4XBZhNr-WYNGVodt53GCBRXC" +) diff --git a/pkg/internal/testtool/main.go b/pkg/internal/tester/main.go index edffab7e..72e20426 100644 --- a/pkg/internal/testtool/main.go +++ b/pkg/internal/tester/main.go @@ -1,5 +1,6 @@ -//go:build testtool +//go:build tester +// The tester program checks containers started by package pkg. package main import ( @@ -17,12 +18,12 @@ import ( "hakurei.app/fhs" "hakurei.app/vfs" - "hakurei.app/pkg/internal/testtool/expected" + "hakurei.app/pkg/internal/tester/expected" ) func main() { log.SetFlags(0) - log.SetPrefix("testtool: ") + log.SetPrefix("tester: ") if os.Getenv("HAKUREI_BINFMT") == "1" { wantArgs := []string{"/interpreter", "/opt/bin/sample"} @@ -50,27 +51,27 @@ func main() { }) var hostNet, layers, promote bool - if len(os.Args) == 2 && os.Args[0] == "testtool" { + if len(os.Args) == 2 && os.Args[0] == "tester" { switch os.Args[1] { case "net": hostNet = true - log.SetPrefix("testtool(net): ") + log.SetPrefix("tester(net): ") break case "layers": layers = true - log.SetPrefix("testtool(layers): ") + log.SetPrefix("tester(layers): ") break case "promote": promote = true - log.SetPrefix("testtool(promote): ") + log.SetPrefix("tester(promote): ") default: log.Fatalf("Args: %q", os.Args) return } - } else if wantArgs := []string{"testtool"}; !slices.Equal(os.Args, wantArgs) { + } else if wantArgs := []string{"tester"}; !slices.Equal(os.Args, wantArgs) { log.Fatalf("Args: %q, want %q", os.Args, wantArgs) } @@ -79,7 +80,7 @@ func main() { if len(environ) == 2 { overlayRoot = true if !layers && !promote { - log.SetPrefix("testtool(overlay root): ") + log.SetPrefix("tester(overlay root): ") } wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"} } @@ -89,8 +90,8 @@ func main() { var overlayWork bool const ( - wantExec = "/opt/bin/testtool" - wantExecWork = "/work/bin/testtool" + wantExec = "/opt/bin/tester" + wantExecWork = "/work/bin/tester" ) var iftPath string if got, err := os.Executable(); err != nil { @@ -102,7 +103,7 @@ func main() { switch got { case wantExecWork: overlayWork = true - log.SetPrefix("testtool(overlay work): ") + log.SetPrefix("tester(overlay work): ") default: log.Fatalf("Executable: %q, want %q", got, wantExec) @@ -225,7 +226,7 @@ func main() { ident = expected.Promote } - next() // testtool artifact + next() // test helper artifact next() if overlayWork { @@ -259,6 +260,10 @@ func main() { next() } + if m.Next != nil && m.Next.Root == "/kvm" { + next() + } + if m.Next != nil { log.Println("unexpected extra mount entries") for m.Next != nil { diff --git a/pkg/internal/testtool/expected/sum_amd64.go b/pkg/internal/testtool/expected/sum_amd64.go deleted file mode 100644 index 354931be..00000000 --- a/pkg/internal/testtool/expected/sum_amd64.go +++ /dev/null @@ -1,11 +0,0 @@ -package expected - -const ( - Offline = "IKmVhALejtYA3JhN1PCfjKF-Dio1XVcOeMWDHhfSHCbb4iBNhGjFy2UspZveZvb8" - OfflineS = "6ZubjSyCnz4-TKgQZi_BbY-JUwsStYCtD_f-BTDOOotfEbqkm47W20AazHRaLviz" - OvlRoot = "Yox7WpG8B_OqIXWF4HWFZkUfsEl7DGzpFyZM0qtd9T8nQ-7SAkEUurlAe6_DJ6Iu" - Layers = "aAwfxDB-efVo379FNl178jK7HEKU3LEjHbZdaVVjk7GnXGZGEANNsXnB3SCUxM9A" - Net = "IU3GwU2jTBd7HjotvFo2wsDWBQGVGpamwiSKdc2G1EnhM-ZaacxAuK6q0wbTVxra" - Promote = "rOjetGHzT_jPErrBR24IAZKDj8Q9dLLqxFQqloMgjsULss0VXDI--n6LxuMBHvmZ" - Work = "7f9XDeREfvJIjCe0qesND0s0tasExAIcc-OH33-eq9tsrQPUXhJp4KEkpsYH4hCD" -) diff --git a/pkg/internal/testtool/expected/sum_arm64.go b/pkg/internal/testtool/expected/sum_arm64.go deleted file mode 100644 index 2e6f7cbf..00000000 --- a/pkg/internal/testtool/expected/sum_arm64.go +++ /dev/null @@ -1,11 +0,0 @@ -package expected - -const ( - Offline = "MTgCSzwPrzGigp9phvDPRwUTNGLm1KM2EsEtbz-l2cNieqjaGtnzm65CDXlSxXrl" - OfflineS = "p89bQg7_jR4nwaSbpEd08ne1jHOEw7Wlnlyv5FHq7ba1Kml3HSBZwEKRzu0gCqEb" - OvlRoot = "9ifwO6MAaV9kTqRR2O5PJzm8XhfXbr50mFLWyEHQm8OHHcJtAEUgXZzJYoZt9YVV" - Layers = "INuGActFPwJtS7mAR9Bkt7FD4yoHlXHSJ9CaSAQJrpkUBL_k815Cs_jvRfxy5-a7" - Net = "kJvy24uk4ePvHoxSKf4vMOhR4NxLbmyWlA9bTS7d3z3fE9WF1Tvpao9g-QuYU4vm" - Promote = "lYqyYrfjPawIbld5Qz0NbKvd_bmI3nJC1eEnlIQWzsjWE9uA1HuKPAaClnDeyG6L" - Work = "V6k4FqOBMRCmHiqchs5-joVGZ_MhNWZyXm3gdbU0S7S5eMze7G4SwYMTf66ojKQL" -) diff --git a/pkg/internal/testtool/expected/sum_riscv64.go b/pkg/internal/testtool/expected/sum_riscv64.go deleted file mode 100644 index 53872c08..00000000 --- a/pkg/internal/testtool/expected/sum_riscv64.go +++ /dev/null @@ -1,11 +0,0 @@ -package expected - -const ( - Offline = "LlieSHVDe1lnKvfI8t4oGRg_2hPxwyy1drmaqc-EKCOENuS9C2YHrp2VY5pSyhbg" - OfflineS = "qccjM8oYjUuEYPKFFzAxDFVzMerSUKUZc18NdVQoiPWf-70XOsNK8NaUQqjTgTAD" - OvlRoot = "lpV2bb_KGYFHPiOn-jtGop8K_ew0y1p3BezRp0ebM93_a-T9J-vkqGD5uokda1aN" - Layers = "sM3DJm210CHPGHkAquIbrXhspB81FpVC60ApQrvJGD5El2i7cSONj7LWf7EWqKpT" - Net = "JihQUwsVETRYjb2-yIXLYZ6WVsaFebwl8btSYAsNQ2QapS5MZVaI-dYwrVoySycR" - Promote = "lGqwR4W2OjylSVitMQP1lc6wApOBkbMjsmIqkxQbfEZ3DHEQBbJr84ErW6Xb54zo" - Work = "aZgtyv6aMtoHFkHAaJr6nLaA7N_SHjShgWPyQ5UdzaIqYSiVqvCrZqSokGTCcTii" -) diff --git a/pkg/ir_test.go b/pkg/ir_test.go index 9cc024ae..7ad5a8f5 100644 --- a/pkg/ir_test.go +++ b/pkg/ir_test.go @@ -40,8 +40,8 @@ func TestIRRoundtrip(t *testing.T) { "exec-offline", "", nil, 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester"}, pkg.MustPath("/file", false, pkg.NewFile("file", []byte( "stub file", @@ -49,7 +49,7 @@ func TestIRRoundtrip(t *testing.T) { nil, "file:///hakurei.tar", pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))), ))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet( - nil, "file:///testtool.tar.gz", + nil, "file:///test-helper.tar.gz", pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))), ), pkg.Gzip))), )}, @@ -60,8 +60,8 @@ func TestIRRoundtrip(t *testing.T) { 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool", "net"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester", "net"}, pkg.MustPath("/file", false, pkg.NewFile("file", []byte( "stub file", @@ -69,7 +69,7 @@ func TestIRRoundtrip(t *testing.T) { nil, "file:///hakurei.tar", pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))), ))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet( - nil, "file:///testtool.tar.gz", + nil, "file:///test-helper.tar.gz", pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))), ), pkg.Gzip))), )}, @@ -80,8 +80,8 @@ func TestIRRoundtrip(t *testing.T) { 0, false, false, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, - check.MustAbs("/opt/bin/testtool"), - []string{"testtool", "measured"}, + check.MustAbs("/opt/bin/tester"), + []string{"tester", "measured"}, pkg.MustPath("/file", false, pkg.NewFile("file", []byte( "stub file", @@ -89,7 +89,7 @@ func TestIRRoundtrip(t *testing.T) { nil, "file:///hakurei.tar", pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))), ))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet( - nil, "file:///testtool.tar.gz", + nil, "file:///test-helper.tar.gz", pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))), ), pkg.Gzip))), )}, @@ -206,7 +206,7 @@ func (t *TContext) prepareStatus(writeHeader bool) error { if t.status, t.statusErr = os.OpenFile( t.statusPath.String(), syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, - 0400, + 0444, ); t.statusErr != nil { return t.statusErr } @@ -364,6 +364,9 @@ func (f *FContext) linkSubstitute(ids, substitutes string) (err error) { if err == nil { f.statusSPath = statusS } + if errors.Is(err, os.ErrExist) { + err = nil + } return } @@ -954,8 +957,9 @@ type ChecksumMismatchError struct { } func (e *ChecksumMismatchError) Error() string { - return "got " + Encode(e.Got) + - " instead of " + Encode(e.Want) + return "unexpected checksum\n" + + "\t got: " + Encode(e.Got) + "\n" + + "\twant: " + Encode(e.Want) } // LinknamePrefixError describes a malformed linkname to a [Checksum]. @@ -1521,7 +1525,7 @@ func (c *Cache) openFile( id := c.Ident(f) if c.msg.IsVerbose() { rn := reportName(f, id) - c.msg.Verbosef("curing %s%s%s in memory...", c.sgrIdent, rn, c.sgrRes) + c.msg.Verbosef("opening %s%s%s...", c.sgrIdent, rn, c.sgrRes) defer func() { if err == nil { c.msg.Verbosef("opened %s%s%s for reading", c.sgrIdent, rn, c.sgrRes) @@ -2053,6 +2057,9 @@ func WhenceString(whence int) string { } } +// isType returns whether v is of concrete type T. +func isType[T any](v any) bool { _, ok := v.(T); return ok } + // cure implements Cure without acquiring a read lock on abortMu. cure must not // be entered during Abort. func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( @@ -2170,7 +2177,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( } whence = WNew - if c.msg.IsVerbose() { + if c.msg.IsVerbose() && !isType[*Collect](a) { rn := reportName(a, id) c.msg.Verbosef("curing %s%s%s...", c.sgrIdent, rn, c.sgrRes) defer func() { @@ -2504,7 +2511,7 @@ func (c *Cache) cure(a Artifact, curesExempt, rebuild bool) ( err = f.linkSubstitute(ids, substitutes) } if err != nil { - if c.msg.IsVerbose() { + if c.msg.IsVerbose() && !IsCollected(err) { c.msg.Verbosef( "cure %s%s%s: %s%v%s", c.sgrIdent, reportName(ca, id), c.sgrRes, @@ -2794,7 +2801,6 @@ func Open( dirSubstitute, dirIdentifier, dirChecksum, - dirStatus, dirFault, dirWork, } { @@ -2806,6 +2812,17 @@ func Open( } } + for _, name := range []string{ + dirStatus, + } { + if err := os.MkdirAll( + base.Append(name).String(), + 0711, + ); err != nil && !errors.Is(err, os.ErrExist) { + return nil, err + } + } + c := Cache{ parent: ctx, @@ -2919,6 +2936,9 @@ func Open( return &c, nil } +// Path returns the pathname of the directory holding [Cache] state. +func (c *Cache) Path() *check.Absolute { return c.base } + // Collected is returned by [Collect.Cure] to indicate a successful collection. type Collected struct{} diff --git a/pkg/pkg_test.go b/pkg/pkg_test.go index c11b6ac4..66639b5b 100644 --- a/pkg/pkg_test.go +++ b/pkg/pkg_test.go @@ -587,7 +587,9 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) { // destroy empty fault directory if err := os.Remove(base.Append("fault").String()); err != nil { - t.Fatal(err) + if !errors.Is(err, syscall.ENOTEMPTY) { + t.Fatal(err) + } } want := tc.want.hash() @@ -738,6 +740,16 @@ func newWantScrubError(base *check.Absolute) *pkg.ScrubError { } } +// dropFaults removes all fault entries from a cache under testing. +func dropFaults(t *testing.T, base *check.Absolute) { + fault := base.Append("fault") + if err := os.RemoveAll(fault.String()); err != nil { + t.Fatal(err) + } else if err = os.Mkdir(fault.String(), 0); err != nil { + t.Fatal(err) + } +} + func TestCache(t *testing.T) { t.Parallel() @@ -1512,10 +1524,10 @@ func TestCache(t *testing.T) { "identifier/heRgWhNJmhm898V68kw0ta76t4rP1o29tYW_TrdyBWXGfo37K3QWA7Z-JkLXAwcn": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, "identifier/q-VoVBI3IjcWTqwIokpi3y05CP113CHgHslc-XWnhOvDULwzVc3q9Y5xOCm_YyLZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, - "status": {Mode: fs.ModeDir | 0700}, + "status": {Mode: fs.ModeDir | 0711}, "status/q-VoVBI3IjcWTqwIokpi3y05CP113CHgHslc-XWnhOvDULwzVc3q9Y5xOCm_YyLZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x")}, - "status/heRgWhNJmhm898V68kw0ta76t4rP1o29tYW_TrdyBWXGfo37K3QWA7Z-JkLXAwcn": {Mode: 0400, Data: []byte(statusHeader + "destroyed")}, - "status/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: 0400, Data: []byte(statusHeader + "destroyed")}, + "status/heRgWhNJmhm898V68kw0ta76t4rP1o29tYW_TrdyBWXGfo37K3QWA7Z-JkLXAwcn": {Mode: 0444, Data: []byte(statusHeader + "destroyed")}, + "status/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: 0444, Data: []byte(statusHeader + "destroyed")}, "substitute": {Mode: fs.ModeDir | 0700}, "substitute/dP-6_wIDRRouOaOF-nkBy-IaUbLdYHbOUTrptu7j_qfW01mnHSjYJ0oykUmvUd2x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")}, @@ -1610,14 +1622,16 @@ func TestCache(t *testing.T) { "checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl/result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")}, "identifier": {Mode: fs.ModeDir | 0700}, - "identifier/k8yyhlBV3JpYzzywAnrRWkfp2TmgIDNC6FqMFwrrR2x0C3unBLveDNKgigOjG9MM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, - "identifier/Xys8hPpJsKirZmTTuT7SwQ6661TCp_ZRu8iUjPPD7pC5E3ll7xw4zhNlZ2PVcz_-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, "identifier/8sInO_dhPW9kweit7jVuH_vtMkRxlZT7XE4_yEKPZmpGkgVxkmxz0OpXgbMsH6ET": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, + "identifier/9_LKKy-vVT9DxQj-SkqSJFZAc0Yehan3LhldTw8fOIP4XX_DrvkpcPkBV5_46e2q": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, + "identifier/kHH2amyzTN_EpsOyKEOenCROONllTrWAXAwVXOKzx3TgM95-y9ipiz0vkRE-ujBf": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, "identifier/ofaXbB5gIza_7W4Rzo5XDe_8FwlCPKWn3TBcmGgFAkkTWMl5GXGmOzX89Jd2Mk2A": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")}, "identifier/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, - "status": {Mode: fs.ModeDir | 0700}, - "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0400, Data: []byte("\x00")}, + "status": {Mode: fs.ModeDir | 0711}, + "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0444, Data: []byte("\x00")}, + "status/9_LKKy-vVT9DxQj-SkqSJFZAc0Yehan3LhldTw8fOIP4XX_DrvkpcPkBV5_46e2q": {Mode: 0444, Data: []byte(statusHeader + "Lrwxrwxrwx result linkname /proc/nonexistent\n")}, + "status/kHH2amyzTN_EpsOyKEOenCROONllTrWAXAwVXOKzx3TgM95-y9ipiz0vkRE-ujBf": {Mode: 0444, Data: []byte(statusHeader)}, "substitute": {Mode: fs.ModeDir | 0700}, "substitute/socGRnhdsJlJ4Z01ZwHFZdLzFH0rWvoaJrylcUKtvhBdGsE7VoYl-vdUuvfK8B4I": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, @@ -1756,17 +1770,19 @@ func TestCache(t *testing.T) { "identifier/3fYJjSiWhklt7TK9MPVLGk5_8AIU8ostTcFPXULJf_qeAVG6pmC-Cqqu4ZhEXRxn": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, "identifier/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, "identifier/8sInO_dhPW9kweit7jVuH_vtMkRxlZT7XE4_yEKPZmpGkgVxkmxz0OpXgbMsH6ET": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, - "identifier/Xys8hPpJsKirZmTTuT7SwQ6661TCp_ZRu8iUjPPD7pC5E3ll7xw4zhNlZ2PVcz_-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, - "identifier/k8yyhlBV3JpYzzywAnrRWkfp2TmgIDNC6FqMFwrrR2x0C3unBLveDNKgigOjG9MM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, + "identifier/9_LKKy-vVT9DxQj-SkqSJFZAc0Yehan3LhldTw8fOIP4XX_DrvkpcPkBV5_46e2q": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, + "identifier/kHH2amyzTN_EpsOyKEOenCROONllTrWAXAwVXOKzx3TgM95-y9ipiz0vkRE-ujBf": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, "identifier/mXlNz_v77nW7jstn_rJn4RL7dCQlkakM0y-HjO6tkDubtzaj-wCmW1hIr2Gk64_O": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, - "status": {Mode: fs.ModeDir | 0700}, - "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0400, Data: []byte("\x00")}, + "status": {Mode: fs.ModeDir | 0711}, + "status/8VL-egZySw-RbnHZtxGWdf5Z2v9S9CihrdFwa54Bc5EYMQcEgWEem7QrdCTWlCb8": {Mode: 0444, Data: []byte("\x00")}, + "status/9_LKKy-vVT9DxQj-SkqSJFZAc0Yehan3LhldTw8fOIP4XX_DrvkpcPkBV5_46e2q": {Mode: 0444, Data: []byte(statusHeader + "Lrwxrwxrwx result linkname /proc/nonexistent\n")}, + "status/kHH2amyzTN_EpsOyKEOenCROONllTrWAXAwVXOKzx3TgM95-y9ipiz0vkRE-ujBf": {Mode: 0444, Data: []byte(statusHeader)}, "substitute": {Mode: fs.ModeDir | 0700}, - "substitute/socGRnhdsJlJ4Z01ZwHFZdLzFH0rWvoaJrylcUKtvhBdGsE7VoYl-vdUuvfK8B4I": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, "substitute/MccYezQ4pshkeBV4w_aAEIwvZdKHL3OAgAkR3ouZ43EF94Ur1HBHaN7njPfJAj50": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, "substitute/VV5W0_DiA1iylaqbKFThp1ZpV_y1ccVS1XyqDnDaXoHQ-EEyPI4XFrQHcHOF2hWG": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")}, + "substitute/socGRnhdsJlJ4Z01ZwHFZdLzFH0rWvoaJrylcUKtvhBdGsE7VoYl-vdUuvfK8B4I": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")}, "work": {Mode: fs.ModeDir | 0700}, }}, @@ -1794,8 +1810,9 @@ func TestErrors(t *testing.T) { Want: (pkg.Checksum)(bytes.Repeat([]byte{ 0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f, }, 8)), - }, "got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - " instead of deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}, + }, "unexpected checksum\n" + + "\t got: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + + "\twant: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}, {"ResponseStatusError", pkg.ResponseStatusError( http.StatusNotAcceptable, @@ -1856,7 +1873,9 @@ func TestScrubError(t *testing.T) { {"sample", *newWantScrubError( fhs.AbsVarLib.Append("cure"), ), `checksum mismatches: -got vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX instead of _wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +unexpected checksum + got: vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX + want: _wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA dangling identifiers: _gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @@ -1911,7 +1930,9 @@ errors during scrub: }, }, }, `checksum mismatches: -got AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA instead of CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN +unexpected checksum + got: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + want: CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN dangling identifiers: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef @@ -66,6 +66,9 @@ func (a *tarArtifact) Inputs() []Artifact { // IsExclusive returns false: decompressor and tar reader are fully sequential. func (a *tarArtifact) IsExclusive() bool { return false } +// Revision satisfies [RevisionArtifact] for status behaviour. +func (*tarArtifact) Revision() uint64 { return 0 } + // A DisallowedTypeflagError describes a disallowed typeflag encountered while // unpacking a tarball. type DisallowedTypeflagError byte @@ -76,6 +79,11 @@ func (e DisallowedTypeflagError) Error() string { // Cure cures the [Artifact], producing a directory located at work. func (a *tarArtifact) Cure(t *TContext) (err error) { + var status io.Writer + if status, err = t.GetStatusWriter(); err != nil { + return + } + var r io.ReadCloser if r, err = t.Open(a.f); err != nil { return @@ -110,6 +118,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { var header *tar.Header tr := tar.NewReader(r) + msg := t.GetMessage() for header, err = tr.Next(); err == nil; header, err = tr.Next() { typeflag := header.Typeflag if typeflag == 0 { @@ -126,6 +135,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { } } + var m string switch typeflag { case tar.TypeReg: var f *os.File @@ -142,6 +152,13 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { } else if err = f.Close(); err != nil { return } + + m = fmt.Sprintf( + "%s %s (%d bytes)", + header.FileInfo().Mode(), + header.Name, + header.Size, + ) break case tar.TypeLink: @@ -151,6 +168,13 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { ); err != nil { return } + + m = fmt.Sprintf( + "%s %s linkname %s", + header.FileInfo().Mode(), + header.Name, + header.Linkname, + ) break case tar.TypeSymlink: @@ -160,6 +184,13 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { ); err != nil { return } + + m = fmt.Sprintf( + "%s %s linkname %s", + header.FileInfo().Mode(), + header.Name, + header.Linkname, + ) break case tar.TypeDir: @@ -170,21 +201,37 @@ func (a *tarArtifact) Cure(t *TContext) (err error) { if err = root.MkdirAll(header.Name, 0700); err != nil { return } + + m = fmt.Sprintf( + "%s %s", + header.FileInfo().Mode(), + header.Name, + ) break case tar.TypeChar: - t.GetMessage().Verbosef( - "%sskipping character device %d, %d %q%s", - t.cache.sgrWarn, header.Devmajor, header.Devminor, header.Name, t.cache.sgrRes, + m = fmt.Sprintf( + "skipping character device %d, %d %q", + header.Devmajor, header.Devminor, header.Name, ) - continue + break case tar.TypeXGlobalHeader: - continue // ignore + m = "skipping PAX global key-value record" + break default: return DisallowedTypeflagError(typeflag) } + + if m != "" { + msg.Verbose(m) + if _, err = io.WriteString(status, m); err != nil { + return + } else if _, err = status.Write([]byte{'\n'}); err != nil { + return + } + } } if errors.Is(err, io.EOF) { err = nil diff --git a/pkg/tar_test.go b/pkg/tar_test.go index 5462714f..e8a42f73 100644 --- a/pkg/tar_test.go +++ b/pkg/tar_test.go @@ -44,6 +44,24 @@ func TestTar(t *testing.T) { } wantExpandEncode := pkg.Encode(wantExpand.hash()) + const ( + wantStatus = `drwx------ checksum/ +drwx------ checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/ +-r-------- checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check (2 bytes) +drwx------ checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/ +Lrwxrwxrwx checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so linkname /proc/nonexistent/libedac.so +drwx------ checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig/ +drwx------ identifier/ +Lrwxrwxrwx identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY linkname ../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP +Lrwxrwxrwx identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa linkname ../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP +drwx------ work/ +` + + wantStatusExpand = `drwx------ lib/ +Lrwxrwxrwx lib/libedac.so linkname /proc/nonexistent/libedac.so +` + ) + checkWithCache(t, []cacheTestCase{ {"http", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { checkTarHTTP(t, base, c, fstest.MapFS{ @@ -79,8 +97,12 @@ func TestTar(t *testing.T) { "checksum/" + wantEncode + "/work": {Mode: fs.ModeDir | 0500}, "identifier": {Mode: fs.ModeDir | 0700}, - "identifier/snWp53xxNdx6cV5KSJqzyVWzLlTsFd8udOisIu8Rgxly09mGcxufYCoQ5CQrHDyZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)}, - "identifier/v9DUj2R4YK_3ae0m9VuUYI-HJysDDCENu1k10thxioycRzFH1Qejw5bBGukm4IdL": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)}, + "identifier/82-Dwiovp_DGXPBmnmrTk4XtsduvTBBXxnJTBgyp6avsUhjcqmL9yKQLxkwJJM0x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)}, + "identifier/ld6DxwiP2Gj47Ue2L_pFv6NrOth8nkU5W1voMyivd2aVROrorGqPlpbyHUnU1AvI": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)}, + + "status": {Mode: fs.ModeDir | 0711}, + "status/82-Dwiovp_DGXPBmnmrTk4XtsduvTBBXxnJTBgyp6avsUhjcqmL9yKQLxkwJJM0x": {Mode: 0444, Data: []byte(statusHeader + wantStatus)}, + "status/ld6DxwiP2Gj47Ue2L_pFv6NrOth8nkU5W1voMyivd2aVROrorGqPlpbyHUnU1AvI": {Mode: 0444, Data: []byte(statusHeader + wantStatus)}, "substitute": {Mode: fs.ModeDir | 0700}, @@ -103,8 +125,12 @@ func TestTar(t *testing.T) { "checksum/" + wantExpandEncode + "/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")}, "identifier": {Mode: fs.ModeDir | 0700}, - "identifier/1SuvgGltcsgqps9tWqjULG8RCdpx_6KVWXdsMd5xPKvdtysOxqpE-3bLu4j0WpGk": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)}, - "identifier/snWp53xxNdx6cV5KSJqzyVWzLlTsFd8udOisIu8Rgxly09mGcxufYCoQ5CQrHDyZ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)}, + "identifier/82-Dwiovp_DGXPBmnmrTk4XtsduvTBBXxnJTBgyp6avsUhjcqmL9yKQLxkwJJM0x": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)}, + "identifier/fsMFOV4_LoYWbC8GNqL2zjbpzNzT80vPWbSw4L47caKVIta333WIW2TZXoSW8ntH": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)}, + + "status": {Mode: fs.ModeDir | 0711}, + "status/82-Dwiovp_DGXPBmnmrTk4XtsduvTBBXxnJTBgyp6avsUhjcqmL9yKQLxkwJJM0x": {Mode: 0444, Data: []byte(statusHeader + wantStatusExpand)}, + "status/fsMFOV4_LoYWbC8GNqL2zjbpzNzT80vPWbSw4L47caKVIta333WIW2TZXoSW8ntH": {Mode: 0444, Data: []byte(statusHeader + wantStatusExpand)}, "substitute": {Mode: fs.ModeDir | 0700}, @@ -206,13 +232,13 @@ func checkTarHTTP( {"directory", pkg.NewTar(&tarDir), ignorePathname, want, pkg.WNew, nil}, - {"multiple entries", pkg.NewTar(&tarDirMulti), nil, nil, pkg.WNew, errors.New( + {"multiple entries", pkg.NewTar(&tarDirMulti), nil, nil, pkg.WNew, errors.Join(errors.New( "input directory does not contain a single regular file", - )}, + ))}, - {"bad type", pkg.NewTar(&tarDirType), nil, nil, pkg.WNew, errors.New( + {"bad type", pkg.NewTar(&tarDirType), nil, nil, pkg.WNew, errors.Join(errors.New( "input directory does not contain a single regular file", - )}, + ))}, {"error passthrough", pkg.NewTar(&stubArtifact{ kind: pkg.KindExec, @@ -220,6 +246,8 @@ func checkTarHTTP( cure: func(t *pkg.TContext) error { return stub.UniqueError(0xcafe) }, - }), nil, nil, pkg.WNew, stub.UniqueError(0xcafe)}, + }), nil, nil, pkg.WNew, errors.Join(stub.UniqueError(0xcafe))}, }) + + dropFaults(t, base) } |
