Skip to content

Commit 3dc4428

Browse files
Add multicore support to deccheck.py. (GH-20731)
(cherry picked from commit 951d680) Authored-by: Stefan Krah <[email protected]>
1 parent ecdd28c commit 3dc4428

File tree

1 file changed

+110
-21
lines changed

1 file changed

+110
-21
lines changed

Modules/_decimal/tests/deccheck.py

Lines changed: 110 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@
2929
# Usage: python deccheck.py [--short|--medium|--long|--all]
3030
#
3131

32-
import sys, random
32+
33+
import sys
34+
import os
35+
import time
36+
import random
3337
from copy import copy
3438
from collections import defaultdict
39+
40+
import argparse
41+
import subprocess
42+
from subprocess import PIPE, STDOUT
43+
from queue import Queue, Empty
44+
from threading import Thread, Event, Lock
45+
3546
from test.support import import_fresh_module
3647
from randdec import randfloat, all_unary, all_binary, all_ternary
3748
from randdec import unary_optarg, binary_optarg, ternary_optarg
@@ -1124,18 +1135,35 @@ def check_untested(funcdict, c_cls, p_cls):
11241135

11251136
funcdict['untested'] = tuple(sorted(intersect-tested))
11261137

1127-
#for key in ('untested', 'c_only', 'p_only'):
1128-
# s = 'Context' if c_cls == C.Context else 'Decimal'
1129-
# print("\n%s %s:\n%s" % (s, key, funcdict[key]))
1138+
# for key in ('untested', 'c_only', 'p_only'):
1139+
# s = 'Context' if c_cls == C.Context else 'Decimal'
1140+
# print("\n%s %s:\n%s" % (s, key, funcdict[key]))
11301141

11311142

11321143
if __name__ == '__main__':
11331144

1134-
import time
1145+
parser = argparse.ArgumentParser(prog="deccheck.py")
1146+
1147+
group = parser.add_mutually_exclusive_group()
1148+
group.add_argument('--short', dest='time', action="store_const", const='short', default='short', help="short test (default)")
1149+
group.add_argument('--medium', dest='time', action="store_const", const='medium', default='short', help="medium test (reasonable run time)")
1150+
group.add_argument('--long', dest='time', action="store_const", const='long', default='short', help="long test (long run time)")
1151+
group.add_argument('--all', dest='time', action="store_const", const='all', default='short', help="all tests (excessive run time)")
1152+
1153+
group = parser.add_mutually_exclusive_group()
1154+
group.add_argument('--single', dest='single', nargs=1, default=False, metavar="TEST", help="run a single test")
1155+
group.add_argument('--multicore', dest='multicore', action="store_true", default=False, help="use all available cores")
1156+
1157+
args = parser.parse_args()
1158+
assert args.single is False or args.multicore is False
1159+
if args.single:
1160+
args.single = args.single[0]
1161+
11351162

11361163
randseed = int(time.time())
11371164
random.seed(randseed)
11381165

1166+
11391167
# Set up the testspecs list. A testspec is simply a dictionary
11401168
# that determines the amount of different contexts that 'test_method'
11411169
# will generate.
@@ -1168,17 +1196,17 @@ def check_untested(funcdict, c_cls, p_cls):
11681196
{'prec': [34], 'expts': [(-6143, 6144)], 'clamp': 1, 'iter': None}
11691197
]
11701198

1171-
if '--medium' in sys.argv:
1199+
if args.time == 'medium':
11721200
base['expts'].append(('rand', 'rand'))
11731201
# 5 random precisions
11741202
base['samples'] = 5
11751203
testspecs = [small] + ieee + [base]
1176-
if '--long' in sys.argv:
1204+
elif args.time == 'long':
11771205
base['expts'].append(('rand', 'rand'))
11781206
# 10 random precisions
11791207
base['samples'] = 10
11801208
testspecs = [small] + ieee + [base]
1181-
elif '--all' in sys.argv:
1209+
elif args.time == 'all':
11821210
base['expts'].append(('rand', 'rand'))
11831211
# All precisions in [1, 100]
11841212
base['samples'] = 100
@@ -1195,39 +1223,100 @@ def check_untested(funcdict, c_cls, p_cls):
11951223
small['expts'] = [(-prec, prec)]
11961224
testspecs = [small, rand_ieee, base]
11971225

1226+
11981227
check_untested(Functions, C.Decimal, P.Decimal)
11991228
check_untested(ContextFunctions, C.Context, P.Context)
12001229

12011230

