4"""Offline behavioral tests for the root-owned HIL helper."""
6from __future__
import annotations
16from collections.abc
import Callable, Iterator
17from contextlib
import contextmanager
18from pathlib
import Path
19from types
import ModuleType
21REPO_ROOT = Path(__file__).resolve().parents[2]
22HELPER = REPO_ROOT /
"infra/ansible/roles/dev_box/files/ra8-hil-privileged.py"
23EXPECTED_NETWORK_MUTATIONS = 3
26def _member(module: ModuleType, name: str) -> Callable[..., object]:
27 """Return one deliberately private helper seam by its exact name."""
28 return vars(module)[name]
31def _load_helper() -> ModuleType:
32 """Load the checkout helper without invoking its CLI."""
33 spec = importlib.util.spec_from_file_location(
"ra8_hil_privileged", HELPER)
34 if spec
is None or spec.loader
is None:
35 message =
"cannot load HIL privileged helper"
36 raise RuntimeError(message)
37 module = importlib.util.module_from_spec(spec)
38 sys.modules[spec.name] = module
39 spec.loader.exec_module(module)
43def _policy(module: ModuleType) -> dict[str, object]:
44 """Return one internally authenticated physical-interface policy."""
45 policy: dict[str, object] = {
46 "board_iface":
"eth0",
47 "mac":
"02:00:00:00:00:09",
49 "sysfs_device":
"/sys/devices/platform/bench-ethernet",
52 policy[
"declaration_sha256"] = module.hashlib.sha256(
53 _member(module,
"_canonical_policy")(policy)
58def _rejects(module: ModuleType, callable_obj: object, *args: object, **kwargs: object) -> bool:
59 """Return whether one pure request is rejected by policy."""
61 callable_obj(*args, **kwargs)
62 except module.PolicyError:
68def _patched(module: ModuleType, **replacements: object) -> Iterator[
None]:
69 """Temporarily replace helper boundaries for an offline fake backend."""
70 originals = {name: getattr(module, name)
for name
in replacements}
72 for name, value
in replacements.items():
73 setattr(module, name, value)
76 for name, value
in originals.items():
77 setattr(module, name, value)
81 """In-memory journal/device backend with one injectable failure point."""
83 def __init__(self, module: ModuleType, fail_at: str =
"") ->
None:
85 self.fail_at = fail_at
86 self.events: list[str] = []
89 def _event(self, value: str) ->
None:
90 self.events.append(value)
91 if self.fail_at == value:
92 message = f
"injected {value}"
93 raise self.module.PolicyError(message)
95 def save(self, _path: Path, _state: dict[str, object]) ->
None:
99 def apply(self, action: object) ->
None:
100 value =
"off" if action.args[-1]
in {
"off",
"0"}
else "on"
103 def pause(self) -> None:
106 def clear(self, _path: Path) ->
None:
111class _ProcessCycleOps:
112 """File-backed backend used by one real signalled child process."""
123 self.journal = journal
125 self.fail_at = fail_at
126 self.inflight_off = inflight_off
128 def _event(self, value: str) ->
None:
129 with self.events.open(
"a", encoding=
"ascii")
as stream:
130 stream.write(f
"{value}\n")
132 os.fsync(stream.fileno())
133 if self.fail_at == value:
134 message = f
"injected {value}"
135 raise RuntimeError(message)
137 def save(self, _path: Path, _state: dict[str, object]) ->
None:
138 self.journal.write_text(
"pending\n", encoding=
"ascii")
141 def apply(self, action: object) ->
None:
142 value =
"off" if action.args[-1]
in {
"off",
"0"}
else "on"
143 if value ==
"off" and self.inflight_off:
146 if self.fail_at == value:
148 self.state.write_text(value, encoding=
"ascii")
151 def _delayed_off(self) -> None:
152 """Model a spawned off child that must not outlive restoration."""
156 self.state.write_text(
"off", encoding=
"ascii")
157 self._event(
"off-child-completed")
159 self._event(
"off-child-spawned")
160 _, status = os.waitpid(pid, 0)
161 if os.waitstatus_to_exitcode(status) != 0:
162 message =
"in-flight off child failed"
163 raise RuntimeError(message)
165 def pause(self) -> None:
169 def clear(self, _path: Path) ->
None:
171 self.journal.unlink()
174def _route_shape_checks(module: ModuleType) -> list[tuple[str, bool]]:
175 """Accept both iproute2 JSON spellings for one exact /32 route."""
177 def route_present(destination: str) -> bool:
178 payload = [{
"dst": destination,
"dev":
"eth0",
"prefsrc":
"192.168.1.1"}]
179 with _patched(module, _json_command=
lambda _argv: payload):
180 return bool(_member(module,
"_route_present")(
"192.168.1.42",
"eth0"))
183 (
"iproute host-form /32 route is recognized", route_present(
"192.168.1.42")),
185 "iproute explicit /32 route is recognized",
186 route_present(
"192.168.1.42/32"),
191def _request_checks(module: ModuleType) -> list[tuple[str, bool]]:
192 """Return exact topology and argument-injection tests."""
193 power_command = _member(module,
"_usb_power_command")
194 validate_action = _member(module,
"_validate_action")
195 validate_board_ip = _member(module,
"_validate_board_ip")
196 validate_port = _member(module,
"_validate_port")
197 expected = [
"/usr/sbin/uhubctl",
"-S",
"-l",
"2-1.3",
"-p",
"1",
"-a",
"off"]
201 "nominal port argv is exact",
202 power_command(
"usb-port-power", [
"1",
"off"]) == expected,
205 "legacy port 2 restore argv remains exact",
206 power_command(
"usb-port-power", [
"2",
"on"], restoring=
True)
207 == [
"/usr/sbin/uhubctl",
"-S",
"-l",
"2-1.3",
"-p",
"2",
"-a",
"on"],
210 "root power topology is fixed",
211 power_command(
"usb-root-power", [
"on"])[3] ==
"2-1",
214 "nominal board address accepted",
215 validate_board_ip(
"192.168.1.42") ==
"192.168.1.42",
218 "J-Link control port accepted",
219 validate_port(
"3") ==
"3",
223 (f
"port {value!r} rejected", _rejects(module, validate_port, value))
224 for value
in (
"1;id",
"1 2",
"../1",
"-1",
"2")
227 (f
"action {value!r} rejected", _rejects(module, validate_action, value))
228 for value
in (
"off;id",
"on\n",
"--help",
"cycle")
231 (f
"address {value!r} rejected", _rejects(module, validate_board_ip, value))
240 checks.extend(_route_shape_checks(module))
244def _interface_checks(module: ModuleType) -> list[tuple[str, bool]]:
245 """Prove only the fleet-declared permanent physical identity is accepted."""
246 policy = _policy(module)
247 validate = _member(module,
"_validate_iface_facts")
248 strict_policy = _member(module,
"_strict_policy")
249 facts_type = _member(module,
"_LiveIfaceFacts")
251 def facts(**changes: object) -> object:
254 "uplinks": {
"wlan0"},
255 "ipv4_addresses": set(),
256 "mac": policy[
"mac"],
257 "sysfs_device": policy[
"sysfs_device"],
258 "phc_index": policy[
"phc_index"],
261 return facts_type(**values)
264 *_interface_address_checks(module, policy, validate, facts),
266 "second valid-shaped interface cannot be selected",
267 _rejects(module, strict_policy, {**policy,
"board_iface":
"eth1"}),
270 "dotted virtual interface cannot be selected",
271 _rejects(module, strict_policy, {**policy,
"board_iface":
"eth0.42"}),
274 "permanent MAC drift rejected",
275 _rejects(module, validate, policy, facts(mac=
"02:00:00:00:00:10")),
278 "canonical sysfs identity drift rejected",
283 facts(sysfs_device=
"/sys/devices/virtual/net/eth0"),
287 "PHC identity drift rejected",
288 _rejects(module, validate, policy, facts(phc_index=1)),
293def _interface_address_checks(
295 policy: dict[str, object],
296 validate: Callable[..., object],
297 facts: Callable[..., object],
298) -> list[tuple[str, bool]]:
299 """Prove uplink and foreign-address facts fail closed."""
301 (
"exact permanent interface accepted", validate(policy, facts()) ==
"eth0"),
303 "exact helper-owned address is allowed during recovery",
306 facts(ipv4_addresses={
"192.168.1.1"}),
312 "foreign address beside helper ownership is rejected",
317 facts(ipv4_addresses={
"192.168.1.1",
"192.168.1.99"}),
322 "IPv4 default uplink rejected",
323 _rejects(module, validate, policy, facts(uplinks={
"eth0"})),
326 "IPv6 default uplink rejected by same all-table census",
327 _rejects(module, validate, policy, facts(uplinks={
"eth0"})),
332def _cycle_case(module: ModuleType, fail_at: str) -> tuple[list[str], bool, bool]:
333 """Run one rootless cycle with an injected boundary failure."""
334 state, off = _member(module,
"_cycle_request")(
"usb-port-cycle", [
"1"])
335 ops = _FakeCycleOps(module, fail_at)
336 failed = _rejects(module, _member(module,
"_perform_cycle"), Path(
"unused"), state, off, ops)
337 return ops.events, ops.pending, failed
340def _interrupt_cycle_case(
341 module: ModuleType, interrupt_type: type[BaseException]
342) -> tuple[list[str], bool, bool]:
343 """Interrupt a cycle delay and record restoration before propagation."""
344 state, off = _member(module,
"_cycle_request")(
"usb-port-cycle", [
"1"])
345 ops = _FakeCycleOps(module)
347 def interrupt() -> None:
348 ops.events.append(
"pause")
351 ops.pause = interrupt
354 _member(module,
"_perform_cycle")(Path(
"unused"), state, off, ops)
355 except interrupt_type:
357 return ops.events, ops.pending, propagated
360def _cycle_checks(module: ModuleType) -> list[tuple[str, bool]]:
361 """Prove restoration on off/delay faults and journaling on restore faults."""
362 nominal = _cycle_case(module,
"")
363 off = _cycle_case(module,
"off")
364 pause = _cycle_case(module,
"pause")
365 on = _cycle_case(module,
"on")
366 clear = _cycle_case(module,
"clear")
367 keyboard_interrupted = _interrupt_cycle_case(module, KeyboardInterrupt)
368 system_exit_interrupted = _interrupt_cycle_case(module, SystemExit)
371 "nominal cycle journals, restores, and clears",
372 nominal == ([
"save",
"off",
"pause",
"on",
"clear"],
False,
False),
375 "off failure still restores before reporting",
376 off == ([
"save",
"off",
"on",
"clear"],
False,
True),
379 "delay failure still restores before reporting",
380 pause == ([
"save",
"off",
"pause",
"on",
"clear"],
False,
True),
383 "on failure leaves pending restore journal",
384 on == ([
"save",
"off",
"pause",
"on"],
True,
True),
387 "clear failure leaves conservative restore journal",
388 clear == ([
"save",
"off",
"pause",
"on",
"clear"],
True,
True),
391 "KeyboardInterrupt restores on before propagating",
392 keyboard_interrupted == ([
"save",
"off",
"pause",
"on",
"clear"],
False,
True),
395 "SystemExit restores on before propagating",
396 system_exit_interrupted == ([
"save",
"off",
"pause",
"on",
"clear"],
False,
True),
399 "persistent off remains explicit and separate",
402 _member(module,
"_cycle_request"),
408 checks.append((
"interrupted cycle is restored before a later mutation", _recovers(module)))
412def _recovers(module: ModuleType) -> bool:
413 """Return whether a later mutation restores one persisted legacy journal."""
414 recovery_ops = _FakeCycleOps(module)
416 "kind":
"port-power",
421 with _patched(module, _load_restore=
lambda _path: recovery_state):
422 _member(module,
"_recover_restore")(Path(
"unused"), recovery_ops)
423 return recovery_ops.events == [
"on",
"clear"]
and not recovery_ops.pending
433 """Run the exact cycle boundary inside a signallable child process."""
434 module = _load_helper()
435 state, off = _member(module,
"_cycle_request")(
"usb-port-cycle", [
"1"])
437 _member(module,
"_perform_cycle")(
441 _ProcessCycleOps(events, journal, device_state, fail_at, inflight_off),
445 except KeyboardInterrupt:
446 return 8
if signal.getsignal(signal.SIGINT)
is signal.default_int_handler
else 9
450def _signal_process_case(
454 wait_for: str =
"pause",
455 inflight_off: bool =
False,
456) -> tuple[list[str], int, bool, str]:
457 """Send a real terminating signal after a child records transient off."""
458 with tempfile.TemporaryDirectory(prefix=
"ra8-hil-signal-")
as raw:
460 events = root /
"events"
461 journal = root /
"journal"
462 device_state = root /
"device-state"
465 signal.signal(signal.SIGHUP, signal.SIG_DFL)
466 signal.signal(signal.SIGINT, signal.default_int_handler)
467 signal.signal(signal.SIGQUIT, signal.SIG_DFL)
468 signal.signal(signal.SIGTERM, signal.SIG_DFL)
469 resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
470 os._exit(_signal_child(events, journal, device_state, fail_at, inflight_off))
471 deadline = time.monotonic() + 3
472 returncode: int |
None =
None
473 observed: list[str] = []
474 while time.monotonic() < deadline:
475 observed = events.read_text(encoding=
"ascii").splitlines()
if events.exists()
else []
476 waited, status = os.waitpid(pid, os.WNOHANG)
478 returncode = os.waitstatus_to_exitcode(status)
480 if wait_for
in observed:
483 if returncode
is None and wait_for
in observed:
485 if returncode
is None:
486 returncode = _wait_child(pid)
487 observed = events.read_text(encoding=
"ascii").splitlines()
if events.exists()
else []
488 final_state = device_state.read_text(encoding=
"ascii")
if device_state.exists()
else ""
489 return observed, returncode, journal.exists(), final_state
492def _wait_child(pid: int) -> int:
493 """Return one child's status, killing only a wedged selftest child."""
494 deadline = time.monotonic() + 3
495 while time.monotonic() < deadline:
496 waited, status = os.waitpid(pid, os.WNOHANG)
498 return os.waitstatus_to_exitcode(status)
500 os.kill(pid, signal.SIGKILL)
501 _, status = os.waitpid(pid, 0)
502 return os.waitstatus_to_exitcode(status)
505def _signal_cycle_checks() -> list[tuple[str, bool]]:
506 """Prove real signal deferral and conservative failure journaling."""
507 nominal = [
"save",
"off",
"pause",
"on",
"clear"]
508 term = _signal_process_case(signal.SIGTERM)
509 hup = _signal_process_case(signal.SIGHUP)
510 inflight = [
"save",
"off-child-spawned",
"off-child-completed",
"pause",
"on",
"clear"]
511 interrupt = _signal_process_case(
513 wait_for=
"off-child-spawned",
516 quit_signal = _signal_process_case(
518 wait_for=
"off-child-spawned",
521 restore_failed = _signal_process_case(signal.SIGTERM,
"on")
522 clear_failed = _signal_process_case(signal.SIGHUP,
"clear")
525 "SIGTERM restores then retains signal exit",
526 term == (nominal, -signal.SIGTERM,
False,
"on"),
529 "SIGHUP restores then retains signal exit",
530 hup == (nominal, -signal.SIGHUP,
False,
"on"),
533 "in-flight off completes before SIGINT propagation and final on",
534 interrupt == (inflight, 8,
False,
"on"),
537 "in-flight off completes before SIGQUIT exit and final on",
538 quit_signal == (inflight, -signal.SIGQUIT,
False,
"on"),
541 "restore failure surfaces before SIGTERM redelivery and keeps journal",
542 restore_failed == ([
"save",
"off",
"pause",
"on"], 7,
True,
"off"),
545 "clear failure surfaces before SIGHUP redelivery and keeps journal",
546 clear_failed == ([*nominal], 7,
True,
"on"),
552 module: ModuleType, fail_save: int = 0, fail_run: int = 0
553) -> tuple[list[str], bool]:
554 """Run prepare against injected state and ip boundaries."""
555 phases: list[str] = []
556 counters = {
"save": 0,
"run": 0,
"route": 0}
558 def save(_path: Path, state: dict[str, object], _policy: dict[str, object]) ->
None:
559 counters[
"save"] += 1
560 phases.append(f
"{state['address_phase']}/{state['link_phase']}/{state['route_phase']}")
561 if counters[
"save"] == fail_save:
562 message =
"injected save"
563 raise module.PolicyError(message)
565 def run(_argv: list[str], *, check: bool =
True) -> object:
568 if counters[
"run"] == fail_run:
569 message =
"injected ip"
570 raise module.PolicyError(message)
571 return _member(module,
"_CommandResult")(0,
"",
"")
573 def route(_board: str, _iface: str) -> bool:
574 counters[
"route"] += 1
575 return counters[
"route"] > 1
578 "_network_cleanup":
lambda _path, _policy:
None,
579 "_validate_live_iface":
lambda _policy: (
"eth0",
False),
580 "_route_present": route,
581 "_address_present":
lambda _iface: counters[
"run"] >= 1,
582 "_link_is_up":
lambda _iface: counters[
"run"] >= EXPECTED_NETWORK_MUTATIONS - 1,
586 with _patched(module, **replacements):
589 _member(module,
"_network_prepare"),
594 return phases, failed
597def _network_checks(module: ModuleType) -> list[tuple[str, bool]]:
598 """Prove pending/applied ordering across every save and mutation boundary."""
599 phases, failed = _network_success(module)
601 "absent/absent/absent",
602 "pending/absent/absent",
603 "applied/absent/absent",
604 "applied/pending/absent",
605 "applied/applied/absent",
606 "applied/applied/pending",
607 "applied/applied/applied",
611 "network success checkpoints pending then applied",
612 phases == expected
and not failed,
615 for boundary
in range(1, len(expected) + 1):
616 saved, did_fail = _network_success(module, fail_save=boundary)
617 checks.append((f
"save fault {boundary} fails closed", did_fail
and len(saved) == boundary))
618 for boundary
in range(1, 4):
619 saved, did_fail = _network_success(module, fail_run=boundary)
620 checks.append((f
"ip mutation fault {boundary} leaves journal", did_fail
and bool(saved)))
621 checks.extend(_network_cleanup_checks(module))
622 checks.extend(_network_identity_checks(module))
626def _network_cleanup_checks(module: ModuleType) -> list[tuple[str, bool]]:
627 """Prove ambiguous cleanup stops and partial cleanup is checkpointed."""
628 policy = _policy(module)
629 pending = _member(module,
"_new_state")(
"eth0",
"192.168.1.42")
630 pending[
"route_phase"] =
"pending"
631 touched: list[str] = []
634 _load_state=
lambda _path, _policy: pending,
635 _cleanup_route=
lambda *_args: touched.append(
"route"),
637 pending_rejected = _rejects(
638 module, _member(module,
"_network_cleanup"), Path(
"unused"), policy
640 applied = {**pending,
"route_phase":
"applied",
"link_phase":
"applied"}
641 snapshots: list[dict[str, object]] = []
644 def run(_argv: list[str], *, check: bool =
True) -> object:
648 if runs == EXPECTED_NETWORK_MUTATIONS - 1:
649 message =
"injected later cleanup failure"
650 raise module.PolicyError(message)
651 return _member(module,
"_CommandResult")(0,
"",
"")
653 routes = iter((
True,
False))
655 "_load_state":
lambda _path, _policy: applied,
656 "_authenticate_cleanup_iface":
lambda _policy, _state: (
"eth0",
True),
657 "_route_present":
lambda _board, _iface: next(routes),
658 "_link_is_up":
lambda _iface:
True,
659 "_address_present":
lambda _iface:
False,
660 "_save_state":
lambda _path, state, _policy: snapshots.append(dict(state)),
663 with _patched(module, **replacements):
664 later_failed = _rejects(module, _member(module,
"_network_cleanup"), Path(
"unused"), policy)
667 "pending cleanup requires trusted recovery before mutation",
668 pending_rejected
and not touched,
671 "completed cleanup leg is checkpointed before a later fault",
672 later_failed
and snapshots
and snapshots[0][
"route_phase"] ==
"absent",
677def _network_identity_checks(module: ModuleType) -> list[tuple[str, bool]]:
678 """Prove stale cleanup paths authenticate before any root mutation."""
679 policy = _policy(module)
680 state = _member(module,
"_new_state")(
"eth0",
"192.168.1.42")
681 state.update(address_phase=
"applied", link_phase=
"applied", route_phase=
"applied")
682 touched: list[str] = []
684 def reject_auth(_policy: object, _state: object) ->
None:
685 message =
"injected permanent identity drift"
686 raise module.PolicyError(message)
689 "_load_state":
lambda _path, _policy: state,
690 "_authenticate_cleanup_iface": reject_auth,
691 "_run":
lambda *_args, **_kwargs: touched.append(
"run"),
693 with _patched(module, **common, _cleanup_route=
lambda *_args: touched.append(
"route")):
694 cleanup_rejected = _rejects(
695 module, _member(module,
"_network_cleanup"), Path(
"unused"), policy
697 with _patched(module, **common):
698 neigh_rejected = _rejects(
699 module, _member(module,
"_network_neigh_flush"), Path(
"unused"), policy
701 validated: list[str] = []
703 def reject_cleanup(_path: Path, _policy: object) ->
None:
704 message =
"stale state identity drift"
705 raise module.PolicyError(message)
709 _network_cleanup=reject_cleanup,
710 _validate_live_iface=
lambda _policy: validated.append(
"validated"),
712 prepare_rejected = _rejects(
714 _member(module,
"_network_prepare"),
720 (
"cleanup rejects physical drift before mutation", cleanup_rejected
and not touched),
721 (
"neighbour flush rejects physical drift before mutation", neigh_rejected
and not touched),
722 (
"prepare cannot bypass stale-state authentication", prepare_rejected
and not validated),
726def _state_and_sysfs_checks(module: ModuleType) -> list[tuple[str, bool]]:
727 """Return policy/state parser and USB symlink escape tests."""
728 policy = _policy(module)
729 new_state = _member(module,
"_new_state")
730 strict_policy = _member(module,
"_strict_policy")
731 strict_state = _member(module,
"_strict_state")
732 resolve_usb_device = _member(module,
"_resolve_usb_device")
733 valid = new_state(
"eth0",
"192.168.1.42")
735 (
"valid policy accepted", strict_policy(policy) == policy),
737 "stale policy digest rejected",
738 _rejects(module, strict_policy, {**policy,
"declaration_sha256":
"0" * 64}),
740 (
"valid phased state accepted", strict_state(valid, policy) == valid),
742 "missing state field rejected",
743 _rejects(module, strict_state, {
"version": 1}, policy),
746 "pending state is recognized",
747 strict_state({**valid,
"route_phase":
"pending"}, policy)[
"route_phase"] ==
"pending",
750 with tempfile.TemporaryDirectory(prefix=
"ra8-hil-sysfs-")
as raw:
752 devices = root /
"sys/devices"
753 bus = root /
"sys/bus/usb/devices"
754 valid_device = devices /
"platform/usb2/2-1/2-1.3/2-1.3.1"
755 escaped = root /
"outside/2-1.3.2"
756 jlink_device = devices /
"platform/usb2/2-1/2-1.3/2-1.3.3"
757 valid_device.mkdir(parents=
True)
758 jlink_device.mkdir(parents=
True)
759 escaped.mkdir(parents=
True)
760 bus.mkdir(parents=
True)
761 (bus /
"2-1.3.1").symlink_to(valid_device)
762 (bus /
"2-1.3.3").symlink_to(jlink_device)
763 (bus /
"2-1.3.2").symlink_to(escaped)
766 "kernel-style USB symlink accepted",
767 resolve_usb_device(bus, devices,
"2-1.3.1") == valid_device.resolve(),
772 "current J-Link USB symlink accepted",
773 resolve_usb_device(bus, devices,
"2-1.3.3") == jlink_device.resolve(),
778 "USB parent escape rejected",
779 _rejects(module, resolve_usb_device, bus, devices,
"2-1.3.2"),
785def main(argv: list[str] |
None =
None) -> int:
786 """Run every offline helper policy and fault-injection check."""
787 args_list = sys.argv[1:]
if argv
is None else argv
788 parser = argparse.ArgumentParser(description=__doc__)
789 parser.add_argument(
"--selftest", action=
"store_true")
790 args = parser.parse_args(args_list)
791 if not args.selftest:
792 parser.error(
"--selftest is required")
793 module = _load_helper()
795 _request_checks(module)
796 + _interface_checks(module)
797 + _cycle_checks(module)
798 + _signal_cycle_checks()
799 + _network_checks(module)
800 + _state_and_sysfs_checks(module)
802 for label, passed
in checks:
803 print(f
" [{'PASS' if passed else 'FAIL'}] {label}")
804 ok = all(passed
for _, passed
in checks)
805 print(f
"hil_privileged_helper_selftest.py: {'PASS' if ok else 'FAIL'}")
806 return 0
if ok
else 1
809if __name__ ==
"__main__":
810 raise SystemExit(
main())
void main(void)
The application entry point Reset_Handler hands control to.