Skip to content

Commit 863f1d3

Browse files
committed
Add API guardrail tests across all tree families
Introduce method-surface and behavior-contract tests for ordered node trees, heaps, key/value trees, aggregate trees, and trie APIs to lock current public behavior before consistency/refactor work.
1 parent 1ef376a commit 863f1d3

6 files changed

Lines changed: 630 additions & 0 deletions
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# test_api_consistency_matrix.rb - This file is part of the RubyTree package.
2+
#
3+
# Copyright (c) 2026 Anupam Sengupta
4+
#
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without modification,
8+
# are permitted provided that the following conditions are met:
9+
#
10+
# - Redistributions of source code must retain the above copyright notice, this
11+
# list of conditions and the following disclaimer.
12+
#
13+
# - Redistributions in binary form must reproduce the above copyright notice, this
14+
# list of conditions and the following disclaimer in the documentation and/or
15+
# other materials provided with the distribution.
16+
#
17+
# - Neither the name of the organization nor the names of its contributors may
18+
# be used to endorse or promote products derived from this software without
19+
# specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
#
32+
# frozen_string_literal: true
33+
34+
require 'test/unit'
35+
require_relative '../lib/tree/aatree'
36+
require_relative '../lib/tree/avltree'
37+
require_relative '../lib/tree/binaryheap'
38+
require_relative '../lib/tree/binarymaxheap'
39+
require_relative '../lib/tree/binarysearchtree'
40+
require_relative '../lib/tree/btree'
41+
require_relative '../lib/tree/fenwicktree'
42+
require_relative '../lib/tree/intervaltree'
43+
require_relative '../lib/tree/orderstatistictree'
44+
require_relative '../lib/tree/redblacktree'
45+
require_relative '../lib/tree/segmenttree'
46+
require_relative '../lib/tree/splaytree'
47+
require_relative '../lib/tree/treap'
48+
require_relative '../lib/tree/trie'
49+
50+
module TestTree
51+
# API consistency guardrails by semantic family.
52+
class TestApiConsistencyMatrix < Test::Unit::TestCase
53+
def test_ordered_node_tree_method_surface
54+
required = %i[insert add search delete key]
55+
56+
[
57+
Tree::BinarySearchTreeNode.new('root', 10),
58+
Tree::AvlTreeNode.new('root', 10),
59+
Tree::RedBlackTreeNode.new('root', 10),
60+
Tree::SplayTreeNode.new('root', 10),
61+
Tree::TreapNode.new('root', 10),
62+
Tree::OrderStatisticTreeNode.new('root', 10),
63+
Tree::IntervalTreeNode.new('root', 10..20)
64+
].each do |tree|
65+
assert_methods_present(tree, required)
66+
end
67+
end
68+
69+
def test_heap_tree_method_surface
70+
required = %i[insert add peek extract search delete key]
71+
72+
[
73+
Tree::BinaryHeapNode.new('root', 10),
74+
Tree::BinaryMaxHeapNode.new('root', 10)
75+
].each do |tree|
76+
assert_methods_present(tree, required)
77+
end
78+
end
79+
80+
def test_key_value_tree_method_surface
81+
required = %i[
82+
insert << search delete [] []= size length each keys values to_a to_h
83+
as_json to_json <=>
84+
]
85+
class_methods = %i[from_hash json_create]
86+
87+
[Tree::AATree.new, Tree::BTree.new].each do |tree|
88+
assert_methods_present(tree, required)
89+
assert_methods_present(tree.class, class_methods)
90+
end
91+
end
92+
93+
def test_aggregate_tree_method_surface
94+
required = %i[
95+
length each update range_sum [] []= keys values to_a to_h as_json
96+
to_json <=>
97+
]
98+
class_methods = %i[from_hash json_create]
99+
100+
[Tree::FenwickTree.new(3), Tree::SegmentTree.new(3)].each do |tree|
101+
assert_methods_present(tree, required)
102+
assert_methods_present(tree.class, class_methods)
103+
end
104+
105+
assert_equal(true, Tree::FenwickTree.new(3).respond_to?(:sum))
106+
assert_equal(true, Tree::SegmentTree.new(3).respond_to?(:sum))
107+
end
108+
109+
def test_trie_method_surface
110+
trie = Tree::TrieNode.new('')
111+
required = %i[
112+
insert << include? prefix? delete delete? words_with_prefix terminal?
113+
]
114+
115+
assert_methods_present(trie, required)
116+
end
117+
118+
def test_intentionally_absent_shovel_for_array_backed_trees
119+
assert_equal(false, Tree::FenwickTree.new(3).respond_to?(:<<))
120+
assert_equal(false, Tree::SegmentTree.new(3).respond_to?(:<<))
121+
end
122+
123+
private
124+
125+
def assert_methods_present(target, methods)
126+
methods.each do |method_name|
127+
assert_equal(
128+
true,
129+
target.respond_to?(method_name),
130+
"Expected #{target.class} to respond to ##{method_name}"
131+
)
132+
end
133+
end
134+
end
135+
end
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# test_api_contracts_aggregate_trees.rb - This file is part of the RubyTree package.
2+
#
3+
# Copyright (c) 2026 Anupam Sengupta
4+
#
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without modification,
8+
# are permitted provided that the following conditions are met:
9+
#
10+
# - Redistributions of source code must retain the above copyright notice, this
11+
# list of conditions and the following disclaimer.
12+
#
13+
# - Redistributions in binary form must reproduce the above copyright notice, this
14+
# list of conditions and the following disclaimer in the documentation and/or
15+
# other materials provided with the distribution.
16+
#
17+
# - Neither the name of the organization nor the names of its contributors may
18+
# be used to endorse or promote products derived from this software without
19+
# specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
#
32+
# frozen_string_literal: true
33+
34+
require 'test/unit'
35+
require_relative '../lib/tree/fenwicktree'
36+
require_relative '../lib/tree/segmenttree'
37+
38+
module TestTree
39+
# Contract tests for aggregate trees.
40+
class TestApiContractsAggregateTrees < Test::Unit::TestCase
41+
def test_fenwick_contract
42+
tree = Tree::FenwickTree.new(4, [1, 2, 3, 4])
43+
44+
assert_nil(tree.update(2, 5))
45+
assert_equal(11, tree.sum(2))
46+
assert_equal(14, tree.range_sum(1, 3))
47+
48+
stored = tree.send(:[]=, 0, 2)
49+
assert_equal(tree[0], stored)
50+
assert_kind_of(Enumerator, tree.each)
51+
assert_equal([0, 1, 2, 3], tree.keys)
52+
assert_equal(false, tree.respond_to?(:<<))
53+
assert_raise(NoMethodError) { tree << 1 }
54+
end
55+
56+
def test_segment_contract
57+
tree = Tree::SegmentTree.new(4, [1, 2, 3, 4])
58+
59+
assert_kind_of(Numeric, tree.update(2, 10))
60+
assert_equal(16, tree.range_sum(1, 3))
61+
62+
stored = tree.send(:[]=, 0, 8)
63+
assert_equal(tree[0], stored)
64+
assert_kind_of(Enumerator, tree.each)
65+
assert_equal([0, 1, 2, 3], tree.keys)
66+
assert_equal(false, tree.respond_to?(:<<))
67+
assert_raise(NoMethodError) { tree << 1 }
68+
end
69+
70+
def test_aggregate_tree_serialization_contract
71+
[Tree::FenwickTree.new(3, [1, 2, 3]), Tree::SegmentTree.new(3, [1, 2, 3])].each do |tree|
72+
hash = tree.to_h
73+
rebuilt = tree.class.from_hash(hash)
74+
75+
assert_equal(tree.to_a, rebuilt.to_a)
76+
assert_equal(hash, tree.as_json)
77+
assert_equal(tree.to_a, tree.class.json_create(hash).to_a)
78+
end
79+
end
80+
81+
def test_aggregate_tree_validation_contract
82+
assert_raise(ArgumentError) { Tree::FenwickTree.new(0) }
83+
assert_raise(ArgumentError) { Tree::SegmentTree.new(0) }
84+
85+
fenwick = Tree::FenwickTree.new(2)
86+
segment = Tree::SegmentTree.new(2)
87+
88+
assert_raise(ArgumentError) { fenwick.update(0, nil) }
89+
assert_raise(ArgumentError) { segment.update(0, nil) }
90+
end
91+
end
92+
end

