aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkg/exec_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/exec_test.go')
-rw-r--r--pkg/exec_test.go670
1 files changed, 670 insertions, 0 deletions
diff --git a/pkg/exec_test.go b/pkg/exec_test.go
new file mode 100644
index 00000000..051af551
--- /dev/null
+++ b/pkg/exec_test.go
@@ -0,0 +1,670 @@
+package pkg_test
+
+import (
+ "bufio"
+ "bytes"
+ _ "embed"
+ "encoding/gob"
+ "errors"
+ "io/fs"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "slices"
+ "strings"
+ "testing"
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/check"
+ "hakurei.app/container"
+ "hakurei.app/hst"
+ "hakurei.app/internal/info"
+ "hakurei.app/internal/stub"
+ "hakurei.app/pkg"
+
+ "hakurei.app/pkg/internal/testtool/expected"
+)
+
+// testtoolBin is the container test tool 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
+
+func init() {
+ pathname, err := filepath.Abs("internal/testtool/testtool")
+ if err != nil {
+ panic(err)
+ }
+ pkg.RegisterArch("cafe", container.BinfmtEntry{
+ Magic: expected.Magic,
+ Interpreter: check.MustAbs(pathname),
+ })
+}
+
+// scanLinesCR is like [bufio.ScanLines], but also treats a bare \r as an
+// end-of-line marker.
+//
+//go:linkname scanLinesCR hakurei.app/pkg.scanLinesCR
+func scanLinesCR(data []byte, atEOF bool) (advance int, token []byte, err error)
+
+func TestScan(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ data string
+ want []string
+ }{
+ {"progress indicator", "Updating files: 76% (8655/11256)\r" +
+ "Updating files: 77% (8668/11256)\r" +
+ "Updating files: 78% (8780/11256)\r" +
+ "Updating files: 79% (8893/11256)\r" +
+ "Updating files: 80% (9005/11256)\r" +
+ "Updating files: 81% (9118/11256)\r" +
+ "Updating files: 82% (9230/11256)\r" +
+ "Updating files: 83% (9343/11256)\r" +
+ "Updating files: 84% (9456/11256)\r" +
+ "Updating files: 85% (9568/11256)\r" +
+ "Updating files: 86% (9681/11256)\r" +
+ "Updating files: 87% (9793/11256)\r" +
+ "Updating files: 88% (9906/11256)\r" +
+ "Updating files: 89% (10018/11256)\r" +
+ "Updating files: 90% (10131/11256)\r" +
+ "Updating files: 91% (10243/11256)\r" +
+ "Updating files: 92% (10356/11256)\r" +
+ "Updating files: 93% (10469/11256)\r" +
+ "Updating files: 94% (10581/11256)\r" +
+ "Updating files: 95% (10694/11256)\r" +
+ "Updating files: 96% (10806/11256)\r" +
+ "Updating files: 97% (10919/11256)\r" +
+ "Updating files: 98% (11031/11256)\r" +
+ "Updating files: 99% (11144/11256)\r" +
+ "Updating files: 100% (11256/11256)\r" +
+ "Updating files: 100% (11256/11256), done.\n", []string{
+ "Updating files: 76% (8655/11256)",
+ "Updating files: 77% (8668/11256)",
+ "Updating files: 78% (8780/11256)",
+ "Updating files: 79% (8893/11256)",
+ "Updating files: 80% (9005/11256)",
+ "Updating files: 81% (9118/11256)",
+ "Updating files: 82% (9230/11256)",
+ "Updating files: 83% (9343/11256)",
+ "Updating files: 84% (9456/11256)",
+ "Updating files: 85% (9568/11256)",
+ "Updating files: 86% (9681/11256)",
+ "Updating files: 87% (9793/11256)",
+ "Updating files: 88% (9906/11256)",
+ "Updating files: 89% (10018/11256)",
+ "Updating files: 90% (10131/11256)",
+ "Updating files: 91% (10243/11256)",
+ "Updating files: 92% (10356/11256)",
+ "Updating files: 93% (10469/11256)",
+ "Updating files: 94% (10581/11256)",
+ "Updating files: 95% (10694/11256)",
+ "Updating files: 96% (10806/11256)",
+ "Updating files: 97% (10919/11256)",
+ "Updating files: 98% (11031/11256)",
+ "Updating files: 99% (11144/11256)",
+ "Updating files: 100% (11256/11256)",
+ "Updating files: 100% (11256/11256), done.",
+ }},
+
+ {"crlf", "0\r1\n2\n3\r\n4\n5\r6\r\n7\n", []string{
+ "0", "1", "2", "3", "4", "5", "6", "7",
+ }},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ s := bufio.NewScanner(strings.NewReader(tc.data))
+ s.Split(scanLinesCR)
+ got := make([]string, 0, len(tc.want))
+ for s.Scan() {
+ got = append(got, s.Text())
+ }
+ if err := s.Err(); err != nil {
+ t.Fatal(err)
+ }
+ if !slices.Equal(got, tc.want) {
+ t.Fatalf("Scan: %q, want %q", got, tc.want)
+ }
+ })
+ }
+}
+
+func TestExec(t *testing.T) {
+ t.Parallel()
+
+ wantOffline := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+
+ "check": {Mode: 0400, Data: []byte{0}},
+ }
+ wantOfflineEncode := pkg.Encode(wantOffline.hash())
+ failingArtifact := &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("doomed artifact"),
+ cure: func(t *pkg.TContext) error {
+ return stub.UniqueError(0xcafe)
+ },
+ }
+
+ checkWithCache(t, []cacheTestCase{
+ {"offline", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ 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"},
+
+ pkg.MustPath("/file", false, newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xfe, 0},
+ nil,
+ nil, nil,
+ )),
+ pkg.MustPath("/.hakurei", false, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), 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"},
+
+ pkg.MustPath("/file", false, newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xfe, 0},
+ nil,
+ nil, nil,
+ )),
+ // substitution miss fails in testtool due to differing idents
+ pkg.MustPath("/.hakurei", false, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory (substituted)"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), 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"},
+
+ pkg.MustPath("/proc/nonexistent", false, failingArtifact),
+ ), nil, nil, pkg.WNew, pkg.InputError{
+ failingArtifact: stub.UniqueError(0xcafe),
+ }},
+
+ {"invalid paths", pkg.NewExec(
+ "", "", nil, 0, false, false,
+ pkg.AbsWork,
+ []string{"HAKUREI_TEST=1"},
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool"},
+
+ pkg.ExecPath{},
+ ), nil, nil, pkg.WNew, pkg.ErrInvalidPaths},
+ })
+
+ // check init failure passthrough
+ initFailureArtifact := pkg.NewExec(
+ "", "", nil, 0, false, false,
+ pkg.AbsWork,
+ nil,
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool"},
+ )
+ var exitError *exec.ExitError
+ if _, _, err := c.Cure(initFailureArtifact); !errors.As(err, &exitError) ||
+ exitError.ExitCode() != hst.ExitFailure {
+ t.Fatalf("Cure: error = %v, want init exit status 1", err)
+ }
+
+ var faultStatus []byte
+ if faults, err := c.ReadFaults(initFailureArtifact); err != nil {
+ t.Fatal(err)
+ } else if len(faults) != 1 {
+ t.Fatalf("ReadFaults: %v", faults)
+ } else if faultStatus, err = os.ReadFile(faults[0].String()); err != nil {
+ t.Fatal(err)
+ } else if err = faults[0].Destroy(); err != nil {
+ t.Fatal(err)
+ } else {
+ t.Logf("destroyed expected fault at %s", faults[0].Time().UTC())
+ }
+
+ if !bytes.HasPrefix(faultStatus, []byte(
+ "hakurei.app/pkg ",
+ )) || !bytes.Contains(faultStatus, []byte(
+ "\ninit: fork/exec /opt/bin/testtool: no such file or directory\n",
+ )) {
+ t.Errorf("unexpected status:\n%s", string(faultStatus))
+ }
+
+ destroyStatus(t, base, 2, 1)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ "identifier/" + expected.Offline: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+ "identifier/" + expected.OfflineS: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+ "identifier/_OHzj_xdmfpaWfFN-_D9JpGpPlIjh84873E3Ui9xwv9PPPIt0BlzvH-cnPNeRsTl": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"net", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ wantNet := expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+
+ "check": {Mode: 0400, Data: []byte("net")},
+ }
+ cureMany(t, c, []cureStep{
+ {"container", pkg.NewExec(
+ "exec-net", "", new(wantNet.hash()), 0, true, false,
+ pkg.AbsWork,
+ []string{"HAKUREI_TEST=1"},
+ check.MustAbs("/opt/bin/testtool"),
+ []string{"testtool", "net"},
+
+ pkg.MustPath("/file", false, newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xfe, 0},
+ nil,
+ nil, nil,
+ )),
+ pkg.MustPath("/.hakurei", false, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), ignorePathname, wantNet, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
+ "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
+ "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/" + expected.Net: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
+ "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"overlay root", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ 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"},
+
+ pkg.MustPath("/", true, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), ignorePathname, wantOffline, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/" + expected.OvlRoot: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"overlay work", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ 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"},
+
+ pkg.MustPath("/", true, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }), pkg.MustPath("/work/", false, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
+ ), ignorePathname, wantOffline, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/" + expected.Work: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"multiple layers", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ 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"},
+
+ pkg.MustPath("/", true, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }, &stubArtifactF{
+ kind: pkg.KindExec,
+ params: []byte("test sample with dependencies"),
+
+ deps: slices.Repeat([]pkg.Artifact{newStubFile(
+ pkg.KindHTTPGet,
+ pkg.ID{0xfe, 0},
+ nil,
+ nil, nil,
+ ), &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+
+ // this is queued and might run instead of the other
+ // one so do not leave it as nil
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }}, 1<<5 /* concurrent cache hits */),
+
+ cure: func(f *pkg.FContext) error {
+ work := f.GetWorkDir()
+ if err := os.MkdirAll(work.String(), 0700); err != nil {
+ return err
+ }
+ return os.WriteFile(work.Append("check").String(), []byte("layers"), 0400)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), ignorePathname, wantOffline, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
+ "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK": {Mode: fs.ModeDir | 0500},
+ "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
+ "identifier/2uIvmnqtzAx4Ed4PWV8xjZCWeJCwfE2IcCc0evM-Nd8mDUlgSU6nLtPLcpPUcMlG": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
+ "identifier/" + expected.Layers: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"overlay layer promotion", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ testtool, testtoolDestroy := newTesttool()
+
+ 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"},
+
+ pkg.MustPath("/", true, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("another empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, testtool),
+ ), ignorePathname, wantOffline, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ testtoolDestroy(t, base, c)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/Vzj9qaSVBtNyBQv_PW6uojFOWTwraHoaC0C6xdaGG1q_HTvSh4KLVxRcEQU1jd2Y": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+ "identifier/" + expected.Promote: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+
+ {"binfmt", pkg.CValidateKnown | checkDestroySubstitutes, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
+ if info.CanDegrade && os.Getenv("ROSA_SKIP_BINFMT") != "" {
+ t.Skip("binfmt_misc test explicitly skipped")
+ }
+
+ cureMany(t, c, []cureStep{
+ {"container", pkg.NewExec(
+ "exec-binfmt", "cafe", nil, 0, false, true,
+ pkg.AbsWork,
+ []string{"HAKUREI_TEST=1", "HAKUREI_BINFMT=1"},
+ check.MustAbs("/opt/bin/sample"),
+ []string{"sample"},
+
+ pkg.MustPath("/", true, &stubArtifact{
+ kind: pkg.KindTar,
+ params: []byte("empty directory"),
+ cure: func(t *pkg.TContext) error {
+ return os.MkdirAll(t.GetWorkDir().String(), 0700)
+ },
+ }),
+ pkg.MustPath("/opt", false, overrideIdent{pkg.ID{0xfe, 0xff}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ work := t.GetWorkDir()
+ if err := os.MkdirAll(
+ work.Append("bin").String(),
+ 0700,
+ ); err != nil {
+ return err
+ }
+
+ return os.WriteFile(t.GetWorkDir().Append(
+ "bin",
+ "sample",
+ ).String(), []byte(expected.Full), 0500)
+ },
+ }}),
+ ), ignorePathname, expectsFS{
+ ".": {Mode: fs.ModeDir | 0500},
+
+ "check": {Mode: 0400, Data: []byte("binfmt")},
+ }, pkg.WNew, nil},
+ })
+
+ destroyStatus(t, base, 2, 0)
+ }, expectsFS{
+ ".": {Mode: fs.ModeDir | 0700},
+
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV": {Mode: fs.ModeDir | 0500},
+ "checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV/bin": {Mode: fs.ModeDir | 0700},
+ "checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV/bin/sample": {Mode: 0500, Data: []byte("\xca\xfe\xba\xbe\xfd\xfd:3")},
+ "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
+ "checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3": {Mode: fs.ModeDir | 0500},
+ "checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3/check": {Mode: 0400, Data: []byte("binfmt")},
+
+ "identifier": {Mode: fs.ModeDir | 0700},
+ "identifier/c-HodmoEG0f_WGdst9aN-duLMH7QDDXOUi7UcyArXMeM1fc9lYKKQlTCW8ktaK8X": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UnDo4B5KneEUY5b4vRUk_y9MWgkWuw2N8f8a2XayO686xXur-aZmX2-7n_8tKMe3")},
+ "identifier/_v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/5aevg3YpDxjqQZ-pdvXK7YqgkL5JKqcoStYQxeD96kuYar6K2mRQWMHib6NQRnpV")},
+ "identifier/MEaM1v6MLRQ60JQOOKbfDzfJOdC3L8i30IgOpYbQdX68enwF0FN-P36_M4RX_0Vd": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
+
+ "substitute": {Mode: fs.ModeDir | 0700},
+
+ "temp": {Mode: fs.ModeDir | 0700},
+ "work": {Mode: fs.ModeDir | 0700},
+ }},
+ })
+}
+
+// newTesttool returns an [Artifact] that cures into testtoolBin. 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),
+) {
+ // testtoolBin is built during go:generate and is not deterministic
+ testtool = overrideIdent{pkg.ID{0xfe, 0xff}, &stubArtifact{
+ kind: pkg.KindTar,
+ cure: func(t *pkg.TContext) error {
+ work := t.GetWorkDir()
+ if err := os.MkdirAll(
+ work.Append("bin").String(),
+ 0700,
+ ); err != nil {
+ return err
+ }
+
+ if ift, err := net.Interfaces(); err != nil {
+ return err
+ } else {
+ var f *os.File
+ if f, err = os.Create(t.GetWorkDir().Append(
+ "ift",
+ ).String()); err != nil {
+ return err
+ } else {
+ err = gob.NewEncoder(f).Encode(ift)
+ closeErr := f.Close()
+ if err != nil {
+ return err
+ }
+ if closeErr != nil {
+ return closeErr
+ }
+ }
+ }
+
+ return os.WriteFile(t.GetWorkDir().Append(
+ "bin",
+ "testtool",
+ ).String(), testtoolBin, 0500)
+ },
+ }}
+ testtoolDestroy = newDestroyArtifactFunc(testtool)
+ return
+}