280 lines
8.7 KiB
Go
280 lines
8.7 KiB
Go
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// 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 seqfile
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/context"
|
|
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
|
|
"gvisor.dev/gvisor/pkg/sentry/fs"
|
|
"gvisor.dev/gvisor/pkg/sentry/fs/ramfs"
|
|
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
|
)
|
|
|
|
type seqTest struct {
|
|
actual []SeqData
|
|
update bool
|
|
}
|
|
|
|
func (s *seqTest) Init() {
|
|
var sq []SeqData
|
|
// Create some SeqData.
|
|
for i := 0; i < 10; i++ {
|
|
var b []byte
|
|
for j := 0; j < 10; j++ {
|
|
b = append(b, byte(i))
|
|
}
|
|
sq = append(sq, SeqData{
|
|
Buf: b,
|
|
Handle: &testHandle{i: i},
|
|
})
|
|
}
|
|
s.actual = sq
|
|
}
|
|
|
|
// NeedsUpdate reports whether we need to update the data we've previously read.
|
|
func (s *seqTest) NeedsUpdate(int64) bool {
|
|
return s.update
|
|
}
|
|
|
|
// ReadSeqFiledata returns a slice of SeqData which contains elements
|
|
// greater than the handle.
|
|
func (s *seqTest) ReadSeqFileData(ctx context.Context, handle SeqHandle) ([]SeqData, int64) {
|
|
if handle == nil {
|
|
return s.actual, 0
|
|
}
|
|
h := *handle.(*testHandle)
|
|
var ret []SeqData
|
|
for _, b := range s.actual {
|
|
// We want the next one.
|
|
h2 := *b.Handle.(*testHandle)
|
|
if h2.i > h.i {
|
|
ret = append(ret, b)
|
|
}
|
|
}
|
|
return ret, 0
|
|
}
|
|
|
|
// Flatten a slice of slices into one slice.
|
|
func flatten(buf ...[]byte) []byte {
|
|
var flat []byte
|
|
for _, b := range buf {
|
|
flat = append(flat, b...)
|
|
}
|
|
return flat
|
|
}
|
|
|
|
type testHandle struct {
|
|
i int
|
|
}
|
|
|
|
type testTable struct {
|
|
offset int64
|
|
readBufferSize int
|
|
expectedData []byte
|
|
expectedError error
|
|
}
|
|
|
|
func runTableTests(ctx context.Context, table []testTable, dirent *fs.Dirent) error {
|
|
for _, tt := range table {
|
|
file, err := dirent.Inode.InodeOperations.GetFile(ctx, dirent, fs.FileFlags{Read: true})
|
|
if err != nil {
|
|
return fmt.Errorf("GetFile returned error: %v", err)
|
|
}
|
|
|
|
data := make([]byte, tt.readBufferSize)
|
|
resultLen, err := file.Preadv(ctx, usermem.BytesIOSequence(data), tt.offset)
|
|
if err != tt.expectedError {
|
|
return fmt.Errorf("t.Preadv(len: %v, offset: %v) (error) => %v expected %v", tt.readBufferSize, tt.offset, err, tt.expectedError)
|
|
}
|
|
expectedLen := int64(len(tt.expectedData))
|
|
if resultLen != expectedLen {
|
|
// We make this just an error so we wall through and print the data below.
|
|
return fmt.Errorf("t.Preadv(len: %v, offset: %v) (size) => %v expected %v", tt.readBufferSize, tt.offset, resultLen, expectedLen)
|
|
}
|
|
if !bytes.Equal(data[:expectedLen], tt.expectedData) {
|
|
return fmt.Errorf("t.Preadv(len: %v, offset: %v) (data) => %v expected %v", tt.readBufferSize, tt.offset, data[:expectedLen], tt.expectedData)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestSeqFile(t *testing.T) {
|
|
testSource := &seqTest{}
|
|
testSource.Init()
|
|
|
|
// Create a file that can be R/W.
|
|
ctx := contexttest.Context(t)
|
|
m := fs.NewPseudoMountSource(ctx)
|
|
contents := map[string]*fs.Inode{
|
|
"foo": NewSeqFileInode(ctx, testSource, m),
|
|
}
|
|
root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
|
|
|
|
// How about opening it?
|
|
inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
|
|
dirent2, err := root.Lookup(ctx, inode, "foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to walk to foo for n2: %v", err)
|
|
}
|
|
n2 := dirent2.Inode.InodeOperations
|
|
file2, err := n2.GetFile(ctx, dirent2, fs.FileFlags{Read: true, Write: true})
|
|
if err != nil {
|
|
t.Fatalf("GetFile returned error: %v", err)
|
|
}
|
|
|
|
// Writing?
|
|
if _, err := file2.Writev(ctx, usermem.BytesIOSequence([]byte("test"))); err == nil {
|
|
t.Fatalf("managed to write to n2: %v", err)
|
|
}
|
|
|
|
// How about reading?
|
|
dirent3, err := root.Lookup(ctx, inode, "foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to walk to foo: %v", err)
|
|
}
|
|
n3 := dirent3.Inode.InodeOperations
|
|
if n2 != n3 {
|
|
t.Error("got n2 != n3, want same")
|
|
}
|
|
|
|
testSource.update = true
|
|
|
|
table := []testTable{
|
|
// Read past the end.
|
|
{100, 4, []byte{}, io.EOF},
|
|
{110, 4, []byte{}, io.EOF},
|
|
{200, 4, []byte{}, io.EOF},
|
|
// Read a truncated first line.
|
|
{0, 4, testSource.actual[0].Buf[:4], nil},
|
|
// Read the whole first line.
|
|
{0, 10, testSource.actual[0].Buf, nil},
|
|
// Read the whole first line + 5 bytes of second line.
|
|
{0, 15, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:5]), nil},
|
|
// First 4 bytes of the second line.
|
|
{10, 4, testSource.actual[1].Buf[:4], nil},
|
|
// Read the two first lines.
|
|
{0, 20, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf), nil},
|
|
// Read three lines.
|
|
{0, 30, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf), nil},
|
|
// Read everything, but use a bigger buffer than necessary.
|
|
{0, 150, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf, testSource.actual[2].Buf, testSource.actual[3].Buf, testSource.actual[4].Buf, testSource.actual[5].Buf, testSource.actual[6].Buf, testSource.actual[7].Buf, testSource.actual[8].Buf, testSource.actual[9].Buf), nil},
|
|
// Read the last 3 bytes.
|
|
{97, 10, testSource.actual[9].Buf[7:], nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed with testSource.update = %v : %v", testSource.update, err)
|
|
}
|
|
|
|
// Disable updates and do it again.
|
|
testSource.update = false
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed with testSource.update = %v: %v", testSource.update, err)
|
|
}
|
|
}
|
|
|
|
// Test that we behave correctly when the file is updated.
|
|
func TestSeqFileFileUpdated(t *testing.T) {
|
|
testSource := &seqTest{}
|
|
testSource.Init()
|
|
testSource.update = true
|
|
|
|
// Create a file that can be R/W.
|
|
ctx := contexttest.Context(t)
|
|
m := fs.NewPseudoMountSource(ctx)
|
|
contents := map[string]*fs.Inode{
|
|
"foo": NewSeqFileInode(ctx, testSource, m),
|
|
}
|
|
root := ramfs.NewDir(ctx, contents, fs.RootOwner, fs.FilePermsFromMode(0777))
|
|
|
|
// How about opening it?
|
|
inode := fs.NewInode(ctx, root, m, fs.StableAttr{Type: fs.Directory})
|
|
dirent2, err := root.Lookup(ctx, inode, "foo")
|
|
if err != nil {
|
|
t.Fatalf("failed to walk to foo for dirent2: %v", err)
|
|
}
|
|
|
|
table := []testTable{
|
|
{0, 16, flatten(testSource.actual[0].Buf, testSource.actual[1].Buf[:6]), nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed: %v", err)
|
|
}
|
|
// Delete the first entry.
|
|
cut := testSource.actual[0].Buf
|
|
testSource.actual = testSource.actual[1:]
|
|
|
|
table = []testTable{
|
|
// Try reading buffer 0 with an offset. This will not delete the old data.
|
|
{1, 5, cut[1:6], nil},
|
|
// Reset our file by reading at offset 0.
|
|
{0, 10, testSource.actual[0].Buf, nil},
|
|
{16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
|
|
// Read the same data a second time.
|
|
{16, 14, flatten(testSource.actual[1].Buf[6:], testSource.actual[2].Buf), nil},
|
|
// Read the following two lines.
|
|
{30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed after removing first entry: %v", err)
|
|
}
|
|
|
|
// Add a new duplicate line in the middle (6666...)
|
|
after := testSource.actual[5:]
|
|
testSource.actual = testSource.actual[:4]
|
|
// Note the list must be sorted.
|
|
testSource.actual = append(testSource.actual, after[0])
|
|
testSource.actual = append(testSource.actual, after...)
|
|
|
|
table = []testTable{
|
|
{50, 20, flatten(testSource.actual[4].Buf, testSource.actual[5].Buf), nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed after adding middle entry: %v", err)
|
|
}
|
|
// This will be used in a later test.
|
|
oldTestData := testSource.actual
|
|
|
|
// Delete everything.
|
|
testSource.actual = testSource.actual[:0]
|
|
table = []testTable{
|
|
{20, 20, []byte{}, io.EOF},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed after removing all entries: %v", err)
|
|
}
|
|
// Restore some of the data.
|
|
testSource.actual = oldTestData[:1]
|
|
table = []testTable{
|
|
{6, 20, testSource.actual[0].Buf[6:], nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed after adding first entry back: %v", err)
|
|
}
|
|
|
|
// Re-extend the data
|
|
testSource.actual = oldTestData
|
|
table = []testTable{
|
|
{30, 20, flatten(testSource.actual[3].Buf, testSource.actual[4].Buf), nil},
|
|
}
|
|
if err := runTableTests(ctx, table, dirent2); err != nil {
|
|
t.Errorf("runTableTest failed after extending testSource: %v", err)
|
|
}
|
|
}
|