Skip to content

Commit 89ee1d3

Browse files
committed
improve test coverage
1 parent d41432b commit 89ee1d3

File tree

1 file changed

+99
-3
lines changed

1 file changed

+99
-3
lines changed

unpythonic/test/test_mathseq.py

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from .fixtures import session, testset
55

66
from operator import mul
7-
from math import exp
7+
from math import exp, trunc, floor, ceil
8+
from sys import float_info
89

910
from ..mathseq import (s, m, mg,
1011
sadd, smul, spow, cauchyprod,
@@ -51,9 +52,26 @@ def runtests():
5152
test[not almosteq("ab", "abc")]
5253

5354
test[almosteq(1.0, 1.0 + ulp(1.0))]
54-
test[almosteq(1e-309, 0, tol=1.0)]
5555

56-
# explicitly listed elements, same as a genexpr using tuple input
56+
# TODO: counterintuitively, need a large tolerance here, because when one operand is zero,
57+
# TODO: the final tolerance is actually tol*min_normal.
58+
min_normal = float_info.min
59+
test[almosteq(min_normal / 2, 0, tol=1.0)]
60+
61+
too_large = 2**int(1e6)
62+
test_raises[OverflowError, float(too_large), "UPDATE THIS, need a float overflow here."]
63+
test[almosteq(too_large, too_large + 1)] # works, because 1/too_large is very small.
64+
65+
try:
66+
from mpmath import mpf
67+
except ImportError:
68+
error["mpmath not installed in this Python, cannot test arbitrary precision input for mathseq."]
69+
else:
70+
test[almosteq(mpf(1.0), mpf(1.0 + ulp(1.0)))]
71+
test[almosteq(1.0, mpf(1.0 + ulp(1.0)))]
72+
test[almosteq(mpf(1.0), 1.0 + ulp(1.0))]
73+
74+
# explicitly listed elements, same as a genexpr using tuple input
5775
with testset("s, convenience"):
5876
test[tuple(s(1)) == (1,)]
5977
test[tuple(s(1, 2, 3, 4, 5)) == (1, 2, 3, 4, 5)]
@@ -62,6 +80,9 @@ def runtests():
6280
# always infinite length, because final element cannot be used to deduce length.
6381
with testset("s, constant sequence"):
6482
test[tuple(take(10, s(1, ...))) == (1,) * 10]
83+
# exercise the different branches of the analyzer
84+
test[tuple(take(10, s(1, 1, ...))) == (1,) * 10]
85+
test[tuple(take(10, s(1, 1, 1, ...))) == (1,) * 10]
6586
# type stability (not that it does us any good in Python)
6687
test[all(isinstance(x, int) for x in take(10, s(1, ...)))]
6788
test[all(isinstance(x, float) for x in take(10, s(1.0, ...)))]
@@ -70,6 +91,13 @@ def runtests():
7091
with testset("s, arithmetic sequence"):
7192
test[tuple(take(10, s(1, 2, ...))) == tuple(range(1, 11))] # two elements is enough
7293
test[tuple(take(10, s(1, 2, 3, ...))) == tuple(range(1, 11))] # more is allowed if consistent
94+
# Trigger the analyzer corner case where consecutive differences are almost, but not exactly the same.
95+
# 1/3 is a useful constant here, as it has no exact float representation at any finite number of bits.
96+
# That's also a source of party tricks such as 7/3 - 4/3 - 1 = machine epsilon (in IEEE-754).
97+
# (Expand that in binary to see why.)
98+
# Here the 2/3 term is one ulp off from the "exact" result (which has only representation error).
99+
test[all(abs(x - y) <= min(ulp(x), ulp(y)) for x, y in zip(tuple(take(5, s(1 / 3, 2 / 3, 3 / 3, ...))),
100+
(1 / 3, 2 / 3, 3 / 3, 4 / 3, 5 / 3)))]
73101
# type stability
74102
test[all(isinstance(x, int) for x in take(10, s(1, 3, ...)))]
75103
test[all(isinstance(x, int) for x in take(10, s(1, 3, 5, ...)))]
@@ -149,6 +177,13 @@ def runtests():
149177
test[tuple(cauchyprod((2, 4), (1, 3, 5), require="all")) == (2, 10)]
150178
test[tuple(cauchyprod((2,), (1, 3, 5), require="all")) == (2,)]
151179

180+
# both inputs must be iterables for this operation to be defined
181+
test_raises[TypeError, cauchyprod(1, 2)]
182+
test_raises[TypeError, cauchyprod(s(1, 3, 5, ...), 2)]
183+
test_raises[TypeError, cauchyprod(2, s(1, 3, 5, ...))]
184+
# The optional "require" argument must be "all" or "any".
185+
test_raises[ValueError, cauchyprod(s(1, 3, 5, ...), s(2, 4, 6, ...), require="invalid_value")]
186+
152187
with testset("m, mg (infix syntax for arithmetic)"):
153188
# Sequences returned by `s` are `m`'d implicitly.
154189
test[tuple(take(5, s(1, 3, 5, ...) + s(2, 4, 6, ...))) == (3, 7, 11, 15, 19)]
@@ -167,6 +202,48 @@ def runtests():
167202
test[tuple(take(5, s(1, 3, ...)**2)) == (1, 3**2, 5**2, 7**2, 9**2)]
168203
test[tuple(take(5, 2**s(1, 3, ...))) == (2**1, 2**3, 2**5, 2**7, 2**9)]
169204

205+
test[tuple(take(5, abs(s(-1, -2, ...)))) == (1, 2, 3, 4, 5)] # m.__abs__
206+
test[tuple(take(5, +s(-1, -2, ...))) == (-1, -2, -3, -4, -5)] # m.__pos__
207+
test[tuple(take(5, -s(-1, -2, ...))) == (1, 2, 3, 4, 5)] # m.__neg__
208+
test[tuple(take(5, s(1, 2, ...) // 2)) == (0, 1, 1, 2, 2)]
209+
test[tuple(take(5, 10 // s(1, 2, ...))) == (10, 5, 3, 2, 2)]
210+
test[tuple(take(5, s(1, 2, ...) % 2)) == (1, 0, 1, 0, 1)]
211+
test[tuple(take(5, 10 % s(1, 2, ...))) == (0, 0, 1, 2, 0)]
212+
test[tuple(take(5, divmod(s(1, 2, ...), 2))) == ((0, 1), (1, 0), (1, 1), (2, 0), (2, 1))]
213+
test[tuple(take(5, divmod(10, s(1, 2, ...)))) == ((10, 0), (5, 0), (3, 1), (2, 2), (2, 0))]
214+
test[tuple(take(5, round(s(1.111, 2.222, ...)))) == (1, 2, 3, 4, 6)]
215+
# But be careful. As the language reference warns:
216+
# https://docs.python.org/3/library/functions.html#round
217+
# rounding is correct taking into account the float representation, which is base-2.
218+
test[tuple(take(5, round(s(1.111, 2.222, ...), 2))) == (1.11, 2.22, 3.33, 4.44, 5.55)]
219+
220+
test[tuple(take(5, trunc(s(1.111, 2.222, ...)))) == (1, 2, 3, 4, 5)]
221+
test[tuple(take(5, floor(s(1.111, 2.222, ...)))) == (1, 2, 3, 4, 5)]
222+
test[tuple(take(5, ceil(s(1.111, 2.222, ...)))) == (2, 3, 4, 5, 6)]
223+
224+
# bit shifts
225+
test[tuple(take(5, s(1, 2, 4, ...) << 1)) == (2, 4, 8, 16, 32)]
226+
test[tuple(take(5, 1 << s(1, 2, ...))) == (2, 4, 8, 16, 32)]
227+
test[tuple(take(5, s(2, 4, 8, ...) >> 1)) == (1, 2, 4, 8, 16)]
228+
test[tuple(take(5, 32 >> s(1, 2, ...))) == (16, 8, 4, 2, 1)]
229+
230+
# termwise bitwise logical operations
231+
test[tuple(m((0, 1, 0, 1)) & m((0, 0, 1, 1))) == (0, 0, 0, 1)]
232+
test[tuple(m((0, 1, 0, 1)) | m((0, 0, 1, 1))) == (0, 1, 1, 1)]
233+
test[tuple(m((0, 1, 0, 1)) ^ m((0, 0, 1, 1))) == (0, 1, 1, 0)] # xor
234+
test[tuple(1 & m((0, 1))) == (0, 1)]
235+
test[tuple(1 | m((0, 1))) == (1, 1)]
236+
test[tuple(1 ^ m((0, 1))) == (1, 0)]
237+
test[tuple(take(5, ~s(1, 2, ...))) == (-2, -3, -4, -5, -6)] # m.__invert__
238+
239+
# v0.14.3+: termwise comparison
240+
test[tuple(s(1, 2, 3) < s(2, 3, 4)) == (True, True, True)]
241+
test[tuple(s(1, 2, 3) <= s(1, 2, 4)) == (True, True, True)]
242+
test[tuple(s(1, 2, 3) == s(3, 2, 1)) == (False, True, False)]
243+
test[tuple(s(1, 2, 3) != s(3, 2, 1)) == (True, False, True)]
244+
test[tuple(s(1, 2, 3) >= s(2, 3, 4)) == (False, False, False)]
245+
test[tuple(s(1, 2, 3) > s(1, 2, 4)) == (False, False, False)]
246+
170247
a = s(1, 3, ...)
171248
b = s(2, 4, ...)
172249
c = a + b
@@ -208,6 +285,12 @@ def runtests():
208285
test[abs(last(s(0.01, 0.02, ..., 10000)) - 10000.0) <= ulp(10000.0)]
209286

210287
with testset("error cases"):
288+
# invalid specifications
289+
test_raises[SyntaxError, s(...)] # no data to work with
290+
test_raises[SyntaxError, s(..., 1)] # no initial term, ellipsis
291+
test_raises[SyntaxError, s(..., 1, 2)] # no initial term, multiple terms after the ellipsis
292+
test_raises[SyntaxError, s(0, ..., 2, 3)] # multiple terms after the ellipsis
293+
211294
test_raises[SyntaxError,
212295
s(1, ..., 1),
213296
"should detect that the length of a constant sequence cannot be determined from a final element"]
@@ -220,6 +303,15 @@ def runtests():
220303
test_raises[SyntaxError,
221304
s(2, 4, 0, ...),
222305
"should detect that a geometric sequence must have no zero elements"]
306+
test_raises[SyntaxError,
307+
s(2, -4, 8, ..., -32),
308+
"should detect that the parity of the last term of alternating geometric sequence is wrong"]
309+
test_raises[SyntaxError,
310+
s(2, -1 / 4, 16, ..., -65536),
311+
"should detect that the parity of the last term of alternating power sequence is wrong"]
312+
test_raises[SyntaxError,
313+
s(0, 1, 2, 4, ...),
314+
"should detect two incompatible sequence types (arith 0, 1, 2; geom 1, 2, 4)"]
223315
test_raises[SyntaxError,
224316
s(2, 3, 5, 7, 11, ...),
225317
"should detect that s() is not that smart!"]
@@ -263,6 +355,10 @@ def runtests():
263355
test[tuple(take(10, primes())) == (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)]
264356
test[tuple(take(10, fibonacci())) == (1, 1, 2, 3, 5, 8, 13, 21, 34, 55)]
265357

358+
test[tuple(take(10, primes(optimize="speed"))) == (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)]
359+
test[tuple(take(10, primes(optimize="memory"))) == (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)]
360+
test_raises[ValueError, primes(optimize="fun")] # only "speed" and "memory" modes exist
361+
266362
factorials = imemoize(scanl(mul, 1, s(1, 2, ...))) # 0!, 1!, 2!, ...
267363
test[last(take(6, factorials())) == 120]
268364

0 commit comments

Comments
 (0)