mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	Replace os.cpu_count() with os.process_cpu_count() in modules: * compileall * concurrent.futures * multiprocessing Replace os.cpu_count() with os.process_cpu_count() in programs: * _decimal deccheck.py test * freeze.py * multissltests.py * python -m test (regrtest) * wasm_build.py Other changes: * test.pythoninfo logs os.process_cpu_count(). * regrtest gets os.process_cpu_count() / os.cpu_count() in headers.
		
			
				
	
	
		
			1322 lines
		
	
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1322 lines
		
	
	
	
		
			46 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#
 | 
						|
# Copyright (c) 2008-2012 Stefan Krah. All rights reserved.
 | 
						|
#
 | 
						|
# Redistribution and use in source and binary forms, with or without
 | 
						|
# modification, are permitted provided that the following conditions
 | 
						|
# are met:
 | 
						|
#
 | 
						|
# 1. Redistributions of source code must retain the above copyright
 | 
						|
#    notice, this list of conditions and the following disclaimer.
 | 
						|
#
 | 
						|
# 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
#    notice, this list of conditions and the following disclaimer in the
 | 
						|
#    documentation and/or other materials provided with the distribution.
 | 
						|
#
 | 
						|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
 | 
						|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 | 
						|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
# SUCH DAMAGE.
 | 
						|
#
 | 
						|
 | 
						|
#
 | 
						|
# Usage: python deccheck.py [--short|--medium|--long|--all]
 | 
						|
#
 | 
						|
 | 
						|
 | 
						|
import random
 | 
						|
import time
 | 
						|
 | 
						|
RANDSEED = int(time.time())
 | 
						|
random.seed(RANDSEED)
 | 
						|
 | 
						|
import sys
 | 
						|
import os
 | 
						|
from copy import copy
 | 
						|
from collections import defaultdict
 | 
						|
 | 
						|
import argparse
 | 
						|
import subprocess
 | 
						|
from subprocess import PIPE, STDOUT
 | 
						|
from queue import Queue, Empty
 | 
						|
from threading import Thread, Event, Lock
 | 
						|
 | 
						|
from test.support.import_helper import import_fresh_module
 | 
						|
from randdec import randfloat, all_unary, all_binary, all_ternary
 | 
						|
from randdec import unary_optarg, binary_optarg, ternary_optarg
 | 
						|
from formathelper import rand_format, rand_locale
 | 
						|
from _pydecimal import _dec_from_triple
 | 
						|
 | 
						|
C = import_fresh_module('decimal', fresh=['_decimal'])
 | 
						|
P = import_fresh_module('decimal', blocked=['_decimal'])
 | 
						|
EXIT_STATUS = 0
 | 
						|
 | 
						|
 | 
						|
# Contains all categories of Decimal methods.
 | 
						|
Functions = {
 | 
						|
    # Plain unary:
 | 
						|
    'unary': (
 | 
						|
        '__abs__', '__bool__', '__ceil__', '__complex__', '__copy__',
 | 
						|
        '__floor__', '__float__', '__hash__', '__int__', '__neg__',
 | 
						|
        '__pos__', '__reduce__', '__repr__', '__str__', '__trunc__',
 | 
						|
        'adjusted', 'as_integer_ratio', 'as_tuple', 'canonical', 'conjugate',
 | 
						|
        'copy_abs', 'copy_negate', 'is_canonical', 'is_finite', 'is_infinite',
 | 
						|
        'is_nan', 'is_qnan', 'is_signed', 'is_snan', 'is_zero', 'radix'
 | 
						|
    ),
 | 
						|
    # Unary with optional context:
 | 
						|
    'unary_ctx': (
 | 
						|
        'exp', 'is_normal', 'is_subnormal', 'ln', 'log10', 'logb',
 | 
						|
        'logical_invert', 'next_minus', 'next_plus', 'normalize',
 | 
						|
        'number_class', 'sqrt', 'to_eng_string'
 | 
						|
    ),
 | 
						|
    # Unary with optional rounding mode and context:
 | 
						|
    'unary_rnd_ctx': ('to_integral', 'to_integral_exact', 'to_integral_value'),
 | 
						|
    # Plain binary:
 | 
						|
    'binary': (
 | 
						|
        '__add__', '__divmod__', '__eq__', '__floordiv__', '__ge__', '__gt__',
 | 
						|
        '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__pow__',
 | 
						|
        '__radd__', '__rdivmod__', '__rfloordiv__', '__rmod__', '__rmul__',
 | 
						|
        '__rpow__', '__rsub__', '__rtruediv__', '__sub__', '__truediv__',
 | 
						|
        'compare_total', 'compare_total_mag', 'copy_sign', 'quantize',
 | 
						|
        'same_quantum'
 | 
						|
    ),
 | 
						|
    # Binary with optional context:
 | 
						|
    'binary_ctx': (
 | 
						|
        'compare', 'compare_signal', 'logical_and', 'logical_or', 'logical_xor',
 | 
						|
        'max', 'max_mag', 'min', 'min_mag', 'next_toward', 'remainder_near',
 | 
						|
        'rotate', 'scaleb', 'shift'
 | 
						|
    ),
 | 
						|
    # Plain ternary:
 | 
						|
    'ternary': ('__pow__',),
 | 
						|
    # Ternary with optional context:
 | 
						|
    'ternary_ctx': ('fma',),
 | 
						|
    # Special:
 | 
						|
    'special': ('__format__', '__reduce_ex__', '__round__', 'from_float',
 | 
						|
                'quantize'),
 | 
						|
    # Properties:
 | 
						|
    'property': ('real', 'imag')
 | 
						|
}
 | 
						|
 | 
						|
# Contains all categories of Context methods. The n-ary classification
 | 
						|
# applies to the number of Decimal arguments.
 | 
						|
ContextFunctions = {
 | 
						|
    # Plain nullary:
 | 
						|
    'nullary': ('context.__hash__', 'context.__reduce__', 'context.radix'),
 | 
						|
    # Plain unary:
 | 
						|
    'unary': ('context.abs', 'context.canonical', 'context.copy_abs',
 | 
						|
              'context.copy_decimal', 'context.copy_negate',
 | 
						|
              'context.create_decimal', 'context.exp', 'context.is_canonical',
 | 
						|
              'context.is_finite', 'context.is_infinite', 'context.is_nan',
 | 
						|
              'context.is_normal', 'context.is_qnan', 'context.is_signed',
 | 
						|
              'context.is_snan', 'context.is_subnormal', 'context.is_zero',
 | 
						|
              'context.ln', 'context.log10', 'context.logb',
 | 
						|
              'context.logical_invert', 'context.minus', 'context.next_minus',
 | 
						|
              'context.next_plus', 'context.normalize', 'context.number_class',
 | 
						|
              'context.plus', 'context.sqrt', 'context.to_eng_string',
 | 
						|
              'context.to_integral', 'context.to_integral_exact',
 | 
						|
              'context.to_integral_value', 'context.to_sci_string'
 | 
						|
    ),
 | 
						|
    # Plain binary:
 | 
						|
    'binary': ('context.add', 'context.compare', 'context.compare_signal',
 | 
						|
               'context.compare_total', 'context.compare_total_mag',
 | 
						|
               'context.copy_sign', 'context.divide', 'context.divide_int',
 | 
						|
               'context.divmod', 'context.logical_and', 'context.logical_or',
 | 
						|
               'context.logical_xor', 'context.max', 'context.max_mag',
 | 
						|
               'context.min', 'context.min_mag', 'context.multiply',
 | 
						|
               'context.next_toward', 'context.power', 'context.quantize',
 | 
						|
               'context.remainder', 'context.remainder_near', 'context.rotate',
 | 
						|
               'context.same_quantum', 'context.scaleb', 'context.shift',
 | 
						|
               'context.subtract'
 | 
						|
    ),
 | 
						|
    # Plain ternary:
 | 
						|
    'ternary': ('context.fma', 'context.power'),
 | 
						|
    # Special:
 | 
						|
    'special': ('context.__reduce_ex__', 'context.create_decimal_from_float')
 | 
						|
}
 | 
						|
 | 
						|
