summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRahat Mahmood <rahat@google.com>2020-01-22 15:14:43 -0800
committergVisor bot <gvisor-bot@google.com>2020-01-22 15:16:21 -0800
commit896bd654b6622d20cbaf8e82b4554a5375addf81 (patch)
treef3ef298ec869f7ac8126d5a89fe3bc5f28c98c43
parent1d97adaa6d73dd897bb4e89d4533936a95003951 (diff)
De-duplicate common test functionality for VFS2 filesystems.
PiperOrigin-RevId: 291041576
-rw-r--r--pkg/sentry/fsimpl/kernfs/kernfs_test.go12
-rw-r--r--pkg/sentry/fsimpl/proc/tasks_test.go411
-rw-r--r--pkg/sentry/fsimpl/sys/sys_test.go4
-rw-r--r--pkg/sentry/fsimpl/testutil/testutil.go149
4 files changed, 278 insertions, 298 deletions
diff --git a/pkg/sentry/fsimpl/kernfs/kernfs_test.go b/pkg/sentry/fsimpl/kernfs/kernfs_test.go
index aa3fe76ee..fade59491 100644
--- a/pkg/sentry/fsimpl/kernfs/kernfs_test.go
+++ b/pkg/sentry/fsimpl/kernfs/kernfs_test.go
@@ -212,7 +212,7 @@ func TestMkdirGetDentry(t *testing.T) {
defer sys.Destroy()
pop := sys.PathOpAtRoot("dir1/a new directory")
- if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, &pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
+ if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err)
}
sys.GetDentryOrDie(pop).DecRef()
@@ -227,7 +227,7 @@ func TestReadStaticFile(t *testing.T) {
defer sys.Destroy()
pop := sys.PathOpAtRoot("file1")
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{
+ fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
Flags: linux.O_RDONLY,
})
if err != nil {
@@ -254,7 +254,7 @@ func TestCreateNewFileInStaticDir(t *testing.T) {
pop := sys.PathOpAtRoot("dir1/newfile")
opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode}
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, opts)
+ fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, opts)
if err != nil {
t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err)
}
@@ -262,7 +262,7 @@ func TestCreateNewFileInStaticDir(t *testing.T) {
// Close the file. The file should persist.
fd.DecRef()
- fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{
+ fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
Flags: linux.O_RDONLY,
})
if err != nil {
@@ -278,7 +278,7 @@ func TestDirFDReadWrite(t *testing.T) {
defer sys.Destroy()
pop := sys.PathOpAtRoot("/")
- fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{
+ fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, pop, &vfs.OpenOptions{
Flags: linux.O_RDONLY,
})
if err != nil {
@@ -309,7 +309,7 @@ func TestDirFDIterDirents(t *testing.T) {
defer sys.Destroy()
pop := sys.PathOpAtRoot("/")
- sys.AssertDirectoryContains(&pop, map[string]testutil.DirentType{
+ sys.AssertAllDirentTypes(sys.ListDirents(pop), map[string]testutil.DirentType{
"dir1": linux.DT_DIR,
"dir2": linux.DT_DIR,
"file1": linux.DT_REG,
diff --git a/pkg/sentry/fsimpl/proc/tasks_test.go b/pkg/sentry/fsimpl/proc/tasks_test.go
index 41977d816..2c1635f33 100644
--- a/pkg/sentry/fsimpl/proc/tasks_test.go
+++ b/pkg/sentry/fsimpl/proc/tasks_test.go
@@ -44,100 +44,47 @@ var (
proc3 = vfs.Dirent{Type: linux.DT_DIR, NextOff: 258 + 3 + 1}
)
-type testIterDirentsCallback struct {
- dirents []vfs.Dirent
-}
-
-func (t *testIterDirentsCallback) Handle(d vfs.Dirent) bool {
- t.dirents = append(t.dirents, d)
- return true
-}
-
-func checkDots(dirs []vfs.Dirent) ([]vfs.Dirent, error) {
- if got := len(dirs); got < 2 {
- return dirs, fmt.Errorf("wrong number of dirents, want at least: 2, got: %d: %v", got, dirs)
- }
- for i, want := range []string{".", ".."} {
- if got := dirs[i].Name; got != want {
- return dirs, fmt.Errorf("wrong name, want: %s, got: %s", want, got)
- }
- if got := dirs[i].Type; got != linux.DT_DIR {
- return dirs, fmt.Errorf("wrong type, want: %d, got: %d", linux.DT_DIR, got)
- }
- }
- return dirs[2:], nil
-}
-
-func checkTasksStaticFiles(gots []vfs.Dirent) ([]vfs.Dirent, error) {
- wants := map[string]vfs.Dirent{
- "cpuinfo": {Type: linux.DT_REG},
- "loadavg": {Type: linux.DT_REG},
- "meminfo": {Type: linux.DT_REG},
- "mounts": {Type: linux.DT_LNK},
- "net": {Type: linux.DT_DIR},
- "self": selfLink,
- "stat": {Type: linux.DT_REG},
- "sys": {Type: linux.DT_DIR},
- "thread-self": threadSelfLink,
- "uptime": {Type: linux.DT_REG},
- "version": {Type: linux.DT_REG},
- }
- return checkFiles(gots, wants)
-}
-
-func checkTaskStaticFiles(gots []vfs.Dirent) ([]vfs.Dirent, error) {
- wants := map[string]vfs.Dirent{
- "auxv": {Type: linux.DT_REG},
- "cgroup": {Type: linux.DT_REG},
- "cmdline": {Type: linux.DT_REG},
- "comm": {Type: linux.DT_REG},
- "environ": {Type: linux.DT_REG},
- "gid_map": {Type: linux.DT_REG},
- "io": {Type: linux.DT_REG},
- "maps": {Type: linux.DT_REG},
- "ns": {Type: linux.DT_DIR},
- "smaps": {Type: linux.DT_REG},
- "stat": {Type: linux.DT_REG},
- "statm": {Type: linux.DT_REG},
- "status": {Type: linux.DT_REG},
- "task": {Type: linux.DT_DIR},
- "uid_map": {Type: linux.DT_REG},
- }
- return checkFiles(gots, wants)
-}
-
-func checkFiles(gots []vfs.Dirent, wants map[string]vfs.Dirent) ([]vfs.Dirent, error) {
- // Go over all files, when there is a match, the file is removed from both
- // 'gots' and 'wants'. wants is expected to reach 0, as all files must
- // be present. Remaining files in 'gots', is returned to caller to decide
- // whether this is valid or not.
- for i := 0; i < len(gots); i++ {
- got := gots[i]
- want, ok := wants[got.Name]
- if !ok {
- continue
- }
- if want.Type != got.Type {
- return gots, fmt.Errorf("wrong file type, want: %v, got: %v: %+v", want.Type, got.Type, got)
- }
- if want.NextOff != 0 && want.NextOff != got.NextOff {
- return gots, fmt.Errorf("wrong dirent offset, want: %v, got: %v: %+v", want.NextOff, got.NextOff, got)
- }
-
- delete(wants, got.Name)
- gots = append(gots[0:i], gots[i+1:]...)
- i--
- }
- if len(wants) != 0 {
- return gots, fmt.Errorf("not all files were found, missing: %+v", wants)
+var (
+ tasksStaticFiles = map[string]testutil.DirentType{
+ "cpuinfo": linux.DT_REG,
+ "loadavg": linux.DT_REG,
+ "meminfo": linux.DT_REG,
+ "mounts": linux.DT_LNK,
+ "net": linux.DT_DIR,
+ "self": linux.DT_LNK,
+ "stat": linux.DT_REG,
+ "sys": linux.DT_DIR,
+ "thread-self": linux.DT_LNK,
+ "uptime": linux.DT_REG,
+ "version": linux.DT_REG,
+ }
+ tasksStaticFilesNextOffs = map[string]int64{
+ "self": selfLink.NextOff,
+ "thread-self": threadSelfLink.NextOff,
+ }
+ taskStaticFiles = map[string]testutil.DirentType{
+ "auxv": linux.DT_REG,
+ "cgroup": linux.DT_REG,
+ "cmdline": linux.DT_REG,
+ "comm": linux.DT_REG,
+ "environ": linux.DT_REG,
+ "gid_map": linux.DT_REG,
+ "io": linux.DT_REG,
+ "maps": linux.DT_REG,
+ "ns": linux.DT_DIR,
+ "smaps": linux.DT_REG,
+ "stat": linux.DT_REG,
+ "statm": linux.DT_REG,
+ "status": linux.DT_REG,
+ "task": linux.DT_DIR,
+ "uid_map": linux.DT_REG,
}
- return gots, nil
-}
+)
-func setup() (context.Context, *vfs.VirtualFilesystem, vfs.VirtualDentry, error) {
+func setup(t *testing.T) *testutil.System {
k, err := testutil.Boot()
if err != nil {
- return nil, nil, vfs.VirtualDentry{}, fmt.Errorf("creating kernel: %v", err)
+ t.Fatalf("Error creating kernel: %v", err)
}
ctx := k.SupervisorContext()
@@ -157,93 +104,60 @@ func setup() (context.Context, *vfs.VirtualFilesystem, vfs.VirtualDentry, error)
}
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "procfs", &fsOpts)
if err != nil {
- return nil, nil, vfs.VirtualDentry{}, fmt.Errorf("NewMountNamespace(): %v", err)
+ t.Fatalf("NewMountNamespace(): %v", err)
}
- return ctx, vfsObj, mntns.Root(), nil
+ return testutil.NewSystem(ctx, t, vfsObj, mntns)
}
func TestTasksEmpty(t *testing.T) {
- ctx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
- }
- defer root.DecRef()
-
- fd, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt failed: %v", err)
- }
+ s := setup(t)
+ defer s.Destroy()
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
- t.Fatalf("IterDirents(): %v", err)
- }
- cb.dirents, err = checkDots(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- cb.dirents, err = checkTasksStaticFiles(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- if len(cb.dirents) != 0 {
- t.Errorf("found more files than expected: %+v", cb.dirents)
- }
+ collector := s.ListDirents(s.PathOpAtRoot("/"))
+ s.AssertAllDirentTypes(collector, tasksStaticFiles)
+ s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
}
func TestTasks(t *testing.T) {
- ctx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
+ s := setup(t)
+ defer s.Destroy()
+
+ expectedDirents := make(map[string]testutil.DirentType)
+ for n, d := range tasksStaticFiles {
+ expectedDirents[n] = d
}
- defer root.DecRef()
- k := kernel.KernelFromContext(ctx)
+ k := kernel.KernelFromContext(s.Ctx)
var tasks []*kernel.Task
for i := 0; i < 5; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(ctx, fmt.Sprintf("name-%d", i), tc)
+ task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
tasks = append(tasks, task)
+ expectedDirents[fmt.Sprintf("%d", i+1)] = linux.DT_DIR
}
- fd, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
- }
+ collector := s.ListDirents(s.PathOpAtRoot("/"))
+ s.AssertAllDirentTypes(collector, expectedDirents)
+ s.AssertDirentOffsets(collector, tasksStaticFilesNextOffs)
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
- t.Fatalf("IterDirents(): %v", err)
- }
- cb.dirents, err = checkDots(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- cb.dirents, err = checkTasksStaticFiles(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
lastPid := 0
- for _, d := range cb.dirents {
+ dirents := collector.OrderedDirents()
+ doneSkippingNonTaskDirs := false
+ for _, d := range dirents {
pid, err := strconv.Atoi(d.Name)
if err != nil {
+ if !doneSkippingNonTaskDirs {
+ // We haven't gotten to the task dirs yet.
+ continue
+ }
t.Fatalf("Invalid process directory %q", d.Name)
}
+ doneSkippingNonTaskDirs = true
if lastPid > pid {
- t.Errorf("pids not in order: %v", cb.dirents)
+ t.Errorf("pids not in order: %v", dirents)
}
found := false
for _, t := range tasks {
@@ -260,13 +174,16 @@ func TestTasks(t *testing.T) {
t.Errorf("Wrong dirent offset want: %d got: %d: %+v", want, d.NextOff, d)
}
}
+ if !doneSkippingNonTaskDirs {
+ t.Fatalf("Never found any process directories.")
+ }
// Test lookup.
for _, path := range []string{"/1", "/2"} {
- fd, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(path)},
+ fd, err := s.VFS.OpenAt(
+ s.Ctx,
+ s.Creds,
+ s.PathOpAtRoot(path),
&vfs.OpenOptions{},
)
if err != nil {
@@ -274,15 +191,15 @@ func TestTasks(t *testing.T) {
}
buf := make([]byte, 1)
bufIOSeq := usermem.BytesIOSequence(buf)
- if _, err := fd.Read(ctx, bufIOSeq, vfs.ReadOptions{}); err != syserror.EISDIR {
+ if _, err := fd.Read(s.Ctx, bufIOSeq, vfs.ReadOptions{}); err != syserror.EISDIR {
t.Errorf("wrong error reading directory: %v", err)
}
}
- if _, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/9999")},
+ if _, err := s.VFS.OpenAt(
+ s.Ctx,
+ s.Creds,
+ s.PathOpAtRoot("/9999"),
&vfs.OpenOptions{},
); err != syserror.ENOENT {
t.Fatalf("wrong error from vfsfs.OpenAt(/9999): %v", err)
@@ -290,16 +207,13 @@ func TestTasks(t *testing.T) {
}
func TestTasksOffset(t *testing.T) {
- ctx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
- }
- defer root.DecRef()
+ s := setup(t)
+ defer s.Destroy()
- k := kernel.KernelFromContext(ctx)
+ k := kernel.KernelFromContext(s.Ctx)
for i := 0; i < 3; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- if _, err := testutil.CreateTask(ctx, fmt.Sprintf("name-%d", i), tc); err != nil {
+ if _, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc); err != nil {
t.Fatalf("CreateTask(): %v", err)
}
}
@@ -382,134 +296,100 @@ func TestTasksOffset(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
- fd, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
+ s := s.WithSubtest(t)
+ fd, err := s.VFS.OpenAt(
+ s.Ctx,
+ s.Creds,
+ s.PathOpAtRoot("/"),
&vfs.OpenOptions{},
)
if err != nil {
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
}
- if _, err := fd.Impl().Seek(ctx, tc.offset, linux.SEEK_SET); err != nil {
+ if _, err := fd.Seek(s.Ctx, tc.offset, linux.SEEK_SET); err != nil {
t.Fatalf("Seek(%d, SEEK_SET): %v", tc.offset, err)
}
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
- t.Fatalf("IterDirents(): %v", err)
+ var collector testutil.DirentCollector
+ if err := fd.IterDirents(s.Ctx, &collector); err != nil {
+ t.Fatalf("IterDirent(): %v", err)
}
- if cb.dirents, err = checkFiles(cb.dirents, tc.wants); err != nil {
- t.Error(err.Error())
- }
- if len(cb.dirents) != 0 {
- t.Errorf("found more files than expected: %+v", cb.dirents)
+
+ expectedTypes := make(map[string]testutil.DirentType)
+ expectedOffsets := make(map[string]int64)
+ for name, want := range tc.wants {
+ expectedTypes[name] = want.Type
+ if want.NextOff != 0 {
+ expectedOffsets[name] = want.NextOff
+ }
}
+
+ collector.SkipDotsChecks(true) // We seek()ed past the dots.
+ s.AssertAllDirentTypes(&collector, expectedTypes)
+ s.AssertDirentOffsets(&collector, expectedOffsets)
})
}
}
func TestTask(t *testing.T) {
- ctx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
- }
- defer root.DecRef()
+ s := setup(t)
+ defer s.Destroy()
- k := kernel.KernelFromContext(ctx)
+ k := kernel.KernelFromContext(s.Ctx)
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- _, err = testutil.CreateTask(ctx, "name", tc)
+ _, err := testutil.CreateTask(s.Ctx, "name", tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
- fd, err := vfsObj.OpenAt(
- ctx,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/1")},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(/1) failed: %v", err)
- }
-
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
- t.Fatalf("IterDirents(): %v", err)
- }
- cb.dirents, err = checkDots(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- cb.dirents, err = checkTaskStaticFiles(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- if len(cb.dirents) != 0 {
- t.Errorf("found more files than expected: %+v", cb.dirents)
- }
+ collector := s.ListDirents(s.PathOpAtRoot("/1"))
+ s.AssertAllDirentTypes(collector, taskStaticFiles)
}
func TestProcSelf(t *testing.T) {
- ctx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
- }
- defer root.DecRef()
+ s := setup(t)
+ defer s.Destroy()
- k := kernel.KernelFromContext(ctx)
+ k := kernel.KernelFromContext(s.Ctx)
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(ctx, "name", tc)
+ task, err := testutil.CreateTask(s.Ctx, "name", tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
- fd, err := vfsObj.OpenAt(
- task,
- auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/self/"), FollowFinalSymlink: true},
- &vfs.OpenOptions{},
- )
- if err != nil {
- t.Fatalf("vfsfs.OpenAt(/self/) failed: %v", err)
- }
-
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
- t.Fatalf("IterDirents(): %v", err)
- }
- cb.dirents, err = checkDots(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- cb.dirents, err = checkTaskStaticFiles(cb.dirents)
- if err != nil {
- t.Error(err.Error())
- }
- if len(cb.dirents) != 0 {
- t.Errorf("found more files than expected: %+v", cb.dirents)
- }
+ collector := s.WithTemporaryContext(task).ListDirents(&vfs.PathOperation{
+ Root: s.Root,
+ Start: s.Root,
+ Path: fspath.Parse("/self/"),
+ FollowFinalSymlink: true,
+ })
+ s.AssertAllDirentTypes(collector, taskStaticFiles)
}
-func iterateDir(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, fd *vfs.FileDescription) {
+func iterateDir(ctx context.Context, t *testing.T, s *testutil.System, fd *vfs.FileDescription) {
t.Logf("Iterating: /proc%s", fd.MappedName(ctx))
- cb := testIterDirentsCallback{}
- if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
+ var collector testutil.DirentCollector
+ if err := fd.IterDirents(ctx, &collector); err != nil {
t.Fatalf("IterDirents(): %v", err)
}
- var err error
- cb.dirents, err = checkDots(cb.dirents)
- if err != nil {
+ if err := collector.Contains(".", linux.DT_DIR); err != nil {
t.Error(err.Error())
}
- for _, d := range cb.dirents {
+ if err := collector.Contains("..", linux.DT_DIR); err != nil {
+ t.Error(err.Error())
+ }
+
+ for _, d := range collector.Dirents() {
+ if d.Name == "." || d.Name == ".." {
+ continue
+ }
childPath := path.Join(fd.MappedName(ctx), d.Name)
if d.Type == linux.DT_LNK {
- link, err := vfsObj.ReadlinkAt(
+ link, err := s.VFS.ReadlinkAt(
ctx,
auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(childPath)},
+ &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(childPath)},
)
if err != nil {
t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", childPath, err)
@@ -520,10 +400,10 @@ func iterateDir(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem
}
t.Logf("Opening: /proc%s", childPath)
- child, err := vfsObj.OpenAt(
+ child, err := s.VFS.OpenAt(
ctx,
auth.CredentialsFromContext(ctx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(childPath)},
+ &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse(childPath)},
&vfs.OpenOptions{},
)
if err != nil {
@@ -539,24 +419,21 @@ func iterateDir(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem
}
if d.Type == linux.DT_DIR {
// Found another dir, let's do it again!
- iterateDir(ctx, t, vfsObj, root, child)
+ iterateDir(ctx, t, s, child)
}
}
}
// TestTree iterates all directories and stats every file.
func TestTree(t *testing.T) {
- uberCtx, vfsObj, root, err := setup()
- if err != nil {
- t.Fatalf("Setup failed: %v", err)
- }
- defer root.DecRef()
+ s := setup(t)
+ defer s.Destroy()
- k := kernel.KernelFromContext(uberCtx)
+ k := kernel.KernelFromContext(s.Ctx)
var tasks []*kernel.Task
for i := 0; i < 5; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
- task, err := testutil.CreateTask(uberCtx, fmt.Sprintf("name-%d", i), tc)
+ task, err := testutil.CreateTask(s.Ctx, fmt.Sprintf("name-%d", i), tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
@@ -564,14 +441,14 @@ func TestTree(t *testing.T) {
}
ctx := tasks[0]
- fd, err := vfsObj.OpenAt(
+ fd, err := s.VFS.OpenAt(
ctx,
- auth.CredentialsFromContext(uberCtx),
- &vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
+ auth.CredentialsFromContext(s.Ctx),
+ &vfs.PathOperation{Root: s.Root, Start: s.Root, Path: fspath.Parse("/")},
&vfs.OpenOptions{},
)
if err != nil {
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
}
- iterateDir(ctx, t, vfsObj, root, fd)
+ iterateDir(ctx, t, s, fd)
}
diff --git a/pkg/sentry/fsimpl/sys/sys_test.go b/pkg/sentry/fsimpl/sys/sys_test.go
index 60a1634a9..8b1cf0bd0 100644
--- a/pkg/sentry/fsimpl/sys/sys_test.go
+++ b/pkg/sentry/fsimpl/sys/sys_test.go
@@ -56,7 +56,7 @@ func TestReadCPUFile(t *testing.T) {
for _, fname := range []string{"online", "possible", "present"} {
pop := s.PathOpAtRoot(fmt.Sprintf("devices/system/cpu/%s", fname))
- fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, &pop, &vfs.OpenOptions{})
+ fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{})
if err != nil {
t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
}
@@ -75,7 +75,7 @@ func TestSysRootContainsExpectedEntries(t *testing.T) {
s := newTestSystem(t)
defer s.Destroy()
pop := s.PathOpAtRoot("/")
- s.AssertDirectoryContains(&pop, map[string]testutil.DirentType{
+ s.AssertAllDirentTypes(s.ListDirents(pop), map[string]testutil.DirentType{
"block": linux.DT_DIR,
"bus": linux.DT_DIR,
"class": linux.DT_DIR,
diff --git a/pkg/sentry/fsimpl/testutil/testutil.go b/pkg/sentry/fsimpl/testutil/testutil.go
index eada31d94..2a723a89f 100644
--- a/pkg/sentry/fsimpl/testutil/testutil.go
+++ b/pkg/sentry/fsimpl/testutil/testutil.go
@@ -40,8 +40,8 @@ type System struct {
Ctx context.Context
Creds *auth.Credentials
VFS *vfs.VirtualFilesystem
+ Root vfs.VirtualDentry
mns *vfs.MountNamespace
- root vfs.VirtualDentry
}
// NewSystem constructs a System.
@@ -55,14 +55,49 @@ func NewSystem(ctx context.Context, t *testing.T, v *vfs.VirtualFilesystem, mns
Creds: auth.CredentialsFromContext(ctx),
VFS: v,
mns: mns,
- root: mns.Root(),
+ Root: mns.Root(),
}
return s
}
+// WithSubtest creates a temporary test system with a new test harness,
+// referencing all other resources from the original system. This is useful when
+// a system is reused for multiple subtests, and the T needs to change for each
+// case. Note that this is safe when test cases run in parallel, as all
+// resources referenced by the system are immutable, or handle interior
+// mutations in a thread-safe manner.
+//
+// The returned system must not outlive the original and should not be destroyed
+// via System.Destroy.
+func (s *System) WithSubtest(t *testing.T) *System {
+ return &System{
+ t: t,
+ Ctx: s.Ctx,
+ Creds: s.Creds,
+ VFS: s.VFS,
+ mns: s.mns,
+ Root: s.Root,
+ }
+}
+
+// WithTemporaryContext constructs a temporary test system with a new context
+// ctx. The temporary system borrows all resources and references from the
+// original system. The returned temporary system must not outlive the original
+// system, and should not be destroyed via System.Destroy.
+func (s *System) WithTemporaryContext(ctx context.Context) *System {
+ return &System{
+ t: s.t,
+ Ctx: ctx,
+ Creds: s.Creds,
+ VFS: s.VFS,
+ mns: s.mns,
+ Root: s.Root,
+ }
+}
+
// Destroy release resources associated with a test system.
func (s *System) Destroy() {
- s.root.DecRef()
+ s.Root.DecRef()
s.mns.DecRef(s.VFS) // Reference on mns passed to NewSystem.
}
@@ -87,18 +122,18 @@ func (s *System) ReadToEnd(fd *vfs.FileDescription) (string, error) {
// PathOpAtRoot constructs a PathOperation with the given path from
// the root of the filesystem.
-func (s *System) PathOpAtRoot(path string) vfs.PathOperation {
- return vfs.PathOperation{
- Root: s.root,
- Start: s.root,
+func (s *System) PathOpAtRoot(path string) *vfs.PathOperation {
+ return &vfs.PathOperation{
+ Root: s.Root,
+ Start: s.Root,
Path: fspath.Parse(path),
}
}
// GetDentryOrDie attempts to resolve a dentry referred to by the
// provided path operation. If unsuccessful, the test fails.
-func (s *System) GetDentryOrDie(pop vfs.PathOperation) vfs.VirtualDentry {
- vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, &pop, &vfs.GetDentryOptions{})
+func (s *System) GetDentryOrDie(pop *vfs.PathOperation) vfs.VirtualDentry {
+ vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, pop, &vfs.GetDentryOptions{})
if err != nil {
s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err)
}
@@ -108,14 +143,8 @@ func (s *System) GetDentryOrDie(pop vfs.PathOperation) vfs.VirtualDentry {
// DirentType is an alias for values for linux_dirent64.d_type.
type DirentType = uint8
-// AssertDirectoryContains verifies that a directory at pop contains the entries
-// specified. AssertDirectoryContains implicitly checks for "." and "..", these
-// need not be included in entries.
-func (s *System) AssertDirectoryContains(pop *vfs.PathOperation, entries map[string]DirentType) {
- // Also implicitly check for "." and "..".
- entries["."] = linux.DT_DIR
- entries[".."] = linux.DT_DIR
-
+// ListDirents lists the Dirents for a directory at pop.
+func (s *System) ListDirents(pop *vfs.PathOperation) *DirentCollector {
fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{Flags: linux.O_RDONLY})
if err != nil {
s.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
@@ -126,12 +155,52 @@ func (s *System) AssertDirectoryContains(pop *vfs.PathOperation, entries map[str
if err := fd.IterDirents(s.Ctx, collector); err != nil {
s.t.Fatalf("IterDirent failed: %v", err)
}
+ return collector
+}
+
+// AssertAllDirentTypes verifies that the set of dirents in collector contains
+// exactly the specified set of expected entries. AssertAllDirentTypes respects
+// collector.skipDots, and implicitly checks for "." and ".." accordingly.
+func (s *System) AssertAllDirentTypes(collector *DirentCollector, expected map[string]DirentType) {
+ // Also implicitly check for "." and "..", if enabled.
+ if !collector.skipDots {
+ expected["."] = linux.DT_DIR
+ expected[".."] = linux.DT_DIR
+ }
- collectedEntries := make(map[string]DirentType)
+ dentryTypes := make(map[string]DirentType)
+ collector.mu.Lock()
for _, dirent := range collector.dirents {
- collectedEntries[dirent.Name] = dirent.Type
+ dentryTypes[dirent.Name] = dirent.Type
}
- if diff := cmp.Diff(entries, collectedEntries); diff != "" {
+ collector.mu.Unlock()
+ if diff := cmp.Diff(expected, dentryTypes); diff != "" {
+ s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff)
+ }
+}
+
+// AssertDirentOffsets verifies that collector contains at least the entries
+// specified in expected, with the given NextOff field. Entries specified in
+// expected but missing from collector result in failure. Extra entries in
+// collector are ignored. AssertDirentOffsets respects collector.skipDots, and
+// implicitly checks for "." and ".." accordingly.
+func (s *System) AssertDirentOffsets(collector *DirentCollector, expected map[string]int64) {
+ // Also implicitly check for "." and "..", if enabled.
+ if !collector.skipDots {
+ expected["."] = 1
+ expected[".."] = 2
+ }
+
+ dentryNextOffs := make(map[string]int64)
+ collector.mu.Lock()
+ for _, dirent := range collector.dirents {
+ // Ignore extra entries in dentries that are not in expected.
+ if _, ok := expected[dirent.Name]; ok {
+ dentryNextOffs[dirent.Name] = dirent.NextOff
+ }
+ }
+ collector.mu.Unlock()
+ if diff := cmp.Diff(expected, dentryNextOffs); diff != "" {
s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff)
}
}
@@ -141,16 +210,29 @@ func (s *System) AssertDirectoryContains(pop *vfs.PathOperation, entries map[str
// all dirents emitted by the callback.
type DirentCollector struct {
mu sync.Mutex
- dirents map[string]vfs.Dirent
+ order []*vfs.Dirent
+ dirents map[string]*vfs.Dirent
+ // When the collector is used in various Assert* functions, should "." and
+ // ".." be implicitly checked?
+ skipDots bool
+}
+
+// SkipDotsChecks enables or disables the implicit checks on "." and ".." when
+// the collector is used in various Assert* functions. Note that "." and ".."
+// are still collected if passed to d.Handle, so the caller should only disable
+// the checks when they aren't expected.
+func (d *DirentCollector) SkipDotsChecks(value bool) {
+ d.skipDots = value
}
// Handle implements vfs.IterDirentsCallback.Handle.
func (d *DirentCollector) Handle(dirent vfs.Dirent) bool {
d.mu.Lock()
if d.dirents == nil {
- d.dirents = make(map[string]vfs.Dirent)
+ d.dirents = make(map[string]*vfs.Dirent)
}
- d.dirents[dirent.Name] = dirent
+ d.order = append(d.order, &dirent)
+ d.dirents[dirent.Name] = &dirent
d.mu.Unlock()
return true
}
@@ -176,3 +258,24 @@ func (d *DirentCollector) Contains(name string, typ uint8) error {
}
return nil
}
+
+// Dirents returns all dirents discovered by this collector.
+func (d *DirentCollector) Dirents() map[string]*vfs.Dirent {
+ d.mu.Lock()
+ dirents := make(map[string]*vfs.Dirent)
+ for n, d := range d.dirents {
+ dirents[n] = d
+ }
+ d.mu.Unlock()
+ return dirents
+}
+
+// OrderedDirents returns an ordered list of dirents as discovered by this
+// collector.
+func (d *DirentCollector) OrderedDirents() []*vfs.Dirent {
+ d.mu.Lock()
+ dirents := make([]*vfs.Dirent, len(d.order))
+ copy(dirents, d.order)
+ d.mu.Unlock()
+ return dirents
+}