test/test_api_contracts_heaps.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# test_api_contracts_heaps.rb - This file is part of the RubyTree package.
2+
#
3+
# Copyright (c) 2026 Anupam Sengupta
4+
#
5+
# All rights reserved.
6+
#
7+
# Redistribution and use in source and binary forms, with or without modification,
8+
# are permitted provided that the following conditions are met:
9+
#
10+
# - Redistributions of source code must retain the above copyright notice, this
11+
# list of conditions and the following disclaimer.
12+
#
13+
# - Redistributions in binary form must reproduce the above copyright notice, this
14+
# list of conditions and the following disclaimer in the documentation and/or
15+
# other materials provided with the distribution.
16+
#
17+
# - Neither the name of the organization nor the names of its contributors may
18+
# be used to endorse or promote products derived from this software without
19+
# specific prior written permission.
20+
#
21+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
25+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28+
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
#
32+
# frozen_string_literal: true
33+
34+
require 'test/unit'
35+
require_relative '../lib/tree/binaryheap'
36+
require_relative '../lib/tree/binarymaxheap'
37+
38+
module TestTree
39+
# Contract tests for heap trees.
40+
class TestApiContractsHeaps < Test::Unit::TestCase
41+
def test_min_heap_contract
42+
root = Tree::BinaryHeapNode.new('root', 10)
43+
root.insert('n5', 5)
44+
root.add(Tree::BinaryHeapNode.new('n20', 20))
45+
46+
assert_equal(5, root.peek)
47+
assert_equal(5, root.extract)
48+
assert_equal(10, root.peek)
49+
50+
found = root.search(20)
51+
assert_kind_of(Tree::BinaryHeapNode, found)
52+
53+
removed = root.delete(20)
54+
assert_kind_of(Tree::BinaryHeapNode, removed)
55+
assert_nil(root.search(20))
56+
end
57+
58+
def test_max_heap_contract
59+
root = Tree::BinaryMaxHeapNode.new('root', 10)
60+
root.insert('n5', 5)
61+
root.add(Tree::BinaryMaxHeapNode.new('n20', 20))
62+
63+
assert_equal(20, root.peek)
64+
assert_equal(20, root.extract)
65+
assert_equal(10, root.peek)
66+
67+
found = root.search(5)
68+
assert_kind_of(Tree::BinaryMaxHeapNode, found)
69+
70+
removed = root.delete(5)
71+
assert_kind_of(Tree::BinaryMaxHeapNode, removed)
72+
assert_nil(root.search(5))
73+
end
74+
75+
def test_heap_key_and_nil_key_validation
76+
root = Tree::BinaryHeapNode.new('root', 10)
77+
assert_equal(10, root.key)
78+
assert_raise(ArgumentError) { root.search(nil) }
79+
end
80+
end
81+
end

0 commit comments

Comments
 (0)