github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/mem_fs_test.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package vfs 6 7 import ( 8 "fmt" 9 "io" 10 "os" 11 "sort" 12 "strconv" 13 "strings" 14 "testing" 15 16 "github.com/cockroachdb/datadriven" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func runTestCases(t *testing.T, testCases []string, fs *MemFS) { 21 var f File 22 for _, tc := range testCases { 23 s := strings.Split(tc, " ")[1:] 24 25 saveF := s[0] == "f" && s[1] == "=" 26 if saveF { 27 s = s[2:] 28 } 29 30 fails := s[len(s)-1] == "fails" 31 if fails { 32 s = s[:len(s)-1] 33 } 34 35 var ( 36 fi os.FileInfo 37 g File 38 err error 39 ) 40 switch s[0] { 41 case "create": 42 g, err = fs.Create(s[1]) 43 case "link": 44 err = fs.Link(s[1], s[2]) 45 case "open": 46 g, err = fs.Open(s[1]) 47 case "openDir": 48 g, err = fs.OpenDir(s[1]) 49 case "mkdirall": 50 err = fs.MkdirAll(s[1], 0755) 51 case "remove": 52 err = fs.Remove(s[1]) 53 case "rename": 54 err = fs.Rename(s[1], s[2]) 55 case "reuseForWrite": 56 g, err = fs.ReuseForWrite(s[1], s[2]) 57 case "resetToSynced": 58 fs.ResetToSyncedState() 59 case "ignoreSyncs": 60 fs.SetIgnoreSyncs(true) 61 case "stopIgnoringSyncs": 62 fs.SetIgnoreSyncs(false) 63 case "f.write": 64 _, err = f.Write([]byte(s[1])) 65 case "f.sync": 66 err = f.Sync() 67 case "f.read": 68 n, _ := strconv.Atoi(s[1]) 69 buf := make([]byte, n) 70 _, err = io.ReadFull(f, buf) 71 if err != nil { 72 break 73 } 74 if got, want := string(buf), s[3]; got != want { 75 t.Fatalf("%q: got %q, want %q", tc, got, want) 76 } 77 case "f.readat": 78 n, _ := strconv.Atoi(s[1]) 79 off, _ := strconv.Atoi(s[2]) 80 buf := make([]byte, n) 81 _, err = f.ReadAt(buf, int64(off)) 82 if err != nil { 83 break 84 } 85 if got, want := string(buf), s[4]; got != want { 86 t.Fatalf("%q: got %q, want %q", tc, got, want) 87 } 88 case "f.close": 89 f, err = nil, f.Close() 90 case "f.stat.name": 91 fi, err = f.Stat() 92 if err != nil { 93 break 94 } 95 if got, want := fi.Name(), s[2]; got != want { 96 t.Fatalf("%q: got %q, want %q", tc, got, want) 97 } 98 default: 99 t.Fatalf("bad test case: %q", tc) 100 } 101 102 if saveF { 103 f, g = g, nil 104 } else if g != nil { 105 g.Close() 106 } 107 108 if fails { 109 if err == nil { 110 t.Fatalf("%q: got nil error, want non-nil", tc) 111 } 112 } else { 113 if err != nil { 114 t.Fatalf("%q: %v", tc, err) 115 } 116 } 117 } 118 119 // Both "" and "/" are allowed to be used to refer to the root of the FS 120 // for the purposes of cloning. 121 checkClonedIsEquivalent(t, fs, "") 122 checkClonedIsEquivalent(t, fs, "/") 123 } 124 125 // Test that the FS can be cloned and that the clone serializes identically. 126 func checkClonedIsEquivalent(t *testing.T, fs *MemFS, path string) { 127 t.Helper() 128 clone := NewMem() 129 cloned, err := Clone(fs, clone, path, path) 130 require.NoError(t, err) 131 require.True(t, cloned) 132 require.Equal(t, fs.String(), clone.String()) 133 } 134 135 func TestBasics(t *testing.T) { 136 fs := NewMem() 137 testCases := []string{ 138 // Create a top-level file. 139 "1a: create /foo", 140 // Create a child of that file. It should fail, since /foo is not a directory. 141 "2a: create /foo/x fails", 142 // Create a third-level file. It should fail, since /bar has not been created. 143 // Similarly, opening that file should fail. 144 "3a: create /bar/baz/y fails", 145 "3b: open /bar/baz/y fails", 146 // Make the /bar/baz directory; create a third-level file. Creation should now succeed. 147 "4a: mkdirall /bar/baz", 148 "4b: f = create /bar/baz/y", 149 "4c: f.stat.name == y", 150 // Write some data; read it back. 151 "5a: f.write abcde", 152 "5b: f.close", 153 "5c: f = open /bar/baz/y", 154 "5d: f.read 5 == abcde", 155 "5e: f.readat 2 1 == bc", 156 "5f: f.close", 157 // Link /bar/baz/y to /bar/baz/z. We should be able to read from both files 158 // and remove them independently. 159 "6a: link /bar/baz/y /bar/baz/z", 160 "6b: f = open /bar/baz/z", 161 "6c: f.read 5 == abcde", 162 "6d: f.close", 163 "6e: remove /bar/baz/z", 164 "6f: f = open /bar/baz/y", 165 "6g: f.read 5 == abcde", 166 "6h: f.close", 167 // Remove the file twice. The first should succeed, the second should fail. 168 "7a: remove /bar/baz/y", 169 "7b: remove /bar/baz/y fails", 170 "7c: open /bar/baz/y fails", 171 // Rename /foo to /goo. Trying to open /foo should succeed before the rename and 172 // fail afterwards, and vice versa for /goo. 173 "8a: open /foo", 174 "8b: open /goo fails", 175 "8c: rename /foo /goo", 176 "8d: open /foo fails", 177 "8e: open /goo", 178 // Create /bar/baz/z and rename /bar/baz to /bar/caz. 179 "9a: create /bar/baz/z", 180 "9b: open /bar/baz/z", 181 "9c: open /bar/caz/z fails", 182 "9d: rename /bar/baz /bar/caz", 183 "9e: open /bar/baz/z fails", 184 "9f: open /bar/caz/z", 185 // ReuseForWrite 186 "10a: reuseForWrite /bar/caz/z /bar/z", 187 "10b: open /bar/caz/z fails", 188 "10c: open /bar/z", 189 // Opening the root directory works. 190 "11a: f = open /", 191 "11b: f.stat.name == /", 192 } 193 runTestCases(t, testCases, fs) 194 } 195 196 func TestList(t *testing.T) { 197 fs := NewMem() 198 199 dirnames := []string{ 200 "/bar", 201 "/foo/2", 202 } 203 for _, dirname := range dirnames { 204 err := fs.MkdirAll(dirname, 0755) 205 if err != nil { 206 t.Fatalf("MkdirAll %q: %v", dirname, err) 207 } 208 } 209 210 filenames := []string{ 211 "/a", 212 "/bar/baz", 213 "/foo/0", 214 "/foo/1", 215 "/foo/2/a", 216 "/foo/2/b", 217 "/foo/3", 218 "/foot", 219 } 220 for _, filename := range filenames { 221 f, err := fs.Create(filename) 222 if err != nil { 223 t.Fatalf("Create %q: %v", filename, err) 224 } 225 if err := f.Close(); err != nil { 226 t.Fatalf("Close %q: %v", filename, err) 227 } 228 } 229 230 { 231 got := fs.String() 232 const want = ` / 233 0 a 234 bar/ 235 0 baz 236 foo/ 237 0 0 238 0 1 239 2/ 240 0 a 241 0 b 242 0 3 243 0 foot 244 ` 245 if got != want { 246 t.Fatalf("String:\n----got----\n%s----want----\n%s", got, want) 247 } 248 } 249 250 testCases := []string{ 251 "/:a bar foo foot", 252 "/bar:baz", 253 "/bar/:baz", 254 "/baz:", 255 "/baz/:", 256 "/foo:0 1 2 3", 257 "/foo/:0 1 2 3", 258 "/foo/1:", 259 "/foo/1/:", 260 "/foo/2:a b", 261 "/foo/2/:a b", 262 "/foot:", 263 "/foot/:", 264 } 265 for _, tc := range testCases { 266 s := strings.Split(tc, ":") 267 list, _ := fs.List(s[0]) 268 sort.Strings(list) 269 got := strings.Join(list, " ") 270 want := s[1] 271 if got != want { 272 t.Errorf("List %q: got %q, want %q", s[0], got, want) 273 } 274 } 275 } 276 277 func TestMemFile(t *testing.T) { 278 want := "foo" 279 f := NewMemFile([]byte(want)) 280 buf, err := io.ReadAll(f) 281 if err != nil { 282 t.Fatalf("%v", err) 283 } 284 if got := string(buf); got != want { 285 t.Fatalf("got %q, want %q", got, want) 286 } 287 } 288 289 func TestStrictFS(t *testing.T) { 290 fs := NewStrictMem() 291 testCases := []string{ 292 // Created file disappears if directory is not synced. 293 "1a: create /foo", 294 "1b: open /foo", 295 "1c: resetToSynced", 296 "1d: open /foo fails", 297 298 // Create directory and a file in it and write and read from it. 299 "2a: mkdirall /bar", 300 "2b: f = create /bar/y", 301 "2c: f.stat.name == y", 302 // Write some data; read it back. 303 "2d: f.write abcde", 304 "2e: f.close", 305 "2f: f = open /bar/y", 306 "2g: f.read 5 == abcde", 307 "2h: f.close", 308 "2i: open /bar", 309 310 // Resetting causes both the directory and file to disappear. 311 "3a: resetToSynced", 312 "3b: openDir /bar fails", 313 "3c: open /bar/y fails", 314 315 // Create the directory and file again. Link the file to another file in the same dir, 316 // and to a file in the root dir. Sync the root dir. After reset, the created dir and the 317 // file in the root dir are the only ones visible. 318 "4a: mkdirall /bar", 319 "4b: create /bar/y", 320 "4c: f = openDir /", 321 "4d: f.sync", 322 "4e: f.close", 323 "4f: link /bar/y /bar/z", 324 "4g: link /bar/y /z", 325 "4h: f = openDir /", 326 "4i: f.sync", 327 "4j: f.close", 328 "4k: resetToSynced", 329 "4l: openDir /bar", 330 "4m: open /bar/y fails", 331 "4n: open /bar/z fails", 332 "4o: open /z", 333 334 // Create the file in the directory again and this time sync /bar directory. The file is 335 // preserved after reset. 336 "5a: create /bar/y", 337 "5b: f = openDir /bar", 338 "5c: f.sync", 339 "5d: f.close", 340 "5e: resetToSynced", 341 "5f: openDir /bar", 342 "5g: open /bar/y", 343 344 // Unsynced data in the file is lost on reset. 345 "5a: f = create /bar/y", 346 "5b: f.write a", 347 "5c: f.sync", 348 "5d: f.write b", 349 "5e: f.close", 350 "5f: f = openDir /bar", 351 "5g: f.sync", 352 "5h: f.close", 353 "5i: resetToSynced", 354 "5j: f = open /bar/y", 355 "5k: f.read 1 = a", 356 "5l: f.read 1 fails", 357 "5m: f.close", 358 359 // reuseForWrite works correctly in strict mode in that unsynced data does not overwrite 360 // previous contents when a reset happens. 361 "6a: f = create /z", 362 "6b: f.write abcdefgh", 363 "6c: f.sync", 364 "6d: f.close", 365 "6e: f = reuseForWrite /z /y", 366 "6f: f.write x", 367 "6g: f.sync", 368 "6h: f.write y", // will be lost 369 "6i: f.close", 370 "6j: f = openDir /", 371 "6k: f.sync", 372 "6l: f.close", 373 "6m: resetToSynced", 374 "6n: f = open /y", 375 "6o: f.read 8 = xbcdefgh", 376 "6p: f.close", 377 378 // Ignore syncs. 379 "7a: f = create /z", 380 "7b: f.write a", 381 "7c: f.sync", 382 "7d: ignoreSyncs", 383 "7e: f.write b", 384 "7f: f.sync", 385 "7g: f.close", 386 "7h: stopIgnoringSyncs", 387 "7e: f = openDir /", 388 "7f: f.sync", 389 "7g: f.close", 390 "7h: resetToSynced", 391 "7i: f = open /z", 392 "7j: f.read 1 = a", 393 "7k: f.read 1 fails", 394 "7l: f.close", 395 } 396 runTestCases(t, testCases, fs) 397 } 398 399 func TestMemFSLock(t *testing.T) { 400 filesystems := map[string]FS{} 401 fileLocks := map[string]io.Closer{} 402 403 datadriven.RunTest(t, "testdata/memfs_lock", func(t *testing.T, td *datadriven.TestData) string { 404 switch td.Cmd { 405 case "mkfs": 406 for _, arg := range td.CmdArgs { 407 filesystems[arg.String()] = NewMem() 408 } 409 return "OK" 410 411 // lock fs=<filesystem-name> handle=<handle> path=<path> 412 case "lock": 413 var filesystemName string 414 var path string 415 var handle string 416 td.ScanArgs(t, "fs", &filesystemName) 417 td.ScanArgs(t, "path", &path) 418 td.ScanArgs(t, "handle", &handle) 419 fs := filesystems[filesystemName] 420 if fs == nil { 421 return fmt.Sprintf("filesystem %q doesn't exist", filesystemName) 422 } 423 l, err := fs.Lock(path) 424 if err != nil { 425 return err.Error() 426 } 427 fileLocks[handle] = l 428 return "OK" 429 430 // mkdirall fs=<filesystem-name> path=<path> 431 case "mkdirall": 432 var filesystemName string 433 var path string 434 td.ScanArgs(t, "fs", &filesystemName) 435 td.ScanArgs(t, "path", &path) 436 fs := filesystems[filesystemName] 437 if fs == nil { 438 return fmt.Sprintf("filesystem %q doesn't exist", filesystemName) 439 } 440 err := fs.MkdirAll(path, 0755) 441 if err != nil { 442 return err.Error() 443 } 444 return "OK" 445 446 // close handle=<handle> 447 case "close": 448 var handle string 449 td.ScanArgs(t, "handle", &handle) 450 err := fileLocks[handle].Close() 451 delete(fileLocks, handle) 452 if err != nil { 453 return err.Error() 454 } 455 return "OK" 456 default: 457 return fmt.Sprintf("unrecognized command %q", td.Cmd) 458 } 459 }) 460 }