-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_hyperop.py
More file actions
246 lines (194 loc) · 7.74 KB
/
Copy pathtest_hyperop.py
File metadata and controls
246 lines (194 loc) · 7.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
'''
Unit tests for the hyperoper module.
Tests cover:
- Basic hyperoperation definitions
- Input validation and error handling
- Recursion depth protection
- Memoization functionality
- Edge cases and special values
'''
import unittest
from hyperop import oper, op, RecursionDepthExceeded, clear_op_cache
class TestHyperoperationBasics(unittest.TestCase):
"""Test basic hyperoperation computations."""
def test_successor_operation(self):
"""Test H_0(a, b) = b + 1 (successor)."""
self.assertEqual(op(0, 5, 3), 4)
self.assertEqual(op(0, 10, 0), 1)
self.assertEqual(oper(0, 5, 3), 4)
def test_addition_operation(self):
"""Test H_1(a, b) = a + b."""
self.assertEqual(op(1, 3, 4), 7)
self.assertEqual(op(1, 0, 5), 5)
self.assertEqual(op(1, 5, 0), 5)
self.assertEqual(oper(1, 3, 4), 7)
def test_multiplication_operation(self):
"""Test H_2(a, b) = a * b."""
self.assertEqual(op(2, 3, 4), 12)
self.assertEqual(op(2, 5, 0), 0)
self.assertEqual(op(2, 0, 5), 0)
self.assertEqual(oper(2, 3, 4), 12)
def test_exponentiation_operation(self):
"""Test H_3(a, b) = a ** b."""
self.assertEqual(op(3, 2, 3), 8)
self.assertEqual(op(3, 3, 2), 9)
self.assertEqual(op(3, 5, 0), 1)
self.assertEqual(oper(3, 2, 3), 8)
def test_tetration_operation(self):
"""Test H_4(a, b) = a ↑↑ b (tetration)."""
# 2 ↑↑ 3 = 2^2^2 = 16
self.assertEqual(op(4, 2, 3), 16)
# 2 ↑↑ 4 = 2^2^2^2 = 65536
self.assertEqual(op(4, 2, 4), 65536)
class TestBaseCase(unittest.TestCase):
"""Test base cases when b == 0."""
def test_base_case_n0(self):
"""When n=0, H_0(a, 0) = 1."""
self.assertEqual(op(0, 5, 0), 1)
self.assertEqual(oper(0, 5, 0), 1)
def test_base_case_n1(self):
"""When n=1, H_1(a, 0) = a."""
self.assertEqual(op(1, 5, 0), 5)
self.assertEqual(op(1, 0, 0), 0)
self.assertEqual(oper(1, 5, 0), 5)
def test_base_case_n2(self):
"""When n=2, H_2(a, 0) = 0."""
self.assertEqual(op(2, 5, 0), 0)
self.assertEqual(oper(2, 5, 0), 0)
def test_base_case_n_greater_than_2(self):
"""When n > 2, H_n(a, 0) = 1."""
for n in range(3, 10):
self.assertEqual(op(n, 5, 0), 1)
self.assertEqual(oper(n, 5, 0), 1)
class TestInputValidation(unittest.TestCase):
"""Test input validation and error handling."""
def test_non_integer_n(self):
"""Reject non-integer n."""
with self.assertRaises(ValueError):
op(1.5, 2, 3)
with self.assertRaises(ValueError):
oper("2", 2, 3)
def test_non_integer_a(self):
"""Reject non-integer a."""
with self.assertRaises(ValueError):
op(1, 2.5, 3)
with self.assertRaises(ValueError):
oper(1, "2", 3)
def test_non_integer_b(self):
"""Reject non-integer b."""
with self.assertRaises(ValueError):
op(1, 2, 3.5)
with self.assertRaises(ValueError):
oper(1, 2, "3")
def test_negative_n(self):
"""Reject negative n."""
with self.assertRaises(ValueError):
op(-1, 2, 3)
with self.assertRaises(ValueError):
oper(-1, 2, 3)
def test_negative_a(self):
"""Reject negative a."""
with self.assertRaises(ValueError):
op(1, -2, 3)
with self.assertRaises(ValueError):
oper(1, -2, 3)
def test_negative_b(self):
"""Reject negative b."""
with self.assertRaises(ValueError):
op(1, 2, -3)
with self.assertRaises(ValueError):
oper(1, 2, -3)
class TestRecursionDepthProtection(unittest.TestCase):
"""Test recursion depth protection."""
def test_recursion_depth_with_low_limit_oper(self):
"""Test that oper() respects a very low recursion depth limit."""
# oper(4, 2, 6) with _max_depth=5 should exceed the limit
with self.assertRaises(RecursionDepthExceeded):
oper(4, 2, 6, _max_depth=5)
def test_recursion_depth_with_low_limit_op(self):
"""Test that op() respects a very low recursion depth limit."""
# op(4, 2, 6) with _max_depth=5 should exceed the limit
with self.assertRaises(RecursionDepthExceeded):
op(4, 2, 6, _max_depth=5)
def test_sufficient_recursion_depth_limit(self):
"""Test with sufficient recursion depth limit."""
# Should work with sufficient limit
result = op(4, 2, 2)
self.assertEqual(result, 4)
class TestMemoization(unittest.TestCase):
"""Test memoization caching functionality."""
def setUp(self):
"""Clear cache before each test."""
clear_op_cache()
def tearDown(self):
"""Clear cache after each test."""
clear_op_cache()
def test_cache_stores_result(self):
"""Test that results are cached."""
initial_cache_size = op.cache_info().currsize
result1 = op(2, 3, 4)
cache_after_first = op.cache_info().currsize
result2 = op(2, 3, 4)
cache_after_second = op.cache_info().currsize
# Cache should grow after first call
self.assertGreater(cache_after_first, initial_cache_size)
# Cache should not grow after second call (using cached value)
self.assertEqual(cache_after_first, cache_after_second)
# Results should be identical
self.assertEqual(result1, result2)
def test_different_arguments_create_different_cache_entries(self):
"""Test that different arguments create different cache entries."""
clear_op_cache()
op(2, 3, 4)
cache_size_1 = op.cache_info().currsize
op(2, 3, 5)
cache_size_2 = op.cache_info().currsize
# Cache should grow for different arguments
self.assertGreater(cache_size_2, cache_size_1)
def test_clear_cache(self):
"""Test cache clearing functionality."""
op(2, 3, 4)
self.assertGreater(op.cache_info().currsize, 0)
clear_op_cache()
self.assertEqual(op.cache_info().currsize, 0)
def test_memoization_improves_performance(self):
"""Test that memoization provides performance benefit."""
import time
clear_op_cache()
# First call - not cached
start = time.time()
op(3, 5, 5)
first_time = time.time() - start
# Second call - cached
start = time.time()
op(3, 5, 5)
second_time = time.time() - start
# Cached call should be faster (or at least not slower)
# We're lenient with timing tests since they can be flaky
self.assertLessEqual(second_time * 2, first_time + 0.01)
class TestLargeValues(unittest.TestCase):
"""Test handling of large values and overflow protection."""
def test_exponentiation_overflow_protection(self):
"""Test that exponentiation overflow is protected."""
with self.assertRaises(ValueError):
op(3, 10, 101) # 10^101 is too large
def test_manageable_large_values(self):
"""Test that manageable large values work."""
# 2^30 should work
result = op(3, 2, 30)
self.assertEqual(result, 2**30)
class TestConsistency(unittest.TestCase):
"""Test consistency between oper() and op() functions."""
def test_small_values_consistency(self):
"""Test that oper() and op() give same results for small values."""
test_cases = [
(0, 2, 3),
(1, 3, 4),
(2, 2, 5),
(3, 2, 4),
]
for n, a, b in test_cases:
with self.subTest(n=n, a=a, b=b):
self.assertEqual(op(n, a, b), oper(n, a, b))
if __name__ == '__main__':
unittest.main()