| | |
| | |
| | """ |
| | run_gui_tests.py |
| | |
| | List registered tests via `FreeCAD -t`, filter for GUI tests (names containing 'Gui'), and run each |
| | GUI test module using the specified FreeCAD executable. |
| | |
| | Usage: |
| | run_gui_tests.py [FREECAD_EXEC] |
| | |
| | If FREECAD_EXEC is omitted the script falls back to 'FreeCAD' on PATH. |
| | If FREECAD_EXEC is a directory containing bin/FreeCAD, that binary is used. |
| | If FREECAD_EXEC is an executable path, it is used directly. |
| | |
| | This script returns 0 if all GUI modules run successfully. Otherwise it returns the last non-zero |
| | exit code. |
| | """ |
| | from __future__ import annotations |
| | import sys |
| | import subprocess |
| | import os |
| | from pathlib import Path |
| |
|
| |
|
| | def find_executable(arg: str | None) -> str: |
| | """Return the FreeCAD executable path to use. |
| | |
| | If `arg` is None or empty, returns the plain name 'FreeCAD' which will be looked up on PATH. If |
| | `arg` is a directory and contains `bin/FreeCAD` that binary will be returned. If `arg` is a file |
| | path it is returned as-is. Otherwise the original argument is returned. |
| | |
| | Common use cases: use the FreeCAD binary from a build directory or an installed FreeCAD prefix. |
| | """ |
| | if not arg: |
| | return "FreeCAD" |
| | p = Path(arg) |
| | if p.is_dir(): |
| | candidate = p / "bin" / "FreeCAD" |
| | if candidate.exists(): |
| | return str(candidate) |
| | if p.is_file(): |
| | return str(p) |
| | |
| | return arg |
| |
|
| |
|
| | def validate_executable(path: str) -> tuple[bool, str]: |
| | """Return (ok, message). Checks if the executable exists or is likely on PATH. |
| | |
| | This is best effort: if a bare name is given (e.g. 'FreeCAD') we can't stat it here, so we |
| | accept it but warn. If the path points to a file, we check executability. Also warn if the name |
| | looks like the CLI-only 'FreeCADCmd'. |
| | """ |
| | p = Path(path) |
| | if p.is_file(): |
| | if not os.access(str(p), os.X_OK): |
| | return False, f"File exists but is not executable: {path}" |
| | if p.name.endswith("FreeCADCmd"): |
| | return ( |
| | True, |
| | ( |
| | "Warning: executable looks like 'FreeCADCmd' (CLI); GUI tests require the GUI " |
| | "binary 'FreeCAD'." |
| | ), |
| | ) |
| | return True, "" |
| | |
| | if p.name.endswith("FreeCADCmd"): |
| | return ( |
| | True, |
| | ( |
| | "Warning: executable name looks like 'FreeCADCmd' (CLI); GUI tests require the GUI " |
| | "binary 'FreeCAD'." |
| | ), |
| | ) |
| | return True, "" |
| |
|
| |
|
| | def run_and_capture(cmd: list[str]) -> tuple[int, str]: |
| | """Run `cmd` and return (returncode, combined stdout+stderr string). |
| | |
| | If the executable is not found a return code of 127 is returned and a small error string is |
| | provided as output to aid diagnosis. |
| | """ |
| | try: |
| | proc = subprocess.run( |
| | cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, check=False |
| | ) |
| | return proc.returncode, proc.stdout |
| | except FileNotFoundError: |
| | return 127, f"Executable not found: {cmd[0]}\n" |
| |
|
| |
|
| | def parse_registered_tests(output: str) -> list[str]: |
| | """Parse output from `FreeCAD -t` and return a list of registered test unit names. |
| | |
| | The function looks for the section starting with the literal 'Registered test units:' and |
| | then collects non-empty, stripped lines from that point onwards as test names. |
| | """ |
| | lines = output.splitlines() |
| | tests: list[str] = [] |
| | started = False |
| | for ln in lines: |
| | if not started: |
| | if "Registered test units:" in ln: |
| | started = True |
| | continue |
| | s = ln.strip() |
| | if not s: |
| | |
| | continue |
| | tests.append(s) |
| | return tests |
| |
|
| |
|
| | def main(argv: list[str]) -> int: |
| | """Entry point: run GUI test modules registered in the FreeCAD executable. |
| | |
| | Returns the last non-zero exit code from any GUI test module, or 0 on success. |
| | """ |
| | exec_arg = argv[1] if len(argv) > 1 else None |
| | freecad_exec = find_executable(exec_arg) |
| |
|
| | print(f"Using FreeCAD executable: {freecad_exec}") |
| |
|
| | ok, msg = validate_executable(freecad_exec) |
| | if msg: |
| | print(msg, file=sys.stderr) |
| | if not ok: |
| | print(f"Aborting: invalid FreeCAD executable: {freecad_exec}", file=sys.stderr) |
| | return 3 |
| |
|
| | code, out = run_and_capture([freecad_exec, "-t"]) |
| | if code != 0: |
| | print( |
| | f"Warning: listing tests returned exit code {code}; attempting to parse output anyway", |
| | file=sys.stderr, |
| | ) |
| |
|
| | tests = parse_registered_tests(out) |
| | if not tests: |
| | print("No registered tests found; exiting with success.") |
| | return 0 |
| |
|
| | gui_tests = [t for t in tests if "Gui" in t] |
| | if not gui_tests: |
| | print("No GUI tests found in registered tests; nothing to run.") |
| | return 0 |
| |
|
| | print("Found GUI test modules:") |
| | for t in gui_tests: |
| | print(" ", t) |
| |
|
| | last_rc = 0 |
| | for mod in gui_tests: |
| | print(f"\nRunning GUI tests for module: {mod}") |
| | rc, out = run_and_capture([freecad_exec, "-t", mod]) |
| | print(out) |
| | if rc != 0: |
| | print(f"Module {mod} exited with code {rc}", file=sys.stderr) |
| | last_rc = rc |
| |
|
| | return last_rc |
| |
|
| |
|
| | if __name__ == "__main__": |
| | sys.exit(main(sys.argv)) |
| |
|