summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/kernel/pipe/node_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sentry/kernel/pipe/node_test.go')
-rw-r--r--pkg/sentry/kernel/pipe/node_test.go308
1 files changed, 308 insertions, 0 deletions
diff --git a/pkg/sentry/kernel/pipe/node_test.go b/pkg/sentry/kernel/pipe/node_test.go
new file mode 100644
index 000000000..cc1ebf4f6
--- /dev/null
+++ b/pkg/sentry/kernel/pipe/node_test.go
@@ -0,0 +1,308 @@
+// 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 pipe
+
+import (
+ "testing"
+ "time"
+
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/context/contexttest"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/fs"
+ "gvisor.googlesource.com/gvisor/pkg/sentry/usermem"
+ "gvisor.googlesource.com/gvisor/pkg/syserror"
+)
+
+type sleeper struct {
+ context.Context
+ ch chan struct{}
+}
+
+func newSleeperContext(t *testing.T) context.Context {
+ return &sleeper{
+ Context: contexttest.Context(t),
+ ch: make(chan struct{}),
+ }
+}
+
+func (s *sleeper) SleepStart() <-chan struct{} {
+ return s.ch
+}
+
+func (s *sleeper) SleepFinish(bool) {
+}
+
+func (s *sleeper) Cancel() {
+ s.ch <- struct{}{}
+}
+
+type openResult struct {
+ *fs.File
+ error
+}
+
+func testOpenOrDie(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, doneChan chan<- struct{}) (*fs.File, error) {
+ file, err := n.GetFile(ctx, nil, flags)
+ if err != nil {
+ t.Fatalf("open with flags %+v failed: %v", flags, err)
+ }
+ if doneChan != nil {
+ doneChan <- struct{}{}
+ }
+ return file, err
+}
+
+func testOpen(ctx context.Context, t *testing.T, n fs.InodeOperations, flags fs.FileFlags, resChan chan<- openResult) (*fs.File, error) {
+ file, err := n.GetFile(ctx, nil, flags)
+ if resChan != nil {
+ resChan <- openResult{file, err}
+ }
+ return file, err
+}
+
+func newNamedPipe(t *testing.T) *Pipe {
+ return NewPipe(contexttest.Context(t), true, DefaultPipeSize, usermem.PageSize)
+}
+
+func newAnonPipe(t *testing.T) *Pipe {
+ return NewPipe(contexttest.Context(t), false, DefaultPipeSize, usermem.PageSize)
+}
+
+// assertRecvBlocks ensures that a recv attempt on c blocks for at least
+// blockDuration. This is useful for checking that a goroutine that is supposed
+// to be executing a blocking operation is actually blocking.
+func assertRecvBlocks(t *testing.T, c <-chan struct{}, blockDuration time.Duration, failMsg string) {
+ select {
+ case <-c:
+ t.Fatalf(failMsg)
+ case <-time.After(blockDuration):
+ // Ok, blocked for the required duration.
+ }
+}
+
+func TestReadOpenBlocksForWriteOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
+
+ // Verify that the open for read is blocking.
+ assertRecvBlocks(t, rDone, time.Millisecond*100,
+ "open for read not blocking with no writers")
+
+ wDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+
+ <-wDone
+ <-rDone
+}
+
+func TestWriteOpenBlocksForReadOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ wDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+
+ // Verify that the open for write is blocking
+ assertRecvBlocks(t, wDone, time.Millisecond*100,
+ "open for write not blocking with no readers")
+
+ rDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
+
+ <-rDone
+ <-wDone
+}
+
+func TestMultipleWriteOpenDoesntCountAsReadOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rDone1 := make(chan struct{})
+ rDone2 := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone1)
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone2)
+
+ assertRecvBlocks(t, rDone1, time.Millisecond*100,
+ "open for read didn't block with no writers")
+ assertRecvBlocks(t, rDone2, time.Millisecond*100,
+ "open for read didn't block with no writers")
+
+ wDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+
+ <-wDone
+ <-rDone2
+ <-rDone1
+}
+
+func TestClosedReaderBlocksWriteOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rFile, _ := testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil)
+ rFile.DecRef()
+
+ wDone := make(chan struct{})
+ // This open for write should block because the reader is now gone.
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+ assertRecvBlocks(t, wDone, time.Millisecond*100,
+ "open for write didn't block with no concurrent readers")
+
+ // Open for read again. This should unblock the open for write.
+ rDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
+
+ <-rDone
+ <-wDone
+}
+
+func TestReadWriteOpenNeverBlocks(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rwDone := make(chan struct{})
+ // Open for read-write never wait for a reader or writer, even if the
+ // nonblocking flag is not set.
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true, NonBlocking: false}, rwDone)
+ <-rwDone
+}
+
+func TestReadWriteOpenUnblocksReadOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
+
+ rwDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone)
+
+ <-rwDone
+ <-rDone
+}
+
+func TestReadWriteOpenUnblocksWriteOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ wDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+
+ rwDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true, Write: true}, rwDone)
+
+ <-rwDone
+ <-wDone
+}
+
+func TestBlockedOpenIsCancellable(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ done := make(chan openResult)
+ go testOpen(ctx, t, f, fs.FileFlags{Read: true}, done)
+ select {
+ case <-done:
+ t.Fatalf("open for read didn't block with no writers")
+ case <-time.After(time.Millisecond * 100):
+ // Ok.
+ }
+
+ ctx.(*sleeper).Cancel()
+ // If the cancel on the sleeper didn't work, the open for read would never
+ // return.
+ res := <-done
+ if res.error != syserror.ErrInterrupted {
+ t.Fatalf("Cancellation didn't cause GetFile to return fs.ErrInterrupted, got %v.",
+ res.error)
+ }
+}
+
+func TestNonblockingReadOpenNoWriters(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil {
+ t.Fatalf("Nonblocking open for read failed with error %v.", err)
+ }
+}
+
+func TestNonblockingWriteOpenNoReaders(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != syserror.ENXIO {
+ t.Fatalf("Nonblocking open for write failed unexpected error %v.", err)
+ }
+}
+
+func TestNonBlockingReadOpenWithWriter(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ wDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Write: true}, wDone)
+
+ // Open for write blocks since there are no readers yet.
+ assertRecvBlocks(t, wDone, time.Millisecond*100,
+ "Open for write didn't block with no reader.")
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true, NonBlocking: true}, nil); err != nil {
+ t.Fatalf("Nonblocking open for read failed with error %v.", err)
+ }
+
+ // Open for write should now be unblocked.
+ <-wDone
+}
+
+func TestNonBlockingWriteOpenWithReader(t *testing.T) {
+ f := NewInodeOperations(nil, newNamedPipe(t))
+ ctx := newSleeperContext(t)
+
+ rDone := make(chan struct{})
+ go testOpenOrDie(ctx, t, f, fs.FileFlags{Read: true}, rDone)
+
+ // Open for write blocked, since no reader yet.
+ assertRecvBlocks(t, rDone, time.Millisecond*100,
+ "Open for reader didn't block with no writer.")
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true, NonBlocking: true}, nil); err != nil {
+ t.Fatalf("Nonblocking open for write failed with error %v.", err)
+ }
+
+ // Open for write should now be unblocked.
+ <-rDone
+}
+
+func TestAnonReadOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newAnonPipe(t))
+ ctx := newSleeperContext(t)
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Read: true}, nil); err != nil {
+ t.Fatalf("open anon pipe for read failed: %v", err)
+ }
+}
+
+func TestAnonWriteOpen(t *testing.T) {
+ f := NewInodeOperations(nil, newAnonPipe(t))
+ ctx := newSleeperContext(t)
+
+ if _, err := testOpen(ctx, t, f, fs.FileFlags{Write: true}, nil); err != nil {
+ t.Fatalf("open anon pipe for write failed: %v", err)
+ }
+}