Roughly 100 tests fail with this; all the tests need to be audited for assumptions that 16 is the widest width possible…
394 lines
14 KiB
Python
Executable File
394 lines
14 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
# test-running driver for ispc
|
|
|
|
from optparse import OptionParser
|
|
import multiprocessing
|
|
from ctypes import c_int
|
|
import os
|
|
import sys
|
|
import glob
|
|
import re
|
|
import signal
|
|
import random
|
|
import string
|
|
import subprocess
|
|
import shlex
|
|
import platform
|
|
import tempfile
|
|
|
|
# disable fancy error/warning printing with ANSI colors, so grepping for error
|
|
# messages doesn't get confused
|
|
os.environ["TERM"] = "dumb"
|
|
|
|
# This script is affected by http://bugs.python.org/issue5261 on OSX 10.5 Leopard
|
|
# git history has a workaround for that issue.
|
|
|
|
is_windows = (platform.system() == 'Windows' or
|
|
'CYGWIN_NT' in platform.system())
|
|
|
|
parser = OptionParser()
|
|
parser.add_option("-r", "--random-shuffle", dest="random", help="Randomly order tests",
|
|
default=False, action="store_true")
|
|
parser.add_option("-g", "--generics-include", dest="include_file", help="Filename for header implementing functions for generics",
|
|
default=None)
|
|
parser.add_option("-f", "--ispc-flags", dest="ispc_flags", help="Additional flags for ispc (-g, -O1, ...)",
|
|
default="")
|
|
parser.add_option('-t', '--target', dest='target',
|
|
help='Set compilation target (sse2, sse2-x2, sse4, sse4-x2, avx, avx-x2, generic-4, generic-8, generic-16, generic-32)',
|
|
default="sse4")
|
|
parser.add_option('-a', '--arch', dest='arch',
|
|
help='Set architecture (x86, x86-64)',
|
|
default="x86-64")
|
|
parser.add_option("-c", "--compiler", dest="compiler_exe", help="Compiler binary to use to run tests",
|
|
default=None)
|
|
parser.add_option('-o', '--no-opt', dest='no_opt', help='Disable optimization',
|
|
default=False, action="store_true")
|
|
parser.add_option('-j', '--jobs', dest='num_jobs', help='Maximum number of jobs to run in parallel',
|
|
default="1024", type="int")
|
|
parser.add_option('-v', '--verbose', dest='verbose', help='Enable verbose output',
|
|
default=False, action="store_true")
|
|
parser.add_option('--wrap-exe', dest='wrapexe',
|
|
help='Executable to wrap test runs with (e.g. "valgrind")',
|
|
default="")
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if not is_windows:
|
|
ispc_exe = "./ispc"
|
|
else:
|
|
ispc_exe = "../Release/ispc.exe"
|
|
|
|
ispc_exe += " " + options.ispc_flags
|
|
|
|
print ispc_exe
|
|
|
|
is_generic_target = (options.target.find("generic-") != -1 and
|
|
options.target != "generic-1")
|
|
if is_generic_target and options.include_file == None:
|
|
if options.target == "generic-4":
|
|
sys.stderr.write("No generics #include specified; using examples/intrinsics/sse4.h\n")
|
|
options.include_file = "examples/intrinsics/sse4.h"
|
|
elif options.target == "generic-8":
|
|
sys.stderr.write("No generics #include specified and no default available for \"generic-8\" target.\n")
|
|
sys.exit(1)
|
|
elif options.target == "generic-16":
|
|
sys.stderr.write("No generics #include specified; using examples/intrinsics/generic-16.h\n")
|
|
options.include_file = "examples/intrinsics/generic-16.h"
|
|
elif options.target == "generic-32":
|
|
sys.stderr.write("No generics #include specified; using examples/intrinsics/generic-32.h\n")
|
|
options.include_file = "examples/intrinsics/generic-32.h"
|
|
elif options.target == "generic-64":
|
|
sys.stderr.write("No generics #include specified; using examples/intrinsics/generic-64.h\n")
|
|
options.include_file = "examples/intrinsics/generic-64.h"
|
|
|
|
if options.compiler_exe == None:
|
|
if is_windows:
|
|
options.compiler_exe = "cl"
|
|
else:
|
|
options.compiler_exe = "g++"
|
|
|
|
def fix_windows_paths(files):
|
|
ret = [ ]
|
|
for fn in files:
|
|
ret += [ string.replace(fn, '\\', '/') ]
|
|
return ret
|
|
|
|
|
|
# if no specific test files are specified, run all of the tests in tests/,
|
|
# failing_tests/, and tests_errors/
|
|
if len(args) == 0:
|
|
files = glob.glob("tests/*ispc") + glob.glob("failing_tests/*ispc") + \
|
|
glob.glob("tests_errors/*ispc")
|
|
files = fix_windows_paths(files)
|
|
else:
|
|
if is_windows:
|
|
argfiles = [ ]
|
|
for f in args:
|
|
# we have to glob ourselves if this is being run under a DOS
|
|
# shell..
|
|
argfiles += glob.glob(f)
|
|
else:
|
|
argfiles = args
|
|
|
|
files = [ ]
|
|
for f in argfiles:
|
|
if os.path.splitext(string.lower(f))[1] != ".ispc":
|
|
sys.stdout.write("Ignoring file %s, which doesn't have an .ispc extension.\n" % f)
|
|
else:
|
|
files += [ f ]
|
|
|
|
# randomly shuffle the tests if asked to do so
|
|
if (options.random):
|
|
random.seed()
|
|
random.shuffle(files)
|
|
|
|
# counter
|
|
total_tests = 0
|
|
|
|
finished_tests_counter = multiprocessing.Value(c_int)
|
|
finished_tests_counter_lock = multiprocessing.Lock()
|
|
|
|
# utility routine to print an update on the number of tests that have been
|
|
# finished. Should be called with the lock held..
|
|
def update_progress(fn):
|
|
global total_tests
|
|
finished_tests_counter.value = finished_tests_counter.value + 1
|
|
progress_str = " Done %d / %d [%s]" % (finished_tests_counter.value, total_tests, fn)
|
|
# spaces to clear out detrius from previous printing...
|
|
for x in range(30):
|
|
progress_str += ' '
|
|
progress_str += '\r'
|
|
sys.stdout.write(progress_str)
|
|
sys.stdout.flush()
|
|
|
|
def run_command(cmd):
|
|
if options.verbose:
|
|
sys.stdout.write("Running: %s\n" % cmd)
|
|
sp = subprocess.Popen(shlex.split(cmd), stdin=None,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
out = sp.communicate()
|
|
output = ""
|
|
output += out[0].decode("utf-8")
|
|
output += out[1].decode("utf-8")
|
|
|
|
return (sp.returncode, output)
|
|
|
|
# run the commands in cmd_list
|
|
def run_cmds(compile_cmds, run_cmd, filename, expect_failure):
|
|
for cmd in compile_cmds:
|
|
(return_code, output) = run_command(cmd)
|
|
compile_failed = (return_code != 0)
|
|
if compile_failed:
|
|
sys.stdout.write("Compilation of test %s failed \n" % filename)
|
|
if output != "":
|
|
sys.stdout.write("%s" % output)
|
|
return (1, 0)
|
|
|
|
(return_code, output) = run_command(run_cmd)
|
|
run_failed = (return_code != 0)
|
|
|
|
surprise = ((expect_failure and not run_failed) or
|
|
(not expect_failure and run_failed))
|
|
if surprise == True:
|
|
sys.stderr.write("Test %s %s (return code %d) \n" % \
|
|
(filename, "unexpectedly passed" if expect_failure else "failed",
|
|
return_code))
|
|
if output != "":
|
|
sys.stdout.write("%s\n" % output)
|
|
if surprise == True:
|
|
return (0, 1)
|
|
else:
|
|
return (0, 0)
|
|
|
|
|
|
def run_test(filename):
|
|
global is_windows
|
|
if is_windows:
|
|
input_prefix = "../"
|
|
else:
|
|
input_prefix = ""
|
|
|
|
# is this a test to make sure an error is issued?
|
|
want_error = (filename.find("tests_errors") != -1)
|
|
if want_error == True:
|
|
ispc_cmd = ispc_exe + " --werror --nowrap %s --arch=%s --target=%s" % \
|
|
(input_prefix + filename, options.arch, options.target)
|
|
(return_code, output) = run_command(ispc_cmd)
|
|
got_error = (return_code != 0)
|
|
|
|
# figure out the error message we're expecting
|
|
file = open(input_prefix + filename, 'r')
|
|
firstline = file.readline()
|
|
firstline = firstline.replace("//", "")
|
|
firstline = firstline.lstrip()
|
|
firstline = firstline.rstrip()
|
|
file.close()
|
|
|
|
if (output.find(firstline) == -1):
|
|
sys.stderr.write("Didn't see expected error message %s from test %s.\nActual output:\n%s\n" % \
|
|
(firstline, filename, output))
|
|
return (1, 0)
|
|
elif got_error == False:
|
|
sys.stderr.write("Unexpectedly no errors issued from test %s\n" % filename)
|
|
return (1, 0)
|
|
else:
|
|
return (0, 0)
|
|
else:
|
|
# do we expect this test to fail?
|
|
should_fail = (filename.find("failing_") != -1)
|
|
|
|
# We need to figure out the signature of the test
|
|
# function that this test has.
|
|
sig2def = { "f_v(" : 0, "f_f(" : 1, "f_fu(" : 2, "f_fi(" : 3,
|
|
"f_du(" : 4, "f_duf(" : 5, "f_di(" : 6 }
|
|
file = open(input_prefix + filename, 'r')
|
|
match = -1
|
|
for line in file:
|
|
# look for lines with 'export'...
|
|
if line.find("export") == -1:
|
|
continue
|
|
# one of them should have a function with one of the
|
|
# declarations in sig2def
|
|
for pattern, ident in list(sig2def.items()):
|
|
if line.find(pattern) != -1:
|
|
match = ident
|
|
break
|
|
file.close()
|
|
if match == -1:
|
|
sys.stderr.write("Fatal error: unable to find function signature " + \
|
|
"in test %s\n" % filename)
|
|
return (1, 0)
|
|
else:
|
|
global is_generic_target
|
|
if is_generic_target:
|
|
obj_name = "%s.cpp" % filename
|
|
|
|
if is_windows:
|
|
if not is_generic_target:
|
|
obj_name = "%s%s.obj" % (input_prefix, filename)
|
|
exe_name = "%s%s.exe" % (input_prefix, filename)
|
|
|
|
cc_cmd = "%s /I. /I../winstuff /Zi /nologo /DTEST_SIG=%d %stest_static.cpp %s /Fe%s" % \
|
|
(options.compiler_exe, match, input_prefix, obj_name, exe_name)
|
|
if should_fail:
|
|
cc_cmd += " /DEXPECT_FAILURE"
|
|
else:
|
|
if not is_generic_target:
|
|
obj_name = "%s.o" % filename
|
|
exe_name = "%s.run" % filename
|
|
|
|
if options.arch == 'x86':
|
|
gcc_arch = '-m32'
|
|
else:
|
|
gcc_arch = '-m64'
|
|
cc_cmd = "%s -O2 -msse4.2 -I. %s test_static.cpp -DTEST_SIG=%d %s -o %s" % \
|
|
(options.compiler_exe, gcc_arch, match, obj_name, exe_name)
|
|
if platform.system() == 'Darwin':
|
|
cc_cmd += ' -Wl,-no_pie'
|
|
if should_fail:
|
|
cc_cmd += " -DEXPECT_FAILURE"
|
|
|
|
ispc_cmd = ispc_exe + " --woff %s -o %s --arch=%s --target=%s" % \
|
|
(input_prefix+filename, obj_name, options.arch, options.target)
|
|
if options.no_opt:
|
|
ispc_cmd += " -O0"
|
|
if is_generic_target:
|
|
ispc_cmd += " --emit-c++ --c++-include-file=%s" % options.include_file
|
|
|
|
# compile the ispc code, make the executable, and run it...
|
|
(compile_error, run_error) = run_cmds([ispc_cmd, cc_cmd],
|
|
options.wrapexe + " " + exe_name, \
|
|
filename, should_fail)
|
|
|
|
# clean up after running the test
|
|
try:
|
|
if not run_error:
|
|
os.unlink(exe_name)
|
|
if is_windows:
|
|
os.unlink("%s%s.pdb" % (input_prefix, filename))
|
|
os.unlink("%s%s.ilk" % (input_prefix, filename))
|
|
os.unlink(obj_name)
|
|
except:
|
|
None
|
|
|
|
return (compile_error, run_error)
|
|
|
|
# pull tests to run from the given queue and run them. Multiple copies of
|
|
# this function will be running in parallel across all of the CPU cores of
|
|
# the system.
|
|
def run_tasks_from_queue(queue, queue_ret):
|
|
if is_windows:
|
|
tmpdir = "tmp%d" % os.getpid()
|
|
os.mkdir(tmpdir)
|
|
os.chdir(tmpdir)
|
|
else:
|
|
olddir = ""
|
|
|
|
compile_error_files = [ ]
|
|
run_error_files = [ ]
|
|
while True:
|
|
filename = queue.get()
|
|
if (filename == 'STOP'):
|
|
queue_ret.put((compile_error_files, run_error_files))
|
|
if is_windows:
|
|
try:
|
|
os.remove("test_static.obj")
|
|
os.remove("/vc100.pdb")
|
|
os.chdir("..")
|
|
os.rmdir(tmpdir)
|
|
except:
|
|
None
|
|
|
|
sys.exit(0)
|
|
|
|
(compile_error, run_error) = run_test(filename)
|
|
if compile_error != 0:
|
|
compile_error_files += [ filename ]
|
|
if run_error != 0:
|
|
run_error_files += [ filename ]
|
|
|
|
with finished_tests_counter_lock:
|
|
update_progress(filename)
|
|
|
|
task_threads = []
|
|
|
|
def sigint(signum, frame):
|
|
for t in task_threads:
|
|
t.terminate()
|
|
sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
total_tests = len(files)
|
|
|
|
compile_error_files = [ ]
|
|
run_error_files = [ ]
|
|
|
|
nthreads = min(multiprocessing.cpu_count(), options.num_jobs)
|
|
sys.stdout.write("Running %d jobs in parallel. Running %d tests.\n" % (nthreads, total_tests))
|
|
|
|
# put each of the test filenames into a queue
|
|
q = multiprocessing.Queue()
|
|
for fn in files:
|
|
if is_windows:
|
|
fn = fn.replace("\\",'/')
|
|
q.put(fn)
|
|
for x in range(nthreads):
|
|
q.put('STOP')
|
|
qret = multiprocessing.Queue()
|
|
|
|
# need to catch sigint so that we can terminate all of the tasks if
|
|
# we're interrupted
|
|
signal.signal(signal.SIGINT, sigint)
|
|
|
|
# launch jobs to run tests
|
|
for x in range(nthreads):
|
|
t = multiprocessing.Process(target=run_tasks_from_queue, args=(q,qret))
|
|
task_threads.append(t)
|
|
t.start()
|
|
|
|
# wait for them to all finish and then return the number that failed
|
|
# (i.e. return 0 if all is ok)
|
|
for t in task_threads:
|
|
t.join()
|
|
sys.stdout.write("\n")
|
|
|
|
while not qret.empty():
|
|
(c, r) = qret.get()
|
|
compile_error_files += c
|
|
run_error_files += r
|
|
|
|
if len(compile_error_files) > 0:
|
|
compile_error_files.sort()
|
|
sys.stdout.write("%d / %d tests FAILED compilation:\n" % (len(compile_error_files), total_tests))
|
|
for f in compile_error_files:
|
|
sys.stdout.write("\t%s\n" % f)
|
|
if len(run_error_files) > 0:
|
|
run_error_files.sort()
|
|
sys.stdout.write("%d / %d tests FAILED execution:\n" % (len(run_error_files), total_tests))
|
|
for f in run_error_files:
|
|
sys.stdout.write("\t%s\n" % f)
|
|
|
|
sys.exit(len(compile_error_files) + len(run_error_files))
|