diff -r d489394a73de Lib/test/test_decimal.py --- a/Lib/test/test_decimal.py Tue Dec 17 15:12:46 2013 +0200 +++ b/Lib/test/test_decimal.py Tue Dec 17 15:10:59 2013 -0600 @@ -1,4 +1,4 @@ -# Copyright (c) 2004 Python Software Foundation. +# Copyright (c) 2004-2013 Python Software Foundation. # All rights reserved. # Written by Eric Price @@ -17,28 +17,24 @@ Cowlishaw's tests can be downloaded from: http://speleotrove.com/decimal/dectest.zip - -This test module can be called from command line with one parameter (Arithmetic -or Behaviour) to test each part, or without parameter to test both parts. If -you're working through IDLE, you can import this test module and call test_main() -with the corresponding argument. """ import math -import os, sys +import os +import sys import operator import warnings -import pickle, copy +import pickle +import copy import unittest +import doctest import numbers import locale -from test.support import (run_unittest, run_doctest, is_resource_enabled, - requires_IEEE_754) -from test.support import (check_warnings, import_fresh_module, TestFailed, +from test.support import (is_resource_enabled, requires_IEEE_754, + check_warnings, import_fresh_module, TestFailed, run_with_locale, cpython_only) import random import time -import warnings try: import threading except ImportError: @@ -74,9 +70,6 @@ P.Overflow, P.DivisionByZero, P.InvalidOperation, P.FloatOperation] } -def assert_signals(cls, context, attr, expected): - d = getattr(context, attr) - cls.assertTrue(all(d[s] if s in expected else not d[s] for s in d)) ROUND_UP = P.ROUND_UP ROUND_DOWN = P.ROUND_DOWN @@ -93,42 +86,84 @@ ROUND_05UP ] -# Tests are built around these assumed context defaults. -# test_main() restores the original context. +# Tests are built around these assumed context defaults. The DecimalTest +# base class ensures that each test starts with a copy of the appropriate +# default context, and fails the test if it doesn't clean up the context. +# The original contexts are restored by tearDownModule(). ORIGINAL_CONTEXT = { C: C.getcontext().copy() if C else None, P: P.getcontext().copy() } -def init(m): - if not m: return - DefaultTestContext = m.Context( - prec=9, rounding=ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[m], 0) - ) - m.setcontext(DefaultTestContext) - -TESTDATADIR = 'decimaltestdata' -if __name__ == '__main__': - file = sys.argv[0] -else: - file = __file__ -testdir = os.path.dirname(file) or os.curdir -directory = testdir + os.sep + TESTDATADIR + os.sep - -skip_expected = not os.path.isdir(directory) - -# Make sure it actually raises errors when not expected and caught in flags -# Slower, since it runs some things several times. -EXTENDEDERRORTEST = False - -# Test extra functionality in the C version (-DEXTRA_FUNCTIONALITY). -EXTRA_FUNCTIONALITY = True if hasattr(C, 'DecClamped') else False + +CDefaultTestContext = C.Context( + prec=9, rounding=ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[C], 0) + ) if C else None +PyDefaultTestContext = P.Context( + prec=9, rounding=ROUND_HALF_EVEN, traps=dict.fromkeys(Signals[P], 0) + ) + + +class DecimalTest(unittest.TestCase): + def setUp(self): + super().setUp() + self.decimal.setcontext(self.default_context.copy()) + + def tearDown(self): + # compare contexts + base = self.default_context + other = self.decimal.getcontext() + self.assertIs(type(base), type(other)) + self.maxDiff = None + for attr in ['prec', 'rounding', 'flags', 'traps', + 'Emin', 'Emax', 'capitals', 'clamp']: + with self.subTest('context changed', attr=attr): + self.assertEqual(getattr(base, attr), getattr(other, attr)) + + def assertSignals(self, attr, signals, context=None): + if context is None: + context = self.decimal.getcontext() + d = getattr(context, attr) + expected = [] + for e in signals: + if isinstance(e, str): + e = getattr(self.decimal, e) + expected.append(e) + for s in d: + if s in expected: + self.assertTrue(d[s], 'expected %r to be set' % s) + else: + self.assertFalse(d[s], 'expected %r to be unset' % s) + + def assertAndClearFlags(self, *flags, ctx=None): + self.assertSignals('flags', flags, ctx) + if ctx is None: + ctx = self.decimal.getcontext() + ctx.clear_flags() + +@unittest.skipUnless(C, '_decimal module not available') +class CDecimalTest(DecimalTest): + decimal = C + default_context = CDefaultTestContext + +class PyDecimalTest(DecimalTest): + decimal = P + default_context = PyDefaultTestContext + +# Non-special base class for classes that are meant to test both versions +class BaseTestCase(unittest.TestCase): pass + + +# Check for extra functionality in the C version (-DEXTRA_FUNCTIONALITY). +EXTRA_FUNCTIONALITY = hasattr(C, 'DecClamped') requires_extra_functionality = unittest.skipUnless( EXTRA_FUNCTIONALITY, "test requires build with -DEXTRA_FUNCTIONALITY") skip_if_extra_functionality = unittest.skipIf( EXTRA_FUNCTIONALITY, "test requires regular build") - -class IBMTestCases(unittest.TestCase): +TESTDATADIR = os.path.join(os.path.dirname(__file__), 'decimaltestdata') + +@unittest.skipUnless(os.path.isdir(TESTDATADIR), "couldn't find test data") +class IBMTestCases(BaseTestCase): """Class which tests the Decimal class against the IBM test cases.""" def setUp(self): @@ -287,9 +322,6 @@ return self.decimal.Decimal(v, context) def eval_file(self, file): - global skip_expected - if skip_expected: - raise unittest.SkipTest with open(file) as f: for line in f: line = line.replace('\r\n', '').replace('\n', '') @@ -300,7 +332,6 @@ #Exception raised where there shouldn't have been one. self.fail('Exception "'+exception.__class__.__name__ + '" raised on line '+line) - def eval_line(self, s): if s.find(' -> ') >= 0 and s[:2] != '--' and not s.startswith(' --'): s = (s.split('->')[0] + '->' + @@ -333,7 +364,6 @@ funct(value) def eval_equation(self, s): - if not TEST_ALL and random.random() < 0.90: return @@ -483,14 +513,10 @@ def change_clamp(self, clamp): self.context.clamp = clamp -class CIBMTestCases(IBMTestCases): - decimal = C -class PyIBMTestCases(IBMTestCases): - decimal = P # The following classes test the behaviour of Decimal according to PEP 327 -class ExplicitConstructionTest(unittest.TestCase): +class ExplicitConstructionTest(BaseTestCase): '''Unit tests for Explicit Construction cases of Decimal.''' def test_explicit_empty(self): @@ -535,6 +561,7 @@ #empty self.assertEqual(str(Decimal('')), 'NaN') + self.assertAndClearFlags('InvalidOperation') #int self.assertEqual(str(Decimal('45')), '45') @@ -547,6 +574,7 @@ #just not a number self.assertEqual(str(Decimal('ugly')), 'NaN') + self.assertAndClearFlags('InvalidOperation') #leading and trailing whitespace permitted self.assertEqual(str(Decimal('1.3E4 \n')), '1.3E+4') @@ -700,6 +728,8 @@ x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0) self.assertEqual(x, float(Decimal(x))) # roundtrip + self.assertAndClearFlags('FloatOperation') + def test_explicit_context_create_decimal(self): Decimal = self.decimal.Decimal InvalidOperation = self.decimal.InvalidOperation @@ -787,7 +817,6 @@ self.assertTrue(nc.flags[InvalidOperation]) def test_explicit_context_create_from_float(self): - Decimal = self.decimal.Decimal nc = self.decimal.Context() @@ -821,12 +850,8 @@ for input, expected in test_values.items(): self.assertEqual(str(Decimal(input)), expected) -class CExplicitConstructionTest(ExplicitConstructionTest): - decimal = C -class PyExplicitConstructionTest(ExplicitConstructionTest): - decimal = P - -class ImplicitConstructionTest(unittest.TestCase): + +class ImplicitConstructionTest(BaseTestCase): '''Unit tests for Implicit Construction cases of Decimal.''' def test_implicit_from_None(self): @@ -841,6 +866,8 @@ #exceeding precision self.assertEqual(Decimal(5) + 123456789000, Decimal(123456789000)) + self.assertAndClearFlags('Inexact', 'Rounded') + def test_implicit_from_string(self): Decimal = self.decimal.Decimal self.assertRaises(TypeError, eval, 'Decimal(5) + "3"', locals()) @@ -903,12 +930,8 @@ self.assertEqual(eval('Decimal(10)' + sym + 'E()'), '10' + rop + 'str') -class CImplicitConstructionTest(ImplicitConstructionTest): - decimal = C -class PyImplicitConstructionTest(ImplicitConstructionTest): - decimal = P - -class FormatTest(unittest.TestCase): + +class FormatTest(BaseTestCase): '''Unit tests for the format function.''' def test_formatting(self): Decimal = self.decimal.Decimal @@ -1171,12 +1194,8 @@ self.assertEqual(format(Decimal('100000000.123'), 'n'), '100\u066c000\u066c000\u066b123') -class CFormatTest(FormatTest): - decimal = C -class PyFormatTest(FormatTest): - decimal = P - -class ArithmeticOperatorsTest(unittest.TestCase): + +class ArithmeticOperatorsTest(BaseTestCase): '''Unit tests for all arithmetic operators, binary and unary.''' def test_addition(self): @@ -1432,29 +1451,31 @@ equality_ops = operator.eq, operator.ne # results when InvalidOperation is not trapped - for x, y in qnan_pairs + snan_pairs: - for op in order_ops + equality_ops: - got = op(x, y) - expected = True if op is operator.ne else False - self.assertIs(expected, got, - "expected {0!r} for operator.{1}({2!r}, {3!r}); " - "got {4!r}".format( - expected, op.__name__, x, y, got)) - - # repeat the above, but this time trap the InvalidOperation with localcontext() as ctx: - ctx.traps[InvalidOperation] = 1 - - for x, y in qnan_pairs: - for op in equality_ops: + for x, y in qnan_pairs + snan_pairs: + for op in order_ops + equality_ops: got = op(x, y) - expected = True if op is operator.ne else False + expected = op is operator.ne self.assertIs(expected, got, "expected {0!r} for " "operator.{1}({2!r}, {3!r}); " "got {4!r}".format( expected, op.__name__, x, y, got)) + # repeat the above, but this time trap the InvalidOperation + with localcontext() as ctx: + ctx.traps[InvalidOperation] = 1 + + for x, y in qnan_pairs: + for op in equality_ops: + got = op(x, y) + expected = op is operator.ne + self.assertIs(expected, got, + "expected {0!r} for " + "operator.{1}({2!r}, {3!r}); " + "got {4!r}".format( + expected, op.__name__, x, y, got)) + for x, y in snan_pairs: for op in equality_ops: self.assertRaises(InvalidOperation, operator.eq, x, y) @@ -1471,10 +1492,6 @@ self.assertEqual(Decimal(1).copy_sign(-2), d) self.assertRaises(TypeError, Decimal(1).copy_sign, '-2') -class CArithmeticOperatorsTest(ArithmeticOperatorsTest): - decimal = C -class PyArithmeticOperatorsTest(ArithmeticOperatorsTest): - decimal = P # The following are two functions used to test threading in the next class @@ -1561,7 +1578,8 @@ for sig in Overflow, Underflow, DivisionByZero, InvalidOperation: cls.assertFalse(thiscontext.flags[sig]) -class ThreadingTest(unittest.TestCase): +@unittest.skipUnless(threading, 'threading required') +class ThreadingTest(BaseTestCase): '''Unit tests for thread local contexts in Decimal.''' # Take care executing this test from IDLE, there's an issue in threading @@ -1602,14 +1620,8 @@ DefaultContext.Emax = save_emax DefaultContext.Emin = save_emin -@unittest.skipUnless(threading, 'threading required') -class CThreadingTest(ThreadingTest): - decimal = C -@unittest.skipUnless(threading, 'threading required') -class PyThreadingTest(ThreadingTest): - decimal = P - -class UsabilityTest(unittest.TestCase): + +class UsabilityTest(BaseTestCase): '''Unit tests for Usability cases of Decimal.''' def test_comparison_operators(self): @@ -1637,9 +1649,10 @@ #a Decimal and uncomparable self.assertNotEqual(da, 'ugly') - self.assertNotEqual(da, 32.7) self.assertNotEqual(da, object()) self.assertNotEqual(da, object) + self.assertNotEqual(da, 32.7) + self.assertAndClearFlags('FloatOperation') # sortable a = list(map(Decimal, range(100))) @@ -1667,6 +1680,8 @@ self.assertEqual(3.0, db) self.assertNotEqual(0.1, Decimal('0.1')) + self.assertAndClearFlags('FloatOperation') + def test_decimal_complex_comparison(self): Decimal = self.decimal.Decimal @@ -1687,6 +1702,8 @@ self.assertIs(db.__gt__(3.0+0j), NotImplemented) self.assertIs(db.__le__(3.0+0j), NotImplemented) + self.assertAndClearFlags('FloatOperation') + def test_decimal_fraction_comparison(self): D = self.decimal.Decimal F = fractions[self.decimal].Fraction @@ -1961,6 +1978,8 @@ for d, n, r in test_triples: self.assertEqual(str(round(Decimal(d), n)), r) + self.assertAndClearFlags('InvalidOperation', 'Inexact', 'Rounded') + def test_nan_to_float(self): # Test conversions of decimal NANs to float. # See http://bugs.python.org/issue15544 @@ -2385,13 +2404,8 @@ self.assertEqual(Decimal(-12).fma(45, Decimal(67)), Decimal(-12).fma(Decimal(45), Decimal(67))) -class CUsabilityTest(UsabilityTest): - decimal = C -class PyUsabilityTest(UsabilityTest): - decimal = P - -class PythonAPItests(unittest.TestCase): - + +class PythonAPItests(BaseTestCase): def test_abc(self): Decimal = self.decimal.Decimal @@ -2463,7 +2477,6 @@ self.assertEqual(Decimal(math.trunc(d)), r) def test_from_float(self): - Decimal = self.decimal.Decimal class MyDecimal(Decimal): @@ -2543,6 +2556,8 @@ x = d.quantize(context=c, exp=Decimal("1e797"), rounding=ROUND_DOWN) self.assertEqual(x, Decimal('8.71E+799')) + self.assertAndClearFlags('Inexact', 'Rounded') + def test_complex(self): Decimal = self.decimal.Decimal @@ -2672,7 +2687,6 @@ self.assertRaises(TypeError, D(1).canonical, xyz="x") def test_exception_hierarchy(self): - decimal = self.decimal DecimalException = decimal.DecimalException InvalidOperation = decimal.InvalidOperation @@ -2711,13 +2725,8 @@ self.assertTrue(issubclass(decimal.DivisionUndefined, ZeroDivisionError)) self.assertTrue(issubclass(decimal.InvalidContext, InvalidOperation)) -class CPythonAPItests(PythonAPItests): - decimal = C -class PyPythonAPItests(PythonAPItests): - decimal = P - -class ContextAPItests(unittest.TestCase): - + +class ContextAPItests(BaseTestCase): def test_none_args(self): Context = self.decimal.Context InvalidOperation = self.decimal.InvalidOperation @@ -2734,9 +2743,9 @@ self.assertEqual(c.Emin, -999999) self.assertEqual(c.capitals, 1) self.assertEqual(c.clamp, 0) - assert_signals(self, c, 'flags', []) - assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero, - Overflow]) + self.assertSignals('flags', [], c) + self.assertSignals('traps', [InvalidOperation, DivisionByZero, + Overflow], c) @cpython_only def test_from_legacy_strings(self): @@ -2754,7 +2763,6 @@ self.assertRaises(TypeError, setattr, c, 'rounding', s) def test_pickle(self): - Context = self.decimal.Context savedecimal = sys.modules['decimal'] @@ -2808,8 +2816,10 @@ self.assertEqual(d.rounding, RoundingModes[ri]) self.assertEqual(d.capitals, caps) self.assertEqual(d.clamp, clamp) - assert_signals(self, d, 'flags', OrderedSignals[loader][:fi]) - assert_signals(self, d, 'traps', OrderedSignals[loader][:ti]) + self.assertSignals( + 'flags', OrderedSignals[loader][:fi], d) + self.assertSignals( + 'traps', OrderedSignals[loader][:ti], d) sys.modules['decimal'] = savedecimal @@ -2818,6 +2828,7 @@ self.assertIn(Decimal(10), ['a', 1.0, Decimal(10), (1,2), {}]) self.assertNotIn(Decimal(10), ['a', 1.0, (1,2), {}]) + self.assertAndClearFlags('FloatOperation') def test_copy(self): # All copies should be deep @@ -3459,15 +3470,8 @@ self.assertRaises(TypeError, c.to_integral_value, '10') self.assertRaises(TypeError, c.to_integral_value, 10, 'x') -class CContextAPItests(ContextAPItests): - decimal = C -class PyContextAPItests(ContextAPItests): - decimal = P - -class ContextWithStatement(unittest.TestCase): - # Can't do these as docstrings until Python 2.6 - # as doctest can't handle __future__ statements - + +class ContextWithStatement(BaseTestCase): def test_localcontext(self): # Use a copy of the current context in the block getcontext = self.decimal.getcontext @@ -3590,13 +3594,8 @@ self.assertEqual(c4.prec, 4) del c4 -class CContextWithStatement(ContextWithStatement): - decimal = C -class PyContextWithStatement(ContextWithStatement): - decimal = P - -class ContextFlags(unittest.TestCase): - + +class ContextFlags(BaseTestCase): def test_flags_irrelevant(self): # check that the result (numeric result + flags raised) of an # arithmetic operation doesn't depend on the current flags @@ -3862,12 +3861,8 @@ self.assertTrue(context.traps[FloatOperation]) self.assertTrue(context.traps[Inexact]) -class CContextFlags(ContextFlags): - decimal = C -class PyContextFlags(ContextFlags): - decimal = P - -class SpecialContexts(unittest.TestCase): + +class SpecialContexts(BaseTestCase): """Test the context templates.""" def test_context_templates(self): @@ -3881,9 +3876,9 @@ Underflow = self.decimal.Underflow Clamped = self.decimal.Clamped - assert_signals(self, BasicContext, 'traps', - [InvalidOperation, DivisionByZero, Overflow, Underflow, Clamped] - ) + self.assertSignals('traps', [InvalidOperation, DivisionByZero, + Overflow, Underflow, Clamped], + BasicContext) savecontext = getcontext().copy() basic_context_prec = BasicContext.prec @@ -3919,9 +3914,9 @@ self.assertEqual(BasicContext.prec, 9) self.assertEqual(ExtendedContext.prec, 9) - assert_signals(self, DefaultContext, 'traps', - [InvalidOperation, DivisionByZero, Overflow] - ) + self.assertSignals('traps', + [InvalidOperation, DivisionByZero, Overflow], + DefaultContext) savecontext = getcontext().copy() default_context_prec = DefaultContext.prec @@ -3947,13 +3942,8 @@ if ex: raise ex -class CSpecialContexts(SpecialContexts): - decimal = C -class PySpecialContexts(SpecialContexts): - decimal = P - -class ContextInputValidation(unittest.TestCase): - + +class ContextInputValidation(BaseTestCase): def test_invalid_context(self): Context = self.decimal.Context DefaultContext = self.decimal.DefaultContext @@ -4014,13 +4004,8 @@ self.assertRaises(TypeError, Context, flags=(0,1)) self.assertRaises(TypeError, Context, traps=(1,0)) -class CContextInputValidation(ContextInputValidation): - decimal = C -class PyContextInputValidation(ContextInputValidation): - decimal = P - -class ContextSubclassing(unittest.TestCase): - + +class ContextSubclassing(BaseTestCase): def test_context_subclassing(self): decimal = self.decimal Decimal = decimal.Decimal @@ -4128,16 +4113,11 @@ for signal in OrderedSignals[decimal]: self.assertFalse(c.traps[signal]) -class CContextSubclassing(ContextSubclassing): - decimal = C -class PyContextSubclassing(ContextSubclassing): - decimal = P - + +@unittest.skipUnless(C, '_decimal module not available') @skip_if_extra_functionality class CheckAttributes(unittest.TestCase): - def test_module_attributes(self): - # Architecture dependent context limits self.assertEqual(C.MAX_PREC, P.MAX_PREC) self.assertEqual(C.MAX_EMAX, P.MAX_EMAX) @@ -4155,19 +4135,17 @@ self.assertEqual(set(x) - set(y), set()) def test_context_attributes(self): - x = [s for s in dir(C.Context()) if '__' in s or not s.startswith('_')] y = [s for s in dir(P.Context()) if '__' in s or not s.startswith('_')] self.assertEqual(set(x) - set(y), set()) def test_decimal_attributes(self): - x = [s for s in dir(C.Decimal(9)) if '__' in s or not s.startswith('_')] y = [s for s in dir(C.Decimal(9)) if '__' in s or not s.startswith('_')] self.assertEqual(set(x) - set(y), set()) -class Coverage(unittest.TestCase): - + +class Coverage(BaseTestCase): def test_adjusted(self): Decimal = self.decimal.Decimal @@ -4379,19 +4357,21 @@ def test_round(self): # Python3 behavior: round() returns Decimal Decimal = self.decimal.Decimal - getcontext = self.decimal.getcontext - - c = getcontext() - c.prec = 28 - - self.assertEqual(str(Decimal("9.99").__round__()), "10") - self.assertEqual(str(Decimal("9.99e-5").__round__()), "0") - self.assertEqual(str(Decimal("1.23456789").__round__(5)), "1.23457") - self.assertEqual(str(Decimal("1.2345").__round__(10)), "1.2345000000") - self.assertEqual(str(Decimal("1.2345").__round__(-10)), "0E+10") - - self.assertRaises(TypeError, Decimal("1.23").__round__, "5") - self.assertRaises(TypeError, Decimal("1.23").__round__, 5, 8) + localcontext = self.decimal.localcontext + + with localcontext() as c: + c.prec = 28 + + self.assertEqual(str(Decimal("9.99").__round__()), "10") + self.assertEqual(str(Decimal("9.99e-5").__round__()), "0") + self.assertEqual(str(Decimal("1.23456789").__round__(5)), + "1.23457") + self.assertEqual(str(Decimal("1.2345").__round__(10)), + "1.2345000000") + self.assertEqual(str(Decimal("1.2345").__round__(-10)), "0E+10") + + self.assertRaises(TypeError, Decimal("1.23").__round__, "5") + self.assertRaises(TypeError, Decimal("1.23").__round__, 5, 8) def test_create_decimal(self): c = self.decimal.Context() @@ -4423,12 +4403,8 @@ y = c.copy_sign(x, 1) self.assertEqual(y, -x) -class CCoverage(Coverage): - decimal = C -class PyCoverage(Coverage): - decimal = P - -class PyFunctionality(unittest.TestCase): + +class PyFunctionality(PyDecimalTest): """Extra functionality in decimal.py""" def test_py_quantize_watchexp(self): @@ -4464,7 +4440,7 @@ for fmt, d, result in test_values: self.assertEqual(format(Decimal(d), fmt), result) -class PyWhitebox(unittest.TestCase): +class PyWhitebox(PyDecimalTest): """White box testing for decimal.py""" def test_py_exact_power(self): @@ -4498,80 +4474,79 @@ # Do operations and check that it didn't change internal objects. Decimal = P.Decimal DefaultContext = P.DefaultContext - setcontext = P.setcontext - - c = DefaultContext.copy() - c.traps = dict((s, 0) for s in OrderedSignals[P]) - setcontext(c) - - d1 = Decimal('-25e55') - b1 = Decimal('-25e55') - d2 = Decimal('33e+33') - b2 = Decimal('33e+33') - - def checkSameDec(operation, useOther=False): - if useOther: - eval("d1." + operation + "(d2)") - self.assertEqual(d1._sign, b1._sign) - self.assertEqual(d1._int, b1._int) - self.assertEqual(d1._exp, b1._exp) - self.assertEqual(d2._sign, b2._sign) - self.assertEqual(d2._int, b2._int) - self.assertEqual(d2._exp, b2._exp) - else: - eval("d1." + operation + "()") - self.assertEqual(d1._sign, b1._sign) - self.assertEqual(d1._int, b1._int) - self.assertEqual(d1._exp, b1._exp) - - Decimal(d1) - self.assertEqual(d1._sign, b1._sign) - self.assertEqual(d1._int, b1._int) - self.assertEqual(d1._exp, b1._exp) - - checkSameDec("__abs__") - checkSameDec("__add__", True) - checkSameDec("__divmod__", True) - checkSameDec("__eq__", True) - checkSameDec("__ne__", True) - checkSameDec("__le__", True) - checkSameDec("__lt__", True) - checkSameDec("__ge__", True) - checkSameDec("__gt__", True) - checkSameDec("__float__") - checkSameDec("__floordiv__", True) - checkSameDec("__hash__") - checkSameDec("__int__") - checkSameDec("__trunc__") - checkSameDec("__mod__", True) - checkSameDec("__mul__", True) - checkSameDec("__neg__") - checkSameDec("__bool__") - checkSameDec("__pos__") - checkSameDec("__pow__", True) - checkSameDec("__radd__", True) - checkSameDec("__rdivmod__", True) - checkSameDec("__repr__") - checkSameDec("__rfloordiv__", True) - checkSameDec("__rmod__", True) - checkSameDec("__rmul__", True) - checkSameDec("__rpow__", True) - checkSameDec("__rsub__", True) - checkSameDec("__str__") - checkSameDec("__sub__", True) - checkSameDec("__truediv__", True) - checkSameDec("adjusted") - checkSameDec("as_tuple") - checkSameDec("compare", True) - checkSameDec("max", True) - checkSameDec("min", True) - checkSameDec("normalize") - checkSameDec("quantize", True) - checkSameDec("remainder_near", True) - checkSameDec("same_quantum", True) - checkSameDec("sqrt") - checkSameDec("to_eng_string") - checkSameDec("to_integral") + localcontext = P.localcontext + + with localcontext(DefaultContext.copy()) as c: + c.traps = dict((s, 0) for s in OrderedSignals[P]) + + d1 = Decimal('-25e55') + b1 = Decimal('-25e55') + d2 = Decimal('33e+33') + b2 = Decimal('33e+33') + + def checkSameDec(operation, useOther=False): + if useOther: + eval("d1." + operation + "(d2)") + self.assertEqual(d1._sign, b1._sign) + self.assertEqual(d1._int, b1._int) + self.assertEqual(d1._exp, b1._exp) + self.assertEqual(d2._sign, b2._sign) + self.assertEqual(d2._int, b2._int) + self.assertEqual(d2._exp, b2._exp) + else: + eval("d1." + operation + "()") + self.assertEqual(d1._sign, b1._sign) + self.assertEqual(d1._int, b1._int) + self.assertEqual(d1._exp, b1._exp) + + Decimal(d1) + self.assertEqual(d1._sign, b1._sign) + self.assertEqual(d1._int, b1._int) + self.assertEqual(d1._exp, b1._exp) + + checkSameDec("__abs__") + checkSameDec("__add__", True) + checkSameDec("__divmod__", True) + checkSameDec("__eq__", True) + checkSameDec("__ne__", True) + checkSameDec("__le__", True) + checkSameDec("__lt__", True) + checkSameDec("__ge__", True) + checkSameDec("__gt__", True) + checkSameDec("__float__") + checkSameDec("__floordiv__", True) + checkSameDec("__hash__") + checkSameDec("__int__") + checkSameDec("__trunc__") + checkSameDec("__mod__", True) + checkSameDec("__mul__", True) + checkSameDec("__neg__") + checkSameDec("__bool__") + checkSameDec("__pos__") + checkSameDec("__pow__", True) + checkSameDec("__radd__", True) + checkSameDec("__rdivmod__", True) + checkSameDec("__repr__") + checkSameDec("__rfloordiv__", True) + checkSameDec("__rmod__", True) + checkSameDec("__rmul__", True) + checkSameDec("__rpow__", True) + checkSameDec("__rsub__", True) + checkSameDec("__str__") + checkSameDec("__sub__", True) + checkSameDec("__truediv__", True) + checkSameDec("adjusted") + checkSameDec("as_tuple") + checkSameDec("compare", True) + checkSameDec("max", True) + checkSameDec("min", True) + checkSameDec("normalize") + checkSameDec("quantize", True) + checkSameDec("remainder_near", True) + checkSameDec("same_quantum", True) + checkSameDec("sqrt") + checkSameDec("to_eng_string") + checkSameDec("to_integral") def test_py_decimal_id(self): Decimal = P.Decimal @@ -4596,10 +4571,11 @@ self.assertRaises(ValueError, Decimal("3.1234")._round, 0, ROUND_UP) -class CFunctionality(unittest.TestCase): + +@requires_extra_functionality +class CFunctionality(CDecimalTest): """Extra functionality in _decimal""" - @requires_extra_functionality def test_c_ieee_context(self): # issue 8786: Add support for IEEE 754 contexts to decimal module. IEEEContext = C.IEEEContext @@ -4609,8 +4585,8 @@ def assert_rest(self, context): self.assertEqual(context.clamp, 1) - assert_signals(self, context, 'traps', []) - assert_signals(self, context, 'flags', []) + self.assertSignals('traps', [], context) + self.assertSignals('flags', [], context) c = IEEEContext(DECIMAL32) self.assertEqual(c.prec, 7) @@ -4635,7 +4611,6 @@ self.assertRaises(ValueError, IEEEContext, -1) self.assertRaises(ValueError, IEEEContext, 1024) - @requires_extra_functionality def test_c_context(self): Context = C.Context @@ -4643,7 +4618,6 @@ self.assertEqual(c._flags, C.DecClamped) self.assertEqual(c._traps, C.DecRounded) - @requires_extra_functionality def test_constants(self): # Condition flags cond = ( @@ -4681,7 +4655,7 @@ self.assertEqual(C.DecTraps, C.DecErrors|C.DecOverflow|C.DecUnderflow) -class CWhitebox(unittest.TestCase): +class CWhitebox(CDecimalTest): """Whitebox testing for _decimal""" def test_bignum(self): @@ -4846,7 +4820,6 @@ setcontext(saved_context) def test_rounding_strings_interned(self): - self.assertIs(C.ROUND_UP, P.ROUND_UP) self.assertIs(C.ROUND_DOWN, P.ROUND_DOWN) self.assertIs(C.ROUND_CEILING, P.ROUND_CEILING) @@ -5132,7 +5105,6 @@ @requires_extra_functionality def test_c_signal_dict(self): - # SignalDict coverage Context = C.Context DefaultContext = C.DefaultContext @@ -5305,7 +5277,6 @@ InvalidOperation = C.InvalidOperation with localcontext() as c: - c.traps[InvalidOperation] = True # Clamped @@ -5331,10 +5302,10 @@ Underflow = C.Underflow with localcontext() as c: - c.traps[InvalidOperation] = True c.traps[Overflow] = True c.traps[Underflow] = True + c.prec= 28 # SSIZE_MAX x = (1, (), sys.maxsize) @@ -5385,98 +5356,123 @@ y = Decimal(10**(9*25)).__sizeof__() self.assertEqual(y, x+4) -all_tests = [ - CExplicitConstructionTest, PyExplicitConstructionTest, - CImplicitConstructionTest, PyImplicitConstructionTest, - CFormatTest, PyFormatTest, - CArithmeticOperatorsTest, PyArithmeticOperatorsTest, - CThreadingTest, PyThreadingTest, - CUsabilityTest, PyUsabilityTest, - CPythonAPItests, PyPythonAPItests, - CContextAPItests, PyContextAPItests, - CContextWithStatement, PyContextWithStatement, - CContextFlags, PyContextFlags, - CSpecialContexts, PySpecialContexts, - CContextInputValidation, PyContextInputValidation, - CContextSubclassing, PyContextSubclassing, - CCoverage, PyCoverage, - CFunctionality, PyFunctionality, - CWhitebox, PyWhitebox, - CIBMTestCases, PyIBMTestCases, -] - -# Delete C tests if _decimal.so is not present. -if not C: - all_tests = all_tests[1::2] -else: - all_tests.insert(0, CheckAttributes) - - -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 - is enabled in regrtest.py - """ - - init(C) - init(P) - global TEST_ALL, DEBUG - TEST_ALL = arith or is_resource_enabled('decimal') - DEBUG = debug - - if todo_tests is None: - test_classes = all_tests + +class Doctests(BaseTestCase): + @classmethod + def setUpClass(cls): + cls.module = sys.modules['decimal'] + sys.modules['decimal'] = cls.decimal + + @classmethod + def tearDownClass(cls): + sys.modules['decimal'] = cls.module + + def test_doctests(self): + if not getattr(self.decimal.Decimal, '__doc__', None): + if self.decimal is C: + suggestion = 'compile them in' + else: + suggestion = 'run without -OO' + self.skipTest('docstrings not available, ' + suggestion) + if self.decimal is C: + optionflags = doctest.IGNORE_EXCEPTION_DETAIL + else: + optionflags = 0 + failures, tests = doctest.testmod(self.decimal, verbose=0, + optionflags=optionflags) + self.assertFalse(failures, 'doctests failed, see output above') + self.assertIn(tests, [9, # C, run with -OO + 48, # C + 499 # Python + ], 'unexpected number of doctests found') + + # the context will be changed by the doctests, and we don't want to + # clutter them with context cleanup + self.decimal.setcontext(self.default_context) + + +def tearDownModule(): + if C: C.setcontext(ORIGINAL_CONTEXT[C]) + P.setcontext(ORIGINAL_CONTEXT[P]) + if sys.modules['decimal'] is not orig_sys_decimal: + raise TestFailed('unbalanced number of changes to sys.modules') + + +if __name__ == '__main__': + import argparse + p = argparse.ArgumentParser( + description='Run tests against the decimal module and its C ' + 'accelerator (if present). All of the options ' + 'listed below only affect the IBM arithmetic test ' + 'cases.', + epilog='You may also pass standard unittest arguments, ' + 'though specifying test names may conflict with ' + 'specifying IBM test names.') + p.add_argument('--debug', '-d', action='store_true', + help='shows the test name and context before each test') + p.add_argument('--extended', '-e', action='store_true', + help='does more extensive error testing on each test ' + '(takes a lot of extra time due to running each test ' + 'several times)') + g = p.add_mutually_exclusive_group() + g.add_argument('--skip', '-s', action='store_true', + help='skip over 90%% of the arithmetic tests') + g.add_argument('--test', '-t', action='append', nargs='+', + help='run only the named test cases') + + unittest_argv = sys.argv[:1] + ns, unittest_argv[1:] = p.parse_known_args() + + TEST_ALL = not ns.skip + DEBUG = ns.debug + EXTENDEDERRORTEST = ns.extended + if ns.test: + # flatten the list of lists + TODO_TESTS = [i for l in ns.test for i in l] + def load_tests(loader, *ignore): + suite = unittest.TestSuite() + # C/PyIBMTestCases don't exist yet, but will by call time + suite.addTest(loader.loadTestsFromTestCase(CIBMTestCases)) + suite.addTest(loader.loadTestsFromTestCase(PyIBMTestCases)) + return suite else: - test_classes = [CIBMTestCases, PyIBMTestCases] - - # 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(CIBMTestCases, 'test_' + head, tester) - setattr(PyIBMTestCases, 'test_' + head, tester) - del filename, head, tail, tester - - - try: - run_unittest(*test_classes) - if todo_tests is None: - from doctest import IGNORE_EXCEPTION_DETAIL - savedecimal = sys.modules['decimal'] - if C: - sys.modules['decimal'] = C - run_doctest(C, verbose, optionflags=IGNORE_EXCEPTION_DETAIL) - sys.modules['decimal'] = P - run_doctest(P, verbose) - sys.modules['decimal'] = savedecimal - finally: - if C: C.setcontext(ORIGINAL_CONTEXT[C]) - P.setcontext(ORIGINAL_CONTEXT[P]) - if not C: - warnings.warn('C tests skipped: no module named _decimal.', - UserWarning) - if not orig_sys_decimal is sys.modules['decimal']: - raise TestFailed("Internal error: unbalanced number of changes to " - "sys.modules['decimal'].") - + TODO_TESTS = None +else: # __name__ != '__main__', could be a discovery run or under regrtest + TEST_ALL = is_resource_enabled('decimal') + DEBUG = False + EXTENDEDERRORTEST = False + TODO_TESTS = None + + +# Dynamically fill in IBMTestCases from the files in decimaltestdata. +# This procedure ensures that new files are not skipped. +for filename in os.listdir(TESTDATADIR): + 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 + def tester(self, f=filename): + self.eval_file(os.path.join(TESTDATADIR, f)) + setattr(IBMTestCases, 'test_' + head, tester) +del filename, head, tail, tester + + +# Create the C and Py variants of the base test cases +glob_copy = globals().copy() +for name, obj in glob_copy.items(): + if (type(obj) is type and + issubclass(obj, BaseTestCase) and + obj is not BaseTestCase): + for prefix, base in[('C', CDecimalTest), ('Py', PyDecimalTest)]: + new_name = prefix + name + globals()[new_name] = type(new_name, (base, obj), {}) + del globals()[name] # don't let unittest find the base class + +# clean up +del glob_copy, name, obj, prefix, base, new_name +# don't let unittest find the base classes +del DecimalTest, CDecimalTest, PyDecimalTest, BaseTestCase if __name__ == '__main__': - 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: - test_main(arith=True, verbose=True) + unittest.main(argv=unittest_argv)