# Functions that set no context flags but whose result can differ depending
 | 
						|
# on prec, Emin and Emax.
 | 
						|
MaxContextSkip = ['is_normal', 'is_subnormal', 'logical_invert', 'next_minus',
 | 
						|
                  'next_plus', 'number_class', 'logical_and', 'logical_or',
 | 
						|
                  'logical_xor', 'next_toward', 'rotate', 'shift']
 | 
						|
 | 
						|
# Functions that require a restricted exponent range for reasonable runtimes.
 | 
						|
UnaryRestricted = [
 | 
						|
  '__ceil__', '__floor__', '__int__', '__trunc__',
 | 
						|
  'as_integer_ratio', 'to_integral', 'to_integral_value'
 | 
						|
]
 | 
						|
 | 
						|
BinaryRestricted = ['__round__']
 | 
						|
 | 
						|
TernaryRestricted = ['__pow__', 'context.power']
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#                            Unified Context
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
# Translate symbols.
 | 
						|
CondMap = {
 | 
						|
        C.Clamped:             P.Clamped,
 | 
						|
        C.ConversionSyntax:    P.ConversionSyntax,
 | 
						|
        C.DivisionByZero:      P.DivisionByZero,
 | 
						|
        C.DivisionImpossible:  P.InvalidOperation,
 | 
						|
        C.DivisionUndefined:   P.DivisionUndefined,
 | 
						|
        C.Inexact:             P.Inexact,
 | 
						|
        C.InvalidContext:      P.InvalidContext,
 | 
						|
        C.InvalidOperation:    P.InvalidOperation,
 | 
						|
        C.Overflow:            P.Overflow,
 | 
						|
        C.Rounded:             P.Rounded,
 | 
						|
        C.Subnormal:           P.Subnormal,
 | 
						|
        C.Underflow:           P.Underflow,
 | 
						|
        C.FloatOperation:      P.FloatOperation,
 | 
						|
}
 | 
						|
 | 
						|
RoundModes = [C.ROUND_UP, C.ROUND_DOWN, C.ROUND_CEILING, C.ROUND_FLOOR,
 | 
						|
              C.ROUND_HALF_UP, C.ROUND_HALF_DOWN, C.ROUND_HALF_EVEN,
 | 
						|
              C.ROUND_05UP]
 | 
						|
 | 
						|
 | 
						|
class Context(object):
 | 
						|
    """Provides a convenient way of syncing the C and P contexts"""
 | 
						|
 | 
						|
    __slots__ = ['c', 'p']
 | 
						|
 | 
						|
    def __init__(self, c_ctx=None, p_ctx=None):
 | 
						|
        """Initialization is from the C context"""
 | 
						|
        self.c = C.getcontext() if c_ctx is None else c_ctx
 | 
						|
        self.p = P.getcontext() if p_ctx is None else p_ctx
 | 
						|
        self.p.prec = self.c.prec
 | 
						|
        self.p.Emin = self.c.Emin
 | 
						|
        self.p.Emax = self.c.Emax
 | 
						|
        self.p.rounding = self.c.rounding
 | 
						|
        self.p.capitals = self.c.capitals
 | 
						|
        self.settraps([sig for sig in self.c.traps if self.c.traps[sig]])
 | 
						|
        self.setstatus([sig for sig in self.c.flags if self.c.flags[sig]])
 | 
						|
        self.p.clamp = self.c.clamp
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return str(self.c) + '\n' + str(self.p)
 | 
						|
 | 
						|
    def getprec(self):
 | 
						|
        assert(self.c.prec == self.p.prec)
 | 
						|
        return self.c.prec
 | 
						|
 | 
						|
    def setprec(self, val):
 | 
						|
        self.c.prec = val
 | 
						|
        self.p.prec = val
 | 
						|
 | 
						|
    def getemin(self):
 | 
						|
        assert(self.c.Emin == self.p.Emin)
 | 
						|
        return self.c.Emin
 | 
						|
 | 
						|
    def setemin(self, val):
 | 
						|
        self.c.Emin = val
 | 
						|
        self.p.Emin = val
 | 
						|
 | 
						|
    def getemax(self):
 | 
						|
        assert(self.c.Emax == self.p.Emax)
 | 
						|
        return self.c.Emax
 | 
						|
 | 
						|
    def setemax(self, val):
 | 
						|
        self.c.Emax = val
 | 
						|
        self.p.Emax = val
 | 
						|
 | 
						|
    def getround(self):
 | 
						|
        assert(self.c.rounding == self.p.rounding)
 | 
						|
        return self.c.rounding
 | 
						|
 | 
						|
    def setround(self, val):
 | 
						|
        self.c.rounding = val
 | 
						|
        self.p.rounding = val
 | 
						|
 | 
						|
    def getcapitals(self):
 | 
						|
        assert(self.c.capitals == self.p.capitals)
 | 
						|
        return self.c.capitals
 | 
						|
 | 
						|
    def setcapitals(self, val):
 | 
						|
        self.c.capitals = val
 | 
						|
        self.p.capitals = val
 | 
						|
 | 
						|
    def getclamp(self):
 | 
						|
        assert(self.c.clamp == self.p.clamp)
 | 
						|
        return self.c.clamp
 | 
						|
 | 
						|
    def setclamp(self, val):
 | 
						|
        self.c.clamp = val
 | 
						|
        self.p.clamp = val
 | 
						|
 | 
						|
    prec = property(getprec, setprec)
 | 
						|
    Emin = property(getemin, setemin)
 | 
						|
    Emax = property(getemax, setemax)
 | 
						|
    rounding = property(getround, setround)
 | 
						|
    clamp = property(getclamp, setclamp)
 | 
						|
    capitals = property(getcapitals, setcapitals)
 | 
						|
 | 
						|
    def clear_traps(self):
 | 
						|
        self.c.clear_traps()
 | 
						|
        for trap in self.p.traps:
 | 
						|
            self.p.traps[trap] = False
 | 
						|
 | 
						|
    def clear_status(self):
 | 
						|
        self.c.clear_flags()
 | 
						|
        self.p.clear_flags()
 | 
						|
 | 
						|
    def settraps(self, lst):
 | 
						|
        """lst: C signal list"""
 | 
						|
        self.clear_traps()
 | 
						|
        for signal in lst:
 | 
						|
            self.c.traps[signal] = True
 | 
						|
            self.p.traps[CondMap[signal]] = True
 | 
						|
 | 
						|
    def setstatus(self, lst):
 | 
						|
        """lst: C signal list"""
 | 
						|
        self.clear_status()
 | 
						|
        for signal in lst:
 | 
						|
            self.c.flags[signal] = True
 | 
						|
            self.p.flags[CondMap[signal]] = True
 | 
						|
 | 
						|
    def assert_eq_status(self):
 | 
						|
        """assert equality of C and P status"""
 | 
						|
        for signal in self.c.flags:
 | 
						|
            if self.c.flags[signal] == (not self.p.flags[CondMap[signal]]):
 | 
						|
                return False
 | 
						|
        return True
 | 
						|
 | 
						|
 | 
						|
# We don't want exceptions so that we can compare the status flags.
 | 
						|
context = Context()
 | 
						|
context.Emin = C.MIN_EMIN
 | 
						|
context.Emax = C.MAX_EMAX
 | 
						|
context.clear_traps()
 | 
						|
 | 
						|
# When creating decimals, _decimal is ultimately limited by the maximum
 | 
						|
# context values. We emulate this restriction for decimal.py.
 | 
						|
maxcontext = P.Context(
 | 
						|
    prec=C.MAX_PREC,
 | 
						|
    Emin=C.MIN_EMIN,
 | 
						|
    Emax=C.MAX_EMAX,
 | 
						|
    rounding=P.ROUND_HALF_UP,
 | 
						|
    capitals=1
 | 
						|
)
 | 
						|
maxcontext.clamp = 0
 | 
						|
 | 
						|
