summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_decimal.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_decimal.py')
-rw-r--r--Lib/test/test_decimal.py343
1 files changed, 288 insertions, 55 deletions
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 841ea6f..46a448b 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -65,9 +65,7 @@ skip_expected = not os.path.isdir(directory)
# Slower, since it runs some things several times.
EXTENDEDERRORTEST = False
-
#Map the test cases' error names to the actual errors
-
ErrorNames = {'clamped' : Clamped,
'conversion_syntax' : InvalidOperation,
'division_by_zero' : DivisionByZero,
@@ -92,20 +90,88 @@ RoundingDict = {'ceiling' : ROUND_CEILING, #Maps test-case names to roundings.
'half_down' : ROUND_HALF_DOWN,
'half_even' : ROUND_HALF_EVEN,
'half_up' : ROUND_HALF_UP,
- 'up' : ROUND_UP}
+ 'up' : ROUND_UP,
+ '05up' : ROUND_05UP}
# Name adapter to be able to change the Decimal and Context
# interface without changing the test files from Cowlishaw
-nameAdapter = {'toeng':'to_eng_string',
- 'tosci':'to_sci_string',
- 'samequantum':'same_quantum',
- 'tointegral':'to_integral',
- 'remaindernear':'remainder_near',
+nameAdapter = {'and':'logical_and',
+ 'apply':'_apply',
+ 'class':'number_class',
+ 'comparesig':'compare_signal',
+ 'comparetotal':'compare_total',
+ 'comparetotmag':'compare_total_mag',
+ 'copy':'copy_decimal',
+ 'copyabs':'copy_abs',
+ 'copynegate':'copy_negate',
+ 'copysign':'copy_sign',
'divideint':'divide_int',
+ 'invert':'logical_invert',
+ 'iscanonical':'is_canonical',
+ 'isfinite':'is_finite',
+ 'isinfinite':'is_infinite',
+ 'isnan':'is_nan',
+ 'isnormal':'is_normal',
+ 'isqnan':'is_qnan',
+ 'issigned':'is_signed',
+ 'issnan':'is_snan',
+ 'issubnormal':'is_subnormal',
+ 'iszero':'is_zero',
+ 'maxmag':'max_mag',
+ 'minmag':'min_mag',
+ 'nextminus':'next_minus',
+ 'nextplus':'next_plus',
+ 'nexttoward':'next_toward',
+ 'or':'logical_or',
+ 'reduce':'normalize',
+ 'remaindernear':'remainder_near',
+ 'samequantum':'same_quantum',
'squareroot':'sqrt',
- 'apply':'_apply',
+ 'toeng':'to_eng_string',
+ 'tointegral':'to_integral_value',
+ 'tointegralx':'to_integral_exact',
+ 'tosci':'to_sci_string',
+ 'xor':'logical_xor',
}
+# The following functions return True/False rather than a Decimal instance
+
+LOGICAL_FUNCTIONS = (
+ 'is_canonical',
+ 'is_finite',
+ 'is_infinite',
+ 'is_nan',
+ 'is_normal',
+ 'is_qnan',
+ 'is_signed',
+ 'is_snan',
+ 'is_subnormal',
+ 'is_zero',
+ 'same_quantum',
+ )
+
+# For some operations (currently exp, ln, log10, power), the decNumber
+# reference implementation imposes additional restrictions on the
+# context and operands. These restrictions are not part of the
+# specification; however, the effect of these restrictions does show
+# up in some of the testcases. We skip testcases that violate these
+# restrictions, since Decimal behaves differently from decNumber for
+# these testcases so these testcases would otherwise fail.
+
+decNumberRestricted = ('power', 'ln', 'log10', 'exp')
+DEC_MAX_MATH = 999999
+def outside_decNumber_bounds(v, context):
+ if (context.prec > DEC_MAX_MATH or
+ context.Emax > DEC_MAX_MATH or
+ -context.Emin > DEC_MAX_MATH):
+ return True
+ if not v._is_special and v and (
+ len(v._int) > DEC_MAX_MATH or
+ v.adjusted() > DEC_MAX_MATH or
+ v.adjusted() < 1-2*DEC_MAX_MATH):
+ return True
+ return False
+
class DecimalTest(unittest.TestCase):
"""Class which tests the Decimal class against the test cases.
@@ -142,10 +208,6 @@ class DecimalTest(unittest.TestCase):
#print line
try:
t = self.eval_line(line)
- except InvalidOperation:
- print 'Error in test cases:'
- print line
- continue
except DecimalException, exception:
#Exception raised where there shoudn't have been one.
self.fail('Exception "'+exception.__class__.__name__ + '" raised on line '+line)
@@ -194,7 +256,8 @@ class DecimalTest(unittest.TestCase):
Sides = s.split('->')
L = Sides[0].strip().split()
id = L[0]
-# print id,
+ if DEBUG:
+ print "Test ", id,
funct = L[1].lower()
valstemp = L[2:]
L = Sides[1].strip().split()
@@ -246,11 +309,27 @@ class DecimalTest(unittest.TestCase):
self.context.traps[error] = 0
v = self.context.create_decimal(v)
else:
- v = Decimal(v)
+ v = Decimal(v, self.context)
vals.append(v)
ans = FixQuotes(ans)
+ # skip tests that are related to bounds imposed in the decNumber
+ # reference implementation
+ if fname in decNumberRestricted:
+ if fname == 'power':
+ if not (vals[1]._isinteger() and
+ -1999999997 <= vals[1] <= 999999999):
+ if outside_decNumber_bounds(vals[0], self.context) or \
+ outside_decNumber_bounds(vals[1], self.context):
+ #print "Skipping test %s" % s
+ return
+ else:
+ if outside_decNumber_bounds(vals[0], self.context):
+ #print "Skipping test %s" % s
+ return
+
+
if EXTENDEDERRORTEST and fname not in ('to_sci_string', 'to_eng_string'):
for error in theirexceptions:
self.context.traps[error] = 1
@@ -264,9 +343,11 @@ class DecimalTest(unittest.TestCase):
else:
self.fail("Did not raise %s in %s" % (error, s))
self.context.traps[error] = 0
+ if DEBUG:
+ print "--", self.context
try:
result = str(funct(*vals))
- if fname == 'same_quantum':
+ if fname in LOGICAL_FUNCTIONS:
result = str(int(eval(result))) # 'True', 'False' -> '1', '0'
except Signals, error:
self.fail("Raised %s in %s" % (error, s))
@@ -283,8 +364,7 @@ class DecimalTest(unittest.TestCase):
self.assertEqual(result, ans,
'Incorrect answer for ' + s + ' -- got ' + result)
self.assertEqual(myexceptions, theirexceptions,
- 'Incorrect flags set in ' + s + ' -- got ' \
- + str(myexceptions))
+ 'Incorrect flags set in ' + s + ' -- got ' + str(myexceptions))
return
def getexceptions(self):
@@ -301,17 +381,6 @@ class DecimalTest(unittest.TestCase):
def change_clamp(self, clamp):
self.context._clamp = clamp
-# Dynamically build custom test definition for each file in the test
-# directory and add the definitions to the DecimalTest class. This
-# procedure insures that new files do not get skipped.
-for filename in os.listdir(directory):
- if '.decTest' not in filename:
- continue
- head, tail = filename.split('.')
- tester = lambda self, f=filename: self.eval_file(directory + f)
- setattr(DecimalTest, 'test_' + head, tester)
- del filename, head, tail, tester
-
# The following classes test the behaviour of Decimal according to PEP 327
@@ -383,13 +452,19 @@ class DecimalExplicitConstructionTest(unittest.TestCase):
#bad sign
self.assertRaises(ValueError, Decimal, (8, (4, 3, 4, 9, 1), 2) )
+ self.assertRaises(ValueError, Decimal, (0., (4, 3, 4, 9, 1), 2) )
+ self.assertRaises(ValueError, Decimal, (Decimal(1), (4, 3, 4, 9, 1), 2))
#bad exp
self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), 'wrong!') )
+ self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), 0.) )
+ self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 9, 1), '1') )
#bad coefficients
self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, None, 1), 2) )
self.assertRaises(ValueError, Decimal, (1, (4, -3, 4, 9, 1), 2) )
+ self.assertRaises(ValueError, Decimal, (1, (4, 10, 4, 9, 1), 2) )
+ self.assertRaises(ValueError, Decimal, (1, (4, 3, 4, 'a', 1), 2) )
def test_explicit_from_Decimal(self):
@@ -803,7 +878,7 @@ class DecimalUseOfContextTest(unittest.TestCase):
th2.start()
self.finish1.wait()
- self.finish1.wait()
+ self.finish2.wait()
return
if threading is None:
@@ -853,6 +928,10 @@ class DecimalUsabilityTest(unittest.TestCase):
a.sort()
self.assertEqual(a, b)
+ # with None
+ self.assertFalse(Decimal(1) < None)
+ self.assertTrue(Decimal(1) > None)
+
def test_copy_and_deepcopy_methods(self):
d = Decimal('43.24')
c = copy.copy(d)
@@ -863,12 +942,54 @@ class DecimalUsabilityTest(unittest.TestCase):
def test_hash_method(self):
#just that it's hashable
hash(Decimal(23))
+
+ test_values = [Decimal(sign*(2**m + n))
+ for m in [0, 14, 15, 16, 17, 30, 31,
+ 32, 33, 62, 63, 64, 65, 66]
+ for n in range(-10, 10)
+ for sign in [-1, 1]]
+ test_values.extend([
+ Decimal("-0"), # zeros
+ Decimal("0.00"),
+ Decimal("-0.000"),
+ Decimal("0E10"),
+ Decimal("-0E12"),
+ Decimal("10.0"), # negative exponent
+ Decimal("-23.00000"),
+ Decimal("1230E100"), # positive exponent
+ Decimal("-4.5678E50"),
+ # a value for which hash(n) != hash(n % (2**64-1))
+ # in Python pre-2.6
+ Decimal(2**64 + 2**32 - 1),
+ ])
+
+ # check that hash(d) == hash(int(d)) for integral values
+ for value in test_values:
+ self.assertEqual(hash(value), hash(int(value)))
+
#the same hash that to an int
self.assertEqual(hash(Decimal(23)), hash(23))
self.assertRaises(TypeError, hash, Decimal('NaN'))
self.assert_(hash(Decimal('Inf')))
self.assert_(hash(Decimal('-Inf')))
+ # check that the value of the hash doesn't depend on the
+ # current context (issue #1757)
+ c = getcontext()
+ old_precision = c.prec
+ x = Decimal("123456789.1")
+
+ c.prec = 6
+ h1 = hash(x)
+ c.prec = 10
+ h2 = hash(x)
+ c.prec = 16
+ h3 = hash(x)
+
+ self.assertEqual(h1, h2)
+ self.assertEqual(h1, h3)
+ c.prec = old_precision
+
def test_min_and_max_methods(self):
d1 = Decimal('15.32')
@@ -955,13 +1076,35 @@ class DecimalUsabilityTest(unittest.TestCase):
d = Decimal("Infinity")
self.assertEqual(d.as_tuple(), (0, (0,), 'F') )
+ #leading zeros in coefficient should be stripped
+ d = Decimal( (0, (0, 0, 4, 0, 5, 3, 4), -2) )
+ self.assertEqual(d.as_tuple(), (0, (4, 0, 5, 3, 4), -2) )
+ d = Decimal( (1, (0, 0, 0), 37) )
+ self.assertEqual(d.as_tuple(), (1, (0,), 37))
+ d = Decimal( (1, (), 37) )
+ self.assertEqual(d.as_tuple(), (1, (0,), 37))
+
+ #leading zeros in NaN diagnostic info should be stripped
+ d = Decimal( (0, (0, 0, 4, 0, 5, 3, 4), 'n') )
+ self.assertEqual(d.as_tuple(), (0, (4, 0, 5, 3, 4), 'n') )
+ d = Decimal( (1, (0, 0, 0), 'N') )
+ self.assertEqual(d.as_tuple(), (1, (), 'N') )
+ d = Decimal( (1, (), 'n') )
+ self.assertEqual(d.as_tuple(), (1, (), 'n') )
+
+ #coefficient in infinity should be ignored
+ d = Decimal( (0, (4, 5, 3, 4), 'F') )
+ self.assertEqual(d.as_tuple(), (0, (0,), 'F'))
+ d = Decimal( (1, (0, 2, 7, 1), 'F') )
+ self.assertEqual(d.as_tuple(), (1, (0,), 'F'))
+
def test_immutability_operations(self):
# Do operations and check that it didn't change change internal objects.
d1 = Decimal('-25e55')
b1 = Decimal('-25e55')
- d2 = Decimal('33e-33')
- b2 = Decimal('33e-33')
+ d2 = Decimal('33e+33')
+ b2 = Decimal('33e+33')
def checkSameDec(operation, useOther=False):
if useOther:
@@ -1025,6 +1168,21 @@ class DecimalUsabilityTest(unittest.TestCase):
checkSameDec("to_eng_string")
checkSameDec("to_integral")
+ def test_subclassing(self):
+ # Different behaviours when subclassing Decimal
+
+ class MyDecimal(Decimal):
+ pass
+
+ d1 = MyDecimal(1)
+ d2 = MyDecimal(2)
+ d = d1 + d2
+ self.assertTrue(type(d) is Decimal)
+
+ d = d1.max(d2)
+ self.assertTrue(type(d) is Decimal)
+
+
class DecimalPythonAPItests(unittest.TestCase):
def test_pickle(self):
@@ -1091,7 +1249,59 @@ class WithStatementTest(unittest.TestCase):
self.assert_(new_ctx is not set_ctx, 'did not copy the context')
self.assert_(set_ctx is enter_ctx, '__enter__ returned wrong context')
-def test_main(arith=False, verbose=None):
+class ContextFlags(unittest.TestCase):
+ def test_flags_irrelevant(self):
+ # check that the result (numeric result + flags raised) of an
+ # arithmetic operation doesn't depend on the current flags
+
+ context = Context(prec=9, Emin = -999999999, Emax = 999999999,
+ rounding=ROUND_HALF_EVEN, traps=[], flags=[])
+
+ # operations that raise various flags, in the form (function, arglist)
+ operations = [
+ (context._apply, [Decimal("100E-1000000009")]),
+ (context.sqrt, [Decimal(2)]),
+ (context.add, [Decimal("1.23456789"), Decimal("9.87654321")]),
+ (context.multiply, [Decimal("1.23456789"), Decimal("9.87654321")]),
+ (context.subtract, [Decimal("1.23456789"), Decimal("9.87654321")]),
+ ]
+
+ # try various flags individually, then a whole lot at once
+ flagsets = [[Inexact], [Rounded], [Underflow], [Clamped], [Subnormal],
+ [Inexact, Rounded, Underflow, Clamped, Subnormal]]
+
+ for fn, args in operations:
+ # find answer and flags raised using a clean context
+ context.clear_flags()
+ ans = fn(*args)
+ flags = [k for k, v in context.flags.items() if v]
+
+ for extra_flags in flagsets:
+ # set flags, before calling operation
+ context.clear_flags()
+ for flag in extra_flags:
+ context._raise_error(flag)
+ new_ans = fn(*args)
+
+ # flags that we expect to be set after the operation
+ expected_flags = list(flags)
+ for flag in extra_flags:
+ if flag not in expected_flags:
+ expected_flags.append(flag)
+ expected_flags.sort()
+
+ # flags we actually got
+ new_flags = [k for k,v in context.flags.items() if v]
+ new_flags.sort()
+
+ self.assertEqual(ans, new_ans,
+ "operation produces different answers depending on flags set: " +
+ "expected %s, got %s." % (ans, new_ans))
+ self.assertEqual(new_flags, expected_flags,
+ "operation raises different flags depending on flags set: " +
+ "expected %s, got %s" % (expected_flags, new_flags))
+
+def test_main(arith=False, verbose=None, todo_tests=None, debug=None):
""" Execute the tests.
Runs all arithmetic tests if arith is True or if the "decimal" resource
@@ -1099,35 +1309,58 @@ def test_main(arith=False, verbose=None):
"""
init()
- global TEST_ALL
+ global TEST_ALL, DEBUG
TEST_ALL = arith or is_resource_enabled('decimal')
+ DEBUG = debug
+
+ if todo_tests is None:
+ test_classes = [
+ DecimalExplicitConstructionTest,
+ DecimalImplicitConstructionTest,
+ DecimalArithmeticOperatorsTest,
+ DecimalUseOfContextTest,
+ DecimalUsabilityTest,
+ DecimalPythonAPItests,
+ ContextAPItests,
+ DecimalTest,
+ WithStatementTest,
+ ContextFlags
+ ]
+ else:
+ test_classes = [DecimalTest]
+
+ # Dynamically build custom test definition for each file in the test
+ # directory and add the definitions to the DecimalTest class. This
+ # procedure insures that new files do not get skipped.
+ for filename in os.listdir(directory):
+ if '.decTest' not in filename or filename.startswith("."):
+ continue
+ head, tail = filename.split('.')
+ if todo_tests is not None and head not in todo_tests:
+ continue
+ tester = lambda self, f=filename: self.eval_file(directory + f)
+ setattr(DecimalTest, 'test_' + head, tester)
+ del filename, head, tail, tester
- test_classes = [
- DecimalExplicitConstructionTest,
- DecimalImplicitConstructionTest,
- DecimalArithmeticOperatorsTest,
- DecimalUseOfContextTest,
- DecimalUsabilityTest,
- DecimalPythonAPItests,
- ContextAPItests,
- DecimalTest,
- WithStatementTest,
- ]
try:
run_unittest(*test_classes)
- import decimal as DecimalModule
- run_doctest(DecimalModule, verbose)
+ if todo_tests is None:
+ import decimal as DecimalModule
+ run_doctest(DecimalModule, verbose)
finally:
setcontext(ORIGINAL_CONTEXT)
if __name__ == '__main__':
- # Calling with no arguments runs all tests.
- # Calling with "Skip" will skip over 90% of the arithmetic tests.
- if len(sys.argv) == 1:
- test_main(arith=True, verbose=True)
- elif len(sys.argv) == 2:
- arith = sys.argv[1].lower() != 'skip'
- test_main(arith=arith, verbose=True)
+ import optparse
+ p = optparse.OptionParser("test_decimal.py [--debug] [{--skip | test1 [test2 [...]]}]")
+ p.add_option('--debug', '-d', action='store_true', help='shows the test number and context before each test')
+ p.add_option('--skip', '-s', action='store_true', help='skip over 90% of the arithmetic tests')
+ (opt, args) = p.parse_args()
+
+ if opt.skip:
+ test_main(arith=False, verbose=True)
+ elif args:
+ test_main(arith=True, verbose=True, todo_tests=args, debug=opt.debug)
else:
- raise ValueError("test called with wrong arguments, use test_Decimal [Skip]")
+ test_main(arith=True, verbose=True)