Skip to content

Commit 0a852e7

Browse files
committed
Add post-mortem information collection for failed tests
In some situations, when examine failed tests it would be helpful to collect some extra information on the system state. This changeset adds a fixture that triggers data collection. A fixture, contrary to a hook, allows to re-use existing fixtures (e.g. the `strategy`-fixture). Using `autouse=True` this fixture is injected into every test. To make the test result available in fixtures a hook in `pytest_runtest_makereport` is used. The actual diagnosis data is collected inside the strategy. This way the infrastructure is re-useable and knowledge on the system state stays inside the strategy. In case of a test failure this generates log output that may look like this: ---- 8< ---- ========== FAILURES ========== ____ test_tacd_uart_3v3 ______ (...) @pytest.mark.lg_feature("eet") def test_tacd_uart_3v3(strategy, shell, eet, record_property): (...) > assert True == False E assert True == False tests/test_tacd.py:248: AssertionError ---- Captured log teardown ----- (...) WARNING post-mortem: POST-MORTEM INFO: status WARNING post-mortem: | shell WARNING post-mortem: POST-MORTEM INFO: uname -a WARNING post-mortem: | Linux lxatac-00034 6.17.0-20251007-1 #1 SMP PREEMPT Sun Sep 28 21:39:22 UTC 2025 armv7l GNU/Linux WARNING post-mortem: POST-MORTEM INFO: cat /etc/os-release WARNING post-mortem: | ID=tacos WARNING post-mortem: | NAME="TAC OS - The LXA TAC operating system" WARNING post-mortem: | VERSION="25.09+dev (tacos-walnascar)" WARNING post-mortem: | VERSION_ID=25.09-dev WARNING post-mortem: | VERSION_CODENAME="tacos-walnascar" WARNING post-mortem: | PRETTY_NAME="TAC OS - The LXA TAC operating system 25.09+dev (tacos-walnascar)" WARNING post-mortem: | CPE_NAME="cpe:/o:openembedded:tacos:25.09+dev" WARNING post-mortem: POST-MORTEM INFO: cat /etc/buildinfo WARNING post-mortem: | ----------------------- WARNING post-mortem: | Build Configuration: | WARNING post-mortem: | ----------------------- WARNING post-mortem: | DISTRO = tacos WARNING post-mortem: | DISTRO_VERSION = 25.09+dev WARNING post-mortem: | ----------------------- WARNING post-mortem: | Layer Revisions: | WARNING post-mortem: | ----------------------- WARNING post-mortem: | meta = HEAD:d0b46a6624ec9c61c47270745dd0b2d5abbe6ac1 WARNING post-mortem: | meta-poky = HEAD:d0b46a6624ec9c61c47270745dd0b2d5abbe6ac1 WARNING post-mortem: | meta-arm = HEAD:21894cc2ea3197e6bfc1a56d889f757a09dc8b31 WARNING post-mortem: | meta-arm-toolchain = HEAD:21894cc2ea3197e6bfc1a56d889f757a09dc8b31 WARNING post-mortem: | meta-ptx = HEAD:23e46e92946ca0a1b1da4cf3ad212169d46b0af8 WARNING post-mortem: | meta-oe = HEAD:80ab58cc404959ae2f0e8b2e68935b3bfd8e8cfe WARNING post-mortem: | meta-python = HEAD:80ab58cc404959ae2f0e8b2e68935b3bfd8e8cfe WARNING post-mortem: | meta-filesystems = HEAD:80ab58cc404959ae2f0e8b2e68935b3bfd8e8cfe WARNING post-mortem: | meta-networking = HEAD:80ab58cc404959ae2f0e8b2e68935b3bfd8e8cfe WARNING post-mortem: | meta-lxatac-bsp = HEAD:44e447b099a268f87a305915a386568e0ff72b6a WARNING post-mortem: | meta-lxatac-software = HEAD:44e447b099a268f87a305915a386568e0ff72b6a WARNING post-mortem: | meta-labgrid = HEAD:fa2c78d1fc3ff9519ddd14168807519ec0b6d9ee WARNING post-mortem: | meta-rauc = HEAD:a0b83842d131093e89fa4fefaaa1997f05afdf62 WARNING post-mortem: | meta-virtualization = HEAD:4fedb61ebf60745a367d857af255006bd3509a39 WARNING post-mortem: | meta-selinux = HEAD:7ba3272c1f23266a1822442d640a40b6b4feff8c (...) ---- 8< ---- Signed-off-by: Chris Fiege <[email protected]>
1 parent b659473 commit 0a852e7