def RestrictedDecimal(value):
 | 
						|
    maxcontext.traps = copy(context.p.traps)
 | 
						|
    maxcontext.clear_flags()
 | 
						|
    if isinstance(value, str):
 | 
						|
        value = value.strip()
 | 
						|
    dec = maxcontext.create_decimal(value)
 | 
						|
    if maxcontext.flags[P.Inexact] or \
 | 
						|
       maxcontext.flags[P.Rounded] or \
 | 
						|
       maxcontext.flags[P.Clamped] or \
 | 
						|
       maxcontext.flags[P.InvalidOperation]:
 | 
						|
        return context.p._raise_error(P.InvalidOperation)
 | 
						|
    if maxcontext.flags[P.FloatOperation]:
 | 
						|
        context.p.flags[P.FloatOperation] = True
 | 
						|
    return dec
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#      TestSet: Organize data and events during a single test case
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
class RestrictedList(list):
 | 
						|
    """List that can only be modified by appending items."""
 | 
						|
    def __getattribute__(self, name):
 | 
						|
        if name != 'append':
 | 
						|
            raise AttributeError("unsupported operation")
 | 
						|
        return list.__getattribute__(self, name)
 | 
						|
    def unsupported(self, *_):
 | 
						|
        raise AttributeError("unsupported operation")
 | 
						|
    __add__ = __delattr__ = __delitem__ = __iadd__ = __imul__ = unsupported
 | 
						|
    __mul__ = __reversed__ = __rmul__ = __setattr__ = __setitem__ = unsupported
 | 
						|
 | 
						|
class TestSet(object):
 | 
						|
    """A TestSet contains the original input operands, converted operands,
 | 
						|
       Python exceptions that occurred either during conversion or during
 | 
						|
       execution of the actual function, and the final results.
 | 
						|
 | 
						|
       For safety, most attributes are lists that only support the append
 | 
						|
       operation.
 | 
						|
 | 
						|
       If a function name is prefixed with 'context.', the corresponding
 | 
						|
       context method is called.
 | 
						|
    """
 | 
						|
    def __init__(self, funcname, operands):
 | 
						|
        if funcname.startswith("context."):
 | 
						|
            self.funcname = funcname.replace("context.", "")
 | 
						|
            self.contextfunc = True
 | 
						|
        else:
 | 
						|
            self.funcname = funcname
 | 
						|
            self.contextfunc = False
 | 
						|
        self.op = operands               # raw operand tuple
 | 
						|
        self.context = context           # context used for the operation
 | 
						|
        self.cop = RestrictedList()      # converted C.Decimal operands
 | 
						|
        self.cex = RestrictedList()      # Python exceptions for C.Decimal
 | 
						|
        self.cresults = RestrictedList() # C.Decimal results
 | 
						|
        self.pop = RestrictedList()      # converted P.Decimal operands
 | 
						|
        self.pex = RestrictedList()      # Python exceptions for P.Decimal
 | 
						|
        self.presults = RestrictedList() # P.Decimal results
 | 
						|
 | 
						|
        # If the above results are exact, unrounded and not clamped, repeat
 | 
						|
        # the operation with a maxcontext to ensure that huge intermediate
 | 
						|
        # values do not cause a MemoryError.
 | 
						|
        self.with_maxcontext = False
 | 
						|
        self.maxcontext = context.c.copy()
 | 
						|
        self.maxcontext.prec = C.MAX_PREC
 | 
						|
        self.maxcontext.Emax = C.MAX_EMAX
 | 
						|
        self.maxcontext.Emin = C.MIN_EMIN
 | 
						|
        self.maxcontext.clear_flags()
 | 
						|
 | 
						|
        self.maxop = RestrictedList()       # converted C.Decimal operands
 | 
						|
        self.maxex = RestrictedList()       # Python exceptions for C.Decimal
 | 
						|
        self.maxresults = RestrictedList()  # C.Decimal results
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#                SkipHandler: skip known discrepancies
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
class SkipHandler:
 | 
						|
    """Handle known discrepancies between decimal.py and _decimal.so.
 | 
						|
       These are either ULP differences in the power function or
 | 
						|
       extremely minor issues."""
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
        self.ulpdiff = 0
 | 
						|
        self.powmod_zeros = 0
 | 
						|
        self.maxctx = P.Context(Emax=10**18, Emin=-10**18)
 | 
						|
 | 
						|
    def default(self, t):
 | 
						|
        return False
 | 
						|
    __ge__ =  __gt__ = __le__ = __lt__ = __ne__ = __eq__ = default
 | 
						|
    __reduce__ = __format__ = __repr__ = __str__ = default
 | 
						|
 | 
						|
    def harrison_ulp(self, dec):
 | 
						|
        """ftp://ftp.inria.fr/INRIA/publication/publi-pdf/RR/RR-5504.pdf"""
 | 
						|
        a = dec.next_plus()
 | 
						|
        b = dec.next_minus()
 | 
						|
        return abs(a - b)
 | 
						|
 | 
						|
    def standard_ulp(self, dec, prec):
 | 
						|
        return _dec_from_triple(0, '1', dec._exp+len(dec._int)-prec)
 | 
						|
 | 
						|
    def rounding_direction(self, x, mode):
 | 
						|
        """Determine the effective direction of the rounding when
 | 
						|
           the exact result x is rounded according to mode.
 | 
						|
           Return -1 for downwards, 0 for undirected, 1 for upwards,
 | 
						|
           2 for ROUND_05UP."""
 | 
						|
        cmp = 1 if x.compare_total(P.Decimal("+0")) >= 0 else -1
 | 
						|
 | 
						|
        if mode in (P.ROUND_HALF_EVEN, P.ROUND_HALF_UP, P.ROUND_HALF_DOWN):
 | 
						|
            return 0
 | 
						|
        elif mode == P.ROUND_CEILING:
 | 
						|
            return 1
 | 
						|
        elif mode == P.ROUND_FLOOR:
 | 
						|
            return -1
 | 
						|
        elif mode == P.ROUND_UP:
 | 
						|
            return cmp
 | 
						|
        elif mode == P.ROUND_DOWN:
 | 
						|
            return -cmp
 | 
						|
        elif mode == P.ROUND_05UP:
 | 
						|
            return 2
 | 
						|
        else:
 | 
						|
            raise ValueError("Unexpected rounding mode: %s" % mode)
 | 
						|
 | 
						|
    def check_ulpdiff(self, exact, rounded):
 | 
						|
        # current precision
 | 
						|
        p = context.p.prec
 | 
						|
 | 
						|
        # Convert infinities to the largest representable number + 1.
 | 
						|
        x = exact
 | 
						|
        if exact.is_infinite():
 | 
						|
            x = _dec_from_triple(exact._sign, '10', context.p.Emax)
 | 
						|
        y = rounded
 | 
						|
        if rounded.is_infinite():
 | 
						|
            y = _dec_from_triple(rounded._sign, '10', context.p.Emax)
 | 
						|
 | 
						|
        # err = (rounded - exact) / ulp(rounded)
 | 
						|
        self.maxctx.prec = p * 2
 | 
						|
        t = self.maxctx.subtract(y, x)
 | 
						|
        if context.c.flags[C.Clamped] or \
 | 
						|
           context.c.flags[C.Underflow]:
 | 
						|
            # The standard ulp does not work in Underflow territory.
 | 
						|
            ulp = self.harrison_ulp(y)
 | 
						|
        else:
 | 
						|
            ulp = self.standard_ulp(y, p)
 | 
						|
        # Error in ulps.
 | 
						|
        err = self.maxctx.divide(t, ulp)
 | 
						|
 | 
						|
        dir = self.rounding_direction(x, context.p.rounding)
 | 
						|
        if dir == 0:
 | 
						|
            if P.Decimal("-0.6") < err < P.Decimal("0.6"):
 | 
						|
                return True
 | 
						|
        elif dir == 1: # directed, upwards
 | 
						|
            if P.Decimal("-0.1") < err < P.Decimal("1.1"):
 | 
						|
                return True
 | 
						|
        elif dir == -1: # directed, downwards
 | 
						|
            if P.Decimal("-1.1") < err < P.Decimal("0.1"):
 | 
						|
                return True
 | 
						|
        else: # ROUND_05UP
 | 
						|
            if P.Decimal("-1.1") < err < P.Decimal("1.1"):
 | 
						|
                return True
 | 
						|
 | 
						|
        print("ulp: %s  error: %s  exact: %s  c_rounded: %s"
 | 
						|
              % (ulp, err, exact, rounded))
 | 
						|
        return False
 | 
						|
 | 
						|
    def bin_resolve_ulp(self, t):
 | 
						|
        """Check if results of _decimal's power function are within the
 | 
						|
           allowed ulp ranges."""
 | 
						|
        # NaNs are beyond repair.
 | 
						|
        if t.rc.is_nan() or t.rp.is_nan():
 | 
						|
            return False
 | 
						|
 | 
						|
        # "exact" result, double precision, half_even
 | 
						|
        self.maxctx.prec = context.p.prec * 2
 | 
						|
 | 
						|
        op1, op2 = t.pop[0], t.pop[1]
 | 
						|
        if t.contextfunc:
 | 
						|
            exact = getattr(self.maxctx, t.funcname)(op1, op2)
 | 
						|
        else:
 | 
						|
            exact = getattr(op1, t.funcname)(op2, context=self.maxctx)
 | 
						|
 | 
						|
        # _decimal's rounded result
 | 
						|
        rounded = P.Decimal(t.cresults[0])
 | 
						|
 | 
						|
        self.ulpdiff += 1
 | 
						|
        return self.check_ulpdiff(exact, rounded)
 | 
						|
 | 
						|
    ############################ Correct rounding #############################
 | 
						|
    def resolve_underflow(self, t):
 | 
						|
        """In extremely rare cases where the infinite precision result is just
 | 
						|
           below etiny, cdecimal does not set Subnormal/Underflow. Example:
 | 
						|
 | 
						|
           setcontext(Context(prec=21, rounding=ROUND_UP, Emin=-55, Emax=85))
 | 
						|
           Decimal("1.00000000000000000000000000000000000000000000000"
 | 
						|
                   "0000000100000000000000000000000000000000000000000"
 | 
						|
                   "0000000000000025").ln()
 | 
						|
        """
 | 
						|
        if t.cresults != t.presults:
 | 
						|
            return False # Results must be identical.
 | 
						|
        if context.c.flags[C.Rounded] and \
 | 
						|
           context.c.flags[C.Inexact] and \
 | 
						|
           context.p.flags[P.Rounded] and \
 | 
						|
           context.p.flags[P.Inexact]:
 | 
						|
            return True # Subnormal/Underflow may be missing.
 | 
						|
        return False
 | 
						|
 | 
						|
    def exp(self, t):
 | 
						|
        """Resolve Underflow or ULP difference."""
 | 
						|
        return self.resolve_underflow(t)
 | 
						|
 | 
						|
    def log10(self, t):
 | 
						|
        """Resolve Underflow or ULP difference."""
 | 
						|
        return self.resolve_underflow(t)
 | 
						|
 | 
						|
    def ln(self, t):
 | 
						|
        """Resolve Underflow or ULP difference."""
 | 
						|
        return self.resolve_underflow(t)
 | 
						|
 | 
						|
    def __pow__(self, t):
 | 
						|
        """Always calls the resolve function. C.Decimal does not have correct
 | 
						|
           rounding for the power function."""
 | 
						|
        if context.c.flags[C.Rounded] and \
 | 
						|
           context.c.flags[C.Inexact] and \
 | 
						|
           context.p.flags[P.Rounded] and \
 | 
						|
           context.p.flags[P.Inexact]:
 | 
						|
            return self.bin_resolve_ulp(t)
 | 
						|
        else:
 | 
						|
            return False
 | 
						|
    power = __rpow__ = __pow__
 | 
						|
 | 
						|
    ############################## Technicalities #############################
 | 
						|
    def __float__(self, t):
 | 
						|
        """NaN comparison in the verify() function obviously gives an
 | 
						|
           incorrect answer:  nan == nan -> False"""
 | 
						|
        if t.cop[0].is_nan() and t.pop[0].is_nan():
 | 
						|
            return True
 | 
						|
        return False
 | 
						|
    __complex__ = __float__
 | 
						|
 | 
						|
    def __radd__(self, t):
 | 
						|
        """decimal.py gives precedence to the first NaN; this is
 | 
						|
           not important, as __radd__ will not be called for
 | 
						|
           two decimal arguments."""
 | 
						|
        if t.rc.is_nan() and t.rp.is_nan():
 | 
						|
            return True
 | 
						|
        return False
 | 
						|
    __rmul__ = __radd__
 | 
						|
 | 
						|
    ################################ Various ##################################
 | 
						|
    def __round__(self, t):
 | 
						|
        """Exception: Decimal('1').__round__(-100000000000000000000000000)
 | 
						|
           Should it really be InvalidOperation?"""
 | 
						|
        if t.rc is None and t.rp.is_nan():
 | 
						|
            return True
 | 
						|
        return False
 | 
						|
 | 
						|
