github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/node_cache_test.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "runtime" 9 "testing" 10 11 "github.com/keybase/client/go/kbfs/data" 12 "github.com/keybase/client/go/kbfs/kbfsblock" 13 "github.com/keybase/client/go/kbfs/tlf" 14 "github.com/stretchr/testify/require" 15 ) 16 17 func setupNodeCache(t *testing.T, id tlf.ID, branch data.BranchName, flat bool) ( 18 ncs *nodeCacheStandard, parentNode Node, childNode1 Node, childNode2 Node, 19 childPath1 []data.PathNode, childPath2 []data.PathNode) { 20 ncs = newNodeCacheStandard(data.FolderBranch{Tlf: id, Branch: branch}) 21 22 parentPtr := data.BlockPointer{ID: kbfsblock.FakeID(0)} 23 parentName := "parent" 24 var err error 25 parentNode, err = ncs.GetOrCreate( 26 parentPtr, testPPS(parentName), nil, data.Dir) 27 if err != nil { 28 t.Errorf("Couldn't create top-level parent node: %v", err) 29 } 30 if parentNode.GetBasename().Plaintext() != parentName { 31 t.Errorf("Expected basename %s, got %s", 32 parentName, parentNode.GetBasename()) 33 } 34 35 // now create a child node for that parent 36 childPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(1)} 37 childName1 := "child1" 38 childNode1, err = ncs.GetOrCreate( 39 childPtr1, testPPS(childName1), parentNode, data.Dir) 40 if err != nil { 41 t.Errorf("Couldn't create child node: %v", err) 42 } 43 if childNode1.GetBasename().Plaintext() != childName1 { 44 t.Errorf("Expected basename %s, got %s", 45 childName1, childNode1.GetBasename()) 46 } 47 48 parent2 := childNode1 49 if flat { 50 parent2 = parentNode 51 } 52 53 childPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(2)} 54 childName2 := "child2" 55 childNode2, err = ncs.GetOrCreate( 56 childPtr2, testPPS(childName2), parent2, data.Dir) 57 if err != nil { 58 t.Errorf("Couldn't create second child node: %v", err) 59 } 60 if childNode2.GetBasename().Plaintext() != childName2 { 61 t.Errorf("Expected basename %s, got %s", 62 childName2, childNode2.GetBasename()) 63 } 64 65 childPath1 = []data.PathNode{ 66 { 67 BlockPointer: parentPtr, 68 Name: testPPS(parentName), 69 }, 70 { 71 BlockPointer: childPtr1, 72 Name: testPPS(childName1), 73 }, 74 } 75 if flat { 76 childPath2 = []data.PathNode{ 77 { 78 BlockPointer: parentPtr, 79 Name: testPPS(parentName), 80 }, 81 { 82 BlockPointer: childPtr2, 83 Name: testPPS(childName2), 84 }, 85 } 86 } else { 87 childPath2 = []data.PathNode{ 88 { 89 BlockPointer: parentPtr, 90 Name: testPPS(parentName), 91 }, 92 { 93 BlockPointer: childPtr1, 94 Name: testPPS(childName1), 95 }, 96 { 97 BlockPointer: childPtr2, 98 Name: testPPS(childName2), 99 }, 100 } 101 } 102 return 103 } 104 105 // Simulate a GC cycle where all the nodes in liveList still have 106 // references. 107 // 108 // (Doing real GC cycles and running finalizers, etc. is brittle.) 109 func simulateGC(ncs *nodeCacheStandard, liveList []Node) { 110 hasWork := true 111 for hasWork { 112 hasWork = false 113 114 liveSet := make(map[*nodeCore]bool) 115 116 // Everything in liveList is live. 117 for _, n := range liveList { 118 liveSet[n.(*nodeStandard).core] = true 119 } 120 121 // Everything referenced as a parent is live. 122 for _, e := range ncs.nodes { 123 if e.core.parent != nil { 124 p := e.core.parent.Unwrap().(*nodeStandard) 125 liveSet[p.core] = true 126 } 127 } 128 129 // Forget everything not live. 130 for _, e := range ncs.nodes { 131 if _, ok := liveSet[e.core]; !ok { 132 ncs.forget(e.core) 133 hasWork = true 134 } 135 } 136 } 137 } 138 139 // Tests for simple GetOrCreate successes (with and without a parent) 140 func TestNodeCacheGetOrCreateSuccess(t *testing.T) { 141 ncs, parentNode, childNode1A, _, path1, path2 := 142 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 143 parentPtr := path1[0].BlockPointer 144 childPtr1 := path1[1].BlockPointer 145 childPtr2 := path2[1].BlockPointer 146 147 // make sure we get the same node back for the second call 148 childNode1B, err := ncs.GetOrCreate( 149 childPtr1, childNode1A.GetBasename(), parentNode, data.Dir) 150 if err != nil { 151 t.Errorf("Couldn't create child node: %v", err) 152 } 153 if childNode1A.(*nodeStandard).core != childNode1B.(*nodeStandard).core { 154 t.Error("Two creates for the same child!") 155 } 156 157 // now make sure the refCounts are right. 158 if ncs.nodes[parentPtr.Ref()].refCount != 1 { 159 t.Errorf("Parent has wrong refcount: %d", ncs.nodes[parentPtr.Ref()].refCount) 160 } 161 if ncs.nodes[childPtr1.Ref()].refCount != 2 { 162 t.Errorf("Child1 has wrong refcount: %d", ncs.nodes[childPtr1.Ref()].refCount) 163 } 164 if ncs.nodes[childPtr2.Ref()].refCount != 1 { 165 t.Errorf("Child1 has wrong refcount: %d", ncs.nodes[childPtr2.Ref()].refCount) 166 } 167 } 168 169 // Tests that a child can't be created with an unknown parent. 170 func TestNodeCacheGetOrCreateNoParent(t *testing.T) { 171 ncs := newNodeCacheStandard(data.FolderBranch{ 172 Tlf: tlf.FakeID(0, tlf.Private), 173 Branch: "", 174 }) 175 176 parentPtr := data.BlockPointer{ID: kbfsblock.FakeID(0)} 177 parentNode, err := ncs.GetOrCreate( 178 parentPtr, testPPS("parent"), nil, data.Dir) 179 if err != nil { 180 t.Errorf("Couldn't create top-level parent node: %v", err) 181 } 182 183 simulateGC(ncs, []Node{}) 184 185 // now try to create a child node for that parent 186 childPtr1 := data.BlockPointer{ID: kbfsblock.FakeID(1)} 187 _, err = ncs.GetOrCreate(childPtr1, testPPS("child"), parentNode, data.Dir) 188 expectedErr := ParentNodeNotFoundError{parentPtr.Ref()} 189 if err != expectedErr { 190 t.Errorf("Got unexpected error when creating w/o parent: %v", err) 191 } 192 } 193 194 // Tests that UpdatePointer works 195 func TestNodeCacheUpdatePointer(t *testing.T) { 196 ncs := newNodeCacheStandard(data.FolderBranch{ 197 Tlf: tlf.FakeID(0, tlf.Private), 198 Branch: "", 199 }) 200 201 parentPtr := data.BlockPointer{ID: kbfsblock.FakeID(0)} 202 parentNode, err := ncs.GetOrCreate( 203 parentPtr, testPPS("parent"), nil, data.Dir) 204 if err != nil { 205 t.Errorf("Couldn't create top-level parent node: %v", err) 206 } 207 208 newParentPtr := data.BlockPointer{ID: kbfsblock.FakeID(1)} 209 ncs.UpdatePointer(parentPtr.Ref(), newParentPtr) 210 211 if parentNode.(*nodeStandard).core.pathNode.BlockPointer != newParentPtr { 212 t.Errorf("UpdatePointer didn't work.") 213 } 214 } 215 216 // Tests that Move works as expected 217 func TestNodeCacheMoveSuccess(t *testing.T) { 218 ncs, parentNode, childNode1, childNode2, path1, path2 := 219 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 220 parentPtr := path1[0].BlockPointer 221 childPtr1 := path1[1].BlockPointer 222 childPtr2 := path2[1].BlockPointer 223 224 // now move child2 under child1 225 undoMove, err := ncs.Move(childPtr2.Ref(), childNode1, testPPS("child3")) 226 if err != nil { 227 t.Errorf("Couldn't update parent: %v", err) 228 } 229 230 if childNode2.GetBasename().Plaintext() != "child3" { 231 t.Errorf("Child2 has the wrong name after move: %s", 232 childNode2.GetBasename()) 233 } 234 235 if childNode2.(*nodeStandard).core.parent != childNode1 { 236 t.Errorf("UpdateParent didn't work") 237 } 238 239 // now make sure all nodes have 1 reference. 240 if ncs.nodes[parentPtr.Ref()].refCount != 1 { 241 t.Errorf("Parent has wrong refcount: %d", ncs.nodes[parentPtr.Ref()].refCount) 242 } 243 if ncs.nodes[childPtr1.Ref()].refCount != 1 { 244 t.Errorf("Child1 has wrong refcount: %d", ncs.nodes[childPtr1.Ref()].refCount) 245 } 246 if ncs.nodes[childPtr2.Ref()].refCount != 1 { 247 t.Errorf("Child1 has wrong refcount: %d", ncs.nodes[childPtr2.Ref()].refCount) 248 } 249 250 undoMove() 251 if childNode2.GetBasename().Plaintext() != "child2" { 252 t.Errorf("Child2 has the wrong name after move: %s", 253 childNode2.GetBasename()) 254 } 255 if childNode2.(*nodeStandard).core.parent != parentNode { 256 t.Errorf("UpdateParent didn't work") 257 } 258 259 } 260 261 // Tests that a child can't be updated with an unknown parent 262 func TestNodeCacheMoveNoParent(t *testing.T) { 263 ncs, _, childNode1, childNode2, path1, path2 := 264 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 265 childPtr1 := path1[1].BlockPointer 266 childPtr2 := path2[1].BlockPointer 267 268 // get rid of child1 269 simulateGC(ncs, []Node{childNode2}) 270 271 // now move child2 under child1 272 _, err := ncs.Move(childPtr2.Ref(), childNode1, testPPS("child3")) 273 expectedErr := ParentNodeNotFoundError{childPtr1.Ref()} 274 if err != expectedErr { 275 t.Errorf("Got unexpected error when updating parent: %v", err) 276 } 277 } 278 279 func checkNodeCachePath(t *testing.T, id tlf.ID, branch data.BranchName, 280 path data.Path, expectedPath []data.PathNode) { 281 if len(path.Path) != len(expectedPath) { 282 t.Errorf("Bad path length: %v vs %v", len(path.Path), len(expectedPath)) 283 } 284 285 for i, n := range expectedPath { 286 if path.Path[i] != n { 287 t.Errorf("Bad node on path, index %d: %v vs %v", i, path.Path[i], n) 288 } 289 } 290 if path.Tlf != id { 291 t.Errorf("Wrong top dir: %v vs %v", path.Tlf, id) 292 } 293 if path.Branch != branch { 294 t.Errorf("Wrong branch: %s vs %s", path.Branch, branch) 295 } 296 } 297 298 // Tests that a child can be unlinked completely from the parent, and 299 // still have a path, but not a basename. 300 func TestNodeCacheUnlink(t *testing.T) { 301 id := tlf.FakeID(42, tlf.Private) 302 branch := data.BranchName("testBranch") 303 ncs, _, _, childNode2, _, path2 := 304 setupNodeCache(t, id, branch, false) 305 childPtr2 := path2[2].BlockPointer 306 307 // unlink child2 308 undoFn := ncs.Unlink( 309 childPtr2.Ref(), ncs.PathFromNode(childNode2), data.DirEntry{}) 310 if undoFn == nil { 311 t.Fatalf("Couldn't unlink") 312 } 313 314 path := ncs.PathFromNode(childNode2) 315 checkNodeCachePath(t, id, branch, path, path2) 316 317 if childNode2.GetBasename().Plaintext() != "" { 318 t.Errorf("Expected empty basename, got %s", childNode2.GetBasename()) 319 } 320 321 // Undo 322 undoFn() 323 if childNode2.GetBasename().Plaintext() != path2[2].Name.Plaintext() { 324 t.Errorf("Expected basename %s, got %s", 325 path2[2].Name, childNode2.GetBasename()) 326 } 327 } 328 329 // Tests that a child's ancestor can be unlinked completely from its 330 // parent, and the child still has a path and a basename. 331 func TestNodeCacheUnlinkParent(t *testing.T) { 332 id := tlf.FakeID(42, tlf.Private) 333 branch := data.BranchName("testBranch") 334 ncs, _, childNode1, childNode2, _, path2 := 335 setupNodeCache(t, id, branch, false) 336 childPtr1 := path2[1].BlockPointer 337 338 // unlink node 2's parent 339 undoFn := ncs.Unlink( 340 childPtr1.Ref(), ncs.PathFromNode(childNode1), data.DirEntry{}) 341 if undoFn == nil { 342 t.Fatalf("Couldn't unlink") 343 } 344 345 path := ncs.PathFromNode(childNode2) 346 checkNodeCachePath(t, id, branch, path, path2) 347 348 if childNode2.GetBasename().Plaintext() != "child2" { 349 t.Errorf("Expected basename child2, got %s", childNode2.GetBasename()) 350 } 351 } 352 353 // Tests that a child can be unlinked completely from the parent, and 354 // then re-added with a new pointer and still work, but with a new 355 // node core. 356 func TestNodeCacheUnlinkThenRelink(t *testing.T) { 357 id := tlf.FakeID(42, tlf.Private) 358 branch := data.BranchName("testBranch") 359 ncs, _, childNode1, childNode2, _, path2 := 360 setupNodeCache(t, id, branch, false) 361 childPtr2 := path2[2].BlockPointer 362 363 // unlink child2 364 undoFn := ncs.Unlink( 365 childPtr2.Ref(), ncs.PathFromNode(childNode2), data.DirEntry{}) 366 if undoFn == nil { 367 t.Fatalf("Couldn't unlink") 368 } 369 370 newChildName := "newChildName" 371 newChildPtr2 := data.BlockPointer{ID: kbfsblock.FakeID(22)} 372 ncs.UpdatePointer(childPtr2.Ref(), newChildPtr2) // NO-OP 373 childNode2B, err := ncs.GetOrCreate( 374 newChildPtr2, testPPS(newChildName), childNode1, data.Dir) 375 if err != nil { 376 t.Fatalf("Couldn't relink node: %v", err) 377 } 378 if childNode2.GetID() == childNode2B.GetID() { 379 t.Errorf("Relink left the node the same") 380 } 381 382 // Old unlinked node didn't get updated 383 path := ncs.PathFromNode(childNode2) 384 checkNodeCachePath(t, id, branch, path, path2) 385 386 // New node 387 path = ncs.PathFromNode(childNode2B) 388 path2[2].BlockPointer = newChildPtr2 389 path2[2].Name = testPPS(newChildName) 390 checkNodeCachePath(t, id, branch, path, path2) 391 392 if g, e := childNode2.GetBasename().Plaintext(), ""; g != e { 393 t.Errorf("Expected basename %s, got %s", e, g) 394 } 395 if g, e := childNode2B.GetBasename().Plaintext(), newChildName; g != e { 396 t.Errorf("Expected basename %s, got %s", e, g) 397 } 398 } 399 400 // Tests that PathFromNode works correctly 401 func TestNodeCachePathFromNode(t *testing.T) { 402 id := tlf.FakeID(42, tlf.Private) 403 branch := data.BranchName("testBranch") 404 ncs, _, _, childNode2, _, path2 := 405 setupNodeCache(t, id, branch, false) 406 path := ncs.PathFromNode(childNode2) 407 checkNodeCachePath(t, id, branch, path, path2) 408 } 409 410 // Make sure that (simulated) GC works as expected. 411 func TestNodeCacheGCBasic(t *testing.T) { 412 ncs, parentNode, _, childNode2, _, _ := 413 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 414 415 if len(ncs.nodes) != 3 { 416 t.Errorf("Expected %d nodes, got %d", 3, len(ncs.nodes)) 417 } 418 419 simulateGC(ncs, []Node{parentNode, childNode2}) 420 421 if len(ncs.nodes) != 2 { 422 t.Errorf("Expected %d nodes, got %d", 2, len(ncs.nodes)) 423 } 424 425 simulateGC(ncs, []Node{parentNode}) 426 427 if len(ncs.nodes) != 1 { 428 t.Errorf("Expected %d nodes, got %d", 1, len(ncs.nodes)) 429 } 430 431 simulateGC(ncs, []Node{}) 432 433 if len(ncs.nodes) != 0 { 434 t.Errorf("Expected %d nodes, got %d", 0, len(ncs.nodes)) 435 } 436 } 437 438 // Make sure that GC works as expected when a child node holds the 439 // last reference to a parent. 440 func TestNodeCacheGCParent(t *testing.T) { 441 ncs, _, _, childNode2, _, _ := 442 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 443 444 if len(ncs.nodes) != 3 { 445 t.Errorf("Expected %d nodes, got %d", 3, len(ncs.nodes)) 446 } 447 448 simulateGC(ncs, []Node{childNode2}) 449 450 if len(ncs.nodes) != 2 { 451 t.Errorf("Expected %d nodes, got %d", 2, len(ncs.nodes)) 452 } 453 454 simulateGC(ncs, []Node{}) 455 456 if len(ncs.nodes) != 0 { 457 t.Errorf("Expected %d nodes, got %d", 0, len(ncs.nodes)) 458 } 459 } 460 461 var finalizerChan = make(chan struct{}) 462 463 // Like nodeStandardFinalizer(), but sends on finalizerChan 464 // afterwards. 465 func testNodeStandardFinalizer(n *nodeStandard) { 466 nodeStandardFinalizer(n) 467 finalizerChan <- struct{}{} 468 } 469 470 // Make sure that that making a node unreachable runs the finalizer on GC. 471 func TestNodeCacheGCReal(t *testing.T) { 472 ncs, _, childNode1, childNode2, _, _ := 473 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, true) 474 475 if len(ncs.nodes) != 3 { 476 t.Errorf("Expected %d nodes, got %d", 3, len(ncs.nodes)) 477 } 478 479 runtime.SetFinalizer(childNode1, nil) 480 runtime.SetFinalizer(childNode1, testNodeStandardFinalizer) 481 482 childNode1 = nil // nolint 483 runtime.GC() 484 <-finalizerChan 485 486 require.Len(t, ncs.nodes, 2) 487 488 // Make sure childNode2 isn't GCed until after this point. 489 runtime.KeepAlive(childNode2) 490 } 491 492 type wrappedTestNode struct { 493 Node 494 wrapChildCalled bool 495 } 496 497 func (wtn *wrappedTestNode) WrapChild(child Node) Node { 498 child = wtn.Node.WrapChild(child) 499 wtn.wrapChildCalled = true 500 return child 501 } 502 503 func TestNodeCacheWrapChild(t *testing.T) { 504 ncs := newNodeCacheStandard( 505 data.FolderBranch{ 506 Tlf: tlf.FakeID(0, tlf.Private), 507 Branch: data.MasterBranch, 508 }) 509 var wtn1, wtn2 *wrappedTestNode 510 rw1 := func(root Node) Node { 511 wtn1 = &wrappedTestNode{root, false} 512 return wtn1 513 } 514 rw2 := func(root Node) Node { 515 wtn2 = &wrappedTestNode{root, false} 516 return wtn2 517 } 518 ncs.AddRootWrapper(rw1) 519 ncs.AddRootWrapper(rw2) 520 521 rootPtr := data.BlockPointer{ID: kbfsblock.FakeID(0)} 522 rootName := "root" 523 rootNode, err := ncs.GetOrCreate(rootPtr, testPPS(rootName), nil, data.Dir) 524 require.NoError(t, err) 525 526 childPtr := data.BlockPointer{ID: kbfsblock.FakeID(1)} 527 childName := "child1" 528 _, err = ncs.GetOrCreate(childPtr, testPPS(childName), rootNode, data.Dir) 529 require.NoError(t, err) 530 require.True(t, wtn1.wrapChildCalled) 531 require.True(t, wtn2.wrapChildCalled) 532 } 533 534 func TestNodeCacheAllNodeChildren(t *testing.T) { 535 ncs, parentNode, childNode1, childNode2, _, _ := 536 setupNodeCache(t, tlf.FakeID(0, tlf.Private), data.MasterBranch, false) 537 538 // Structure: 539 // parent: 540 // child1: 541 // child2 542 // child3 543 // child4 544 545 childPtr3 := data.BlockPointer{ID: kbfsblock.FakeID(3)} 546 childName3 := "child3" 547 _, err := ncs.GetOrCreate( 548 childPtr3, testPPS(childName3), childNode1, data.Dir) 549 require.NoError(t, err) 550 551 childPtr4 := data.BlockPointer{ID: kbfsblock.FakeID(4)} 552 childName4 := "child4" 553 _, err = ncs.GetOrCreate( 554 childPtr4, testPPS(childName4), parentNode, data.Dir) 555 require.NoError(t, err) 556 557 parentChildren := ncs.AllNodeChildren(parentNode) 558 require.Len(t, parentChildren, 4) 559 560 child1Children := ncs.AllNodeChildren(childNode1) 561 require.Len(t, child1Children, 2) 562 563 child2Children := ncs.AllNodeChildren(childNode2) 564 require.Len(t, child2Children, 0) 565 566 t.Log("Move child3 under the parent node.") 567 _, err = ncs.Move(childPtr3.Ref(), parentNode, testPPS("child3")) 568 require.NoError(t, err) 569 570 parentChildren = ncs.AllNodeChildren(parentNode) 571 require.Len(t, parentChildren, 4) 572 573 child1Children = ncs.AllNodeChildren(childNode1) 574 require.Len(t, child1Children, 1) 575 } 576 577 func TestNodeCacheObfuscator(t *testing.T) { 578 ncs := newNodeCacheStandard( 579 data.FolderBranch{ 580 Tlf: tlf.FakeID(0, tlf.Private), 581 Branch: data.MasterBranch, 582 }) 583 ncs.SetObfuscatorMaker(func() data.Obfuscator { 584 return data.NewNodeObfuscator(nil) 585 }) 586 587 t.Log("Root node should have an obfuscator") 588 rootPtr := data.BlockPointer{ID: kbfsblock.FakeID(0)} 589 rootName := "root" 590 rootNode, err := ncs.GetOrCreate(rootPtr, testPPS(rootName), nil, data.Dir) 591 require.NoError(t, err) 592 rootOb := rootNode.Obfuscator() 593 require.NotNil(t, rootOb) 594 595 t.Log("A new root node should have the same obfuscator") 596 rootNode2 := ncs.Get(rootPtr.Ref()) 597 rootOb2 := rootNode2.Obfuscator() 598 require.NotNil(t, rootOb2) 599 require.True(t, rootOb == rootOb2) 600 601 t.Log("Child file should not have an obfuscator") 602 childPtr := data.BlockPointer{ID: kbfsblock.FakeID(1)} 603 childName := "child1" 604 childNode, err := ncs.GetOrCreate( 605 childPtr, testPPS(childName), rootNode, data.File) 606 require.NoError(t, err) 607 childOb := childNode.Obfuscator() 608 require.Nil(t, childOb) 609 }