Skip to content

Commit 93b213f

Browse files
committed
Properly clean up tests on KeyboardInterrupt
Before when stopping execution with KeyboardInterrupt (i.e., Ctrl+C) there was no mechanism to properly clean up the state (e.g., remove temporary files, close sockets, remove Linux network namespaces). Now, KeyboardInterrupt is caught in the main loop, making the cleanup more seamless and less error-prone. Signed-off-by: Marek Pikuła <[email protected]>
1 parent b28d1ae commit 93b213f

File tree

3 files changed

+97
-86
lines changed

3 files changed

+97
-86
lines changed

scripts/tests/chiptest/accessories.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ def init(self):
5050
def uninit(self):
5151
self._stop_xmlrpc_server()
5252

53+
def terminate(self):
54+
self.uninit()
55+
5356
@property
5457
def accessories(self):
5558
"""List of registered accessory applications."""

scripts/tests/chiptest/linux.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,8 @@ class IsolatedNetworkNamespace:
139139
"ip netns del app-{index}",
140140
]
141141

142-
def __init__(self, index: int = 0, setup_app_link_up: bool = True,
143-
setup_tool_link_up: bool = True, app_link_name: str = 'eth-app',
144-
tool_link_name: str = 'eth-tool', unshared: bool = False):
142+
def __init__(self, index: int = 0, setup_app_link_up: bool = True, setup_tool_link_up: bool = True,
143+
wait_for_dad: bool = True, app_link_name: str = 'eth-app', tool_link_name: str = 'eth-tool', unshared: bool = False):
145144