shandler = SkipHandler()
 | 
						|
def skip_error(t):
 | 
						|
    return getattr(shandler, t.funcname, shandler.default)(t)
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#                      Handling verification errors
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
class VerifyError(Exception):
 | 
						|
    """Verification failed."""
 | 
						|
    pass
 | 
						|
 | 
						|
def function_as_string(t):
 | 
						|
    if t.contextfunc:
 | 
						|
        cargs = t.cop
 | 
						|
        pargs = t.pop
 | 
						|
        maxargs = t.maxop
 | 
						|
        cfunc = "c_func: %s(" % t.funcname
 | 
						|
        pfunc = "p_func: %s(" % t.funcname
 | 
						|
        maxfunc = "max_func: %s(" % t.funcname
 | 
						|
    else:
 | 
						|
        cself, cargs = t.cop[0], t.cop[1:]
 | 
						|
        pself, pargs = t.pop[0], t.pop[1:]
 | 
						|
        maxself, maxargs = t.maxop[0], t.maxop[1:]
 | 
						|
        cfunc = "c_func: %s.%s(" % (repr(cself), t.funcname)
 | 
						|
        pfunc = "p_func: %s.%s(" % (repr(pself), t.funcname)
 | 
						|
        maxfunc = "max_func: %s.%s(" % (repr(maxself), t.funcname)
 | 
						|
 | 
						|
    err = cfunc
 | 
						|
    for arg in cargs:
 | 
						|
        err += "%s, " % repr(arg)
 | 
						|
    err = err.rstrip(", ")
 | 
						|
    err += ")\n"
 | 
						|
 | 
						|
    err += pfunc
 | 
						|
    for arg in pargs:
 | 
						|
        err += "%s, " % repr(arg)
 | 
						|
    err = err.rstrip(", ")
 | 
						|
    err += ")"
 | 
						|
 | 
						|
    if t.with_maxcontext:
 | 
						|
        err += "\n"
 | 
						|
        err += maxfunc
 | 
						|
        for arg in maxargs:
 | 
						|
            err += "%s, " % repr(arg)
 | 
						|
        err = err.rstrip(", ")
 | 
						|
        err += ")"
 | 
						|
 | 
						|
    return err
 | 
						|
 | 
						|
def raise_error(t):
 | 
						|
    global EXIT_STATUS
 | 
						|
 | 
						|
    if skip_error(t):
 | 
						|
        return
 | 
						|
    EXIT_STATUS = 1
 | 
						|
 | 
						|
    err = "Error in %s:\n\n" % t.funcname
 | 
						|
    err += "input operands: %s\n\n" % (t.op,)
 | 
						|
    err += function_as_string(t)
 | 
						|
 | 
						|
    err += "\n\nc_result: %s\np_result: %s\n" % (t.cresults, t.presults)
 | 
						|
    if t.with_maxcontext:
 | 
						|
        err += "max_result: %s\n\n" % (t.maxresults)
 | 
						|
    else:
 | 
						|
        err += "\n"
 | 
						|
 | 
						|
    err += "c_exceptions: %s\np_exceptions: %s\n" % (t.cex, t.pex)
 | 
						|
    if t.with_maxcontext:
 | 
						|
        err += "max_exceptions: %s\n\n" % t.maxex
 | 
						|
    else:
 | 
						|
        err += "\n"
 | 
						|
 | 
						|
    err += "%s\n" % str(t.context)
 | 
						|
    if t.with_maxcontext:
 | 
						|
        err += "%s\n" % str(t.maxcontext)
 | 
						|
    else:
 | 
						|
        err += "\n"
 | 
						|
 | 
						|
    raise VerifyError(err)
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#                        Main testing functions
 | 
						|
