blob: 25f486f1982be212cb81e71a0fd4b9ebc64914d3 [file]
#region License
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#endregion
using System;
using System.Linq;
using Gremlin.Net.Structure;
using Xunit;
namespace Gremlin.Net.UnitTest.Structure
{
public class TreeTests
{
// Builds: marko -> {lop, josh -> {ripple, lop}}
private static Tree BuildSampleTree()
{
var tree = new Tree();
var marko = tree.GetOrCreateChild("marko");
marko.GetOrCreateChild("lop");
var josh = marko.GetOrCreateChild("josh");
josh.GetOrCreateChild("ripple");
josh.GetOrCreateChild("lop");
return tree;
}
[Fact]
public void RootNodesShouldReturnKeysInInsertionOrder()
{
var tree = new Tree();
tree.GetOrCreateChild("a");
tree.GetOrCreateChild("b");
tree.GetOrCreateChild("c");
Assert.Equal(new object?[] { "a", "b", "c" }, tree.RootNodes());
}
[Fact]
public void NodeCountShouldCountAllKeysRecursively()
{
// marko, lop, josh, ripple, lop => 5 keys total
Assert.Equal(5, BuildSampleTree().NodeCount());
Assert.Equal(0, new Tree().NodeCount());
}
[Fact]
public void GetNodesAtDepthShouldReturnKeysForEachLevel()
{
var tree = BuildSampleTree();
Assert.Equal(new object?[] { "marko" }, tree.GetNodesAtDepth(0));
var depth1 = tree.GetNodesAtDepth(1);
Assert.Equal(new object?[] { "lop", "josh" }, depth1);
var depth2 = tree.GetNodesAtDepth(2);
Assert.Equal(new object?[] { "ripple", "lop" }, depth2);
}
[Fact]
public void GetNodesAtDepthShouldReturnEmptyForNegativeOrOutOfRangeDepth()
{
var tree = BuildSampleTree();
Assert.Empty(tree.GetNodesAtDepth(-1));
Assert.Empty(tree.GetNodesAtDepth(3));
Assert.Empty(tree.GetNodesAtDepth(99));
}
[Fact]
public void GetTreesAtDepthShouldReturnSubtreesForEachLevel()
{
var tree = BuildSampleTree();
var depth0 = tree.GetTreesAtDepth(0);
Assert.Single(depth0);
Assert.Same(tree, depth0[0]);
// depth 1 holds the marko subtree
var depth1 = tree.GetTreesAtDepth(1);
Assert.Single(depth1);
Assert.True(depth1[0].HasChild("lop"));
Assert.True(depth1[0].HasChild("josh"));
// depth 2 holds the lop (leaf) and josh subtrees
var depth2 = tree.GetTreesAtDepth(2);
Assert.Equal(2, depth2.Count);
Assert.Empty(tree.GetTreesAtDepth(-1));
Assert.Empty(tree.GetTreesAtDepth(5));
}
[Fact]
public void GetLeafNodesShouldReturnKeysWithEmptySubtrees()
{
var tree = BuildSampleTree();
// leaves: lop (under marko), ripple and lop (under josh)
Assert.Equal(new object?[] { "lop", "ripple", "lop" }, tree.GetLeafNodes());
}
[Fact]
public void GetLeafTreesShouldReturnSingleKeyTreesForEachLeaf()
{
var tree = BuildSampleTree();
var leafTrees = tree.GetLeafTrees();
Assert.Equal(3, leafTrees.Count);
foreach (var leafTree in leafTrees)
{
Assert.Single(leafTree.RootNodes());
}
Assert.Equal(new object?[] { "lop", "ripple", "lop" },
leafTrees.SelectMany(t => t.RootNodes()).ToArray());
}
[Fact]
public void SplitParentsShouldReturnOneTreePerRootKey()
{
var tree = new Tree();
tree.GetOrCreateChild("a").GetOrCreateChild("a1");
tree.GetOrCreateChild("b");
var parents = tree.SplitParents();
Assert.Equal(2, parents.Count);
Assert.True(parents[0].HasChild("a"));
Assert.True(parents[1].HasChild("b"));
Assert.True(parents[0].ChildAt("a").HasChild("a1"));
}
[Fact]
public void SplitParentsShouldReturnSelfForSingleRoot()
{
var tree = new Tree();
tree.GetOrCreateChild("only");
var parents = tree.SplitParents();
Assert.Single(parents);
Assert.Same(tree, parents[0]);
}
[Fact]
public void AddTreeShouldMergeOverlappingKeysRecursively()
{
var tree = new Tree();
tree.GetOrCreateChild("marko").GetOrCreateChild("lop");
var other = new Tree();
var otherMarko = other.GetOrCreateChild("marko");
otherMarko.GetOrCreateChild("josh");
other.GetOrCreateChild("vadas");
tree.AddTree(other);
// marko branch merged, vadas branch adopted
Assert.Equal(new object?[] { "marko", "vadas" }, tree.RootNodes());
var merged = tree.ChildAt("marko");
Assert.True(merged.HasChild("lop"));
Assert.True(merged.HasChild("josh"));
}
[Fact]
public void AddTreeShouldThrowOnNull()
{
var tree = new Tree();
Assert.Throws<ArgumentNullException>(() => tree.AddTree(null!));
}
[Fact]
public void ContainsShouldFindValuesRecursively()
{
var tree = BuildSampleTree();
Assert.True(tree.Contains("marko"));
Assert.True(tree.Contains("ripple"));
Assert.True(tree.Contains("lop"));
Assert.False(tree.Contains("peter"));
}
[Fact]
public void FindSubtreeShouldReturnFirstMatchingSubtree()
{
var tree = BuildSampleTree();
var joshSubtree = tree.FindSubtree("josh");
Assert.NotNull(joshSubtree);
Assert.True(joshSubtree!.HasChild("ripple"));
Assert.True(joshSubtree.HasChild("lop"));
// a leaf key resolves to an empty subtree
var rippleSubtree = tree.FindSubtree("ripple");
Assert.NotNull(rippleSubtree);
Assert.True(rippleSubtree!.IsLeaf());
Assert.Null(tree.FindSubtree("absent"));
}
[Fact]
public void EqualsShouldBeOrderInsensitiveAcrossSiblings()
{
var t1 = new Tree();
t1.GetOrCreateChild("a").GetOrCreateChild("b");
t1.GetOrCreateChild("c");
var t2 = new Tree();
// reversed sibling insertion order
t2.GetOrCreateChild("c");
t2.GetOrCreateChild("a").GetOrCreateChild("b");
Assert.True(t1.Equals(t2));
Assert.True(t2.Equals(t1));
Assert.Equal(t1, t2);
}
[Fact]
public void EqualsShouldReturnFalseForNull()
{
var tree = new Tree();
tree.GetOrCreateChild("a");
Assert.False(tree.Equals(null));
Assert.False(tree.Equals((object?) null));
}
[Fact]
public void EqualsShouldReturnTrueForSameReference()
{
var tree = BuildSampleTree();
Assert.True(tree.Equals(tree));
Assert.True(tree.Equals((object) tree));
}
[Fact]
public void EqualsShouldReturnFalseForDifferentEntryCount()
{
var t1 = new Tree();
t1.GetOrCreateChild("a");
t1.GetOrCreateChild("b");
var t2 = new Tree();
t2.GetOrCreateChild("a");
Assert.False(t1.Equals(t2));
}
[Fact]
public void EqualsShouldReturnFalseForDifferingSubtree()
{
var t1 = new Tree();
t1.GetOrCreateChild("a").GetOrCreateChild("b");
var t2 = new Tree();
t2.GetOrCreateChild("a").GetOrCreateChild("x");
Assert.False(t1.Equals(t2));
}
[Fact]
public void EqualsObjectOverrideShouldReturnFalseForOtherTypes()
{
var tree = new Tree();
tree.GetOrCreateChild("a");
Assert.False(tree.Equals("not a tree"));
Assert.False(tree.Equals(42));
}
[Fact]
public void EmptyTreesShouldBeEqual()
{
Assert.True(new Tree().Equals(new Tree()));
Assert.Equal(new Tree(), new Tree());
}
[Fact]
public void GetHashCodeShouldBeEqualForEqualTrees()
{
var t1 = new Tree();
t1.GetOrCreateChild("a").GetOrCreateChild("b");
t1.GetOrCreateChild("c");
var t2 = new Tree();
// reversed sibling order, structurally equal
t2.GetOrCreateChild("c");
t2.GetOrCreateChild("a").GetOrCreateChild("b");
Assert.True(t1.Equals(t2));
Assert.Equal(t1.GetHashCode(), t2.GetHashCode());
}
[Fact]
public void GetHashCodeShouldBeStableAcrossCalls()
{
var tree = BuildSampleTree();
Assert.Equal(tree.GetHashCode(), tree.GetHashCode());
}
[Fact]
public void GetHashCodeShouldHandleNullKey()
{
// GetHashCode folds in entry.Key?.GetHashCode() ?? 0, so a null key
// must not throw.
var tree = new Tree();
tree.GetOrCreateChild(null).GetOrCreateChild("child");
var hash = tree.GetHashCode();
var other = new Tree();
other.GetOrCreateChild(null).GetOrCreateChild("child");
Assert.Equal(hash, other.GetHashCode());
}
}
}