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 }