#
 | 
						|
#  The procedure is always (t is the TestSet):
 | 
						|
#
 | 
						|
#   convert(t) -> Initialize the TestSet as necessary.
 | 
						|
#
 | 
						|
#                 Return 0 for early abortion (e.g. if a TypeError
 | 
						|
#                 occurs during conversion, there is nothing to test).
 | 
						|
#
 | 
						|
#                 Return 1 for continuing with the test case.
 | 
						|
#
 | 
						|
#   callfuncs(t) -> Call the relevant function for each implementation
 | 
						|
#                   and record the results in the TestSet.
 | 
						|
#
 | 
						|
#   verify(t) -> Verify the results. If verification fails, details
 | 
						|
#                are printed to stdout.
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
def all_nan(a):
 | 
						|
    if isinstance(a, C.Decimal):
 | 
						|
        return a.is_nan()
 | 
						|
    elif isinstance(a, tuple):
 | 
						|
        return all(all_nan(v) for v in a)
 | 
						|
    return False
 | 
						|
 | 
						|
def convert(t, convstr=True):
 | 
						|
    """ t is the testset. At this stage the testset contains a tuple of
 | 
						|
        operands t.op of various types. For decimal methods the first
 | 
						|
        operand (self) is always converted to Decimal. If 'convstr' is
 | 
						|
        true, string operands are converted as well.
 | 
						|
 | 
						|
        Context operands are of type deccheck.Context, rounding mode
 | 
						|
        operands are given as a tuple (C.rounding, P.rounding).
 | 
						|
 | 
						|
        Other types (float, int, etc.) are left unchanged.
 | 
						|
    """
 | 
						|
    for i, op in enumerate(t.op):
 | 
						|
 | 
						|
        context.clear_status()
 | 
						|
        t.maxcontext.clear_flags()
 | 
						|
 | 
						|
        if op in RoundModes:
 | 
						|
            t.cop.append(op)
 | 
						|
            t.pop.append(op)
 | 
						|
            t.maxop.append(op)
 | 
						|
 | 
						|
        elif not t.contextfunc and i == 0 or \
 | 
						|
             convstr and isinstance(op, str):
 | 
						|
            try:
 | 
						|
                c = C.Decimal(op)
 | 
						|
                cex = None
 | 
						|
            except (TypeError, ValueError, OverflowError) as e:
 | 
						|
                c = None
 | 
						|
                cex = e.__class__
 | 
						|
 | 
						|
            try:
 | 
						|
                p = RestrictedDecimal(op)
 | 
						|
                pex = None
 | 
						|
            except (TypeError, ValueError, OverflowError) as e:
 | 
						|
                p = None
 | 
						|
                pex = e.__class__
 | 
						|
 | 
						|
            try:
 | 
						|
                C.setcontext(t.maxcontext)
 | 
						|
                maxop = C.Decimal(op)
 | 
						|
                maxex = None
 | 
						|
            except (TypeError, ValueError, OverflowError) as e:
 | 
						|
                maxop = None
 | 
						|
                maxex = e.__class__
 | 
						|
            finally:
 | 
						|
                C.setcontext(context.c)
 | 
						|
 | 
						|
            t.cop.append(c)
 | 
						|
            t.cex.append(cex)
 | 
						|
 | 
						|
            t.pop.append(p)
 | 
						|
            t.pex.append(pex)
 | 
						|
 | 
						|
            t.maxop.append(maxop)
 | 
						|
            t.maxex.append(maxex)
 | 
						|
 | 
						|
            if cex is pex:
 | 
						|
                if str(c) != str(p) or not context.assert_eq_status():
 | 
						|
                    raise_error(t)
 | 
						|
                if cex and pex:
 | 
						|
                    # nothing to test
 | 
						|
                    return 0
 | 
						|
            else:
 | 
						|
                raise_error(t)
 | 
						|
 | 
						|
            # The exceptions in the maxcontext operation can legitimately
 | 
						|
            # differ, only test that maxex implies cex:
 | 
						|
            if maxex is not None and cex is not maxex:
 | 
						|
                raise_error(t)
 | 
						|
 | 
						|
        elif isinstance(op, Context):
 | 
						|
            t.context = op
 | 
						|
            t.cop.append(op.c)
 | 
						|
            t.pop.append(op.p)
 | 
						|
            t.maxop.append(t.maxcontext)
 | 
						|
 | 
						|
        else:
 | 
						|
            t.cop.append(op)
 | 
						|
            t.pop.append(op)
 | 
						|
            t.maxop.append(op)
 | 
						|
 | 
						|
    return 1
 | 
						|
 | 
						|
def callfuncs(t):
 | 
						|
    """ t is the testset. At this stage the testset contains operand lists
 | 
						|
        t.cop and t.pop for the C and Python versions of decimal.
 | 
						|
        For Decimal methods, the first operands are of type C.Decimal and
 | 
						|
        P.Decimal respectively. The remaining operands can have various types.
 | 
						|
        For Context methods, all operands can have any type.
 | 
						|
 | 
						|
        t.rc and t.rp are the results of the operation.
 | 
						|
    """
 | 
						|
    context.clear_status()
 | 
						|
    t.maxcontext.clear_flags()
 | 
						|
 | 
						|
    try:
 | 
						|
        if t.contextfunc:
 | 
						|
            cargs = t.cop
 | 
						|
            t.rc = getattr(context.c, t.funcname)(*cargs)
 | 
						|
        else:
 | 
						|
            cself = t.cop[0]
 | 
						|
            cargs = t.cop[1:]
 | 
						|
            t.rc = getattr(cself, t.funcname)(*cargs)
 | 
						|
        t.cex.append(None)
 | 
						|
    except (TypeError, ValueError, OverflowError, MemoryError) as e:
 | 
						|
        t.rc = None
 | 
						|
        t.cex.append(e.__class__)
 | 
						|
 | 
						|
    try:
 | 
						|
        if t.contextfunc:
 | 
						|
            pargs = t.pop
 | 
						|
            t.rp = getattr(context.p, t.funcname)(*pargs)
 | 
						|
        else:
 | 
						|
            pself = t.pop[0]
 | 
						|
            pargs = t.pop[1:]
 | 
						|
            t.rp = getattr(pself, t.funcname)(*pargs)
 | 
						|
        t.pex.append(None)
 | 
						|
    except (TypeError, ValueError, OverflowError, MemoryError) as e:
 | 
						|
        t.rp = None
 | 
						|
        t.pex.append(e.__class__)
 | 
						|
 | 
						|
    # If the above results are exact, unrounded, normal etc., repeat the
 | 
						|
    # operation with a maxcontext to ensure that huge intermediate values
 | 
						|
    # do not cause a MemoryError.
 | 
						|
    if (t.funcname not in MaxContextSkip and
 | 
						|
        not context.c.flags[C.InvalidOperation] and
 | 
						|
        not context.c.flags[C.Inexact] and
 | 
						|
        not context.c.flags[C.Rounded] and
 | 
						|
        not context.c.flags[C.Subnormal] and
 | 
						|
        not context.c.flags[C.Clamped] and
 | 
						|
        not context.clamp and # results are padded to context.prec if context.clamp==1.
 | 
						|
        not any(isinstance(v, C.Context) for v in t.cop)): # another context is used.
 | 
						|
        t.with_maxcontext = True
 | 
						|
        try:
 | 
						|
            if t.contextfunc:
 | 
						|
                maxargs = t.maxop
 | 
						|
                t.rmax = getattr(t.maxcontext, t.funcname)(*maxargs)
 | 
						|
            else:
 | 
						|
                maxself = t.maxop[0]
 | 
						|
                maxargs = t.maxop[1:]
 | 
						|
                try:
 | 
						|
                    C.setcontext(t.maxcontext)
 | 
						|
                    t.rmax = getattr(maxself, t.funcname)(*maxargs)
 | 
						|
                finally:
 | 
						|
                    C.setcontext(context.c)
 | 
						|
            t.maxex.append(None)
 | 
						|
        except (TypeError, ValueError, OverflowError, MemoryError) as e:
 | 
						|
            t.rmax = None
 | 
						|
            t.maxex.append(e.__class__)
 | 
						|
 | 
						|
