diff options
Diffstat (limited to 'pkg/exec_test.go')
| -rw-r--r-- | pkg/exec_test.go | 670 |
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 +} |
