package pkg_test //go:generate env CGO_ENABLED=0 go build -tags testtool -o testdata/testtool ./testdata import ( _ "embed" "encoding/gob" "errors" "net" "os" "os/exec" "slices" "testing" "hakurei.app/container/check" "hakurei.app/container/stub" "hakurei.app/hst" "hakurei.app/internal/pkg" ) // 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() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( 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(t *pkg.TContext) error { return os.MkdirAll(t.GetWorkDir().String(), 0700) }, }), pkg.MustPath("/opt", false, testtool), ), ignorePathname, wantChecksumOffline, nil}, {"error passthrough", pkg.NewExec( 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(t *pkg.TContext) error { return stub.UniqueError(0xcafe) }, }), ), nil, pkg.Checksum{}, errors.Join(stub.UniqueError(0xcafe))}, {"invalid paths", pkg.NewExec( 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( 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() wantChecksum := pkg.MustDecode( "a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W", ) cureMany(t, c, []cureStep{ {"container", pkg.NewExec( &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(t *pkg.TContext) error { return os.MkdirAll(t.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() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( 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(t *pkg.TContext) error { return os.MkdirAll(t.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() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( 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(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, wantChecksumOffline, nil}, }) testtoolDestroy(t, base, c) }, pkg.MustDecode("RibudsoY1X4_dtshfvL5LYfCPcxVnP0ikOn3yBHzOrt6BpevQiANLJF6Xua76-gM")}, {"multiple layers", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { c.SetStrict(true) testtool, testtoolDestroy := newTesttool() cureMany(t, c, []cureStep{ {"container", pkg.NewExec( nil, 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, wantChecksumOffline, nil}, }) testtoolDestroy(t, base, c) }, pkg.MustDecode("a05V6iVGAfLO8aG0VSSFyB1QvnzJoMmGbMX6ud8CMMbatvyBv90_xGn1qWEIsjkQ")}, }) } // 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 }