File tree

3 files changed

+79
-0
lines changed

3 files changed

+79
-0
lines changed

conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import pytest
77

8+
pytest_plugins = ["postmortem"]
9+
810

911
@pytest.fixture(scope="function")
1012
def barebox(strategy):

lxatacstrategy.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,31 @@ def activate_optionals(self):
204204
if self.ethmux:
205205
self.target.activate(self.ethmux)
206206
self.ethmux.set(True) # Connect upstream Ethernet to Lab network as default
207+
208+
def postmortem_info(self) -> dict[str, list[str]]:
209+
pm_info: dict[str, list[str]] = {"status": [self.status.name]}
210+
211+
def get_info(shell, command):
212+
pm_info[command] = shell.run_check(command)
213+
214+
if self.status == Status.barebox:
215+
get_info(self.barebox, "version")
216+
get_info(self.barebox, "mount")
217+
get_info(self.barebox, "global")
218+
get_info(self.barebox, "nv")
219+
get_info(self.barebox, "dmesg")
220+
elif self.status == Status.shell:
221+
get_info(self.shell, "uname -a")
222+
get_info(self.shell, "cat /etc/os-release")
223+
get_info(self.shell, "cat /etc/buildinfo")
224+
get_info(self.shell, "dmesg -l 5")
225+
get_info(self.shell, "findmnt")
226+
get_info(self.shell, "lsns")
227+
get_info(self.shell, "ip -brief address")
228+
get_info(self.shell, "ip -brief route")
229+
get_info(self.shell, "ip -brief -6 route")
230+
get_info(self.shell, "df --human-readable")
231+
get_info(self.shell, "free -m")
232+
get_info(self.shell, "systemctl list-units --failed")
233+
234+
return pm_info

postmortem.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import logging
2+
3+
import pytest
4+
from pytest import CollectReport, StashKey
5+
6+
_phase_report_key = StashKey[dict[str, CollectReport]]()
7+
_pm_logger = logging.getLogger("post-mortem")
8+
9+
10+
@pytest.hookimpl(wrapper=True, tryfirst=True)
11+
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo[None]):
12+
"""
13+
Make the report for a step available in the item's stash, so that
14+
fixtures can read the result in their teardown phase.
15+
Heavily inspired by: https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures
16+
Implements the runtest_makereport-hook: https://docs.pytest.org/en/latest/reference/reference.html#std-hook-pytest_runtest_makereport
17+
18+
:param item: The item the report is generated for.
19+
:param call: CallInfo for this phase
20+
:return: The report re received from the previous hook.
21+
"""
22+
rep = yield
23+
24+
# store test results for each phase of a call, which can
25+
# be "setup", "call", "teardown"
26+
item.stash.setdefault(_phase_report_key, {})[rep.when] = rep
27+
return rep
28+
29+
30+
@pytest.fixture(autouse=True)
31+
def pm_system(request: pytest.FixtureRequest, strategy, record_property):
32+
"""
33+
Retrieves post-mortem diagnosis information from the strategy and emits the information to the log with level
34+
WARNING and also adds the information to the `junit.xml`.
35+
36+
The strategy must implement a strategy.postmotem_info().
37+
It is up to the strategy to decide which information to collect depending on the DUTs status and the connections
38+
available (e.g. serial or ssh).
39+
"""
40+
yield
41+
42+
report = request.node.stash[_phase_report_key]
43+
if "call" in report and report["call"].failed:
44+
post_mortem_info: dict[str, list[str]] = strategy.postmortem_info()
45+
record_property("postmortem", post_mortem_info)
46+
for key, value in post_mortem_info.items():
47+
_pm_logger.warning(f"POST-MORTEM INFO: {key}")
48+
for line in value:
49+
_pm_logger.warning(f"| {line}")

0 commit comments

Comments
 (0)