1202-
log("\n\nRandom seed: %d\n\n", randseed)
1231+
if args.multicore:
1232+
q = Queue()
1233+
elif args.single:
1234+
log("Random seed: %d", randseed)
1235+
else:
1236+
log("\n\nRandom seed: %d\n\n", randseed)
1237+
1238+
1239+
FOUND_METHOD = False
1240+
def do_single(method, f):
1241+
global FOUND_METHOD
1242+
if args.multicore:
1243+
q.put(method)
1244+
elif not args.single or args.single == method:
1245+
FOUND_METHOD = True
1246+
f()
12031247

12041248
# Decimal methods:
12051249
for method in Functions['unary'] + Functions['unary_ctx'] + \
12061250
Functions['unary_rnd_ctx']:
1207-
test_method(method, testspecs, test_unary)
1251+
do_single(method, lambda: test_method(method, testspecs, test_unary))
12081252

12091253
for method in Functions['binary'] + Functions['binary_ctx']:
1210-
test_method(method, testspecs, test_binary)
1254+
do_single(method, lambda: test_method(method, testspecs, test_binary))
12111255

12121256
for method in Functions['ternary'] + Functions['ternary_ctx']:
1213-
test_method(method, testspecs, test_ternary)
1257+
name = '__powmod__' if method == '__pow__' else method
1258+
do_single(name, lambda: test_method(method, testspecs, test_ternary))
12141259

1215-
test_method('__format__', testspecs, test_format)
1216-
test_method('__round__', testspecs, test_round)
1217-
test_method('from_float', testspecs, test_from_float)
1218-
test_method('quantize', testspecs, test_quantize_api)
1260+
do_single('__format__', lambda: test_method('__format__', testspecs, test_format))
1261+
do_single('__round__', lambda: test_method('__round__', testspecs, test_round))
1262+
do_single('from_float', lambda: test_method('from_float', testspecs, test_from_float))
1263+
do_single('quantize_api', lambda: test_method('quantize', testspecs, test_quantize_api))
12191264

12201265
# Context methods:
12211266
for method in ContextFunctions['unary']:
1222-
test_method(method, testspecs, test_unary)
1267+
do_single(method, lambda: test_method(method, testspecs, test_unary))
12231268

12241269
for method in ContextFunctions['binary']:
1225-
test_method(method, testspecs, test_binary)
1270+
do_single(method, lambda: test_method(method, testspecs, test_binary))
12261271

12271272
for method in ContextFunctions['ternary']:
1228-
test_method(method, testspecs, test_ternary)
1273+
name = 'context.powmod' if method == 'context.power' else method
1274+
do_single(name, lambda: test_method(method, testspecs, test_ternary))
1275+
1276+
do_single('context.create_decimal_from_float',
1277+
lambda: test_method('context.create_decimal_from_float',
1278+
testspecs, test_from_float))
1279+
1280+
if args.multicore:
1281+
error = Event()
1282+
write_lock = Lock()
12291283

1230-
test_method('context.create_decimal_from_float', testspecs, test_from_float)
1284+
def write_output(out, returncode):
1285+
if returncode != 0:
1286+
error.set()
1287+
1288+
with write_lock:
1289+
sys.stdout.buffer.write(out + b"\n")
1290+
sys.stdout.buffer.flush()
1291+
1292+
def tfunc():
1293+
while not error.is_set():
1294+
try:
1295+
test = q.get(block=False, timeout=-1)
1296+
except Empty:
1297+
return
12311298

1299+
cmd = [sys.executable, "deccheck.py", "--%s" % args.time, "--single", test]
1300+
p = subprocess.Popen(cmd, stdout=PIPE, stderr=STDOUT)
1301+
out, _ = p.communicate()
1302+
write_output(out, p.returncode)
12321303

1233-
sys.exit(EXIT_STATUS)
1304+
N = os.cpu_count()
1305+
t = N * [None]
1306+
1307+
for i in range(N):
1308+
t[i] = Thread(target=tfunc)
1309+
t[i].start()
1310+
1311+
for i in range(N):
1312+
t[i].join()
1313+
1314+
sys.exit(1 if error.is_set() else 0)
1315+
1316+
elif args.single:
1317+
if not FOUND_METHOD:
1318+
log("\nerror: cannot find method \"%s\"" % args.single)
1319+
EXIT_STATUS = 1
1320+
sys.exit(EXIT_STATUS)
1321+
else:
1322+
sys.exit(EXIT_STATUS)

0 commit comments

Comments
 (0)