Skip to content

Commit 59e094e

Browse files
committed
Split tree tests by mixin
Move traversal, conversion, structure, navigation, merge, metrics, and path coverage into focused test files and trim the core test_tree suite. This matches the mixin layout, keeps coverage intact, and quiets RuboCop issues by removing script shebangs and tightening test-only serialization.
1 parent 56e214d commit 59e094e

8 files changed

Lines changed: 1585 additions & 1451 deletions

test/test_tree.rb

100755100644
Lines changed: 33 additions & 1451 deletions
Large diffs are not rendered by default.

test/test_tree_conversion.rb

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# test_tree_conversion.rb - This file is part of the RubyTree package.
2+
#
3+
# Copyright (c) 2026 Anupam Sengupta. All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without modification,
6+
# are permitted provided that the following conditions are met:
7+
#
8+
# - Redistributions of source code must retain the above copyright notice, this
9+
# list of conditions and the following disclaimer.
10+
#
11+
# - Redistributions in binary form must reproduce the above copyright notice, this
12+
# list of conditions and the following disclaimer in the documentation and/or
13+
# other materials provided with the distribution.
14+
#
15+
# - Neither the name of the organization nor the names of its contributors may
16+
# be used to endorse or promote products derived from this software without
17+
# specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
#
30+
# frozen_string_literal: true
31+
32+
require 'test/unit'
33+
require 'json'
34+
require_relative '../lib/tree/tree_deps'
35+
require_relative 'support/fixtures_shared'
36+
37+
module TestTree
38+
class TestTreeConversion < Test::Unit::TestCase
39+
include TreeTestFixtures
40+
41+
def setup
42+
nodes = build_basic_tree_nodes
43+
@root = nodes[:root]
44+
@child1 = nodes[:child1]
45+
@child2 = nodes[:child2]
46+
@child3 = nodes[:child3]
47+
@child4 = nodes[:child4]
48+
@child5 = nodes[:child5]
49+
end
50+
51+
def setup_test_tree
52+
attach_basic_tree(
53+
root: @root,
54+
child1: @child1,
55+
child2: @child2,
56+
child3: @child3,
57+
child4: @child4,
58+
child5: @child5
59+
)
60+
end
61+
62+
def test_from_hash
63+
hash = { [:A, 'Root content'] => {
64+
B: {
65+
E: {},
66+
F: {
67+
H: {},
68+
[:I, 'Leaf content'] => {}
69+
}
70+
},
71+
C: {},
72+
D: {
73+
G: {}
74+
}
75+
} }
76+
77+
tree = Tree::TreeNode.from_hash(hash)
78+
79+
assert_same(Tree::TreeNode, tree.class)
80+
assert_same(tree.name, :A)
81+
assert_equal(true, tree.root?)
82+
assert_equal(false, tree.leaf?)
83+
assert_equal(9, tree.size)
84+
assert_equal('Root content', tree.content)
85+
assert_equal(3, tree.children.count)
86+
87+
leaf_with_content = tree[:B][:F][:I]
88+
assert_equal('Leaf content', leaf_with_content.content)
89+
assert_equal(true, leaf_with_content.leaf?)
90+
91+
leaf_without_content = tree[:C]
92+
assert_equal(true, leaf_without_content.leaf?)
93+
94+
interior_node = tree[:B][:F]
95+
assert_equal(false, interior_node.leaf?)
96+
assert_equal(2, interior_node.children.count)
97+
98+
assert_raise(ArgumentError) { Tree::TreeNode.from_hash({}) }
99+
assert_raise(ArgumentError) { Tree::TreeNode.from_hash({ A: {}, B: {} }) }
100+
end
101+
102+
def test_from_hash_with_nils
103+
hash = { [:A, 'Root content'] => {
104+
B: {
105+
E: nil,
106+
F: {
107+
H: nil,
108+
[:I, 'Leaf content'] => nil
109+
}
110+
},
111+
C: nil,
112+
D: {
113+
G: nil
114+
}
115+
} }
116+
117+
tree = Tree::TreeNode.from_hash(hash)
118+
119+
assert_same(Tree::TreeNode, tree.class)
120+
assert_same(:A, tree.name)
121+
assert_equal(true, tree.root?)
122+
assert_equal(false, tree.leaf?)
123+
assert_equal(9, tree.size)
124+
assert_equal('Root content', tree.content)
125+
assert_equal(3, tree.children.count)
126+
127+
leaf_with_content = tree[:B][:F][:I]
128+
assert_equal('Leaf content', leaf_with_content.content)
129+
assert_equal(true, leaf_with_content.leaf?)
130+
131+
leaf_without_content = tree[:C]
132+
assert_equal(true, leaf_without_content.leaf?)
133+
134+
interior_node = tree[:B][:F]
135+
assert_equal(false, interior_node.leaf?)
136+
assert_equal(2, interior_node.children.count)
137+
end
138+
139+
def test_add_from_hash
140+
tree = Tree::TreeNode.new(:A)
141+
142+
hash = {}
143+
assert_equal([], tree.add_from_hash(hash))
144+
145+
hash = { B: { C: { D: nil }, E: {}, F: {} }, [:G, 'G content'] => {} }
146+
added_children = tree.add_from_hash(hash)
147+
assert_equal(Array, added_children.class)
148+
assert_equal(2, added_children.count)
149+
assert_equal(7, tree.size)
150+
assert_equal('G content', tree[:G].content)
151+
assert_equal(true, tree[:G].leaf?)
152+
assert_equal(5, tree[:B].size)
153+
assert_equal(3, tree[:B].children.count)
154+
155+
assert_raise(ArgumentError) { tree.add_from_hash([]) }
156+
assert_raise(ArgumentError) { tree.add_from_hash('not a hash') }
157+
assert_raise(ArgumentError) { tree.add_from_hash({ X: 'Not a hash or nil' }) }
158+
end
159+
160+
def test_to_h
161+
a = Tree::TreeNode.new(:A)
162+
b = Tree::TreeNode.new(:B)
163+
c = Tree::TreeNode.new(:C)
164+
e = Tree::TreeNode.new(:E)
165+
f = Tree::TreeNode.new(:F)
166+
g = Tree::TreeNode.new(:G)
167+
168+
a << b
169+
a << c
170+
c << f
171+
c << g
172+
b << e
173+
174+
exported = a.to_h
175+
expected = { A: { B: { E: {} }, C: { F: {}, G: {} } } }
176+
assert_equal(expected, exported)
177+
end
178+
179+
def test_to_h_from_hash_symmetry
180+
input = { A: { B: { E: { I: {}, J: {} }, F: {}, G: {} }, C: { H: { K: {} } } } }
181+
node = Tree::TreeNode.from_hash(input)
182+
assert_equal(input, node.to_h)
183+
end
184+
185+
def test_marshal_dump
186+
test_root = Tree::TreeNode.new('ROOT', 'Root Node')
187+
test_content = { 'KEY1' => 'Value1', 'KEY2' => 'Value2' }
188+
test_child = Tree::TreeNode.new('Child', test_content)
189+
test_content2 = %w[AValue1 AValue2 AValue3]
190+
test_grand_child = Tree::TreeNode.new('Grand Child 1', test_content2)
191+
test_root << test_child << test_grand_child
192+
193+
data = Marshal.dump(test_root)
194+
# rubocop:disable Security/MarshalLoad
195+
new_root = Marshal.load(data)
196+
# rubocop:enable Security/MarshalLoad
197+
198+
assert_equal(test_root.name, new_root.name, 'Must identify as ROOT')
199+
assert_equal(test_root.content, new_root.content, "Must have root's content")
200+
assert(new_root.root?, 'Must be the ROOT node')
201+
assert(new_root.children?, 'Must have a child node')
202+
203+
new_child = new_root[test_child.name]
204+
assert_equal(test_child.name, new_child.name, 'Must have child 1')
205+
assert(new_child.content?, 'Child must have content')
206+
assert(new_child.only_child?, 'Child must be the only child')
207+
208+
new_child_content = new_child.content
209+
assert_equal(Hash, new_child_content.class, "Class of child's content should be a hash")
210+
assert_equal(test_child.content.size, new_child_content.size, 'The content should have same size')
211+
212+
new_grand_child = new_child[test_grand_child.name]
213+
assert_equal(test_grand_child.name, new_grand_child.name, 'Must have grand child')
214+
assert(new_grand_child.content?, 'Grand-child must have content')
215+
assert(new_grand_child.only_child?, 'Grand-child must be the only child')
216+
217+
new_grand_child_content = new_grand_child.content
218+
assert_equal(Array, new_grand_child_content.class, "Class of grand-child's content should be an Array")
219+
assert_equal(test_grand_child.content.size, new_grand_child_content.size, 'The content should have same size')
220+
end
221+
222+
alias test_marshal_load test_marshal_dump
223+
224+
def test_json_serialization
225+
setup_test_tree
226+
227+
expected_json = {
228+
'name' => 'ROOT',
229+
'content' => 'Root Node',
230+
JSON.create_id => 'Tree::TreeNode',
231+
'children' => [
232+
{ 'name' => 'Child1', 'content' => 'Child Node 1', JSON.create_id => 'Tree::TreeNode' },
233+
{ 'name' => 'Child2', 'content' => 'Child Node 2', JSON.create_id => 'Tree::TreeNode' },
234+
{
235+
'name' => 'Child3',
236+
'content' => 'Child Node 3',
237+
JSON.create_id => 'Tree::TreeNode',
238+
'children' => [
239+
{ 'name' => 'Child4', 'content' => 'Grand Child 1', JSON.create_id => 'Tree::TreeNode' }
240+
]
241+
}
242+
]
243+
}.to_json
244+
245+
assert_equal(expected_json, @root.to_json)
246+
end
247+
248+
def test_json_deserialization
249+
tree_as_json = {
250+
'name' => 'ROOT',
251+
'content' => 'Root Node',
252+
JSON.create_id => 'Tree::TreeNode',
253+
'children' => [
254+
{ 'name' => 'Child1', 'content' => 'Child Node 1', JSON.create_id => 'Tree::TreeNode' },
255+
{ 'name' => 'Child2', 'content' => 'Child Node 2', JSON.create_id => 'Tree::TreeNode' },
256+
{
257+
'name' => 'Child3',
258+
'content' => 'Child Node 3',
259+
JSON.create_id => 'Tree::TreeNode',
260+
'children' => [
261+
{ 'name' => 'Child4', 'content' => 'Grand Child 1', JSON.create_id => 'Tree::TreeNode' }
262+
]
263+
}
264+
]
265+
}.to_json
266+
267+
tree = JSON.parse(tree_as_json, create_additions: true)
268+
269+
assert_equal(@root.name, tree.root.name, 'Root should be returned')
270+
assert_equal(@child1.name, tree[0].name, 'Child 1 should be returned')
271+
assert_equal(@child2.name, tree[1].name, 'Child 2 should be returned')
272+
assert_equal(@child3.name, tree[2].name, 'Child 3 should be returned')
273+
assert_equal(@child4.name, tree[2][0].name, 'Grand Child 1 should be returned')
274+
end
275+
276+
def test_json_round_trip
277+
root_node = Tree::TreeNode.new('ROOT', 'Root Content')
278+
root_node << Tree::TreeNode.new('CHILD1',
279+
'Child1 Content') << Tree::TreeNode.new('GRAND_CHILD1', 'GrandChild1 Content')
280+
root_node << Tree::TreeNode.new('CHILD2', 'Child2 Content')
281+
282+
j = root_node.to_json
283+
284+
k = JSON.parse(j, create_additions: true)
285+
286+
assert_equal(k.name, root_node.name, 'Root should be returned')
287+
assert_equal(k[0].name, root_node[0].name, 'Child 1 should be returned')
288+
assert_equal(k[0][0].name, root_node[0][0].name, 'Grand Child 1 should be returned')
289+
assert_equal(k[1].name, root_node[1].name, 'Child 2 should be returned')
290+
end
291+
end
292+
end

0 commit comments

Comments
 (0)