def verify(t, stat):
 | 
						|
    """ t is the testset. At this stage the testset contains the following
 | 
						|
        tuples:
 | 
						|
 | 
						|
            t.op: original operands
 | 
						|
            t.cop: C.Decimal operands (see convert for details)
 | 
						|
            t.pop: P.Decimal operands (see convert for details)
 | 
						|
            t.rc: C result
 | 
						|
            t.rp: Python result
 | 
						|
 | 
						|
        t.rc and t.rp can have various types.
 | 
						|
    """
 | 
						|
    t.cresults.append(str(t.rc))
 | 
						|
    t.presults.append(str(t.rp))
 | 
						|
    if t.with_maxcontext:
 | 
						|
        t.maxresults.append(str(t.rmax))
 | 
						|
 | 
						|
    if isinstance(t.rc, C.Decimal) and isinstance(t.rp, P.Decimal):
 | 
						|
        # General case: both results are Decimals.
 | 
						|
        t.cresults.append(t.rc.to_eng_string())
 | 
						|
        t.cresults.append(t.rc.as_tuple())
 | 
						|
        t.cresults.append(str(t.rc.imag))
 | 
						|
        t.cresults.append(str(t.rc.real))
 | 
						|
        t.presults.append(t.rp.to_eng_string())
 | 
						|
        t.presults.append(t.rp.as_tuple())
 | 
						|
        t.presults.append(str(t.rp.imag))
 | 
						|
        t.presults.append(str(t.rp.real))
 | 
						|
 | 
						|
        if t.with_maxcontext and isinstance(t.rmax, C.Decimal):
 | 
						|
            t.maxresults.append(t.rmax.to_eng_string())
 | 
						|
            t.maxresults.append(t.rmax.as_tuple())
 | 
						|
            t.maxresults.append(str(t.rmax.imag))
 | 
						|
            t.maxresults.append(str(t.rmax.real))
 | 
						|
 | 
						|
        nc = t.rc.number_class().lstrip('+-s')
 | 
						|
        stat[nc] += 1
 | 
						|
    else:
 | 
						|
        # Results from e.g. __divmod__ can only be compared as strings.
 | 
						|
        if not isinstance(t.rc, tuple) and not isinstance(t.rp, tuple):
 | 
						|
            if t.rc != t.rp:
 | 
						|
                raise_error(t)
 | 
						|
            if t.with_maxcontext and not isinstance(t.rmax, tuple):
 | 
						|
                if t.rmax != t.rc:
 | 
						|
                    raise_error(t)
 | 
						|
        stat[type(t.rc).__name__] += 1
 | 
						|
 | 
						|
    # The return value lists must be equal.
 | 
						|
    if t.cresults != t.presults:
 | 
						|
        raise_error(t)
 | 
						|
    # The Python exception lists (TypeError, etc.) must be equal.
 | 
						|
    if t.cex != t.pex:
 | 
						|
        raise_error(t)
 | 
						|
    # The context flags must be equal.
 | 
						|
    if not t.context.assert_eq_status():
 | 
						|
        raise_error(t)
 | 
						|
 | 
						|
    if t.with_maxcontext:
 | 
						|
        # NaN payloads etc. depend on precision and clamp.
 | 
						|
        if all_nan(t.rc) and all_nan(t.rmax):
 | 
						|
            return
 | 
						|
        # The return value lists must be equal.
 | 
						|
        if t.maxresults != t.cresults:
 | 
						|
            raise_error(t)
 | 
						|
        # The Python exception lists (TypeError, etc.) must be equal.
 | 
						|
        if t.maxex != t.cex:
 | 
						|
            raise_error(t)
 | 
						|
        # The context flags must be equal.
 | 
						|
        if t.maxcontext.flags != t.context.c.flags:
 | 
						|
            raise_error(t)
 | 
						|
 | 
						|
 | 
						|
# ======================================================================
 | 
						|
#                           Main test loops
 | 
						|
#
 | 
						|
#  test_method(method, testspecs, testfunc) ->
 | 
						|
#
 | 
						|
#     Loop through various context settings. The degree of
 | 
						|
#     thoroughness is determined by 'testspec'. For each
 | 
						|
#     setting, call 'testfunc'. Generally, 'testfunc' itself
 | 
						|
#     a loop, iterating through many test cases generated
 | 
						|
#     by the functions in randdec.py.
 | 
						|
#
 | 
						|
#  test_n-ary(method, prec, exp_range, restricted_range, itr, stat) ->
 | 
						|
#
 | 
						|
#     'test_unary', 'test_binary' and 'test_ternary' are the
 | 
						|
#     main test functions passed to 'test_method'. They deal
 | 
						|
#     with the regular cases. The thoroughness of testing is
 | 
						|
#     determined by 'itr'.
 | 
						|
#
 | 
						|
#     'prec', 'exp_range' and 'restricted_range' are passed
 | 
						|
#     to the test-generating functions and limit the generated
 | 
						|
#     values. In some cases, for reasonable run times a
 | 
						|
#     maximum exponent of 9999 is required.
 | 
						|
#
 | 
						|
#     The 'stat' parameter is passed down to the 'verify'
 | 
						|
#     function, which records statistics for the result values.
 | 
						|
# ======================================================================
 | 
						|
 | 
						|
def log(fmt, args=None):
 | 
						|
    if args:
 | 
						|
        sys.stdout.write(''.join((fmt, '\n')) % args)
 | 
						|
    else:
 | 
						|
        sys.stdout.write(''.join((str(fmt), '\n')))
 | 
						|
    sys.stdout.flush()
 | 
						|
 | 
						|
def test_method(method, testspecs, testfunc):
 | 
						|
    """Iterate a test function through many context settings."""
 | 
						|
    log("testing %s ...", method)
 | 
						|
    stat = defaultdict(int)
 | 
						|
    for spec in testspecs:
 | 
						|
        if 'samples' in spec:
 | 
						|
            spec['prec'] = sorted(random.sample(range(1, 101),
 | 
						|
                                  spec['samples']))
 | 
						|
        for prec in spec['prec']:
 | 
						|
            context.prec = prec
 | 
						|
            for expts in spec['expts']:
 | 
						|
                emin, emax = expts
 | 
						|
                if emin == 'rand':
 | 
						|
                    context.Emin = random.randrange(-1000, 0)
 | 
						|
                    context.Emax = random.randrange(prec, 1000)
 | 
						|
                else:
 | 
						|
                    context.Emin, context.Emax = emin, emax
 | 
						|
                if prec > context.Emax: continue
 | 
						|
                log("    prec: %d  emin: %d  emax: %d",
 | 
						|
                    (context.prec, context.Emin, context.Emax))
 | 
						|
                restr_range = 9999 if context.Emax > 9999 else context.Emax+99
 | 
						|
                for rounding in RoundModes:
 | 
						|
                    context.rounding = rounding
 | 
						|
                    context.capitals = random.randrange(2)
 | 
						|
                    if spec['clamp'] == 'rand':
 | 
						|
                        context.clamp = random.randrange(2)
 | 
						|
                    else:
 | 
						|
                        context.clamp = spec['clamp']
 | 
						|
                    exprange = context.c.Emax
 | 
						|
                    testfunc(method, prec, exprange, restr_range,
 | 
						|
                             spec['iter'], stat)
 | 
						|
    log("    result types: %s" % sorted([t for t in stat.items()]))
 | 
						|
 | 
						|
