48def run_application(executable, seconds, shutdown_timeout):
50 with open(os.devnull,
"rb")
as devnull:
51 process = subprocess.Popen(
52 [str(executable)], cwd=executable.parent, stdin=devnull,
53 start_new_session=
True,
57 code = process.wait(timeout=seconds)
58 except subprocess.TimeoutExpired:
63 return "PASS",
"Exited normally (code 0)"
64 return "FAIL", f
"Exited unexpectedly (code {code})"
67 os.killpg(process.pid, signal.SIGTERM)
69 code = process.wait(timeout=shutdown_timeout)
70 except subprocess.TimeoutExpired:
71 os.killpg(process.pid, signal.SIGKILL)
73 return "FAIL",
"Did not stop after SIGTERM; SIGKILL required"
75 if code
in (-signal.SIGTERM, 128 + signal.SIGTERM):
76 return "PASS", f
"Stayed alive {seconds:g}s; terminated by test runner (code {code})"
78 return "PASS",
"Stayed alive; shutdown returned code 0"
79 return "FAIL", f
"Unexpected exit code during shutdown: {code}"
81 if process.poll()
is None:
82 os.killpg(process.pid, signal.SIGKILL)
87 parser = argparse.ArgumentParser(description=__doc__)
88 parser.add_argument(
"--release", action=
"store_true", help=
"Build Release rather than Debug")
89 parser.add_argument(
"--seconds", type=float, default=3.0, help=
"GUI observation time (default 3)")
90 parser.add_argument(
"--shutdown-timeout", type=float, default=3.0)
91 parser.add_argument(
"--skip-engine-build", action=
"store_true", help=
"Reuse existing engine build")
92 args = parser.parse_args()
93 if os.name !=
"posix" or not (
94 sys.platform ==
"linux" or sys.platform.startswith(
"freebsd")
96 parser.error(
"This smoke test supports Linux and FreeBSD only")
97 if args.seconds <= 0
or args.shutdown_timeout <= 0:
98 parser.error(
"Timeouts must be positive")
100 build_type =
"Release" if args.release
else "Debug"
101 build_flag =
"--release" if args.release
else "--debug"
103 print(f
"Rendering Engine root: {ROOT}")
104 print(f
"Build configuration: {build_type}")
105 print(f
"Applications found: {len(apps)}", flush=
True)
107 if not args.skip_engine_build:
108 run_build([
"sh", str(ROOT /
"build_engine.sh"),
"--engine-only", build_flag], ROOT)
112 print(f
"\n=== {app.name} ===", flush=
True)
114 run_build([
"sh", str(app /
"build_project.sh"), build_flag], app)
115 executable = ROOT /
"Build" /
"UserApplications" / app.name / build_type / app.name
116 if not executable.is_file():
117 raise RuntimeError(f
"Executable not found: {executable}")
118 if not os.access(executable, os.X_OK):
119 raise RuntimeError(f
"Executable not executable: {executable}")
120 status, reason = run_application(executable, args.seconds, args.shutdown_timeout)
121 except (OSError, RuntimeError)
as exc:
122 status, reason =
"FAIL", str(exc)
123 results.append((app.name, status, reason))
124 print(f
"{status}: {app.name}: {reason}", flush=
True)
126 print(
"\n=== Summary ===")
127 for name, status, reason
in results:
128 print(f
"{status:4} {name}: {reason}")
129 passed = sum(status ==
"PASS" for _, status, _
in results)
130 print(f
"Passed {passed}/{len(results)}")
131 return 0
if passed == len(results)
else 1