| #!/usr/bin/python3 |
| # SPDX-License-Identifier: BSD-3-Clause |
| |
| import os |
| import pytest |
| import struct |
| import subprocess |
| from datetime import datetime |
| from datetime import timedelta |
| |
| # Defined in include/commonlib/bsd/elog.h |
| ELOG_TYPE_SYSTEM_BOOT = 0x17 |
| ELOG_TYPE_EOL = 0xff |
| ELOG_EVENT_HEADER_SIZE = 8 |
| ELOG_EVENT_CHECKSUM_SIZE = 1 |
| |
| |
| def convert_to_event(s: str) -> dict: |
| fields = s.split("|") |
| assert len(fields) == 3 or len(fields) == 4 |
| |
| return { |
| "index": int(fields[0]), |
| "timestamp": datetime.strptime(fields[1].strip(), "%Y-%m-%d %H:%M:%S"), |
| "desc": fields[2].strip(), |
| "data": fields[3].strip() if len(fields) == 4 else None, |
| } |
| |
| |
| def compare_event(expected: dict, got: dict) -> None: |
| # Ignore the keys that might be in "got", but not in "expected". |
| # In particular "timestamp" might not want to be tested. |
| for key in expected: |
| assert key in got.keys() |
| assert expected[key] == got[key] |
| |
| |
| @pytest.fixture(scope="session") |
| def elogtool_path(request): |
| exe = request.config.option.elogtool_path |
| assert os.path.exists(exe) |
| return exe |
| |
| |
| @pytest.fixture(scope="function") |
| def elogfile(tmp_path): |
| header_size = 8 |
| tail_size = 512 - header_size |
| |
| # Elog header: |
| # Magic (4 bytes) = "ELOG" |
| # Version (1 byte) = 1 |
| # Size (1 byte) = 8 |
| # Reserved (2 bytes) = 0xffff |
| header = struct.pack("4sBBH", bytes("ELOG", "utf-8"), 1, 8, 0xffff) |
| |
| # Fill the tail with EOL events. |
| tail = bytes([ELOG_TYPE_EOL] * tail_size) |
| buf = header + tail |
| |
| buf_path = tmp_path / "elog_empty.bin" |
| with buf_path.open("wb") as fd: |
| fd.write(buf) |
| fd.flush() |
| return str(buf_path) |
| assert False |
| |
| |
| def elog_list(elogtool_path: str, path: str) -> list: |
| output = subprocess.run([elogtool_path, 'list', '-f', path], |
| capture_output=True, check=True) |
| log = output.stdout.decode("utf-8").strip() |
| |
| lines = log.splitlines() |
| lines = [convert_to_event(s.strip()) for s in lines] |
| return lines |
| |
| |
| def elog_clear(elogtool_path: str, path: str) -> None: |
| subprocess.run([elogtool_path, 'clear', '-f', path], check=True) |
| |
| |
| def elog_add(elogtool_path: str, path: str, typ: int, data: bytearray) -> None: |
| subprocess.run([elogtool_path, 'add', '-f', path, |
| hex(typ), data.hex()], check=True) |
| |
| |
| def test_list_empty(elogtool_path, elogfile): |
| events = elog_list(elogtool_path, elogfile) |
| assert len(events) == 0 |
| |
| |
| def test_clear_empty(elogtool_path, elogfile): |
| elog_clear(elogtool_path, elogfile) |
| events = elog_list(elogtool_path, elogfile) |
| |
| # Must have one event, the "Log area cleared" event. |
| assert len(events) == 1 |
| |
| expected = {"index": 0, |
| "desc": "Log area cleared", |
| # "0", since it was an empty elog buffer. No bytes were cleared. |
| "data": "0"} |
| compare_event(expected, events[0]) |
| |
| |
| def test_clear_not_empty(elogtool_path, elogfile): |
| tot_events = 10 |
| data_size = 4 |
| event_size = ELOG_EVENT_HEADER_SIZE + data_size + ELOG_EVENT_CHECKSUM_SIZE |
| written_bytes = tot_events * event_size |
| |
| for i in range(tot_events): |
| # Adding boot_count for completeness. But it is ignored in this test. |
| boot_count = i.to_bytes(data_size, "little") |
| elog_add(elogtool_path, elogfile, ELOG_TYPE_SYSTEM_BOOT, boot_count) |
| elog_clear(elogtool_path, elogfile) |
| events = elog_list(elogtool_path, elogfile) |
| |
| # Must have one event, the "Log area cleared" event. |
| assert len(events) == 1 |
| |
| expected = {"index": 0, |
| "desc": "Log area cleared", |
| "data": str(written_bytes) |
| } |
| compare_event(expected, events[0]) |
| |
| |
| def test_add_single_event(elogtool_path, elogfile): |
| # "before - one second" is needed because datetime.now() fills the |
| # microsecond variable. But eventlog doesn't use it, and has it hardcoded to |
| # zero. |
| before = datetime.now() - timedelta(seconds=1) |
| boot_count = 128 |
| elog_add(elogtool_path, elogfile, ELOG_TYPE_SYSTEM_BOOT, |
| boot_count.to_bytes(4, "little")) |
| after = datetime.now() |
| |
| events = elog_list(elogtool_path, elogfile) |
| assert len(events) == 1 |
| |
| ev = events[0] |
| expected = {"index": 0, |
| "desc": "System boot", |
| "data": str(boot_count) |
| } |
| compare_event(expected, ev) |
| |
| assert before < ev["timestamp"] < after |