1#!/usr/bin/python3 2# SPDX-License-Identifier: BSD-3-Clause 3 4import os 5import pytest 6import struct 7import subprocess 8from datetime import datetime 9from datetime import timedelta 10 11# Defined in include/commonlib/bsd/elog.h 12ELOG_TYPE_SYSTEM_BOOT = 0x17 13ELOG_TYPE_EOL = 0xff 14ELOG_EVENT_HEADER_SIZE = 8 15ELOG_EVENT_CHECKSUM_SIZE = 1 16 17 18def convert_to_event(s: str) -> dict: 19 fields = s.split("|") 20 assert len(fields) == 3 or len(fields) == 4 21 22 return { 23 "index": int(fields[0]), 24 "timestamp": datetime.strptime(fields[1].strip(), "%Y-%m-%d %H:%M:%S"), 25 "desc": fields[2].strip(), 26 "data": fields[3].strip() if len(fields) == 4 else None, 27 } 28 29 30def compare_event(expected: dict, got: dict) -> None: 31 # Ignore the keys that might be in "got", but not in "expected". 32 # In particular "timestamp" might not want to be tested. 33 for key in expected: 34 assert key in got.keys() 35 assert expected[key] == got[key] 36 37 38@pytest.fixture(scope="session") 39def elogtool_path(request): 40 exe = request.config.option.elogtool_path 41 assert os.path.exists(exe) 42 return exe 43 44 45@pytest.fixture(scope="function") 46def elogfile(tmp_path): 47 header_size = 8 48 tail_size = 512 - header_size 49 50 # Elog header: 51 # Magic (4 bytes) = "ELOG" 52 # Version (1 byte) = 1 53 # Size (1 byte) = 8 54 # Reserved (2 bytes) = 0xffff 55 header = struct.pack("4sBBH", bytes("ELOG", "utf-8"), 1, 8, 0xffff) 56 57 # Fill the tail with EOL events. 58 tail = bytes([ELOG_TYPE_EOL] * tail_size) 59 buf = header + tail 60 61 buf_path = tmp_path / "elog_empty.bin" 62 with buf_path.open("wb") as fd: 63 fd.write(buf) 64 fd.flush() 65 return str(buf_path) 66 assert False 67 68 69def elog_list(elogtool_path: str, path: str) -> list: 70 output = subprocess.run([elogtool_path, 'list', '-f', path], 71 capture_output=True, check=True) 72 log = output.stdout.decode("utf-8").strip() 73 74 lines = log.splitlines() 75 lines = [convert_to_event(s.strip()) for s in lines] 76 return lines 77 78 79def elog_clear(elogtool_path: str, path: str) -> None: 80 subprocess.run([elogtool_path, 'clear', '-f', path], check=True) 81 82 83def elog_add(elogtool_path: str, path: str, typ: int, data: bytearray) -> None: 84 subprocess.run([elogtool_path, 'add', '-f', path, 85 hex(typ), data.hex()], check=True) 86 87 88def test_list_empty(elogtool_path, elogfile): 89 events = elog_list(elogtool_path, elogfile) 90 assert len(events) == 0 91 92 93def test_clear_empty(elogtool_path, elogfile): 94 elog_clear(elogtool_path, elogfile) 95 events = elog_list(elogtool_path, elogfile) 96 97 # Must have one event, the "Log area cleared" event. 98 assert len(events) == 1 99 100 expected = {"index": 0, 101 "desc": "Log area cleared", 102 # "0", since it was an empty elog buffer. No bytes were cleared. 103 "data": "0"} 104 compare_event(expected, events[0]) 105 106 107def test_clear_not_empty(elogtool_path, elogfile): 108 tot_events = 10 109 data_size = 4 110 event_size = ELOG_EVENT_HEADER_SIZE + data_size + ELOG_EVENT_CHECKSUM_SIZE 111 written_bytes = tot_events * event_size 112 113 for i in range(tot_events): 114 # Adding boot_count for completeness. But it is ignored in this test. 115 boot_count = i.to_bytes(data_size, "little") 116 elog_add(elogtool_path, elogfile, ELOG_TYPE_SYSTEM_BOOT, boot_count) 117 elog_clear(elogtool_path, elogfile) 118 events = elog_list(elogtool_path, elogfile) 119 120 # Must have one event, the "Log area cleared" event. 121 assert len(events) == 1 122 123 expected = {"index": 0, 124 "desc": "Log area cleared", 125 "data": str(written_bytes) 126 } 127 compare_event(expected, events[0]) 128 129 130def test_add_single_event(elogtool_path, elogfile): 131 # "before - one second" is needed because datetime.now() fills the 132 # microsecond variable. But eventlog doesn't use it, and has it hardcoded to 133 # zero. 134 before = datetime.now() - timedelta(seconds=1) 135 boot_count = 128 136 elog_add(elogtool_path, elogfile, ELOG_TYPE_SYSTEM_BOOT, 137 boot_count.to_bytes(4, "little")) 138 after = datetime.now() 139 140 events = elog_list(elogtool_path, elogfile) 141 assert len(events) == 1 142 143 ev = events[0] 144 expected = {"index": 0, 145 "desc": "System boot", 146 "data": str(boot_count) 147 } 148 compare_event(expected, ev) 149 150 assert before < ev["timestamp"] < after 151