Add tests for Comparer and FileNode edge cases

This commit is contained in:
Aslan Dukaev 2026-01-08 17:01:29 +03:00
commit 46757fae77
2 changed files with 682 additions and 0 deletions

View file

@ -331,3 +331,267 @@ func TestEfficiencySlice_Less(t *testing.T) {
assert.False(t, efs.Less(0, 1))
})
}
func TestComparer_GetPathErrors_Additional(t *testing.T) {
t.Run("get path errors with actual errors", func(t *testing.T) {
// Create ref trees with some content that will generate path errors
tree1 := NewFileTree()
tree1.Name = "tree1"
// Add some paths to tree1
fakeData := FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
}
tree1.AddPath("/file1.txt", fakeData)
tree2 := NewFileTree()
tree2.Name = "tree2"
cmp := NewComparer([]*FileTree{tree1, tree2})
// Test getting path errors for a key
key := NewTreeIndexKey(0, 0, 1, 1)
pathErrors, err := cmp.GetPathErrors(key)
// Should not error
assert.NoError(t, err)
assert.NotNil(t, pathErrors)
})
t.Run("get path errors caches result", func(t *testing.T) {
tree1 := NewFileTree()
tree1.Name = "tree1"
fakeData := FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
}
tree1.AddPath("/file1.txt", fakeData)
cmp := NewComparer([]*FileTree{tree1})
key := NewTreeIndexKey(0, 0, 0, 0)
// Call GetPathErrors twice
pathErrors1, err1 := cmp.GetPathErrors(key)
pathErrors2, err2 := cmp.GetPathErrors(key)
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.Equal(t, pathErrors1, pathErrors2)
})
}
func TestComparer_get_ErrorCases(t *testing.T) {
t.Run("get returns error on invalid range", func(t *testing.T) {
tree1 := NewFileTree()
tree1.Name = "tree1"
cmp := NewComparer([]*FileTree{tree1})
// Use invalid range (index out of bounds)
key := NewTreeIndexKey(0, 10, 0, 0)
// StackTreeRange will panic with index out of range
assert.Panics(t, func() {
cmp.get(key)
}, "Expected panic when using invalid range")
})
}
func TestComparer_BuildCache_ErrorCases(t *testing.T) {
t.Run("build cache with single tree and errors", func(t *testing.T) {
tree1 := NewFileTree()
tree1.Name = "tree1"
// Add a path that might cause issues
fakeData := FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
}
_, _, err := tree1.AddPath("/file1.txt", fakeData)
assert.NoError(t, err)
cmp := NewComparer([]*FileTree{tree1})
errors := cmp.BuildCache()
// Should not error with valid tree
assert.Empty(t, errors)
})
t.Run("build cache with multiple trees", func(t *testing.T) {
trees := make([]*FileTree, 3)
for i := 0; i < 3; i++ {
trees[i] = NewFileTree()
trees[i].Name = fmt.Sprintf("tree%d", i)
// Add some content
fakeData := FileInfo{
Path: fmt.Sprintf("/file%d.txt", i),
TypeFlag: 1,
hash: uint64(i),
}
_, _, err := trees[i].AddPath(fakeData.Path, fakeData)
assert.NoError(t, err)
}
cmp := NewComparer(trees)
errors := cmp.BuildCache()
// Should not error
assert.Empty(t, errors)
})
t.Run("build cache with empty ref trees", func(t *testing.T) {
cmp := NewComparer([]*FileTree{})
errors := cmp.BuildCache()
// Should not error even with empty trees
assert.Empty(t, errors)
})
}
func TestComparer_GetTree_Caching(t *testing.T) {
t.Run("cached tree returns same instance", func(t *testing.T) {
tree1 := NewFileTree()
tree1.Name = "tree1"
fakeData := FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
}
_, _, err := tree1.AddPath("/file1.txt", fakeData)
assert.NoError(t, err)
cmp := NewComparer([]*FileTree{tree1})
key := NewTreeIndexKey(0, 0, 0, 0)
// Call GetTree twice
tree1, err1 := cmp.GetTree(key)
tree2, err2 := cmp.GetTree(key)
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.NotNil(t, tree1)
assert.NotNil(t, tree2)
// Should return the same cached instance
assert.Same(t, tree1, tree2)
})
t.Run("get tree populates cache", func(t *testing.T) {
tree1 := NewFileTree()
tree1.Name = "tree1"
fakeData := FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
}
_, _, err := tree1.AddPath("/file1.txt", fakeData)
assert.NoError(t, err)
cmp := NewComparer([]*FileTree{tree1})
key := NewTreeIndexKey(0, 0, 0, 0)
// Cache should be empty initially
_, exists := cmp.trees[key]
assert.False(t, exists)
// Get tree should populate cache
_, err = cmp.GetTree(key)
assert.NoError(t, err)
// Cache should now have the tree
_, exists = cmp.trees[key]
assert.True(t, exists)
})
}
func TestComparer_Indexes_Iteration(t *testing.T) {
t.Run("natural indexes iteration", func(t *testing.T) {
trees := make([]*FileTree, 3)
for i := 0; i < 3; i++ {
trees[i] = NewFileTree()
trees[i].Name = fmt.Sprintf("tree%d", i)
}
cmp := NewComparer(trees)
// Collect all indexes
indexes := make([]TreeIndexKey, 0)
for index := range cmp.NaturalIndexes() {
indexes = append(indexes, index)
}
// Should have 3 indexes (one for each tree)
assert.Len(t, indexes, 3)
// Verify the indexes
assert.Equal(t, NewTreeIndexKey(0, 0, 0, 0), indexes[0])
assert.Equal(t, NewTreeIndexKey(0, 0, 1, 1), indexes[1])
assert.Equal(t, NewTreeIndexKey(0, 1, 2, 2), indexes[2])
})
t.Run("aggregated indexes iteration", func(t *testing.T) {
trees := make([]*FileTree, 3)
for i := 0; i < 3; i++ {
trees[i] = NewFileTree()
trees[i].Name = fmt.Sprintf("tree%d", i)
}
cmp := NewComparer(trees)
// Collect all indexes
indexes := make([]TreeIndexKey, 0)
for index := range cmp.AggregatedIndexes() {
indexes = append(indexes, index)
}
// Should have 3 indexes
assert.Len(t, indexes, 3)
// Verify the indexes
assert.Equal(t, NewTreeIndexKey(0, 0, 0, 0), indexes[0])
assert.Equal(t, NewTreeIndexKey(0, 0, 1, 1), indexes[1])
assert.Equal(t, NewTreeIndexKey(0, 0, 1, 2), indexes[2])
})
}
func TestTreeIndexKey_String_EdgeCases(t *testing.T) {
t.Run("all zeros", func(t *testing.T) {
key := NewTreeIndexKey(0, 0, 0, 0)
assert.Equal(t, "Index(0:0)", key.String())
})
t.Run("single layer both sides", func(t *testing.T) {
key := NewTreeIndexKey(0, 0, 1, 1)
assert.Equal(t, "Index(0:1)", key.String())
})
t.Run("multiple bottom single top", func(t *testing.T) {
key := NewTreeIndexKey(0, 2, 3, 3)
assert.Equal(t, "Index(0-2:3)", key.String())
})
t.Run("single bottom multiple top", func(t *testing.T) {
key := NewTreeIndexKey(0, 0, 1, 3)
assert.Equal(t, "Index(0:1-3)", key.String())
})
t.Run("multiple both sides", func(t *testing.T) {
key := NewTreeIndexKey(0, 2, 3, 5)
assert.Equal(t, "Index(0-2:3-5)", key.String())
})
}

