diff options
Diffstat (limited to 'runtests.py')
-rwxr-xr-x | runtests.py | 159 |
1 files changed, 146 insertions, 13 deletions
diff --git a/runtests.py b/runtests.py index 17620478d..bad93f53a 100755 --- a/runtests.py +++ b/runtests.py @@ -52,7 +52,7 @@ else: import sys -import os +import os, glob # In case we are run from the source directory, we don't want to import the # project from there: @@ -108,6 +108,8 @@ def main(argv): help="Start IPython shell with PYTHONPATH set") parser.add_argument("--shell", action="store_true", help="Start Unix shell with PYTHONPATH set") + parser.add_argument("--mypy", action="store_true", + help="Run mypy on files with NumPy on the MYPYPATH") parser.add_argument("--debug", "-g", action="store_true", help="Debug build") parser.add_argument("--parallel", "-j", type=int, default=0, @@ -120,6 +122,9 @@ def main(argv): help="Specify a list of dispatched CPU optimizations"), parser.add_argument("--disable-optimization", action="store_true", help="Disable CPU optimized code(dispatch,simd,fast...)"), + parser.add_argument("--simd-test", default=None, + help="Specify a list of CPU optimizations to be " + "tested against NumPy SIMD interface"), parser.add_argument("--show-build-log", action="store_true", help="Show build output rather than using a log file") parser.add_argument("--bench", action="store_true", @@ -131,7 +136,7 @@ def main(argv): "COMMIT. Note that you need to commit your " "changes first!")) parser.add_argument("args", metavar="ARGS", default=[], nargs=REMAINDER, - help="Arguments to pass to Nose, asv, Python or shell") + help="Arguments to pass to pytest, asv, mypy, Python or shell") args = parser.parse_args(argv) if args.durations < 0: @@ -202,7 +207,7 @@ def main(argv): import warnings; warnings.filterwarnings("always") import IPython import numpy as np - IPython.embed(user_ns={"np": np}) + IPython.embed(colors='neutral', user_ns={"np": np}) sys.exit(0) if args.shell: @@ -211,6 +216,36 @@ def main(argv): subprocess.call([shell] + extra_argv) sys.exit(0) + if args.mypy: + try: + import mypy.api + except ImportError: + raise RuntimeError( + "Mypy not found. Please install it by running " + "pip install -r test_requirements.txt from the repo root" + ) + + os.environ['MYPYPATH'] = site_dir + # By default mypy won't color the output since it isn't being + # invoked from a tty. + os.environ['MYPY_FORCE_COLOR'] = '1' + + config = os.path.join( + site_dir, + "numpy", + "typing", + "tests", + "data", + "mypy.ini", + ) + + report, errors, status = mypy.api.run( + ['--config-file', config] + args.args + ) + print(report, end='') + print(errors, end='', file=sys.stderr) + sys.exit(status) + if args.coverage: dst_dir = os.path.join(ROOT_DIR, 'build', 'coverage') fn = os.path.join(dst_dir, 'coverage_html.js') @@ -278,8 +313,16 @@ def main(argv): out = subprocess.check_output(['git', 'rev-parse', commit_a]) commit_a = out.strip().decode('ascii') + # generate config file with the required build options + asv_cfpath = [ + '--config', asv_compare_config( + os.path.join(ROOT_DIR, 'benchmarks'), args, + # to clear the cache if the user changed build options + (commit_a, commit_b) + ) + ] cmd = ['asv', 'continuous', '-e', '-f', '1.05', - commit_a, commit_b] + bench_args + commit_a, commit_b] + asv_cfpath + bench_args ret = subprocess.call(cmd, cwd=os.path.join(ROOT_DIR, 'benchmarks')) sys.exit(ret) @@ -329,7 +372,6 @@ def main(argv): else: sys.exit(1) - def build_project(args): """ Build a dev version of the project. @@ -341,10 +383,7 @@ def build_project(args): """ - # from setuptools v49.2.0, setuptools warns if distutils is imported first, - # so pre-emptively import setuptools - import setuptools - import distutils.sysconfig + import sysconfig root_ok = [os.path.exists(os.path.join(ROOT_DIR, fn)) for fn in PROJECT_ROOT_FILES] @@ -360,7 +399,7 @@ def build_project(args): # Always use ccache, if installed env['PATH'] = os.pathsep.join(EXTRA_PATH + env.get('PATH', '').split(os.pathsep)) - cvars = distutils.sysconfig.get_config_vars() + cvars = sysconfig.get_config_vars() compiler = env.get('CC') or cvars.get('CC', '') if 'gcc' in compiler: # Check that this isn't clang masquerading as gcc. @@ -393,8 +432,6 @@ def build_project(args): cmd += ["build"] if args.parallel > 1: cmd += ["-j", str(args.parallel)] - if args.debug_info: - cmd += ["build_src", "--verbose-cfg"] if args.warn_error: cmd += ["--warn-error"] if args.cpu_baseline: @@ -403,6 +440,10 @@ def build_project(args): cmd += ["--cpu-dispatch", args.cpu_dispatch] if args.disable_optimization: cmd += ["--disable-optimization"] + if args.simd_test is not None: + cmd += ["--simd-test", args.simd_test] + if args.debug_info: + cmd += ["build_src", "--verbose-cfg"] # Install; avoid producing eggs so numpy can be imported from dst_dir. cmd += ['install', '--prefix=' + dst_dir, '--single-version-externally-managed', @@ -417,7 +458,7 @@ def build_project(args): os.makedirs(site_dir) if not os.path.exists(site_dir_noarch): os.makedirs(site_dir_noarch) - env['PYTHONPATH'] = site_dir + ':' + site_dir_noarch + env['PYTHONPATH'] = site_dir + os.pathsep + site_dir_noarch log_filename = os.path.join(ROOT_DIR, 'build.log') @@ -462,6 +503,98 @@ def build_project(args): return site_dir, site_dir_noarch +def asv_compare_config(bench_path, args, h_commits): + """ + Fill the required build options through custom variable + 'numpy_build_options' and return the generated config path. + """ + conf_path = os.path.join(bench_path, "asv_compare.conf.json.tpl") + nconf_path = os.path.join(bench_path, "_asv_compare.conf.json") + + # add custom build + build = [] + if args.parallel > 1: + build += ["-j", str(args.parallel)] + if args.cpu_baseline: + build += ["--cpu-baseline", args.cpu_baseline] + if args.cpu_dispatch: + build += ["--cpu-dispatch", args.cpu_dispatch] + if args.disable_optimization: + build += ["--disable-optimization"] + + is_cached = asv_substitute_config(conf_path, nconf_path, + numpy_build_options = ' '.join([f'\\"{v}\\"' for v in build]), + ) + if not is_cached: + asv_clear_cache(bench_path, h_commits) + return nconf_path + +def asv_clear_cache(bench_path, h_commits, env_dir="env"): + """ + Force ASV to clear the cache according to specified commit hashes. + """ + # FIXME: only clear the cache from the current environment dir + asv_build_pattern = os.path.join(bench_path, env_dir, "*", "asv-build-cache") + for asv_build_cache in glob.glob(asv_build_pattern, recursive=True): + for c in h_commits: + try: shutil.rmtree(os.path.join(asv_build_cache, c)) + except OSError: pass + +def asv_substitute_config(in_config, out_config, **custom_vars): + """ + A workaround to allow substituting custom tokens within + ASV configuration file since there's no official way to add custom + variables(e.g. env vars). + + Parameters + ---------- + in_config : str + The path of ASV configuration file, e.g. '/path/to/asv.conf.json' + out_config : str + The path of generated configuration file, + e.g. '/path/to/asv_substituted.conf.json'. + + The other keyword arguments represent the custom variables. + + Returns + ------- + True(is cached) if 'out_config' is already generated with + the same '**custom_vars' and updated with latest 'in_config', + False otherwise. + + Examples + -------- + See asv_compare_config(). + """ + assert in_config != out_config + assert len(custom_vars) > 0 + + def sdbm_hash(*factors): + chash = 0 + for f in factors: + for char in str(f): + chash = ord(char) + (chash << 6) + (chash << 16) - chash + chash &= 0xFFFFFFFF + return chash + + vars_hash = sdbm_hash(custom_vars, os.path.getmtime(in_config)) + try: + with open(out_config, "r") as wfd: + hash_line = wfd.readline().split('hash:') + if len(hash_line) > 1 and int(hash_line[1]) == vars_hash: + return True + except IOError: + pass + + custom_vars = {f'{{{k}}}':v for k, v in custom_vars.items()} + with open(in_config, "r") as rfd, open(out_config, "w") as wfd: + wfd.write(f"// hash:{vars_hash}\n") + wfd.write("// This file is automatically generated by runtests.py\n") + for line in rfd: + for key, val in custom_vars.items(): + line = line.replace(key, val) + wfd.write(line) + return False # # GCOV support |