Coverage for vibefpmgui/test_runner.py: 86%

39 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-11-01 20:51 +0000

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 39 ↛ 40line 39 didn't jump to line 40 because the condition on line 39 was never true

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: 50 ↛ 51line 50 didn't jump to line 51 because the condition on line 50 was never true

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__": 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true

74 sys.exit(main(sys.argv[1:]))