Add multicore support to deccheck.py. (GH-20731)

This commit is contained in:
Stefan Krah 2020-06-08 19:33:12 +02:00 committed by GitHub
parent 0c59f440f4
commit 951d680d56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -29,9 +29,20 @@
# Usage: python deccheck.py [--short|--medium|--long|--all] # Usage: python deccheck.py [--short|--medium|--long|--all]
# #
import sys, random
import sys
import os
import time
import random
from copy import copy from copy import copy
from collections import defaultdict 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 import_fresh_module from test.support import import_fresh_module
from randdec import randfloat, all_unary, all_binary, all_ternary from randdec import randfloat, all_unary, all_binary, all_ternary
from randdec import unary_optarg, binary_optarg, ternary_optarg from randdec import unary_optarg, binary_optarg, ternary_optarg
@ -1124,18 +1135,35 @@ def check_untested(funcdict, c_cls, p_cls):
funcdict['untested'] = tuple(sorted(intersect-tested)) funcdict['untested'] = tuple(sorted(intersect-tested))
#for key in ('untested', 'c_only', 'p_only'): # for key in ('untested', 'c_only', 'p_only'):
# s = 'Context' if c_cls == C.Context else 'Decimal' # s = 'Context' if c_cls == C.Context else 'Decimal'
# print("\n%s %s:\n%s" % (s, key, funcdict[key])) # print("\n%s %s:\n%s" % (s, key, funcdict[key]))
if __name__ == '__main__': if __name__ == '__main__':
import time 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]
randseed = int(time.time()) randseed = int(time.time())
random.seed(randseed) random.seed(randseed)
# Set up the testspecs list. A testspec is simply a dictionary # Set up the testspecs list. A testspec is simply a dictionary
# that determines the amount of different contexts that 'test_method' # that determines the amount of different contexts that 'test_method'
# will generate. # will generate.
@ -1168,17 +1196,17 @@ if __name__ == '__main__':
{'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None} {'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
] ]
if '--medium' in sys.argv: if args.time == 'medium':
base['expts'].append(('rand', 'rand')) base['expts'].append(('rand', 'rand'))
# 5 random precisions # 5 random precisions
base['samples'] = 5 base['samples'] = 5
testspecs = [small] + ieee + [base] testspecs = [small] + ieee + [base]
if '--long' in sys.argv: elif args.time == 'long':
base['expts'].append(('rand', 'rand')) base['expts'].append(('rand', 'rand'))
# 10 random precisions # 10 random precisions
base['samples'] = 10 base['samples'] = 10
testspecs = [small] + ieee + [base] testspecs = [small] + ieee + [base]
elif '--all' in sys.argv: elif args.time == 'all':
base['expts'].append(('rand', 'rand')) base['expts'].append(('rand', 'rand'))
# All precisions in [1, 100] # All precisions in [1, 100]
base['samples'] = 100 base['samples'] = 100
@ -1195,39 +1223,100 @@ if __name__ == '__main__':
small['expts'] = [(-prec, prec)] small['expts'] = [(-prec, prec)]
testspecs = [small, rand_ieee, base] testspecs = [small, rand_ieee, base]
check_untested(Functions, C.Decimal, P.Decimal) check_untested(Functions, C.Decimal, P.Decimal)
check_untested(ContextFunctions, C.Context, P.Context) check_untested(ContextFunctions, C.Context, P.Context)
log("\n\nRandom seed: %d\n\n", randseed) 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: # Decimal methods:
for method in Functions['unary'] + Functions['unary_ctx'] + \ for method in Functions['unary'] + Functions['unary_ctx'] + \
Functions['unary_rnd_ctx']: Functions['unary_rnd_ctx']:
test_method(method, testspecs, test_unary) do_single(method, lambda: test_method(method, testspecs, test_unary))
for method in Functions['binary'] + Functions['binary_ctx']: for method in Functions['binary'] + Functions['binary_ctx']:
test_method(method, testspecs, test_binary) do_single(method, lambda: test_method(method, testspecs, test_binary))
for method in Functions['ternary'] + Functions['ternary_ctx']: for method in Functions['ternary'] + Functions['ternary_ctx']:
test_method(method, testspecs, test_ternary) name = '__powmod__' if method == '__pow__' else method
do_single(name, lambda: test_method(method, testspecs, test_ternary))
test_method('__format__', testspecs, test_format) do_single('__format__', lambda: test_method('__format__', testspecs, test_format))
test_method('__round__', testspecs, test_round) do_single('__round__', lambda: test_method('__round__', testspecs, test_round))
test_method('from_float', testspecs, test_from_float) do_single('from_float', lambda: test_method('from_float', testspecs, test_from_float))
test_method('quantize', testspecs, test_quantize_api) do_single('quantize_api', lambda: test_method('quantize', testspecs, test_quantize_api))
# Context methods: # Context methods:
for method in ContextFunctions['unary']: for method in ContextFunctions['unary']:
test_method(method, testspecs, test_unary) do_single(method, lambda: test_method(method, testspecs, test_unary))
for method in ContextFunctions['binary']: for method in ContextFunctions['binary']:
test_method(method, testspecs, test_binary) do_single(method, lambda: test_method(method, testspecs, test_binary))
for method in ContextFunctions['ternary']: for method in ContextFunctions['ternary']:
test_method(method, testspecs, test_ternary) name = 'context.powmod' if method == 'context.power' else method
do_single(name, lambda: test_method(method, testspecs, test_ternary))
test_method('context.create_decimal_from_float', testspecs, test_from_float) 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()
sys.exit(EXIT_STATUS) 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.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)