test/sandbox: compare filesystem hierarchy
For checking deterministic aspects of fs outcome. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
9953768de5
commit
39e32799b3
98
test/sandbox/fs.go
Normal file
98
test/sandbox/fs.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package sandbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
"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 %q: %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 = path.Join(prefix, name)
|
||||||
|
|
||||||
|
if fi, err := got.Info(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if fi.Mode() != want.Mode {
|
||||||
|
printf("[FAIL] m %q: %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)
|
||||||
|
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
|
||||||
|
}
|
78
test/sandbox/fs_test.go
Normal file
78
test/sandbox/fs_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package sandbox_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/test/sandbox"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fsPasswdSample = "u0_a20:x:65534:65534:Fortify:/var/lib/persist/module/fortify/u0/a20:/run/current-system/sw/bin/zsh"
|
||||||
|
fsGroupSample = "fortify: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{".fortify": {Mode: 0x800001ed}},
|
||||||
|
&sandbox.FS{Dir: map[string]*sandbox.FS{".fortify": {Mode: 0x800001ed}}},
|
||||||
|
"[ OK ] s .fortify\x00[ OK ] d .\x00", nil},
|
||||||
|
{"bad length", fstest.MapFS{".fortify": {Mode: 0x800001ed}},
|
||||||
|
&sandbox.FS{Dir: make(map[string]*sandbox.FS)},
|
||||||
|
"[FAIL] d \".\": \".fortify/\"\x00", sandbox.ErrFSBadLength},
|
||||||
|
{"top level bad mode", fstest.MapFS{".fortify": {Mode: 0x800001ed}},
|
||||||
|
&sandbox.FS{Dir: map[string]*sandbox.FS{".fortify": {Mode: 0xdeadbeef}}},
|
||||||
|
"[FAIL] m \".fortify\": 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\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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user