def test_unary(method, prec, exp_range, restricted_range, itr, stat):
 | 
						|
    """Iterate a unary function through many test cases."""
 | 
						|
    if method in UnaryRestricted:
 | 
						|
        exp_range = restricted_range
 | 
						|
    for op in all_unary(prec, exp_range, itr):
 | 
						|
        t = TestSet(method, op)
 | 
						|
        try:
 | 
						|
            if not convert(t):
 | 
						|
                continue
 | 
						|
            callfuncs(t)
 | 
						|
            verify(t, stat)
 | 
						|
        except VerifyError as err:
 | 
						|
            log(err)
 | 
						|
 | 
						|
    if not method.startswith('__'):
 | 
						|
        for op in unary_optarg(prec, exp_range, itr):
 | 
						|
            t = TestSet(method, op)
 | 
						|
            try:
 | 
						|
                if not convert(t):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
def test_binary(method, prec, exp_range, restricted_range, itr, stat):
 | 
						|
    """Iterate a binary function through many test cases."""
 | 
						|
    if method in BinaryRestricted:
 | 
						|
        exp_range = restricted_range
 | 
						|
    for op in all_binary(prec, exp_range, itr):
 | 
						|
        t = TestSet(method, op)
 | 
						|
        try:
 | 
						|
            if not convert(t):
 | 
						|
                continue
 | 
						|
            callfuncs(t)
 | 
						|
            verify(t, stat)
 | 
						|
        except VerifyError as err:
 | 
						|
            log(err)
 | 
						|
 | 
						|
    if not method.startswith('__'):
 | 
						|
        for op in binary_optarg(prec, exp_range, itr):
 | 
						|
            t = TestSet(method, op)
 | 
						|
            try:
 | 
						|
                if not convert(t):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
def test_ternary(method, prec, exp_range, restricted_range, itr, stat):
 | 
						|
    """Iterate a ternary function through many test cases."""
 | 
						|
    if method in TernaryRestricted:
 | 
						|
        exp_range = restricted_range
 | 
						|
    for op in all_ternary(prec, exp_range, itr):
 | 
						|
        t = TestSet(method, op)
 | 
						|
        try:
 | 
						|
            if not convert(t):
 | 
						|
                continue
 | 
						|
            callfuncs(t)
 | 
						|
            verify(t, stat)
 | 
						|
        except VerifyError as err:
 | 
						|
            log(err)
 | 
						|
 | 
						|
    if not method.startswith('__'):
 | 
						|
        for op in ternary_optarg(prec, exp_range, itr):
 | 
						|
            t = TestSet(method, op)
 | 
						|
            try:
 | 
						|
                if not convert(t):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
def test_format(method, prec, exp_range, restricted_range, itr, stat):
 | 
						|
    """Iterate the __format__ method through many test cases."""
 | 
						|
    for op in all_unary(prec, exp_range, itr):
 | 
						|
        fmt1 = rand_format(chr(random.randrange(0, 128)), 'EeGgn')
 | 
						|
        fmt2 = rand_locale()
 | 
						|
        for fmt in (fmt1, fmt2):
 | 
						|
            fmtop = (op[0], fmt)
 | 
						|
            t = TestSet(method, fmtop)
 | 
						|
            try:
 | 
						|
                if not convert(t, convstr=False):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
    for op in all_unary(prec, 9999, itr):
 | 
						|
        fmt1 = rand_format(chr(random.randrange(0, 128)), 'Ff%')
 | 
						|
        fmt2 = rand_locale()
 | 
						|
        for fmt in (fmt1, fmt2):
 | 
						|
            fmtop = (op[0], fmt)
 | 
						|
            t = TestSet(method, fmtop)
 | 
						|
            try:
 | 
						|
                if not convert(t, convstr=False):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
def test_round(method, prec, exprange, restricted_range, itr, stat):
 | 
						|
    """Iterate the __round__ method through many test cases."""
 | 
						|
    for op in all_unary(prec, 9999, itr):
 | 
						|
        n = random.randrange(10)
 | 
						|
        roundop = (op[0], n)
 | 
						|
        t = TestSet(method, roundop)
 | 
						|
        try:
 | 
						|
            if not convert(t):
 | 
						|
                continue
 | 
						|
            callfuncs(t)
 | 
						|
            verify(t, stat)
 | 
						|
        except VerifyError as err:
 | 
						|
            log(err)
 | 
						|
 | 
						|
def test_from_float(method, prec, exprange, restricted_range, itr, stat):
 | 
						|
    """Iterate the __float__ method through many test cases."""
 | 
						|
    for rounding in RoundModes:
 | 
						|
        context.rounding = rounding
 | 
						|
        for i in range(1000):
 | 
						|
            f = randfloat()
 | 
						|
            op = (f,) if method.startswith("context.") else ("sNaN", f)
 | 
						|
            t = TestSet(method, op)
 | 
						|
            try:
 | 
						|
                if not convert(t):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
def randcontext(exprange):
 | 
						|
    c = Context(C.Context(), P.Context())
 | 
						|
    c.Emax = random.randrange(1, exprange+1)
 | 
						|
    c.Emin = random.randrange(-exprange, 0)
 | 
						|
    maxprec = 100 if c.Emax >= 100 else c.Emax
 | 
						|
    c.prec = random.randrange(1, maxprec+1)
 | 
						|
    c.clamp = random.randrange(2)
 | 
						|
    c.clear_traps()
 | 
						|
    return c
 | 
						|
 | 
						|
def test_quantize_api(method, prec, exprange, restricted_range, itr, stat):
 | 
						|
    """Iterate the 'quantize' method through many test cases, using
 | 
						|
       the optional arguments."""
 | 
						|
    for op in all_binary(prec, restricted_range, itr):
 | 
						|
        for rounding in RoundModes:
 | 
						|
            c = randcontext(exprange)
 | 
						|
            quantizeop = (op[0], op[1], rounding, c)
 | 
						|
            t = TestSet(method, quantizeop)
 | 
						|
            try:
 | 
						|
                if not convert(t):
 | 
						|
                    continue
 | 
						|
                callfuncs(t)
 | 
						|
                verify(t, stat)
 | 
						|
            except VerifyError as err:
 | 
						|
                log(err)
 | 
						|
 | 
						|
 | 
						|
