diff options
Diffstat (limited to 'Lib/test/test_decimal.py')
| -rw-r--r-- | Lib/test/test_decimal.py | 343 |
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) |
