336 lines
8.4 KiB
Go
336 lines
8.4 KiB
Go
// 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)
|
|
}
|
|
}
|
|
}
|