package pkg_test //go:generate env CGO_ENABLED=0 go build -tags testtool -o testdata/testtool ./testdata import ( _ "embed" "errors" "log" "os" "os/exec" "testing" "hakurei.app/container/check" "hakurei.app/container/stub" "hakurei.app/hst" "hakurei.app/internal/pkg" "hakurei.app/message" ) // testtoolBin is the container test tool binary made available to the // execArtifact for testing its curing environment. // //go:embed testdata/testtool var testtoolBin []byte func TestExec(t *testing.T) { t.Parallel() wantChecksumOffline := pkg.MustDecode( "GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9", ) checkWithCache(t, []cacheTestCase{ {"offline", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { c.SetStrict(true) testtool, testtoolDestroy := newTesttool() msg := message.New(log.New(os.Stderr, "container: ", 0)) msg.SwapVerbose(testing.Verbose()) cureMany(t, c, []cureStep{ {"container", pkg.NewExec( t.Context(), msg, 0, nil, 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(c *pkg.CureContext) error { return os.MkdirAll(c.GetWorkDir().String(), 0700) }, }), pkg.MustPath("/opt", false, testtool), ), ignorePathname, wantChecksumOffline, nil}, {"error passthrough", pkg.NewExec( t.Context(), msg, 0, nil, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, pkg.MustPath("/proc/nonexistent", false, stubArtifact{ kind: pkg.KindTar, params: []byte("doomed artifact"), cure: func(c *pkg.CureContext) error { return stub.UniqueError(0xcafe) }, }), ), nil, pkg.Checksum{}, errors.Join(stub.UniqueError(0xcafe))}, {"invalid paths", pkg.NewExec( t.Context(), msg, 0, nil, pkg.AbsWork, []string{"HAKUREI_TEST=1"}, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, pkg.ExecPath{}, ), nil, pkg.Checksum{}, os.ErrInvalid}, }) // check init failure passthrough var exitError *exec.ExitError if _, _, err := c.Cure(pkg.NewExec( t.Context(), msg, 0, nil, pkg.AbsWork, nil, check.MustAbs("/opt/bin/testtool"), []string{"testtool"}, )); !errors.As(err, &exitError) || exitError.ExitCode() != hst.ExitFailure { t.Fatalf("Cure: error = %v, want init exit status 1", err) } testtoolDestroy(t, base, c) }, pkg.MustDecode("UiV6kMz7KrTsc_yphiyQzFLqjRanHxUOwrBMtkKuWo4mOO6WgPFAcoUEeSp7eVIW")}, {"net", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { c.SetStrict(true) testtool, testtoolDestroy := newTesttool() msg := message.New(log.New(os.Stderr, "container: ", 0)) msg.SwapVerbose(testing.Verbose()) wantChecksum := pkg.MustDecode( "a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W", ) cureMany(t, c, []cureStep{ {"container", pkg.NewExec( t.Context(), msg, 0, &wantChecksum, 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(c *pkg.CureContext) error { return os.MkdirAll(c.GetWorkDir().String(), 0700) }, }), pkg.MustPath("/opt", false, testtool), ), ignorePathname, wantChecksum, nil}, }) testtoolDestroy(t, base, c) }, pkg.MustDecode("ek4K-0d4iRSArkY2TCs3WK34DbiYeOmhE_4vsJTSu_6roY4ZF3YG6eKRooal-i1o")}, {"overlay root", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { c.SetStrict(true) testtool, testtoolDestroy := newTesttool() msg := message.New(log.New(os.Stderr, "container: ", 0)) msg.SwapVerbose(testing.Verbose()) cureMany(t, c, []cureStep{ {"container", pkg.NewExec( t.Context(), msg, 0, nil, 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(c *pkg.CureContext) error { return os.MkdirAll(c.GetWorkDir().String(), 0700) }, }), pkg.MustPath("/opt", false, testtool), ), ignorePathname, wantChecksumOffline, nil}, }) testtoolDestroy(t, base, c) }, pkg.MustDecode("VIqqpf0ip9jcyw63i6E8lCMGUcLivQBe4Bevt3WusNac-1MSy5bzB647qGUBzl-W")}, {"overlay work", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { c.SetStrict(true) testtool, testtoolDestroy := newTesttool() msg := message.New(log.New(os.Stderr, "container: ", 0)) msg.SwapVerbose(testing.Verbose()) cureMany(t, c, []cureStep{ {"container", pkg.NewExec( t.Context(), msg, 0, nil, 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(c *pkg.CureContext) error { return os.MkdirAll(c.GetWorkDir().String(), 0700) }, }), pkg.MustPath("/work/", false, stubArtifact{ kind: pkg.KindTar, params: []byte("empty directory"), cure: func(c *pkg.CureContext) error { return os.MkdirAll(c.GetWorkDir().String(), 0700) }, }), pkg.Path(pkg.AbsWork, false /* ignored */, testtool), ), ignorePathname, wantChecksumOffline, nil}, }) testtoolDestroy(t, base, c) }, pkg.MustDecode("RibudsoY1X4_dtshfvL5LYfCPcxVnP0ikOn3yBHzOrt6BpevQiANLJF6Xua76-gM")}, }) } // 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(c *pkg.CureContext) error { work := c.GetWorkDir() if err := os.MkdirAll( work.Append("bin").String(), 0700, ); err != nil { return err } return os.WriteFile(c.GetWorkDir().Append( "bin", "testtool", ).String(), testtoolBin, 0500) }, }} testtoolDestroy = newDestroyArtifactFunc(testtool) return }