# Demonstrate coveage and accuracy weaknesses in test.test_math # # This program runs the regression test for the math module, # but on modified versions of it to check coverage and the # detection of innacuracies. import math, io, random import test.test_math as test_math class LoggedCallable: "Wrap s real function of one argument so that it logs calls" def __init__(self, f, name): self.f = f self.name = name self.pos = [] self.neg = [] self.other = [] def __call__(self, *args): if len(args)==1 and isinstance(args[0], float): x = args[0] if not math.isfinite(x) or x == 0.0: # Use str to finesse nan != nan and -0.0 == 0.0 self.other.append(str(x)) elif x > 0.0: self.pos.append(x) else: self.neg.append(x) return self.f(*args) def formatted_lists(self, fmt, limit=None): def format_element(x): return format(x, fmt) if isinstance(x, float) else str(x) if limit: m = limit//2 n = limit - m with io.StringIO() as s: print(self.name, file=s) for args in (self.pos, self.neg, self.other): args = sorted(set(args)) count = len(args) if limit and count > limit: args[n:-m] = ['...'] args = map(format_element, args) print(' [{}]'.format(', '.join(args)), file=s) return s.getvalue() class LoggedMath: "A proxy for the stdlib math with logging on specified targets." def __init__(self, targets): self.targets = [] for name in targets: f = LoggedCallable(getattr(math, name), name) setattr(self, name, f) self.targets.append(f) def __getattr__(self, name): # non-targets return getattr(math, name) def print(self, fmt='.4g', limit=None): for f in self.targets: print(f.formatted_lists(fmt, limit)) class InaccurateCallable: "Wrap a function so that it becomes inaccurate" def __init__(self, f, error): self.f = f self.error = abs(error) def __call__(self, *args): return self.f(*args) + random.uniform(-self.error, self.error) class InaccurateMath: "A proxy for the stdlib math where specified targets are inaccurate." def __init__(self, targets, error=5e-6): for name in targets: f = InaccurateCallable(getattr(math, name), error) setattr(self, name, f) def __getattr__(self, name): # non-targets return getattr(math, name) def check_coverage(): # Log arguments to these functions as used by test_math targets = [ 'sqrt', 'sin', 'asin', 'cos', 'acos', 'tan', 'atan', 'exp', 'expm1', 'log', 'log10', 'log1p', 'sinh', 'asinh', 'cosh', 'acosh', 'tanh', 'atanh', ] m = LoggedMath(targets, ) test_math.math = m # run the test test_math.test_main() # dump the coverage stats m.print('.4g', 10) def check_accuracy(): # Fuzz the results of these functions tested by test_math targets = [ 'sqrt', 'sin', 'asin', 'cos', 'acos', 'tan', 'atan', #'exp', 'expm1', 'log', 'log10', #'log1p', 'sinh', 'asinh', 'cosh', 'acosh', #'tanh', 'atanh', ] test_math.math = InaccurateMath(targets, 2e-6) # run the test test_math.test_main() if __name__ == '__main__': check_coverage() check_accuracy()