146145
if not unshared:
147146
# If not running in an unshared network namespace yet, try
@@ -159,12 +158,13 @@ def __init__(self, index: int = 0, setup_app_link_up: bool = True,
159158
self.setup_app_link_up(wait_for_dad=False)
160159
if setup_tool_link_up:
161160
self._setup_tool_link_up(wait_for_dad=False)
162-
self._wait_for_duplicate_address_detection()
161+
if wait_for_dad:
162+
self.wait_for_duplicate_address_detection()
163163

164164
def netns_for_subprocess_kind(self, kind: SubprocessKind):
165165
return "{}-{}".format(kind.name.lower(), self.index)
166166

167-
def _wait_for_duplicate_address_detection(self):
167+
def wait_for_duplicate_address_detection(self):
168168
# IPv6 does Duplicate Address Detection even though
169169
# we know ULAs provided are isolated. Wait for 'tentative'
170170
# address to be gone.
@@ -183,12 +183,12 @@ def _setup(self):
183183
def setup_app_link_up(self, wait_for_dad: bool = True):
184184
self._run(*self.COMMANDS_APP_LINK_UP)
185185
if wait_for_dad:
186-
self._wait_for_duplicate_address_detection()
186+
self.wait_for_duplicate_address_detection()
187187

188188
def _setup_tool_link_up(self, wait_for_dad: bool = True):
189189
self._run(*self.COMMANDS_TOOL_LINK_UP)
190190
if wait_for_dad:
191-
self._wait_for_duplicate_address_detection()
191+
self.wait_for_duplicate_address_detection()
192192

193193
def _run(self, *command: str):
194194
for c in command:

scripts/tests/run_test_suite.py

Lines changed: 87 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ def cmd_list(context: click.Context) -> None:
241241
print("%s%s" % (test.name, tags))
242242

243243

244+
class Terminatable(typing.Protocol):
245+
def terminate(self) -> None: ...
246+
247+
244248
@main.command(
245249
'run', help='Execute the tests')
246250
@click.option(
@@ -414,90 +418,94 @@ def build_app(arg_value: Path | None, kind: SubprocessKind, key: str) -> Subproc
414418

415419
ble_controller_app = None
416420
ble_controller_tool = None
417-
418-
if sys.platform == 'linux':
419-
ns = chiptest.linux.IsolatedNetworkNamespace(
420-
index=0,
421-
# Do not bring up the app interface link automatically when doing BLE-WiFi commissioning.
422-
setup_app_link_up=not ble_wifi,
423-
# Change the app link name so the interface will be recognized as WiFi or Ethernet
424-
# depending on the commissioning method used.
425-
app_link_name='wlx-app' if ble_wifi else 'eth-app',
426-
unshared=context.obj.in_unshare)
427-
428-
if ble_wifi:
429-
bus = chiptest.linux.DBusTestSystemBus()
430-
bluetooth = chiptest.linux.BluetoothMock()
431-
wifi = chiptest.linux.WpaSupplicantMock("MatterAP", "MatterAPPassword", ns)
432-
ble_controller_app = 0 # Bind app to the first BLE controller
433-
ble_controller_tool = 1 # Bind tool to the second BLE controller
434-
435-
executor = chiptest.linux.LinuxNamespacedExecutor(ns)
436-
elif sys.platform == 'darwin':
437-
executor = chiptest.darwin.DarwinExecutor()
438-
else:
439-
log.warning("No platform-specific executor for '%s'", sys.platform)
440-
executor = Executor()
441-
442-
runner = chiptest.runner.Runner(executor=executor)
443-
444-
log.info("Each test will be executed %d times", iterations)
445-
446-
apps_register = AppsRegister()
447-
apps_register.init()
421+
to_terminate: list[Terminatable] = []
448422

449423
def cleanup() -> None:
450-
apps_register.uninit()
424+
for item in reversed(to_terminate):
425+
try:
426+
log.info("Cleaning up %s", item.__class__.__name__)
427+
item.terminate()
428+
except Exception as e:
429+
log.error("Encountered error during cleanup: %s", e, exc_info=True)
430+
431+
try:
451432
if sys.platform == 'linux':
433+
to_terminate.append(ns := chiptest.linux.IsolatedNetworkNamespace(
434+
index=0,
435+
# Do not bring up the app interface link automatically when doing BLE-WiFi commissioning.
436+
setup_app_link_up=not ble_wifi,
437+
# Change the app link name so the interface will be recognized as WiFi or Ethernet
438+
# depending on the commissioning method used.
439+
app_link_name='wlx-app' if ble_wifi else 'eth-app',
440+
unshared=context.obj.in_unshare, wait_for_dad=False))
441+
ns.wait_for_duplicate_address_detection()
442+
452443
if ble_wifi:
453-
wifi.terminate()
454-
bluetooth.terminate()
455-
bus.terminate()
456-
ns.terminate()
457-
458-
for i in range(iterations):
459-
log.info("Starting iteration %d", i+1)
460-
observed_failures = 0
461-
for test in context.obj.tests:
462-
if context.obj.include_tags:
463-
if not (test.tags & context.obj.include_tags):
464-
log.debug("Test '%s' not included", test.name)
465-
continue
466-
467-
if context.obj.exclude_tags:
468-
if test.tags & context.obj.exclude_tags:
469-
log.debug("Test '%s' excluded", test.name)
470-
continue
471-
472-
test_start = time.monotonic()
473-
try:
474-
if context.obj.dry_run:
475-
log.info("Would run test: '%s'", test.name)
476-
else:
477-
log.info("%-20s - Starting test", test.name)
478-
test.Run(
479-
runner, apps_register, paths, pics_file, test_timeout_seconds, context.obj.dry_run,
480-
test_runtime=context.obj.runtime,
481-
ble_controller_app=ble_controller_app,
482-
ble_controller_tool=ble_controller_tool,
483-
)
484-
if not context.obj.dry_run:
444+
to_terminate.append(chiptest.linux.DBusTestSystemBus())
445+
to_terminate.append(chiptest.linux.BluetoothMock())
446+
to_terminate.append(chiptest.linux.WpaSupplicantMock("MatterAP", "MatterAPPassword", ns))
447+
ble_controller_app = 0 # Bind app to the first BLE controller
448+
ble_controller_tool = 1 # Bind tool to the second BLE controller
449+
450+
executor = chiptest.linux.LinuxNamespacedExecutor(ns)
451+
elif sys.platform == 'darwin':
452+
executor = chiptest.darwin.DarwinExecutor()
453+
else:
454+
log.warning("No platform-specific executor for '%s'", sys.platform)
455+
executor = Executor()
456+
457+
runner = chiptest.runner.Runner(executor=executor)
458+
459+
log.info("Each test will be executed %d times", iterations)
460+
461+
to_terminate.append(apps_register := AppsRegister())
462+
apps_register.init()
463+
464+
for i in range(iterations):
465+
log.info("Starting iteration %d", i+1)
466+
observed_failures = 0
467+
for test in context.obj.tests:
468+
if context.obj.include_tags:
469+
if not (test.tags & context.obj.include_tags):
470+
log.debug("Test '%s' not included", test.name)
471+
continue
472+
473+
if context.obj.exclude_tags:
474+
if test.tags & context.obj.exclude_tags:
475+
log.debug("Test '%s' excluded", test.name)
476+
continue
477+
478+
test_start = time.monotonic()
479+
try:
480+
if context.obj.dry_run:
481+
log.info("Would run test: '%s'", test.name)
482+
else:
483+
log.info("%-20s - Starting test", test.name)
484+
test.Run(
485+
runner, apps_register, paths, pics_file, test_timeout_seconds, context.obj.dry_run,
486+
test_runtime=context.obj.runtime,
487+
ble_controller_app=ble_controller_app,
488+
ble_controller_tool=ble_controller_tool,
489+
)
490+
if not context.obj.dry_run:
491+
test_end = time.monotonic()
492+
log.info("%-30s - Completed in %0.2f seconds", test.name, test_end - test_start)
493+
except Exception:
485494
test_end = time.monotonic()
486-
log.info("%-30s - Completed in %0.2f seconds", test.name, test_end - test_start)
487-
except Exception:
488-
test_end = time.monotonic()
489-
log.exception("%-30s - FAILED in %0.2f seconds", test.name, test_end - test_start)
490-
observed_failures += 1
491-
if not keep_going:
492-
cleanup()
493-
sys.exit(2)
494-
495-
if observed_failures != expected_failures:
496-
log.error("Iteration %d: expected failure count %d, but got %d", i, expected_failures, observed_failures)
497-
cleanup()
498-
sys.exit(2)
499-
500-
cleanup()
495+
log.exception("%-30s - FAILED in %0.2f seconds", test.name, test_end - test_start)
496+
observed_failures += 1
497+
if not keep_going:
498+
cleanup()
499+
sys.exit(2)
500+
501+
if observed_failures != expected_failures:
502+
log.error("Iteration %d: expected failure count %d, but got %d", i, expected_failures, observed_failures)
503+
cleanup()
504+
sys.exit(2)
505+
except KeyboardInterrupt:
506+
log.info("Interrupting execution on user request")
507+
finally:
508+
cleanup()
501509

502510

503511
# On Linux, allow an execution shell to be prepared

0 commit comments

Comments
 (0)