vibefpmgui.test_runner
Runner wrapper passed to fpm test --runner.
fpm calls <runner> <test executable> [args...] for each test, one after
another. This script prints a marker before and after running the test so
the GUI can tell where each test starts and ends and what it returned::
@@vibefpm:start:check_fast@@
...test output...
@@vibefpm:end:check_fast:0:0.012@@
With --collect first, the test isn't run: its command line is printed
instead (@@vibefpm:exe:check_fast@@["/abs/path/check_fast", "arg"]), for
the GUI to run the tests itself, in parallel (see
vibefpmgui.parallel_tests).
A runner the user gave fpm themselves (e.g. mpirun -np 4) is passed in
the VIBEFPM_INNER_RUNNER environment variable and still wraps the test.
Only the standard library is used, so this runs with python -I.
1"""Runner wrapper passed to ``fpm test --runner``. 2 3fpm calls ``<runner> <test executable> [args...]`` for each test, one after 4another. This script prints a marker before and after running the test so 5the GUI can tell where each test starts and ends and what it returned:: 6 7 @@vibefpm:start:check_fast@@ 8 ...test output... 9 @@vibefpm:end:check_fast:0:0.012@@ 10 11With ``--collect`` first, the test isn't run: its command line is printed 12instead (``@@vibefpm:exe:check_fast@@["/abs/path/check_fast", "arg"]``), for 13the GUI to run the tests itself, in parallel (see 14`vibefpmgui.parallel_tests`). 15 16A runner the user gave fpm themselves (e.g. ``mpirun -np 4``) is passed in 17the ``VIBEFPM_INNER_RUNNER`` environment variable and still wraps the test. 18 19Only the standard library is used, so this runs with ``python -I``. 20""" 21 22import json 23import os 24import shlex 25import subprocess 26import sys 27import time 28 29MARKER = "@@vibefpm:{}@@" 30 31 32def executable_name(executable: str) -> str: 33 name = os.path.basename(executable) 34 return name[:-4] if name.lower().endswith(".exe") else name 35 36 37def split_runner(runner: str) -> list[str]: 38 """A runner command (e.g. "mpirun -np 4") as a list.""" 39 if os.name == "nt": # backslashes are path separators there, not escapes 40 parts = shlex.split(runner, posix=False) 41 return [p[1:-1] if len(p) >= 2 and p[0] == p[-1] and p[0] in "\"'" else p for p in parts] 42 return shlex.split(runner) 43 44 45def main(argv: list[str]) -> int: 46 if not argv: 47 print("usage: test_runner.py EXECUTABLE [ARGS...]", file=sys.stderr) 48 return 2 49 if argv[0] == "--collect": 50 if len(argv) < 2: 51 return 2 52 name = executable_name(argv[1]) 53 print(MARKER.format(f"exe:{name}") + json.dumps([os.path.abspath(argv[1]), *argv[2:]]), flush=True) 54 return 0 55 name = executable_name(argv[0]) 56 command = split_runner(os.environ.get("VIBEFPM_INNER_RUNNER", "")) + argv 57 58 # The test writes straight to our stdout, so flush our own text first. 59 # The leading newline keeps the marker off a line the previous test left 60 # unfinished. 61 print("\n" + MARKER.format(f"start:{name}"), flush=True) 62 started = time.monotonic() 63 try: 64 code = subprocess.call(command) 65 except OSError as exc: 66 print(f"could not run {command[0]}: {exc}", flush=True) 67 code = 127 68 seconds = time.monotonic() - started 69 print("\n" + MARKER.format(f"end:{name}:{code}:{seconds:.3f}"), flush=True) 70 return code 71 72 73if __name__ == "__main__": 74 sys.exit(main(sys.argv[1:]))
MARKER =
'@@vibefpm:{}@@'
def
executable_name(executable: str) -> str:
def
split_runner(runner: str) -> list[str]:
38def split_runner(runner: str) -> list[str]: 39 """A runner command (e.g. "mpirun -np 4") as a list.""" 40 if os.name == "nt": # backslashes are path separators there, not escapes 41 parts = shlex.split(runner, posix=False) 42 return [p[1:-1] if len(p) >= 2 and p[0] == p[-1] and p[0] in "\"'" else p for p in parts] 43 return shlex.split(runner)
A runner command (e.g. "mpirun -np 4") as a list.
def
main(argv: list[str]) -> int:
46def main(argv: list[str]) -> int: 47 if not argv: 48 print("usage: test_runner.py EXECUTABLE [ARGS...]", file=sys.stderr) 49 return 2 50 if argv[0] == "--collect": 51 if len(argv) < 2: 52 return 2 53 name = executable_name(argv[1]) 54 print(MARKER.format(f"exe:{name}") + json.dumps([os.path.abspath(argv[1]), *argv[2:]]), flush=True) 55 return 0 56 name = executable_name(argv[0]) 57 command = split_runner(os.environ.get("VIBEFPM_INNER_RUNNER", "")) + argv 58 59 # The test writes straight to our stdout, so flush our own text first. 60 # The leading newline keeps the marker off a line the previous test left 61 # unfinished. 62 print("\n" + MARKER.format(f"start:{name}"), flush=True) 63 started = time.monotonic() 64 try: 65 code = subprocess.call(command) 66 except OSError as exc: 67 print(f"could not run {command[0]}: {exc}", flush=True) 68 code = 127 69 seconds = time.monotonic() - started 70 print("\n" + MARKER.format(f"end:{name}:{code}:{seconds:.3f}"), flush=True) 71 return code