diff options
author | Googler <noreply@google.com> | 2018-04-27 10:37:02 -0700 |
---|---|---|
committer | Adin Scannell <ascannell@google.com> | 2018-04-28 01:44:26 -0400 |
commit | d02b74a5dcfed4bfc8f2f8e545bca4d2afabb296 (patch) | |
tree | 54f95eef73aee6bacbfc736fffc631be2605ed53 /pkg/p9/p9test | |
parent | f70210e742919f40aa2f0934a22f1c9ba6dada62 (diff) |
Check in gVisor.
PiperOrigin-RevId: 194583126
Change-Id: Ica1d8821a90f74e7e745962d71801c598c652463
Diffstat (limited to 'pkg/p9/p9test')
-rw-r--r-- | pkg/p9/p9test/BUILD | 28 | ||||
-rw-r--r-- | pkg/p9/p9test/client_test.go | 335 | ||||
-rw-r--r-- | pkg/p9/p9test/mocks.go | 490 |
3 files changed, 853 insertions, 0 deletions
diff --git a/pkg/p9/p9test/BUILD b/pkg/p9/p9test/BUILD new file mode 100644 index 000000000..339c86089 --- /dev/null +++ b/pkg/p9/p9test/BUILD @@ -0,0 +1,28 @@ +package(licenses = ["notice"]) # Apache 2.0 + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_test( + name = "p9test_test", + size = "small", + srcs = ["client_test.go"], + embed = [":p9test"], + deps = [ + "//pkg/fd", + "//pkg/p9", + "//pkg/unet", + ], +) + +go_library( + name = "p9test", + srcs = [ + "mocks.go", + ], + importpath = "gvisor.googlesource.com/gvisor/pkg/p9/p9test", + visibility = ["//:sandbox"], + deps = [ + "//pkg/fd", + "//pkg/p9", + ], +) diff --git a/pkg/p9/p9test/client_test.go b/pkg/p9/p9test/client_test.go new file mode 100644 index 000000000..8e35d6017 --- /dev/null +++ b/pkg/p9/p9test/client_test.go @@ -0,0 +1,335 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9test + +import ( + "io/ioutil" + "os" + "reflect" + "syscall" + "testing" + + "gvisor.googlesource.com/gvisor/pkg/fd" + "gvisor.googlesource.com/gvisor/pkg/p9" + "gvisor.googlesource.com/gvisor/pkg/unet" +) + +func TestDonateFD(t *testing.T) { + // Temporary file. + osFile, err := ioutil.TempFile("", "p9") + if err != nil { + t.Fatalf("could not create temporary file: %v", err) + } + os.Remove(osFile.Name()) + + hfi, err := osFile.Stat() + if err != nil { + osFile.Close() + t.Fatalf("stat failed: %v", err) + } + osFileStat := hfi.Sys().(*syscall.Stat_t) + + f, err := fd.NewFromFile(osFile) + // osFile should always be closed. + osFile.Close() + if err != nil { + t.Fatalf("unable to create file: %v", err) + } + + // Craft attacher to attach to the mocked file which will return our + // temporary file. + fileMock := &FileMock{OpenMock: OpenMock{File: f}} + attacher := &AttachMock{File: fileMock} + + // Make socket pair. + serverSocket, clientSocket, err := unet.SocketPair(false) + if err != nil { + t.Fatalf("socketpair got err %v wanted nil", err) + } + defer clientSocket.Close() + server := p9.NewServer(attacher) + go server.Handle(serverSocket) + client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString()) + if err != nil { + t.Fatalf("new client got %v, expected nil", err) + } + + // Attach to the mocked file. + cFile, err := client.Attach("") + if err != nil { + t.Fatalf("attach failed: %v", err) + } + + // Try to open the mocked file. + clientHostFile, _, _, err := cFile.Open(0) + if err != nil { + t.Fatalf("open failed: %v", err) + } + var clientStat syscall.Stat_t + if err := syscall.Fstat(clientHostFile.FD(), &clientStat); err != nil { + t.Fatalf("stat failed: %v", err) + } + + // Compare inode nums to make sure it's the same file. + if clientStat.Ino != osFileStat.Ino { + t.Errorf("fd donation failed") + } +} + +// TestClient is a megatest. +// +// This allows us to probe various edge cases, while changing the state of the +// underlying server in expected ways. The test slowly builds server state and +// is documented inline. +// +// We wind up with the following, after probing edge cases: +// +// FID 1: ServerFile (sf). +// FID 2: Directory (d). +// FID 3: File (f). +// FID 4: Symlink (s). +// +// Although you should use the FID method on the individual files. +func TestClient(t *testing.T) { + var ( + // Sentinel error. + sentinelErr = syscall.Errno(4383) + + // Backend mocks. + a = &AttachMock{} + sf = &FileMock{} + d = &FileMock{} + f = &FileMock{} + s = &FileMock{} + + // Client Files for the above. + sfFile p9.File + ) + + testSteps := []struct { + name string + fn func(*p9.Client) error + want error + }{ + { + name: "bad-attach", + want: sentinelErr, + fn: func(c *p9.Client) error { + a.File = nil + a.Err = sentinelErr + _, err := c.Attach("") + return err + }, + }, + { + name: "attach", + fn: func(c *p9.Client) error { + a.Called = false + a.File = sf + a.Err = nil + var err error + sfFile, err = c.Attach("foo") + if !a.Called { + t.Errorf("Attach never Called?") + } + if a.AttachName != "foo" { + // This wasn't carried through? + t.Errorf("attachName got %v wanted foo", a.AttachName) + } + return err + }, + }, + { + name: "bad-walk", + want: sentinelErr, + fn: func(c *p9.Client) error { + sf.WalkMock.File = d + sf.WalkMock.Err = sentinelErr + _, _, err := sfFile.Walk([]string{"foo", "bar"}) + return err + }, + }, + { + name: "walk-to-dir", + fn: func(c *p9.Client) error { + sf.WalkMock.Called = false + sf.WalkMock.File = d + sf.WalkMock.Err = nil + sf.WalkMock.QIDs = []p9.QID{{Type: 1}} + var qids []p9.QID + var err error + qids, _, err = sfFile.Walk([]string{"foo", "bar"}) + if !sf.WalkMock.Called { + t.Errorf("Walk never Called?") + } + if !reflect.DeepEqual(sf.WalkMock.Names, []string{"foo", "bar"}) { + t.Errorf("got names %v wanted []{foo, bar}", sf.WalkMock.Names) + } + if len(qids) != 1 || qids[0].Type != 1 { + t.Errorf("got qids %v wanted []{{Type: 1}}", qids) + } + return err + }, + }, + { + name: "walkgetattr-to-dir", + fn: func(c *p9.Client) error { + sf.WalkGetAttrMock.Called = false + sf.WalkGetAttrMock.File = d + sf.WalkGetAttrMock.Err = nil + sf.WalkGetAttrMock.QIDs = []p9.QID{{Type: 1}} + sf.WalkGetAttrMock.Attr = p9.Attr{UID: 1} + sf.WalkGetAttrMock.Valid = p9.AttrMask{Mode: true} + var qids []p9.QID + var err error + var mask p9.AttrMask + var attr p9.Attr + qids, _, mask, attr, err = sfFile.WalkGetAttr([]string{"foo", "bar"}) + if !sf.WalkGetAttrMock.Called { + t.Errorf("Walk never Called?") + } + if !reflect.DeepEqual(sf.WalkGetAttrMock.Names, []string{"foo", "bar"}) { + t.Errorf("got names %v wanted []{foo, bar}", sf.WalkGetAttrMock.Names) + } + if len(qids) != 1 || qids[0].Type != 1 { + t.Errorf("got qids %v wanted []{{Type: 1}}", qids) + } + if !reflect.DeepEqual(attr, sf.WalkGetAttrMock.Attr) { + t.Errorf("got attrs %s wanted %s", attr, sf.WalkGetAttrMock.Attr) + } + if !reflect.DeepEqual(mask, sf.WalkGetAttrMock.Valid) { + t.Errorf("got mask %s wanted %s", mask, sf.WalkGetAttrMock.Valid) + } + return err + }, + }, + { + name: "walk-to-file", + fn: func(c *p9.Client) error { + // Basic sanity check is done in walk-to-dir. + // + // Here we just create basic file FIDs to use. + sf.WalkMock.File = f + sf.WalkMock.Err = nil + var err error + _, _, err = sfFile.Walk(nil) + return err + }, + }, + { + name: "walk-to-symlink", + fn: func(c *p9.Client) error { + // See note in walk-to-file. + sf.WalkMock.File = s + sf.WalkMock.Err = nil + var err error + _, _, err = sfFile.Walk(nil) + return err + }, + }, + { + name: "bad-statfs", + want: sentinelErr, + fn: func(c *p9.Client) error { + sf.StatFSMock.Err = sentinelErr + _, err := sfFile.StatFS() + return err + }, + }, + { + name: "statfs", + fn: func(c *p9.Client) error { + sf.StatFSMock.Called = false + sf.StatFSMock.Stat = p9.FSStat{Type: 1} + sf.StatFSMock.Err = nil + stat, err := sfFile.StatFS() + if !sf.StatFSMock.Called { + t.Errorf("StatfS never Called?") + } + if stat.Type != 1 { + t.Errorf("got stat %v wanted {Type: 1}", stat) + } + return err + }, + }, + } + + // First, create a new server and connection. + serverSocket, clientSocket, err := unet.SocketPair(false) + if err != nil { + t.Fatalf("socketpair got err %v wanted nil", err) + } + defer clientSocket.Close() + server := p9.NewServer(a) + go server.Handle(serverSocket) + client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString()) + if err != nil { + t.Fatalf("new client got err %v, wanted nil", err) + } + + // Now, run through each of the test steps. + for _, step := range testSteps { + err := step.fn(client) + if err != step.want { + // Don't fail, just note this one step failed. + t.Errorf("step %q got %v wanted %v", step.name, err, step.want) + } + } +} + +func BenchmarkClient(b *testing.B) { + // Backend mock. + a := &AttachMock{ + File: &FileMock{ + ReadAtMock: ReadAtMock{N: 1}, + }, + } + + // First, create a new server and connection. + serverSocket, clientSocket, err := unet.SocketPair(false) + if err != nil { + b.Fatalf("socketpair got err %v wanted nil", err) + } + defer clientSocket.Close() + server := p9.NewServer(a) + go server.Handle(serverSocket) + client, err := p9.NewClient(clientSocket, 1024*1024 /* 1M message size */, p9.HighestVersionString()) + if err != nil { + b.Fatalf("new client got %v, expected nil", err) + } + + // Attach to the server. + f, err := client.Attach("") + if err != nil { + b.Fatalf("error during attach, got %v wanted nil", err) + } + + // Open the file. + if _, _, _, err := f.Open(p9.ReadOnly); err != nil { + b.Fatalf("error during open, got %v wanted nil", err) + } + + // Reset the clock. + b.ResetTimer() + + // Do N reads. + var buf [1]byte + for i := 0; i < b.N; i++ { + _, err := f.ReadAt(buf[:], 0) + if err != nil { + b.Fatalf("error during read %d, got %v wanted nil", i, err) + } + } +} diff --git a/pkg/p9/p9test/mocks.go b/pkg/p9/p9test/mocks.go new file mode 100644 index 000000000..e10f206dd --- /dev/null +++ b/pkg/p9/p9test/mocks.go @@ -0,0 +1,490 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package p9test + +import ( + "gvisor.googlesource.com/gvisor/pkg/fd" + "gvisor.googlesource.com/gvisor/pkg/p9" +) + +// StatFSMock mocks p9.File.StatFS. +type StatFSMock struct { + Called bool + + // Return. + Stat p9.FSStat + Err error +} + +// StatFS implements p9.File.StatFS. +func (f *StatFSMock) StatFS() (p9.FSStat, error) { + f.Called = true + return f.Stat, f.Err +} + +// GetAttrMock mocks p9.File.GetAttr. +type GetAttrMock struct { + Called bool + + // Args. + Req p9.AttrMask + + // Return. + QID p9.QID + Valid p9.AttrMask + Attr p9.Attr + Err error +} + +// GetAttr implements p9.File.GetAttr. +func (g *GetAttrMock) GetAttr(req p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) { + g.Called, g.Req = true, req + return g.QID, g.Valid, g.Attr, g.Err +} + +// WalkGetAttrMock mocks p9.File.WalkGetAttr. +type WalkGetAttrMock struct { + Called bool + + // Args. + Names []string + + // Return. + QIDs []p9.QID + File p9.File + Valid p9.AttrMask + Attr p9.Attr + Err error +} + +// WalkGetAttr implements p9.File.WalkGetAttr. +func (w *WalkGetAttrMock) WalkGetAttr(names []string) ([]p9.QID, p9.File, p9.AttrMask, p9.Attr, error) { + w.Called, w.Names = true, names + return w.QIDs, w.File, w.Valid, w.Attr, w.Err +} + +// SetAttrMock mocks p9.File.SetAttr. +type SetAttrMock struct { + Called bool + + // Args. + Valid p9.SetAttrMask + Attr p9.SetAttr + + // Return. + Err error +} + +// SetAttr implements p9.File.SetAttr. +func (s *SetAttrMock) SetAttr(valid p9.SetAttrMask, attr p9.SetAttr) error { + s.Called, s.Valid, s.Attr = true, valid, attr + return s.Err +} + +// RemoveMock mocks p9.File.Remove. +type RemoveMock struct { + Called bool + + // Return. + Err error +} + +// Remove implements p9.File.Remove. +func (r *RemoveMock) Remove() error { + r.Called = true + return r.Err +} + +// OpenMock mocks p9.File.Open. +type OpenMock struct { + Called bool + + // Args. + Flags p9.OpenFlags + + // Return. + File *fd.FD + QID p9.QID + IOUnit uint32 + Err error +} + +// Open implements p9.File.Open. +func (o *OpenMock) Open(flags p9.OpenFlags) (*fd.FD, p9.QID, uint32, error) { + o.Called, o.Flags = true, flags + return o.File, o.QID, o.IOUnit, o.Err +} + +// ReadAtMock mocks p9.File.ReadAt. +type ReadAtMock struct { + Called bool + + // Args. + P []byte + Offset uint64 + + // Return. + N int + Err error +} + +// ReadAt implements p9.File.ReadAt. +func (r *ReadAtMock) ReadAt(p []byte, offset uint64) (int, error) { + r.Called, r.P, r.Offset = true, p, offset + return r.N, r.Err +} + +// WriteAtMock mocks p9.File.WriteAt. +type WriteAtMock struct { + Called bool + + // Args. + P []byte + Offset uint64 + + // Return. + N int + Err error +} + +// WriteAt implements p9.File.WriteAt. +func (w *WriteAtMock) WriteAt(p []byte, offset uint64) (int, error) { + w.Called, w.P, w.Offset = true, p, offset + return w.N, w.Err +} + +// FSyncMock mocks p9.File.FSync. +type FSyncMock struct { + Called bool + + // Return. + Err error +} + +// FSync implements p9.File.FSync. +func (f *FSyncMock) FSync() error { + f.Called = true + return f.Err +} + +// MkdirMock mocks p9.File.Mkdir. +type MkdirMock struct { + Called bool + + // Args. + Name string + Permissions p9.FileMode + UID p9.UID + GID p9.GID + + // Return. + QID p9.QID + Err error +} + +// Mkdir implements p9.File.Mkdir. +func (s *MkdirMock) Mkdir(name string, permissions p9.FileMode, uid p9.UID, gid p9.GID) (p9.QID, error) { + s.Called, s.Name, s.Permissions, s.UID, s.GID = true, name, permissions, uid, gid + return s.QID, s.Err +} + +// SymlinkMock mocks p9.File.Symlink. +type SymlinkMock struct { + Called bool + + // Args. + Oldname string + Newname string + UID p9.UID + GID p9.GID + + // Return. + QID p9.QID + Err error +} + +// Symlink implements p9.File.Symlink. +func (s *SymlinkMock) Symlink(oldname string, newname string, uid p9.UID, gid p9.GID) (p9.QID, error) { + s.Called, s.Oldname, s.Newname, s.UID, s.GID = true, oldname, newname, uid, gid + return s.QID, s.Err +} + +// MknodMock mocks p9.File.Mknod. +type MknodMock struct { + Called bool + + // Args. + Name string + Permissions p9.FileMode + Major uint32 + Minor uint32 + UID p9.UID + GID p9.GID + + // Return. + QID p9.QID + Err error +} + +// Mknod implements p9.File.Mknod. +func (m *MknodMock) Mknod(name string, permissions p9.FileMode, major uint32, minor uint32, uid p9.UID, gid p9.GID) (p9.QID, error) { + m.Called, m.Name, m.Permissions, m.Major, m.Minor, m.UID, m.GID = true, name, permissions, major, minor, uid, gid + return m.QID, m.Err +} + +// UnlinkAtMock mocks p9.File.UnlinkAt. +type UnlinkAtMock struct { + Called bool + + // Args. + Name string + Flags uint32 + + // Return. + Err error +} + +// UnlinkAt implements p9.File.UnlinkAt. +func (u *UnlinkAtMock) UnlinkAt(name string, flags uint32) error { + u.Called, u.Name, u.Flags = true, name, flags + return u.Err +} + +// ReaddirMock mocks p9.File.Readdir. +type ReaddirMock struct { + Called bool + + // Args. + Offset uint64 + Count uint32 + + // Return. + Dirents []p9.Dirent + Err error +} + +// Readdir implements p9.File.Readdir. +func (r *ReaddirMock) Readdir(offset uint64, count uint32) ([]p9.Dirent, error) { + r.Called, r.Offset, r.Count = true, offset, count + return r.Dirents, r.Err +} + +// ReadlinkMock mocks p9.File.Readlink. +type ReadlinkMock struct { + Called bool + + // Return. + Target string + Err error +} + +// Readlink implements p9.File.Readlink. +func (r *ReadlinkMock) Readlink() (string, error) { + r.Called = true + return r.Target, r.Err +} + +// AttachMock mocks p9.Attacher.Attach. +type AttachMock struct { + Called bool + + // Args. + AttachName string + + // Return. + File p9.File + Err error +} + +// Attach implements p9.Attacher.Attach. +func (a *AttachMock) Attach(attachName string) (p9.File, error) { + a.Called, a.AttachName = true, attachName + return a.File, a.Err +} + +// WalkMock mocks p9.File.Walk. +type WalkMock struct { + Called bool + + // Args. + Names []string + + // Return. + QIDs []p9.QID + File p9.File + Err error +} + +// Walk implements p9.File.Walk. +func (w *WalkMock) Walk(names []string) ([]p9.QID, p9.File, error) { + w.Called, w.Names = true, names + return w.QIDs, w.File, w.Err +} + +// RenameMock mocks p9.File.Rename. +type RenameMock struct { + Called bool + + // Args. + Directory p9.File + Name string + + // Return. + Err error +} + +// Rename implements p9.File.Rename. +func (r *RenameMock) Rename(directory p9.File, name string) error { + r.Called, r.Directory, r.Name = true, directory, name + return r.Err +} + +// CloseMock mocks p9.File.Close. +type CloseMock struct { + Called bool + + // Return. + Err error +} + +// Close implements p9.File.Close. +func (d *CloseMock) Close() error { + d.Called = true + return d.Err +} + +// CreateMock mocks p9.File.Create. +type CreateMock struct { + Called bool + + // Args. + Name string + Flags p9.OpenFlags + Permissions p9.FileMode + UID p9.UID + GID p9.GID + + // Return. + HostFile *fd.FD + File p9.File + QID p9.QID + IOUnit uint32 + Err error +} + +// Create implements p9.File.Create. +func (c *CreateMock) Create(name string, flags p9.OpenFlags, permissions p9.FileMode, uid p9.UID, gid p9.GID) (*fd.FD, p9.File, p9.QID, uint32, error) { + c.Called, c.Name, c.Flags, c.Permissions, c.UID, c.GID = true, name, flags, permissions, uid, gid + return c.HostFile, c.File, c.QID, c.IOUnit, c.Err +} + +// LinkMock mocks p9.File.Link. +type LinkMock struct { + Called bool + + // Args. + Target p9.File + Newname string + + // Return. + Err error +} + +// Link implements p9.File.Link. +func (l *LinkMock) Link(target p9.File, newname string) error { + l.Called, l.Target, l.Newname = true, target, newname + return l.Err +} + +// RenameAtMock mocks p9.File.RenameAt. +type RenameAtMock struct { + Called bool + + // Args. + Oldname string + Newdir p9.File + Newname string + + // Return. + Err error +} + +// RenameAt implements p9.File.RenameAt. +func (r *RenameAtMock) RenameAt(oldname string, newdir p9.File, newname string) error { + r.Called, r.Oldname, r.Newdir, r.Newname = true, oldname, newdir, newname + return r.Err +} + +// FlushMock mocks p9.File.Flush. +type FlushMock struct { + Called bool + + // Return. + Err error +} + +// Flush implements p9.File.Flush. +func (f *FlushMock) Flush() error { + return f.Err +} + +// ConnectMock mocks p9.File.Connect. +type ConnectMock struct { + Called bool + + // Args. + Flags p9.ConnectFlags + + // Return. + File *fd.FD + Err error +} + +// Connect implements p9.File.Connect. +func (o *ConnectMock) Connect(flags p9.ConnectFlags) (*fd.FD, error) { + o.Called, o.Flags = true, flags + return o.File, o.Err +} + +// FileMock mocks p9.File. +type FileMock struct { + WalkMock + WalkGetAttrMock + StatFSMock + GetAttrMock + SetAttrMock + RemoveMock + RenameMock + CloseMock + OpenMock + ReadAtMock + WriteAtMock + FSyncMock + CreateMock + MkdirMock + SymlinkMock + LinkMock + MknodMock + RenameAtMock + UnlinkAtMock + ReaddirMock + ReadlinkMock + FlushMock + ConnectMock +} + +var ( + _ p9.File = &FileMock{} +) |