github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/data/dir_data_test.go (about) 1 // Copyright 2018 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 data 6 7 import ( 8 "fmt" 9 "reflect" 10 "strconv" 11 "testing" 12 13 "github.com/keybase/client/go/kbfs/idutil" 14 "github.com/keybase/client/go/kbfs/kbfsblock" 15 "github.com/keybase/client/go/kbfs/libkey" 16 libkeytest "github.com/keybase/client/go/kbfs/libkey/test" 17 "github.com/keybase/client/go/kbfs/tlf" 18 "github.com/keybase/client/go/libkb" 19 "github.com/keybase/client/go/logger" 20 "github.com/keybase/client/go/protocol/keybase1" 21 "github.com/stretchr/testify/require" 22 "golang.org/x/net/context" 23 ) 24 25 func setupDirDataTest(t *testing.T, maxPtrsPerBlock, numDirEntries int) ( 26 *DirData, BlockCache, DirtyBlockCache) { 27 // Make a fake dir. 28 ptr := BlockPointer{ 29 ID: kbfsblock.FakeID(42), 30 DirectType: DirectBlock, 31 } 32 id := tlf.FakeID(1, tlf.Private) 33 dir := Path{ 34 FolderBranch{Tlf: id}, 35 []PathNode{{ptr, NewPathPartString("dir", nil)}}, 36 nil, 37 } 38 chargedTo := keybase1.MakeTestUID(1).AsUserOrTeam() 39 bsplit := &BlockSplitterSimple{10, maxPtrsPerBlock, 10, numDirEntries} 40 kmd := libkeytest.NewEmptyKeyMetadata(id, 1) 41 42 cleanCache := NewBlockCacheStandard(1<<10, 1<<20) 43 dirtyBcache := SimpleDirtyBlockCacheStandard() 44 getter := func(ctx context.Context, _ libkey.KeyMetadata, ptr BlockPointer, 45 _ Path, _ BlockReqType) (*DirBlock, bool, error) { 46 isDirty := true 47 block, err := dirtyBcache.Get(ctx, id, ptr, MasterBranch) 48 if err != nil { 49 // Check the clean cache. 50 block, err = cleanCache.Get(ptr) 51 if err != nil { 52 return nil, false, err 53 } 54 isDirty = false 55 } 56 dblock, ok := block.(*DirBlock) 57 if !ok { 58 return nil, false, 59 fmt.Errorf("Block for %s is not a dir block", ptr) 60 } 61 return dblock, isDirty, nil 62 } 63 cacher := func(ctx context.Context, ptr BlockPointer, block Block) error { 64 return dirtyBcache.Put(ctx, id, ptr, MasterBranch, block) 65 } 66 67 log := logger.NewTestLogger(t) 68 dd := NewDirData( 69 dir, chargedTo, bsplit, kmd, getter, cacher, log, 70 libkb.NewVDebugLog(log)) 71 return dd, cleanCache, dirtyBcache 72 } 73 74 func addFakeDirDataEntryToBlock(dblock *DirBlock, name string, size uint64) { 75 dblock.Children[name] = DirEntry{ 76 EntryInfo: EntryInfo{ 77 Size: size, 78 }, 79 } 80 } 81 82 func TestDirDataGetChildren(t *testing.T) { 83 dd, cleanBcache, _ := setupDirDataTest(t, 2, 2) 84 ctx := context.Background() 85 topBlock := NewDirBlock().(*DirBlock) 86 err := cleanBcache.Put( 87 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 88 SkipCacheHash) 89 require.NoError(t, err) 90 91 t.Log("No entries, direct block") 92 children, err := dd.GetChildren(ctx) 93 require.NoError(t, err) 94 require.Len(t, children, 0) 95 96 t.Log("Single entry, direct block") 97 addFakeDirDataEntryToBlock(topBlock, "a", 1) 98 children, err = dd.GetChildren(ctx) 99 require.NoError(t, err) 100 require.Len(t, children, 1) 101 require.Equal(t, uint64(1), children[NewPathPartString("a", nil)].Size) 102 103 t.Log("Two entries, direct block") 104 addFakeDirDataEntryToBlock(topBlock, "b", 2) 105 children, err = dd.GetChildren(ctx) 106 require.NoError(t, err) 107 require.Len(t, children, 2) 108 require.Equal(t, uint64(1), children[NewPathPartString("a", nil)].Size) 109 require.Equal(t, uint64(2), children[NewPathPartString("b", nil)].Size) 110 111 t.Log("Indirect blocks") 112 dd.tree.file.Path[len(dd.tree.file.Path)-1].DirectType = IndirectBlock 113 newTopBlock := NewDirBlock().(*DirBlock) 114 newTopBlock.IsInd = true 115 ptr1 := BlockPointer{ 116 ID: kbfsblock.FakeID(43), 117 DirectType: DirectBlock, 118 } 119 newTopBlock.IPtrs = append(newTopBlock.IPtrs, IndirectDirPtr{ 120 BlockInfo: BlockInfo{ptr1, 0}, 121 Off: "", 122 }) 123 ptr2 := BlockPointer{ 124 ID: kbfsblock.FakeID(44), 125 DirectType: DirectBlock, 126 } 127 newTopBlock.IPtrs = append(newTopBlock.IPtrs, IndirectDirPtr{ 128 BlockInfo: BlockInfo{ptr2, 0}, 129 Off: "m", 130 }) 131 block2 := NewDirBlock().(*DirBlock) 132 addFakeDirDataEntryToBlock(block2, "z1", 3) 133 addFakeDirDataEntryToBlock(block2, "z2", 4) 134 err = cleanBcache.Put( 135 dd.rootBlockPointer(), dd.tree.file.Tlf, newTopBlock, TransientEntry, 136 SkipCacheHash) 137 require.NoError(t, err) 138 err = cleanBcache.Put( 139 ptr1, dd.tree.file.Tlf, topBlock, TransientEntry, SkipCacheHash) 140 require.NoError(t, err) 141 err = cleanBcache.Put( 142 ptr2, dd.tree.file.Tlf, block2, TransientEntry, SkipCacheHash) 143 require.NoError(t, err) 144 children, err = dd.GetChildren(ctx) 145 require.NoError(t, err) 146 require.Len(t, children, 4) 147 require.Equal(t, uint64(1), children[NewPathPartString("a", nil)].Size) 148 require.Equal(t, uint64(2), children[NewPathPartString("b", nil)].Size) 149 require.Equal(t, uint64(3), children[NewPathPartString("z1", nil)].Size) 150 require.Equal(t, uint64(4), children[NewPathPartString("z2", nil)].Size) 151 152 } 153 154 func testDirDataCheckLookup( 155 ctx context.Context, t *testing.T, dd *DirData, name string, size uint64) { 156 de, err := dd.Lookup(ctx, NewPathPartString(name, nil)) 157 require.NoError(t, err) 158 require.Equal(t, size, de.Size) 159 } 160 161 func TestDirDataLookup(t *testing.T) { 162 dd, cleanBcache, _ := setupDirDataTest(t, 2, 2) 163 ctx := context.Background() 164 topBlock := NewDirBlock().(*DirBlock) 165 err := cleanBcache.Put( 166 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 167 SkipCacheHash) 168 require.NoError(t, err) 169 170 t.Log("No entries, direct block") 171 _, err = dd.Lookup(ctx, NewPathPartString("a", nil)) 172 require.Equal(t, idutil.NoSuchNameError{Name: "a"}, err) 173 174 t.Log("Single entry, direct block") 175 addFakeDirDataEntryToBlock(topBlock, "a", 1) 176 testDirDataCheckLookup(ctx, t, dd, "a", 1) 177 _, err = dd.Lookup(ctx, NewPathPartString("b", nil)) 178 require.Equal(t, idutil.NoSuchNameError{Name: "b"}, err) 179 180 t.Log("Indirect blocks") 181 addFakeDirDataEntryToBlock(topBlock, "b", 2) 182 dd.tree.file.Path[len(dd.tree.file.Path)-1].DirectType = IndirectBlock 183 newTopBlock := NewDirBlock().(*DirBlock) 184 newTopBlock.IsInd = true 185 ptr1 := BlockPointer{ 186 ID: kbfsblock.FakeID(43), 187 DirectType: DirectBlock, 188 } 189 newTopBlock.IPtrs = append(newTopBlock.IPtrs, IndirectDirPtr{ 190 BlockInfo: BlockInfo{ptr1, 0}, 191 Off: "", 192 }) 193 ptr2 := BlockPointer{ 194 ID: kbfsblock.FakeID(44), 195 DirectType: DirectBlock, 196 } 197 newTopBlock.IPtrs = append(newTopBlock.IPtrs, IndirectDirPtr{ 198 BlockInfo: BlockInfo{ptr2, 0}, 199 Off: "m", 200 }) 201 block2 := NewDirBlock().(*DirBlock) 202 addFakeDirDataEntryToBlock(block2, "z1", 3) 203 addFakeDirDataEntryToBlock(block2, "z2", 4) 204 err = cleanBcache.Put( 205 dd.rootBlockPointer(), dd.tree.file.Tlf, newTopBlock, TransientEntry, 206 SkipCacheHash) 207 require.NoError(t, err) 208 err = cleanBcache.Put( 209 ptr1, dd.tree.file.Tlf, topBlock, TransientEntry, SkipCacheHash) 210 require.NoError(t, err) 211 err = cleanBcache.Put( 212 ptr2, dd.tree.file.Tlf, block2, TransientEntry, SkipCacheHash) 213 require.NoError(t, err) 214 215 testDirDataCheckLookup(ctx, t, dd, "a", 1) 216 testDirDataCheckLookup(ctx, t, dd, "b", 2) 217 testDirDataCheckLookup(ctx, t, dd, "z1", 3) 218 testDirDataCheckLookup(ctx, t, dd, "z2", 4) 219 } 220 221 func addFakeDirDataEntry( 222 ctx context.Context, t *testing.T, dd *DirData, name string, size uint64) { 223 _, err := dd.AddEntry(ctx, NewPathPartString(name, nil), DirEntry{ 224 EntryInfo: EntryInfo{ 225 Size: size, 226 }, 227 }) 228 require.NoError(t, err) 229 } 230 231 type testDirDataLeaf struct { 232 off StringOffset 233 numEntries int 234 dirty bool 235 } 236 237 func testDirDataCheckLeafs( 238 t *testing.T, dd *DirData, cleanBcache BlockCache, 239 dirtyBcache DirtyBlockCache, expectedLeafs []testDirDataLeaf, 240 maxPtrsPerBlock, numDirEntries int) { 241 // Top block should always be dirty. 242 ctx := context.Background() 243 cacheBlock, err := dirtyBcache.Get( 244 ctx, dd.tree.file.Tlf, dd.tree.rootBlockPointer(), MasterBranch) 245 require.NoError(t, err) 246 topBlock := cacheBlock.(*DirBlock) 247 require.True(t, topBlock.IsIndirect()) 248 249 dirtyBlocks := make(map[*DirBlock]bool) 250 dirtyBlocks[topBlock] = true 251 252 var leafs []testDirDataLeaf 253 // Iterate and collect leafs. 254 indBlocks := []*DirBlock{topBlock} 255 for len(indBlocks) > 0 { 256 var newIndBlocks []*DirBlock 257 for i, iptr := range indBlocks[0].IPtrs { 258 var nextOff *StringOffset 259 if i+1 < len(indBlocks[0].IPtrs) { 260 nextOff = &indBlocks[0].IPtrs[i+1].Off 261 } 262 263 cacheBlock, err = dirtyBcache.Get( 264 ctx, dd.tree.file.Tlf, iptr.BlockPointer, MasterBranch) 265 wasDirty := err == nil 266 if wasDirty { 267 dirtyBlocks[cacheBlock.(*DirBlock)] = true 268 // Parent must have also been dirty. 269 require.Contains(t, dirtyBlocks, indBlocks[0]) 270 } else { 271 cacheBlock, err = cleanBcache.Get(iptr.BlockPointer) 272 require.NoError(t, err) 273 } 274 dblock := cacheBlock.(*DirBlock) 275 if dblock.IsIndirect() { 276 require.True(t, len(dblock.IPtrs) <= maxPtrsPerBlock) 277 // Make sure all the offsets are between the two 278 // parent offsets. 279 for _, childIPtr := range dblock.IPtrs { 280 require.True(t, childIPtr.Off >= iptr.Off, 281 fmt.Sprintf("Child off %s comes before iptr off %s", 282 childIPtr.Off, iptr.Off)) 283 if nextOff != nil { 284 require.True(t, childIPtr.Off < *nextOff, 285 fmt.Sprintf("Child off %s comes after next off %s", 286 childIPtr.Off, *nextOff)) 287 } 288 } 289 newIndBlocks = append(newIndBlocks, dblock) 290 } else { 291 require.True(t, len(dblock.Children) <= numDirEntries) 292 // Make sure all the children are between the two 293 // parent offsets. 294 for name := range dblock.Children { 295 require.True(t, name >= string(iptr.Off)) 296 if nextOff != nil { 297 require.True(t, name < string(*nextOff)) 298 } 299 } 300 leafs = append(leafs, testDirDataLeaf{ 301 iptr.Off, len(dblock.Children), wasDirty}) 302 } 303 } 304 indBlocks = append(newIndBlocks, indBlocks[1:]...) 305 } 306 307 require.True(t, reflect.DeepEqual(leafs, expectedLeafs), 308 fmt.Sprintf("leafs=%v, expectedLeafs=%v", leafs, expectedLeafs)) 309 } 310 311 func testDirDataCleanCache( 312 t *testing.T, dd *DirData, cleanBCache BlockCache, 313 dirtyBCache DirtyBlockCache) { 314 dbc := dirtyBCache.(*DirtyBlockCacheStandard) 315 for id, block := range dbc.cache { 316 ptr := BlockPointer{ID: id.id} 317 err := cleanBCache.Put( 318 ptr, dd.tree.file.Tlf, block, TransientEntry, SkipCacheHash) 319 require.NoError(t, err) 320 } 321 dbc.cache = make(map[dirtyBlockID]Block) 322 } 323 324 func TestDirDataAddEntry(t *testing.T) { 325 dd, cleanBcache, dirtyBcache := setupDirDataTest(t, 2, 2) 326 ctx := context.Background() 327 topBlock := NewDirBlock().(*DirBlock) 328 err := cleanBcache.Put( 329 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 330 SkipCacheHash) 331 require.NoError(t, err) 332 333 t.Log("Add first entry") 334 addFakeDirDataEntry(ctx, t, dd, "a", 1) 335 require.True(t, dirtyBcache.IsDirty( 336 dd.tree.file.Tlf, dd.rootBlockPointer(), MasterBranch)) 337 require.Len(t, topBlock.Children, 1) 338 339 t.Log("Force a split") 340 addFakeDirDataEntry(ctx, t, dd, "b", 2) 341 addFakeDirDataEntry(ctx, t, dd, "c", 3) 342 expectedLeafs := []testDirDataLeaf{ 343 {"", 1, true}, 344 {"b", 2, true}, 345 } 346 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 347 348 t.Log("Fill in the first block") 349 addFakeDirDataEntry(ctx, t, dd, "a1", 4) 350 expectedLeafs = []testDirDataLeaf{ 351 {"", 2, true}, 352 {"b", 2, true}, 353 } 354 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 355 356 t.Log("Shift a block over") 357 addFakeDirDataEntry(ctx, t, dd, "a2", 5) 358 expectedLeafs = []testDirDataLeaf{ 359 {"", 1, true}, 360 {"a1", 2, true}, 361 {"b", 2, true}, 362 } 363 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 364 365 t.Log("Clean up the cache and dirty just one leaf") 366 testDirDataCleanCache(t, dd, cleanBcache, dirtyBcache) 367 addFakeDirDataEntry(ctx, t, dd, "a0", 6) 368 expectedLeafs = []testDirDataLeaf{ 369 {"", 2, true}, 370 {"a1", 2, false}, 371 {"b", 2, false}, 372 } 373 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 374 375 t.Log("Expand a bunch more") 376 addFakeDirDataEntry(ctx, t, dd, "a00", 7) 377 addFakeDirDataEntry(ctx, t, dd, "b1", 8) 378 addFakeDirDataEntry(ctx, t, dd, "d", 9) 379 addFakeDirDataEntry(ctx, t, dd, "a000", 10) 380 addFakeDirDataEntry(ctx, t, dd, "z", 11) 381 addFakeDirDataEntry(ctx, t, dd, "q", 12) 382 addFakeDirDataEntry(ctx, t, dd, "b2", 13) 383 addFakeDirDataEntry(ctx, t, dd, " 1", 14) 384 expectedLeafs = []testDirDataLeaf{ 385 {"", 2, true}, // " 1" and "a" 386 {"a0", 1, true}, // "a0" 387 {"a00", 2, true}, // "a00" and "a000" 388 {"a1", 2, true}, // "a1" and "a2" 389 {"b", 1, true}, // "b" 390 {"b1", 2, true}, // "b1" and "b2" 391 {"c", 1, true}, // "c" 392 {"d", 1, true}, // "d" 393 {"q", 2, true}, // "q" and "z" 394 } 395 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 396 397 t.Log("Verify lookups") 398 testDirDataCheckLookup(ctx, t, dd, "a", 1) 399 testDirDataCheckLookup(ctx, t, dd, "b", 2) 400 testDirDataCheckLookup(ctx, t, dd, "c", 3) 401 testDirDataCheckLookup(ctx, t, dd, "a1", 4) 402 testDirDataCheckLookup(ctx, t, dd, "a2", 5) 403 testDirDataCheckLookup(ctx, t, dd, "a0", 6) 404 testDirDataCheckLookup(ctx, t, dd, "a00", 7) 405 testDirDataCheckLookup(ctx, t, dd, "b1", 8) 406 testDirDataCheckLookup(ctx, t, dd, "d", 9) 407 testDirDataCheckLookup(ctx, t, dd, "a000", 10) 408 testDirDataCheckLookup(ctx, t, dd, "z", 11) 409 testDirDataCheckLookup(ctx, t, dd, "q", 12) 410 testDirDataCheckLookup(ctx, t, dd, "b2", 13) 411 testDirDataCheckLookup(ctx, t, dd, " 1", 14) 412 413 t.Log("Adding an existing name should error") 414 _, err = dd.AddEntry(ctx, NewPathPartString("a", nil), DirEntry{ 415 EntryInfo: EntryInfo{ 416 Size: 100, 417 }, 418 }) 419 require.Equal(t, NameExistsError{"a"}, err) 420 } 421 422 func TestDirDataRemoveEntry(t *testing.T) { 423 dd, cleanBcache, dirtyBcache := setupDirDataTest(t, 2, 2) 424 ctx := context.Background() 425 topBlock := NewDirBlock().(*DirBlock) 426 err := cleanBcache.Put( 427 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 428 SkipCacheHash) 429 require.NoError(t, err) 430 431 t.Log("Make a simple dir and remove one entry") 432 addFakeDirDataEntry(ctx, t, dd, "a", 1) 433 addFakeDirDataEntry(ctx, t, dd, "z", 2) 434 _, err = dd.RemoveEntry(ctx, NewPathPartString("z", nil)) 435 require.NoError(t, err) 436 require.Len(t, topBlock.Children, 1) 437 testDirDataCheckLookup(ctx, t, dd, "a", 1) 438 _, err = dd.Lookup(ctx, NewPathPartString("z", nil)) 439 require.Equal(t, idutil.NoSuchNameError{Name: "z"}, err) 440 441 t.Log("Make a big complicated tree and remove an entry") 442 addFakeDirDataEntry(ctx, t, dd, "b", 2) 443 addFakeDirDataEntry(ctx, t, dd, "c", 3) 444 addFakeDirDataEntry(ctx, t, dd, "a1", 4) 445 addFakeDirDataEntry(ctx, t, dd, "a2", 5) 446 addFakeDirDataEntry(ctx, t, dd, "a0", 6) 447 addFakeDirDataEntry(ctx, t, dd, "a00", 7) 448 addFakeDirDataEntry(ctx, t, dd, "b1", 8) 449 addFakeDirDataEntry(ctx, t, dd, "d", 9) 450 addFakeDirDataEntry(ctx, t, dd, "a000", 10) 451 addFakeDirDataEntry(ctx, t, dd, "z", 11) 452 addFakeDirDataEntry(ctx, t, dd, "q", 12) 453 addFakeDirDataEntry(ctx, t, dd, "b2", 13) 454 addFakeDirDataEntry(ctx, t, dd, " 1", 14) 455 testDirDataCleanCache(t, dd, cleanBcache, dirtyBcache) 456 457 _, err = dd.RemoveEntry(ctx, NewPathPartString("c", nil)) 458 require.NoError(t, err) 459 expectedLeafs := []testDirDataLeaf{ 460 {"", 2, false}, // " 1" and "a" 461 {"a0", 1, false}, // "a0" 462 {"a00", 2, false}, // "a00" and "a000" 463 {"a1", 2, false}, // "a1" and "a2" 464 {"b", 1, false}, // "b" 465 {"b1", 2, false}, // "b1" and "b2" 466 {"c", 0, true}, // now empty 467 {"d", 1, false}, // "d" 468 {"q", 2, false}, // "q" and "z" 469 } 470 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 471 } 472 473 func TestDirDataUpdateEntry(t *testing.T) { 474 dd, cleanBcache, dirtyBcache := setupDirDataTest(t, 2, 2) 475 ctx := context.Background() 476 topBlock := NewDirBlock().(*DirBlock) 477 err := cleanBcache.Put( 478 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 479 SkipCacheHash) 480 require.NoError(t, err) 481 482 t.Log("Make a simple dir and update one entry") 483 addFakeDirDataEntry(ctx, t, dd, "a", 1) 484 _, err = dd.UpdateEntry(ctx, NewPathPartString("a", nil), DirEntry{ 485 EntryInfo: EntryInfo{ 486 Size: 100, 487 }, 488 }) 489 require.NoError(t, err) 490 testDirDataCheckLookup(ctx, t, dd, "a", 100) 491 492 t.Log("Make a big complicated tree and update an entry") 493 addFakeDirDataEntry(ctx, t, dd, "b", 2) 494 addFakeDirDataEntry(ctx, t, dd, "c", 3) 495 addFakeDirDataEntry(ctx, t, dd, "a1", 4) 496 addFakeDirDataEntry(ctx, t, dd, "a2", 5) 497 addFakeDirDataEntry(ctx, t, dd, "a0", 6) 498 addFakeDirDataEntry(ctx, t, dd, "a00", 7) 499 addFakeDirDataEntry(ctx, t, dd, "b1", 8) 500 addFakeDirDataEntry(ctx, t, dd, "d", 9) 501 addFakeDirDataEntry(ctx, t, dd, "a000", 10) 502 addFakeDirDataEntry(ctx, t, dd, "z", 11) 503 addFakeDirDataEntry(ctx, t, dd, "q", 12) 504 addFakeDirDataEntry(ctx, t, dd, "b2", 13) 505 addFakeDirDataEntry(ctx, t, dd, " 1", 14) 506 testDirDataCleanCache(t, dd, cleanBcache, dirtyBcache) 507 508 _, err = dd.UpdateEntry(ctx, NewPathPartString("c", nil), DirEntry{ 509 EntryInfo: EntryInfo{ 510 Size: 1000, 511 }, 512 }) 513 require.NoError(t, err) 514 testDirDataCheckLookup(ctx, t, dd, "c", 1000) 515 expectedLeafs := []testDirDataLeaf{ 516 {"", 2, false}, // " 1" and "a" 517 {"a0", 1, false}, // "a0" 518 {"a00", 2, false}, // "a00" and "a000" 519 {"a1", 2, false}, // "a1" and "a2" 520 {"b", 1, false}, // "b" 521 {"b1", 2, false}, // "b1" and "b2" 522 {"c", 1, true}, // "c" 523 {"d", 1, false}, // "d" 524 {"q", 2, false}, // "q" and "z" 525 } 526 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 2) 527 t.Log("Updating an non-existing name should error") 528 _, err = dd.UpdateEntry(ctx, NewPathPartString("foo", nil), DirEntry{ 529 EntryInfo: EntryInfo{ 530 Size: 100, 531 }, 532 }) 533 require.Equal(t, idutil.NoSuchNameError{Name: "foo"}, err) 534 535 } 536 537 func TestDirDataShifting(t *testing.T) { 538 dd, cleanBcache, dirtyBcache := setupDirDataTest(t, 2, 1) 539 ctx := context.Background() 540 topBlock := NewDirBlock().(*DirBlock) 541 err := cleanBcache.Put( 542 dd.rootBlockPointer(), dd.tree.file.Tlf, topBlock, TransientEntry, 543 SkipCacheHash) 544 require.NoError(t, err) 545 546 for i := 0; i <= 10; i++ { 547 addFakeDirDataEntry(ctx, t, dd, strconv.Itoa(i), uint64(i+1)) 548 } 549 testDirDataCheckLookup(ctx, t, dd, "10", 11) 550 expectedLeafs := []testDirDataLeaf{ 551 {"", 1, true}, 552 {"1", 1, true}, 553 {"10", 1, true}, 554 {"2", 1, true}, 555 {"3", 1, true}, 556 {"4", 1, true}, 557 {"5", 1, true}, 558 {"6", 1, true}, 559 {"7", 1, true}, 560 {"8", 1, true}, 561 {"9", 1, true}, 562 } 563 testDirDataCheckLeafs(t, dd, cleanBcache, dirtyBcache, expectedLeafs, 2, 1) 564 }