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  }