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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user