gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/p9/p9test/p9test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package p9test provides standard mocks for p9.
    16  package p9test
    17  
    18  import (
    19  	"fmt"
    20  	"testing"
    21  
    22  	"github.com/golang/mock/gomock"
    23  	"golang.org/x/sys/unix"
    24  	"gvisor.dev/gvisor/pkg/atomicbitops"
    25  	"gvisor.dev/gvisor/pkg/p9"
    26  	"gvisor.dev/gvisor/pkg/sync"
    27  	"gvisor.dev/gvisor/pkg/unet"
    28  )
    29  
    30  // Harness is an attacher mock.
    31  type Harness struct {
    32  	t            *testing.T
    33  	mockCtrl     *gomock.Controller
    34  	Attacher     *MockAttacher
    35  	wg           sync.WaitGroup
    36  	clientSocket *unet.Socket
    37  	mu           sync.Mutex
    38  	created      []*Mock
    39  }
    40  
    41  // globalPath is a QID.Path Generator.
    42  var globalPath atomicbitops.Uint64
    43  
    44  // MakePath returns a globally unique path.
    45  func MakePath() uint64 {
    46  	return globalPath.Add(1)
    47  }
    48  
    49  // Generator is a function that generates a new file.
    50  type Generator func(parent *Mock) *Mock
    51  
    52  // Mock is a common mock element.
    53  type Mock struct {
    54  	p9.DefaultWalkGetAttr
    55  	*MockFile
    56  	parent   *Mock
    57  	closed   bool
    58  	harness  *Harness
    59  	QID      p9.QID
    60  	Attr     p9.Attr
    61  	children map[string]Generator
    62  
    63  	// WalkCallback is a special function that will be called from within
    64  	// the walk context. This is needed for the concurrent tests within
    65  	// this package.
    66  	WalkCallback func() error
    67  }
    68  
    69  // globalMu protects the children maps in all mocks. Note that this is not a
    70  // particularly elegant solution, but because the test has walks from the root
    71  // through to final nodes, we must share maps below, and it's easiest to simply
    72  // protect against concurrent access globally.
    73  var globalMu sync.RWMutex
    74  
    75  // AddChild adds a new child to the Mock.
    76  func (m *Mock) AddChild(name string, generator Generator) {
    77  	globalMu.Lock()
    78  	defer globalMu.Unlock()
    79  	m.children[name] = generator
    80  }
    81  
    82  // RemoveChild removes the child with the given name.
    83  func (m *Mock) RemoveChild(name string) {
    84  	globalMu.Lock()
    85  	defer globalMu.Unlock()
    86  	delete(m.children, name)
    87  }
    88  
    89  // Matches implements gomock.Matcher.Matches.
    90  func (m *Mock) Matches(x any) bool {
    91  	if om, ok := x.(*Mock); ok {
    92  		return m.QID.Path == om.QID.Path
    93  	}
    94  	return false
    95  }
    96  
    97  // String implements gomock.Matcher.String.
    98  func (m *Mock) String() string {
    99  	return fmt.Sprintf("Mock{Mode: 0x%x, QID.Path: %d}", m.Attr.Mode, m.QID.Path)
   100  }
   101  
   102  // GetAttr returns the current attributes.
   103  func (m *Mock) GetAttr(mask p9.AttrMask) (p9.QID, p9.AttrMask, p9.Attr, error) {
   104  	return m.QID, p9.AttrMaskAll(), m.Attr, nil
   105  }
   106  
   107  // Walk supports clone and walking in directories.
   108  func (m *Mock) Walk(names []string) ([]p9.QID, p9.File, error) {
   109  	if m.WalkCallback != nil {
   110  		if err := m.WalkCallback(); err != nil {
   111  			return nil, nil, err
   112  		}
   113  	}
   114  	if len(names) == 0 {
   115  		// Clone the file appropriately.
   116  		nm := m.harness.NewMock(m.parent, m.QID.Path, m.Attr)
   117  		nm.children = m.children // Inherit children.
   118  		return []p9.QID{nm.QID}, nm, nil
   119  	} else if len(names) != 1 {
   120  		m.harness.t.Fail() // Should not happen.
   121  		return nil, nil, unix.EINVAL
   122  	}
   123  
   124  	if m.Attr.Mode.IsDir() {
   125  		globalMu.RLock()
   126  		defer globalMu.RUnlock()
   127  		if fn, ok := m.children[names[0]]; ok {
   128  			// Generate the child.
   129  			nm := fn(m)
   130  			return []p9.QID{nm.QID}, nm, nil
   131  		}
   132  		// No child found.
   133  		return nil, nil, unix.ENOENT
   134  	}
   135  
   136  	// Call the underlying mock.
   137  	return m.MockFile.Walk(names)
   138  }
   139  
   140  // WalkGetAttr calls the default implementation; this is a client-side optimization.
   141  func (m *Mock) WalkGetAttr(names []string) ([]p9.QID, p9.File, p9.AttrMask, p9.Attr, error) {
   142  	return m.DefaultWalkGetAttr.WalkGetAttr(names)
   143  }
   144  
   145  // Pop pops off the most recently created Mock and assert that this mock
   146  // represents the same file passed in. If nil is passed in, no check is
   147  // performed.
   148  //
   149  // Precondition: there must be at least one Mock or this will panic.
   150  func (h *Harness) Pop(clientFile p9.File) *Mock {
   151  	h.mu.Lock()
   152  	defer h.mu.Unlock()
   153  
   154  	if clientFile == nil {
   155  		// If no clientFile is provided, then we always return the last
   156  		// created file. The caller can safely use this as long as
   157  		// there is no concurrency.
   158  		m := h.created[len(h.created)-1]
   159  		h.created = h.created[:len(h.created)-1]
   160  		return m
   161  	}
   162  
   163  	qid, _, _, err := clientFile.GetAttr(p9.AttrMaskAll())
   164  	if err != nil {
   165  		// We do not expect this to happen.
   166  		panic(fmt.Sprintf("err during Pop: %v", err))
   167  	}
   168  
   169  	// Find the relevant file in our created list. We must scan the last
   170  	// from back to front to ensure that we favor the most recently
   171  	// generated file.
   172  	for i := len(h.created) - 1; i >= 0; i-- {
   173  		m := h.created[i]
   174  		if qid.Path == m.QID.Path {
   175  			// Copy and truncate.
   176  			copy(h.created[i:], h.created[i+1:])
   177  			h.created = h.created[:len(h.created)-1]
   178  			return m
   179  		}
   180  	}
   181  
   182  	// Unable to find relevant file.
   183  	panic(fmt.Sprintf("unable to locate file with QID %+v", qid.Path))
   184  }
   185  
   186  // NewMock returns a new base file.
   187  func (h *Harness) NewMock(parent *Mock, path uint64, attr p9.Attr) *Mock {
   188  	m := &Mock{
   189  		MockFile: NewMockFile(h.mockCtrl),
   190  		parent:   parent,
   191  		harness:  h,
   192  		QID: p9.QID{
   193  			Type: p9.QIDType((attr.Mode & p9.FileModeMask) >> 12),
   194  			Path: path,
   195  		},
   196  		Attr: attr,
   197  	}
   198  
   199  	// Always ensure Close is after the parent's close. Note that this
   200  	// can't be done via a straight-forward After call, because the parent
   201  	// might change after initial creation. We ensure that this is true at
   202  	// close time.
   203  	m.EXPECT().Close().Return(nil).Times(1).Do(func() {
   204  		if m.parent != nil && m.parent.closed {
   205  			h.t.FailNow()
   206  		}
   207  		// Note that this should not be racy, as this operation should
   208  		// be protected by the Times(1) above first.
   209  		m.closed = true
   210  	})
   211  
   212  	// Remember what was created.
   213  	h.mu.Lock()
   214  	defer h.mu.Unlock()
   215  	h.created = append(h.created, m)
   216  
   217  	return m
   218  }
   219  
   220  // NewFile returns a new file mock.
   221  //
   222  // Note that ReadAt and WriteAt must be mocked separately.
   223  func (h *Harness) NewFile() Generator {
   224  	return func(parent *Mock) *Mock {
   225  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeRegular})
   226  	}
   227  }
   228  
   229  // NewDirectory returns a new mock directory.
   230  //
   231  // Note that Mkdir, Link, Mknod, RenameAt, UnlinkAt and Readdir must be mocked
   232  // separately. Walk is provided and children may be manipulated via AddChild
   233  // and RemoveChild. After calling Walk remotely, one can use Pop to find the
   234  // corresponding backend mock on the server side.
   235  func (h *Harness) NewDirectory(contents map[string]Generator) Generator {
   236  	return func(parent *Mock) *Mock {
   237  		m := h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeDirectory})
   238  		m.children = contents // Save contents.
   239  		return m
   240  	}
   241  }
   242  
   243  // NewSymlink returns a new mock directory.
   244  //
   245  // Note that Readlink must be mocked separately.
   246  func (h *Harness) NewSymlink() Generator {
   247  	return func(parent *Mock) *Mock {
   248  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeSymlink})
   249  	}
   250  }
   251  
   252  // NewBlockDevice returns a new mock block device.
   253  func (h *Harness) NewBlockDevice() Generator {
   254  	return func(parent *Mock) *Mock {
   255  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeBlockDevice})
   256  	}
   257  }
   258  
   259  // NewCharacterDevice returns a new mock character device.
   260  func (h *Harness) NewCharacterDevice() Generator {
   261  	return func(parent *Mock) *Mock {
   262  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeCharacterDevice})
   263  	}
   264  }
   265  
   266  // NewNamedPipe returns a new mock named pipe.
   267  func (h *Harness) NewNamedPipe() Generator {
   268  	return func(parent *Mock) *Mock {
   269  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeNamedPipe})
   270  	}
   271  }
   272  
   273  // NewSocket returns a new mock socket.
   274  func (h *Harness) NewSocket() Generator {
   275  	return func(parent *Mock) *Mock {
   276  		return h.NewMock(parent, MakePath(), p9.Attr{Mode: p9.ModeSocket})
   277  	}
   278  }
   279  
   280  // Finish completes all checks and shuts down the server.
   281  func (h *Harness) Finish() {
   282  	h.clientSocket.Shutdown()
   283  	h.wg.Wait()
   284  	h.mockCtrl.Finish()
   285  }
   286  
   287  // NewHarness creates and returns a new test server.
   288  //
   289  // It should always be used as:
   290  //
   291  //	h, c := NewHarness(t)
   292  //	defer h.Finish()
   293  func NewHarness(t *testing.T) (*Harness, *p9.Client) {
   294  	// Create the mock.
   295  	mockCtrl := gomock.NewController(t)
   296  	h := &Harness{
   297  		t:        t,
   298  		mockCtrl: mockCtrl,
   299  		Attacher: NewMockAttacher(mockCtrl),
   300  	}
   301  
   302  	// Make socket pair.
   303  	serverSocket, clientSocket, err := unet.SocketPair(false)
   304  	if err != nil {
   305  		t.Fatalf("socketpair got err %v wanted nil", err)
   306  	}
   307  
   308  	// Start the server, synchronized on exit.
   309  	h.Attacher.EXPECT().ServerOptions().Return(p9.AttacherOptions{}).Times(1)
   310  	server := p9.NewServer(h.Attacher)
   311  	h.wg.Add(1)
   312  	go func() {
   313  		defer h.wg.Done()
   314  		server.Handle(serverSocket)
   315  	}()
   316  
   317  	// Create the client.
   318  	client, err := p9.NewClient(clientSocket, p9.DefaultMessageSize, p9.HighestVersionString())
   319  	if err != nil {
   320  		serverSocket.Close()
   321  		clientSocket.Close()
   322  		t.Fatalf("new client got %v, expected nil", err)
   323  		return nil, nil // Never hit.
   324  	}
   325  
   326  	// Capture the client socket.
   327  	h.clientSocket = clientSocket
   328  	return h, client
   329  }