diff options
Diffstat (limited to 'test/internal')
| -rw-r--r-- | test/internal/sandbox/assert.go | 4 | ||||
| -rw-r--r-- | test/internal/sandbox/fs.go | 102 | ||||
| -rw-r--r-- | test/internal/sandbox/fs_test.go | 80 | ||||
| -rw-r--r-- | test/internal/testsuite/fs.go | 129 | ||||
| -rw-r--r-- | test/internal/testsuite/fs_test.go | 85 | ||||
| -rw-r--r-- | test/internal/testsuite/testsuite.go | 10 | ||||
| -rw-r--r-- | test/internal/testsuite/testsuite_root.go | 3 |
7 files changed, 219 insertions, 194 deletions
diff --git a/test/internal/sandbox/assert.go b/test/internal/sandbox/assert.go index e2bcc8ae..1194befb 100644 --- a/test/internal/sandbox/assert.go +++ b/test/internal/sandbox/assert.go @@ -33,7 +33,7 @@ func fatalf(format string, v ...any) { fatalfFunc(format, v...) } type TestCase struct { Env []string `json:"env"` - FS *FS `json:"fs"` + FS *testsuite.FS `json:"fs"` Mount []*mountinfo.Entry `json:"mount"` Seccomp bool `json:"seccomp"` @@ -110,7 +110,7 @@ func (t *T) MustCheck(want *TestCase) { } if want.FS != nil && t.FS != nil { - if err := want.FS.Compare(".", t.FS); err != nil { + if err := want.FS.Compare(printfFunc, ".", t.FS); err != nil { fatalf("%v", err) } } else { diff --git a/test/internal/sandbox/fs.go b/test/internal/sandbox/fs.go deleted file mode 100644 index b37d30b9..00000000 --- a/test/internal/sandbox/fs.go +++ /dev/null @@ -1,102 +0,0 @@ -//go:build testtool - -package sandbox - -import ( - "errors" - "fmt" - "io/fs" - "path/filepath" - "strings" -) - -var ( - ErrFSBadLength = errors.New("bad dir length") - ErrFSBadData = errors.New("data differs") - ErrFSBadMode = errors.New("mode differs") - ErrFSInvalidEnt = errors.New("invalid entry condition") -) - -type FS struct { - Mode fs.FileMode `json:"mode"` - Dir map[string]*FS `json:"dir"` - Data *string `json:"data"` -} - -func printDir(prefix string, dir []fs.DirEntry) { - names := make([]string, len(dir)) - for i, ent := range dir { - name := ent.Name() - if ent.IsDir() { - name += "/" - } - names[i] = fmt.Sprintf("%q", name) - } - printf("[FAIL] d %s: %s", prefix, strings.Join(names, " ")) -} - -func (s *FS) Compare(prefix string, e fs.FS) error { - if s.Data != nil { - if s.Dir != nil { - panic("invalid state") - } - panic("invalid compare call") - } - - if s.Dir == nil { - printf("[ OK ] s %s", prefix) - return nil - } - - var dir []fs.DirEntry - if d, err := fs.ReadDir(e, prefix); err != nil { - return err - } else if len(d) != len(s.Dir) { - printDir(prefix, d) - return ErrFSBadLength - } else { - dir = d - } - - for _, got := range dir { - name := got.Name() - - if want, ok := s.Dir[name]; !ok { - printDir(prefix, dir) - return fs.ErrNotExist - } else if want.Dir != nil && !got.IsDir() { - printDir(prefix, dir) - return ErrFSInvalidEnt - } else { - name = filepath.Join(prefix, name) - - if fi, err := got.Info(); err != nil { - return err - } else if fi.Mode() != want.Mode { - printf("[FAIL] m %s: %x, want %x", - name, uint32(fi.Mode()), uint32(want.Mode)) - return ErrFSBadMode - } - - if want.Data != nil { - if want.Dir != nil { - panic("invalid state") - } - if v, err := fs.ReadFile(e, name); err != nil { - return err - } else if string(v) != *want.Data { - printf("[FAIL] f %s", name) - printf("got: %s", v) - printf("want: %s", *want.Data) - return ErrFSBadData - } - printf("[ OK ] f %s", name) - } else if err := want.Compare(name, e); err != nil { - return err - } - } - } - printf("[ OK ] d %s", prefix) - - return nil -} diff --git a/test/internal/sandbox/fs_test.go b/test/internal/sandbox/fs_test.go deleted file mode 100644 index a7813ee5..00000000 --- a/test/internal/sandbox/fs_test.go +++ /dev/null @@ -1,80 +0,0 @@ -//go:build testtool - -package sandbox_test - -import ( - "errors" - "fmt" - "io/fs" - "strings" - "testing" - "testing/fstest" - - "hakurei.app/test/internal/sandbox" -) - -var ( - fsPasswdSample = "u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh" - fsGroupSample = "hakurei:x:65534:" -) - -func TestCompare(t *testing.T) { - testCases := []struct { - name string - - sample fstest.MapFS - want *sandbox.FS - wantOut string - wantErr error - }{ - {"skip", fstest.MapFS{}, &sandbox.FS{}, "[ OK ] s .\x00", nil}, - {"simple pass", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".hakurei": {Mode: 0x800001ed}}}, - "[ OK ] s .hakurei\x00[ OK ] d .\x00", nil}, - {"bad length", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: make(map[string]*sandbox.FS)}, - "[FAIL] d .: \".hakurei/\"\x00", sandbox.ErrFSBadLength}, - {"top level bad mode", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".hakurei": {Mode: 0xdeadbeef}}}, - "[FAIL] m .hakurei: 800001ed, want deadbeef\x00", sandbox.ErrFSBadMode}, - {"invalid entry condition", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{"test": {Dir: make(map[string]*sandbox.FS)}}}, - "[FAIL] d .: \"test\"\x00", sandbox.ErrFSInvalidEnt}, - {"nonexistent", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, - &sandbox.FS{Dir: map[string]*sandbox.FS{".test": {}}}, - "[FAIL] d .: \"test\"\x00", fs.ErrNotExist}, - {"file", fstest.MapFS{"etc": {Mode: 0x800001c0}, - "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, - "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, - }, &sandbox.FS{Dir: map[string]*sandbox.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*sandbox.FS{ - "passwd": {Mode: 0x1a4, Data: &fsPasswdSample}, - "group": {Mode: 0x1a4, Data: &fsGroupSample}, - }}}}, "[ OK ] f etc/group\x00[ OK ] f etc/passwd\x00[ OK ] d etc\x00[ OK ] d .\x00", nil}, - {"file differ", fstest.MapFS{"etc": {Mode: 0x800001c0}, - "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, - "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, - }, &sandbox.FS{Dir: map[string]*sandbox.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*sandbox.FS{ - "passwd": {Mode: 0x1a4, Data: &fsGroupSample}, - "group": {Mode: 0x1a4, Data: &fsGroupSample}, - }}}}, "[ OK ] f etc/group\x00[FAIL] f etc/passwd\x00got: u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh\x00want: hakurei:x:65534:\x00", sandbox.ErrFSBadData}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - gotOut := new(strings.Builder) - oldPrint := sandbox.SwapPrint(func(format string, v ...any) { _, _ = fmt.Fprintf(gotOut, format+"\x00", v...) }) - t.Cleanup(func() { sandbox.SwapPrint(oldPrint) }) - - err := tc.want.Compare(".", tc.sample) - if !errors.Is(err, tc.wantErr) { - t.Errorf("Compare: error = %v; wantErr %v", - err, tc.wantErr) - } - - if gotOut.String() != tc.wantOut { - t.Errorf("Compare: output %q; want %q", - gotOut, tc.wantOut) - } - }) - } -} diff --git a/test/internal/testsuite/fs.go b/test/internal/testsuite/fs.go new file mode 100644 index 00000000..9acd24b6 --- /dev/null +++ b/test/internal/testsuite/fs.go @@ -0,0 +1,129 @@ +package testsuite + +import ( + "errors" + "fmt" + "io/fs" + "path/filepath" + "strings" +) + +var ( + // ErrFSBadLength is returned by [FS.Compare] for a directory with an + // unexpected amount of dents. + ErrFSBadLength = errors.New("bad dir length") + // ErrFSBadData is returned by [FS.Compare] for a file with unexpected + // contents. + ErrFSBadData = errors.New("data differs") + // ErrFSBadMode is returned by [FS.Compare] for an entry with unexpected + // mode. + ErrFSBadMode = errors.New("mode differs") + // ErrFSInvalidEnt is returned by [FS.Compare] if an invalid [FS] is visited. + ErrFSInvalidEnt = errors.New("invalid entry condition") +) + +// FS represents part of a filesystem hierarchy. +type FS struct { + // Expected mode of corresponding entry. + Mode fs.FileMode `json:"mode"` + // Expected directory contents. The directory is not descended if Dir is nil. + Dir map[string]*FS `json:"dir"` + // Expected file contents. The file is not read if Data is nil. + Data *string `json:"data"` +} + +// dprintf calls printf if it is non-nil. +func dprintf(printf func(format string, a ...any), format string, a ...any) { + if printf == nil { + return + } + printf(format, a...) +} + +// printDir prints a failed [FS.Compare] directory. +func printDir( + printf func(format string, a ...any), + prefix string, + dir []fs.DirEntry, +) { + names := make([]string, len(dir)) + for i, ent := range dir { + name := ent.Name() + if ent.IsDir() { + name += "/" + } + names[i] = fmt.Sprintf("%q", name) + } + dprintf(printf, "[FAIL] d %s: %s", prefix, strings.Join(names, " ")) +} + +// Compare compares the contents of prefix against the hierarchy described by s. +func (s *FS) Compare( + printf func(format string, a ...any), + prefix string, + e fs.FS, +) error { + if s.Data != nil { + if s.Dir != nil { + panic("invalid state") + } + panic("invalid compare call") + } + + if s.Dir == nil { + dprintf(printf, "[ OK ] s %s", prefix) + return nil + } + + var dir []fs.DirEntry + if d, err := fs.ReadDir(e, prefix); err != nil { + return err + } else if len(d) != len(s.Dir) { + printDir(printf, prefix, d) + return ErrFSBadLength + } else { + dir = d + } + + for _, got := range dir { + name := got.Name() + + if want, ok := s.Dir[name]; !ok { + printDir(printf, prefix, dir) + return fs.ErrNotExist + } else if want.Dir != nil && !got.IsDir() { + printDir(printf, prefix, dir) + return ErrFSInvalidEnt + } else { + name = filepath.Join(prefix, name) + + if fi, err := got.Info(); err != nil { + return err + } else if fi.Mode() != want.Mode { + dprintf(printf, "[FAIL] m %s: %#o, want %#o", + name, uint32(fi.Mode()), uint32(want.Mode)) + return ErrFSBadMode + } + + if want.Data != nil { + if want.Dir != nil { + panic("invalid state") + } + if v, err := fs.ReadFile(e, name); err != nil { + return err + } else if string(v) != *want.Data { + dprintf(printf, + "[FAIL] f %s\n\t got: %s\n\twant: %s", + name, v, *want.Data, + ) + return ErrFSBadData + } + dprintf(printf, "[ OK ] f %s", name) + } else if err := want.Compare(printf, name, e); err != nil { + return err + } + } + } + dprintf(printf, "[ OK ] d %s", prefix) + return nil +} diff --git a/test/internal/testsuite/fs_test.go b/test/internal/testsuite/fs_test.go new file mode 100644 index 00000000..5c93bb46 --- /dev/null +++ b/test/internal/testsuite/fs_test.go @@ -0,0 +1,85 @@ +package testsuite_test + +import ( + "bytes" + "errors" + "fmt" + "io/fs" + "testing" + "testing/fstest" + + "hakurei.app/test/internal/testsuite" +) + +func TestCompare(t *testing.T) { + var ( + fsPasswdSample = "u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh" + fsGroupSample = "hakurei:x:65534:" + ) + + testCases := []struct { + name string + + sample fstest.MapFS + want *testsuite.FS + wantOut string + wantErr error + }{ + {"skip", fstest.MapFS{}, &testsuite.FS{}, "[ OK ] s .\x00", nil}, + {"simple pass", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, + &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0x800001ed}}}, + "[ OK ] s .hakurei\x00[ OK ] d .\x00", nil}, + {"bad length", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, + &testsuite.FS{Dir: make(map[string]*testsuite.FS)}, + "[FAIL] d .: \".hakurei/\"\x00", testsuite.ErrFSBadLength}, + {"top level bad mode", fstest.MapFS{".hakurei": {Mode: 0x800001ed}}, + &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0xdeadbeef}}}, + "[FAIL] m .hakurei: 020000000755, want 033653337357\x00", testsuite.ErrFSBadMode}, + {"invalid entry condition", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, + &testsuite.FS{Dir: map[string]*testsuite.FS{"test": {Dir: make(map[string]*testsuite.FS)}}}, + "[FAIL] d .: \"test\"\x00", testsuite.ErrFSInvalidEnt}, + {"nonexistent", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}}, + &testsuite.FS{Dir: map[string]*testsuite.FS{".test": {}}}, + "[FAIL] d .: \"test\"\x00", fs.ErrNotExist}, + {"file", fstest.MapFS{"etc": {Mode: 0x800001c0}, + "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, + "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, + }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{ + "passwd": {Mode: 0x1a4, Data: &fsPasswdSample}, + "group": {Mode: 0x1a4, Data: &fsGroupSample}, + }}}}, "[ OK ] f etc/group\x00[ OK ] f etc/passwd\x00[ OK ] d etc\x00[ OK ] d .\x00", nil}, + {"file differ", fstest.MapFS{"etc": {Mode: 0x800001c0}, + "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644}, + "etc/group": {Data: []byte(fsGroupSample), Mode: 0644}, + }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{ + "passwd": {Mode: 0x1a4, Data: &fsGroupSample}, + "group": {Mode: 0x1a4, Data: &fsGroupSample}, + }}}}, "[ OK ] f etc/group\x00[FAIL] f etc/passwd\n\t got: u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh\n\twant: hakurei:x:65534:\x00", testsuite.ErrFSBadData}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + + err := tc.want.Compare( + func(format string, a ...any) { + _, _ = fmt.Fprintf(&buf, format+"\x00", a...) + }, + ".", tc.sample, + ) + if !errors.Is(err, tc.wantErr) { + t.Errorf( + "Compare: error = %v; wantErr %v", + err, tc.wantErr, + ) + } + + if buf.String() != tc.wantOut { + t.Errorf( + "Compare: output %q; want %q", + &buf, tc.wantOut, + ) + } + }) + } +} diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go index ef250ec8..6b4cd717 100644 --- a/test/internal/testsuite/testsuite.go +++ b/test/internal/testsuite/testsuite.go @@ -11,18 +11,8 @@ import ( "os/signal" "os/user" "syscall" - "testing" ) -func init() { - if testing.Testing() { - return - } - - log.SetFlags(0) - log.SetPrefix("testsuite: ") -} - // ReceiveSignals blocks until a termination signal arrives, and terminates. func ReceiveSignals() { s := make(chan os.Signal, 3) diff --git a/test/internal/testsuite/testsuite_root.go b/test/internal/testsuite/testsuite_root.go index 830e272c..11f503ef 100644 --- a/test/internal/testsuite/testsuite_root.go +++ b/test/internal/testsuite/testsuite_root.go @@ -11,4 +11,7 @@ func init() { if os.Geteuid() != 0 { log.Fatal("this program must run as root") } + + log.SetFlags(0) + log.SetPrefix("testsuite: ") } |