def check_untested(funcdict, c_cls, p_cls):
 | 
						|
    """Determine untested, C-only and Python-only attributes.
 | 
						|
       Uncomment print lines for debugging."""
 | 
						|
    c_attr = set(dir(c_cls))
 | 
						|
    p_attr = set(dir(p_cls))
 | 
						|
    intersect = c_attr & p_attr
 | 
						|
 | 
						|
    funcdict['c_only'] = tuple(sorted(c_attr-intersect))
 | 
						|
    funcdict['p_only'] = tuple(sorted(p_attr-intersect))
 | 
						|
 | 
						|
    tested = set()
 | 
						|
    for lst in funcdict.values():
 | 
						|
        for v in lst:
 | 
						|
            v = v.replace("context.", "") if c_cls == C.Context else v
 | 
						|
            tested.add(v)
 | 
						|
 | 
						|
    funcdict['untested'] = tuple(sorted(intersect-tested))
 | 
						|
 | 
						|
    # for key in ('untested', 'c_only', 'p_only'):
 | 
						|
    #     s = 'Context' if c_cls == C.Context else 'Decimal'
 | 
						|
    #     print("\n%s %s:\n%s" % (s, key, funcdict[key]))
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
 | 
						|
    parser = argparse.ArgumentParser(prog="deccheck.py")
 | 
						|
 | 
						|
    group = parser.add_mutually_exclusive_group()
 | 
						|
    group.add_argument('--short', dest='time', action="store_const", const='short', default='short', help="short test (default)")
 | 
						|
    group.add_argument('--medium', dest='time', action="store_const", const='medium', default='short', help="medium test (reasonable run time)")
 | 
						|
    group.add_argument('--long', dest='time', action="store_const", const='long', default='short', help="long test (long run time)")
 | 
						|
    group.add_argument('--all', dest='time', action="store_const", const='all', default='short', help="all tests (excessive run time)")
 | 
						|
 | 
						|
    group = parser.add_mutually_exclusive_group()
 | 
						|
    group.add_argument('--single', dest='single', nargs=1, default=False, metavar="TEST", help="run a single test")
 | 
						|
    group.add_argument('--multicore', dest='multicore', action="store_true", default=False, help="use all available cores")
 | 
						|
 | 
						|
    args = parser.parse_args()
 | 
						|
    assert args.single is False or args.multicore is False
 | 
						|
    if args.single:
 | 
						|
        args.single = args.single[0]
 | 
						|
 | 
						|
 | 
						|
    # Set up the testspecs list. A testspec is simply a dictionary
 | 
						|
    # that determines the amount of different contexts that 'test_method'
 | 
						|
    # will generate.
 | 
						|
    base_expts = [(C.MIN_EMIN, C.MAX_EMAX)]
 | 
						|
    if C.MAX_EMAX == 999999999999999999:
 | 
						|
        base_expts.append((-999999999, 999999999))
 | 
						|
 | 
						|
    # Basic contexts.
 | 
						|
    base = {
 | 
						|
        'expts': base_expts,
 | 
						|
        'prec': [],
 | 
						|
        'clamp': 'rand',
 | 
						|
        'iter': None,
 | 
						|
        'samples': None,
 | 
						|
    }
 | 
						|
    # Contexts with small values for prec, emin, emax.
 | 
						|
    small = {
 | 
						|
        'prec': [1, 2, 3, 4, 5],
 | 
						|
        'expts': [(-1, 1), (-2, 2), (-3, 3), (-4, 4), (-5, 5)],
 | 
						|
        'clamp': 'rand',
 | 
						|
        'iter': None
 | 
						|
    }
 | 
						|
    # IEEE interchange format.
 | 
						|
    ieee = [
 | 
						|
        # DECIMAL32
 | 
						|
        {'prec': [7], 'expts': [(-95, 96)], 'clamp': 1, 'iter': None},
 | 
						|
        # DECIMAL64
 | 
						|
        {'prec': [16], 'expts': [(-383, 384)], 'clamp': 1, 'iter': None},
 | 
						|
        # DECIMAL128
 | 
						|
        {'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
 | 
						|
    ]
 | 
						|
 | 
						|
    if args.time == 'medium':
 | 
						|
        base['expts'].append(('rand', 'rand'))
 | 
						|
        # 5 random precisions
 | 
						|
        base['samples'] = 5
 | 
						|
        testspecs = [small] + ieee + [base]
 | 
						|
    elif args.time == 'long':
 | 
						|
        base['expts'].append(('rand', 'rand'))
 | 
						|
        # 10 random precisions
 | 
						|
        base['samples'] = 10
 | 
						|
        testspecs = [small] + ieee + [base]
 | 
						|
    elif args.time == 'all':
 | 
						|
        base['expts'].append(('rand', 'rand'))
 | 
						|
        # All precisions in [1, 100]
 | 
						|
        base['samples'] = 100
 | 
						|
        testspecs = [small] + ieee + [base]
 | 
						|
    else: # --short
 | 
						|
        rand_ieee = random.choice(ieee)
 | 
						|
        base['iter'] = small['iter'] = rand_ieee['iter'] = 1
 | 
						|
        # 1 random precision and exponent pair
 | 
						|
        base['samples'] = 1
 | 
						|
        base['expts'] = [random.choice(base_expts)]
 | 
						|
        # 1 random precision and exponent pair
 | 
						|
        prec = random.randrange(1, 6)
 | 
						|
        small['prec'] = [prec]
 | 
						|
        small['expts'] = [(-prec, prec)]
 | 
						|
        testspecs = [small, rand_ieee, base]
 | 
						|
 | 
						|
 | 
						|
    check_untested(Functions, C.Decimal, P.Decimal)
 | 
						|
    check_untested(ContextFunctions, C.Context, P.Context)
 | 
						|
 | 
						|
 | 
						|
    if args.multicore:
 | 
						|
        q = Queue()
 | 
						|
    elif args.single:
 | 
						|
        log("Random seed: %d", RANDSEED)
 | 
						|
    else:
 | 
						|
        log("\n\nRandom seed: %d\n\n", RANDSEED)
 | 
						|
 | 
						|
 | 
						|
    FOUND_METHOD = False
 | 
						|
    def do_single(method, f):
 | 
						|
        global FOUND_METHOD
 | 
						|
        if args.multicore:
 | 
						|
            q.put(method)
 | 
						|
        elif not args.single or args.single == method:
 | 
						|
            FOUND_METHOD = True
 | 
						|
            f()
 | 
						|
 | 
						|
    # Decimal methods:
 | 
						|
    for method in Functions['unary'] + Functions['unary_ctx'] + \
 | 
						|
                  Functions['unary_rnd_ctx']:
 | 
						|
        do_single(method, lambda: test_method(method, testspecs, test_unary))
 | 
						|
 | 
						|
    for method in Functions['binary'] + Functions['binary_ctx']:
 | 
						|
        do_single(method, lambda: test_method(method, testspecs, test_binary))
 | 
						|
 | 
						|
    for method in Functions['ternary'] + Functions['ternary_ctx']:
 | 
						|
        name = '__powmod__' if method == '__pow__' else method
 | 
						|
        do_single(name, lambda: test_method(method, testspecs, test_ternary))
 | 
						|
 | 
						|
    do_single('__format__', lambda: test_method('__format__', testspecs, test_format))
 | 
						|
    do_single('__round__', lambda: test_method('__round__', testspecs, test_round))
 | 
						|
    do_single('from_float', lambda: test_method('from_float', testspecs, test_from_float))
 | 
						|
    do_single('quantize_api', lambda: test_method('quantize', testspecs, test_quantize_api))
 | 
						|
 | 
						|
    # Context methods:
 | 
						|
    for method in ContextFunctions['unary']:
 | 
						|
        do_single(method, lambda: test_method(method, testspecs, test_unary))
 | 
						|
 | 
						|
    for method in ContextFunctions['binary']:
 | 
						|
        do_single(method, lambda: test_method(method, testspecs, test_binary))
 | 
						|
 | 
						|
    for method in ContextFunctions['ternary']:
 | 
						|
        name = 'context.powmod' if method == 'context.power' else method
 | 
						|
        do_single(name, lambda: test_method(method, testspecs, test_ternary))
 | 
						|
 | 
						|
    do_single('context.create_decimal_from_float',
 | 
						|
              lambda: test_method('context.create_decimal_from_float',
 | 
						|
                                   testspecs, test_from_float))
 | 
						|
 | 
						|
    if args.multicore:
 | 
						|
        error = Event()
 | 
						|
        write_lock = Lock()
 | 
						|
 | 
						|
        def write_output(out, returncode):
 | 
						|
            if returncode != 0:
 | 
						|
                error.set()
 | 
						|
 | 
						|
            with write_lock:
 | 
						|
                sys.stdout.buffer.write(out + b"\n")
 | 
						|
                sys.stdout.buffer.flush()
 | 
						|
 | 
						|
        def tfunc():
 | 
						|
            while not error.is_set():
 | 
						|
                try:
 | 
						|
                    test = q.get(block=False, timeout=-1)
 | 
						|
                except Empty:
 | 
						|
                    return
 | 
						|
 | 
						|
                cmd = [sys.executable, "deccheck.py", "--%s" % args.time, "--single", test]
 | 
						|
                p = subprocess.Popen(cmd, stdout=PIPE, stderr=STDOUT)
 | 
						|
                out, _ = p.communicate()
 | 
						|
                write_output(out, p.returncode)
 | 
						|
 | 
						|
        N = os.process_cpu_count()
 | 
						|
        t = N * [None]
 | 
						|
 | 
						|
        for i in range(N):
 | 
						|
            t[i] = Thread(target=tfunc)
 | 
						|
            t[i].start()
 | 
						|
 | 
						|
        for i in range(N):
 | 
						|
            t[i].join()
 | 
						|
 | 
						|
        sys.exit(1 if error.is_set() else 0)
 | 
						|
 | 
						|
    elif args.single:
 | 
						|
        if not FOUND_METHOD:
 | 
						|
            log("\nerror: cannot find method \"%s\"" % args.single)
 | 
						|
            EXIT_STATUS = 1
 | 
						|
        sys.exit(EXIT_STATUS)
 | 
						|
    else:
 | 
						|
        sys.exit(EXIT_STATUS)
 |