|
| 1 | +# deps: pytest, pytest-embedded, pytest-embedded-serial, pytest-embedded-idf |
| 2 | +# Detects reboot loops using only standard boot/panic text patterns. |
| 3 | + |
| 4 | +import re |
| 5 | +import time |
| 6 | + |
| 7 | +# Common boot banners from ROM/bootloader |
| 8 | +BOOT_MARKERS = [ |
| 9 | + re.compile(r"\bESP-ROM:", re.I), |
| 10 | + re.compile(r"\brst:0x[0-9a-f]+\b", re.I), # e.g. rst:0xc (SW_CPU_RESET) |
| 11 | + re.compile(r"\bboot:0x[0-9a-f]+\b", re.I), # e.g. boot:0x20c |
| 12 | + re.compile(r"\bboot:\s+ESP-IDF\b", re.I), # "I (...) boot: ESP-IDF v..." |
| 13 | + re.compile(r"\bentry 0x[0-9a-f]+\b", re.I), |
| 14 | +] |
| 15 | + |
| 16 | +# Common fatal/panic signatures |
| 17 | +PANIC_MARKERS = [ |
| 18 | + re.compile(r"\bBacktrace:\b", re.I), |
| 19 | + re.compile(r"\babort\(\)\s+was\s+called\b", re.I), |
| 20 | + re.compile(r"\bGuru Meditation Error\b", re.I), |
| 21 | + re.compile(r"\bWDT\b", re.I), # watchdog resets |
| 22 | +] |
| 23 | + |
| 24 | +def _has_any(line: str, pats) -> bool: |
| 25 | + return any(p.search(line) for p in pats) |
| 26 | + |
| 27 | +def test_boots_once_and_does_not_loop(dut): |
| 28 | + """ |
| 29 | + Procedure: |
| 30 | + 1) Let pytest-embedded flash and start the DUT. |
| 31 | + 2) Consume serial until we see the first complete boot sequence. |
| 32 | + 3) Then watch for 30s; if any boot markers or panic markers reappear, fail. |
| 33 | + """ |
| 34 | + # Phase 1: consume initial boot (first marker set must appear) |
| 35 | + saw_first_boot = False |
| 36 | + t0 = time.time() |
| 37 | + while time.time() - t0 < 15.0: # up to 15s to get through bootloader->app |
| 38 | + buf = dut.read(timeout=1.0) or b"" |
| 39 | + line = buf.decode(errors="ignore") |
| 40 | + if _has_any(line, BOOT_MARKERS): |
| 41 | + saw_first_boot = True |
| 42 | + # keep consuming until boot banners quiet down for a moment |
| 43 | + # (heuristic) break once we hit a lull |
| 44 | + if saw_first_boot and not line.strip(): |
| 45 | + break |
| 46 | + assert saw_first_boot, "Never observed initial ESP-IDF/ROM boot markers on UART" |
| 47 | + |
| 48 | + # Phase 2: verify no reboots/panics for 30s |
| 49 | + reboots = 0 |
| 50 | + panics = 0 |
| 51 | + t1 = time.time() |
| 52 | + while time.time() - t1 < 30.0: |
| 53 | + buf = dut.read(timeout=1.0) or b"" |
| 54 | + line = buf.decode(errors="ignore") |
| 55 | + if _has_any(line, PANIC_MARKERS): |
| 56 | + panics += 1 |
| 57 | + raise AssertionError(f"Panic detected: {line.strip()[:200]}") |
| 58 | + if _has_any(line, BOOT_MARKERS): |
| 59 | + reboots += 1 |
| 60 | + raise AssertionError(f"Reboot/boot-loop detected (marker seen again): {line.strip()[:200]}") |
| 61 | + |
| 62 | + # If we reach here, the app stayed up without printing boot/panic markers again. |
0 commit comments