View file

@ -166,3 +166,421 @@ func TestDirSize(t *testing.T) {
t.Errorf("Expected metadata '%s' got '%s'", expected, actual)
}
}
func TestFileNode_compare(t *testing.T) {
t.Run("both nil returns Unmodified", func(t *testing.T) {
// Need to call compare on nil receiver
var node *FileNode
result := node.compare(nil)
if result != Unmodified {
t.Errorf("Expected Unmodified but got %v", result)
}
})
t.Run("non-nil node and nil other returns Removed", func(t *testing.T) {
tree := NewFileTree()
tree.Root.Name = "test"
result := tree.Root.compare(nil)
if result != Removed {
t.Errorf("Expected Removed but got %v", result)
}
})
t.Run("whiteout file returns Removed", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file.txt", FileInfo{
Path: "/file.txt",
TypeFlag: 1,
hash: 123,
})
whiteoutNode := &FileNode{
Name: ".wh.file.txt",
Data: NodeData{
FileInfo: FileInfo{
Path: "/.wh.file.txt",
TypeFlag: 1,
},
},
}
result := node.compare(whiteoutNode)
if result != Removed {
t.Errorf("Expected Removed for whiteout but got %v", result)
}
})
t.Run("mismatched node names panic", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file1.txt", FileInfo{
Path: "/file1.txt",
TypeFlag: 1,
hash: 123,
})
other := &FileNode{
Name: "file2.txt",
Data: NodeData{
FileInfo: FileInfo{
Path: "/file2.txt",
TypeFlag: 1,
hash: 123,
},
},
}
defer func() {
if r := recover(); r == nil {
t.Errorf("Expected panic when comparing mismatched nodes")
}
}()
node.compare(other)
})
t.Run("same nodes return Unmodified", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file.txt", FileInfo{
Path: "/file.txt",
TypeFlag: 1,
hash: 123,
Mode: 0644,
Uid: 1000,
Gid: 1000,
})
other := &FileNode{
Tree: tree,
Name: "file.txt",
Data: NodeData{
FileInfo: FileInfo{
Path: "/file.txt",
TypeFlag: 1,
hash: 123,
Mode: 0644,
Uid: 1000,
Gid: 1000,
},
},
}
result := node.compare(other)
if result != Unmodified {
t.Errorf("Expected Unmodified but got %v", result)
}
})
t.Run("different hash returns Modified", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file.txt", FileInfo{
Path: "/file.txt",
TypeFlag: 1,
hash: 123,
})
other := &FileNode{
Tree: tree,
Name: "file.txt",
Data: NodeData{
FileInfo: FileInfo{
Path: "/file.txt",
TypeFlag: 1,
hash: 456,
},
},
}
result := node.compare(other)
if result != Modified {
t.Errorf("Expected Modified but got %v", result)
}
})
}
func TestFileNode_AddChild_EdgeCases(t *testing.T) {
t.Run("add child with existing name", func(t *testing.T) {
tree := NewFileTree()
payload1 := FileInfo{Path: "/file1"}
payload2 := FileInfo{Path: "/file2"}
node1 := tree.Root.AddChild("test", payload1)
node2 := tree.Root.AddChild("test", payload2)
// Should add a new child even with same name
if node1 == nil || node2 == nil {
t.Errorf("Expected both nodes to be created")
}
if tree.Root.Children["test"] == nil {
t.Errorf("Expected child to exist in tree")
}
})
t.Run("add child to nested node", func(t *testing.T) {
tree := NewFileTree()
parent := tree.Root.AddChild("parent", FileInfo{})
child := parent.AddChild("child", FileInfo{Path: "/parent/child"})
if child == nil {
t.Errorf("Expected child to be created")
}
if len(parent.Children) != 1 {
t.Errorf("Expected parent to have 1 child, got %d", len(parent.Children))
}
})
}
func TestFileNode_Remove_EdgeCases(t *testing.T) {
t.Run("remove node with children", func(t *testing.T) {
tree := NewFileTree()
parent := tree.Root.AddChild("parent", FileInfo{})
parent.AddChild("child1", FileInfo{})
parent.AddChild("child2", FileInfo{})
initialSize := tree.Size
err := parent.Remove()
checkError(t, err, "unable to remove node")
if tree.Size >= initialSize {
t.Errorf("Expected tree size to decrease after removal")
}
})
t.Run("remove root node", func(t *testing.T) {
tree := NewFileTree()
tree.Root.AddChild("child1", FileInfo{})
tree.Root.AddChild("child2", FileInfo{})
err := tree.Root.Remove()
// Root removal should fail
if err == nil {
t.Errorf("Expected error when removing root node")
}
if err != nil && err.Error() != "cannot remove the tree root" {
t.Errorf("Expected 'cannot remove the tree root' error, got: %v", err)
}
})
}
func TestFileNode_String(t *testing.T) {
t.Run("string representation", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/test.txt", FileInfo{
Path: "/test.txt",
TypeFlag: 1,
Mode: 0644,
Uid: 1000,
Gid: 1000,
})
node.Data.DiffType = Modified
str := node.String()
if str == "" {
t.Errorf("Expected non-empty string representation")
}
})
t.Run("string representation for directory", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/dir", FileInfo{
Path: "/dir",
TypeFlag: 1,
})
node.Data.FileInfo.IsDir = true
str := node.String()
if str == "" {
t.Errorf("Expected non-empty string representation for directory")
}
})
}
func TestFileNode_MetadataString(t *testing.T) {
t.Run("metadata string for regular file", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/test.txt", FileInfo{
Path: "/test.txt",
TypeFlag: 1,
Size: 1024,
Mode: 0644,
Uid: 1000,
Gid: 1000,
})
metadata := node.MetadataString()
if metadata == "" {
t.Errorf("Expected non-empty metadata string")
}
})
t.Run("metadata string for directory", func(t *testing.T) {
tree := NewFileTree()
_, _, err := tree.AddPath("/dir", FileInfo{
Path: "/dir",
TypeFlag: 1,
})
checkError(t, err, "unable to setup test")
node, _ := tree.GetNode("/dir")
node.Data.FileInfo.IsDir = true
metadata := node.MetadataString()
if metadata == "" {
t.Errorf("Expected non-empty metadata string for directory")
}
})
}
func TestFileNode_GetSize(t *testing.T) {
t.Run("get size for regular file", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file.txt", FileInfo{
Path: "/file.txt",
TypeFlag: 1,
Size: 2048,
})
size := node.GetSize()
if size != 2048 {
t.Errorf("Expected size 2048, got %d", size)
}
})
t.Run("get size for directory with children", func(t *testing.T) {
tree := NewFileTree()
_, _, err := tree.AddPath("/dir", FileInfo{
Path: "/dir",
TypeFlag: 1,
})
checkError(t, err, "unable to setup test")
node, _ := tree.GetNode("/dir")
node.Data.FileInfo.IsDir = true
tree.AddPath("/dir/file1.txt", FileInfo{Size: 100})
tree.AddPath("/dir/file2.txt", FileInfo{Size: 200})
size := node.GetSize()
if size != 300 {
t.Errorf("Expected total size 300, got %d", size)
}
})
t.Run("get size for empty directory", func(t *testing.T) {
tree := NewFileTree()
_, _, err := tree.AddPath("/dir", FileInfo{
Path: "/dir",
TypeFlag: 1,
})
checkError(t, err, "unable to setup test")
node, _ := tree.GetNode("/dir")
node.Data.FileInfo.IsDir = true
size := node.GetSize()
if size != 0 {
t.Errorf("Expected size 0 for empty directory, got %d", size)
}
})
}
func TestFileNode_VisitDepthChildFirst(t *testing.T) {
t.Run("visit tree child first", func(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/dir", FileInfo{})
tree.AddPath("/dir/file1.txt", FileInfo{})
tree.AddPath("/dir/file2.txt", FileInfo{})
node, _ := tree.GetNode("/dir")
var visited []string
visitor := func(n *FileNode) error {
visited = append(visited, n.Path())
return nil
}
evaluator := func(n *FileNode) bool {
return true
}
sorter := GetSortOrderStrategy(ByName)
err := node.VisitDepthChildFirst(visitor, evaluator, sorter)
checkError(t, err, "unable to visit tree")
if len(visited) == 0 {
t.Errorf("Expected nodes to be visited")
}
})
}
func TestFileNode_VisitDepthParentFirst(t *testing.T) {
t.Run("visit tree parent first", func(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/dir", FileInfo{})
tree.AddPath("/dir/file1.txt", FileInfo{})
tree.AddPath("/dir/file2.txt", FileInfo{})
node, _ := tree.GetNode("/dir")
var visited []string
visitor := func(n *FileNode) error {
visited = append(visited, n.Path())
return nil
}
evaluator := func(n *FileNode) bool {
return true
}
sorter := GetSortOrderStrategy(ByName)
err := node.VisitDepthParentFirst(visitor, evaluator, sorter)
checkError(t, err, "unable to visit tree")
if len(visited) == 0 {
t.Errorf("Expected nodes to be visited")
}
})
}
func TestFileNode_AssignDiffType(t *testing.T) {
t.Run("assign diff type to node and children", func(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/dir", FileInfo{})
tree.AddPath("/dir/file1.txt", FileInfo{})
tree.AddPath("/dir/file2.txt", FileInfo{})
node, _ := tree.GetNode("/dir")
err := node.AssignDiffType(Added)
checkError(t, err, "unable to assign diff type")
// Check that the node and its children have the correct diff type
if node.Data.DiffType != Added {
t.Errorf("Expected node diff type to be Added, got %v", node.Data.DiffType)
}
})
}
func TestFileNode_IsLeaf(t *testing.T) {
t.Run("leaf node has no children", func(t *testing.T) {
tree := NewFileTree()
node, _, _ := tree.AddPath("/file.txt", FileInfo{})
if !node.IsLeaf() {
t.Errorf("Expected file node to be a leaf")
}
})
t.Run("directory node is not a leaf", func(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/dir", FileInfo{})
tree.AddPath("/dir/file.txt", FileInfo{})
node, _ := tree.GetNode("/dir")
if node.IsLeaf() {
t.Errorf("Expected directory node to not be a leaf")
}
})
}