2 # SPDX-License-Identifier: BSD-3-Clause
8 from datetime
import datetime
9 from datetime
import timedelta
11 # Defined in include/commonlib/bsd/elog.h
12 ELOG_TYPE_SYSTEM_BOOT
= 0x17
14 ELOG_EVENT_HEADER_SIZE
= 8
15 ELOG_EVENT_CHECKSUM_SIZE
= 1
18 def convert_to_event(s
: str) -> dict:
20 assert len(fields
) == 3 or len(fields
) == 4
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,
30 def 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.
34 assert key
in got
.keys()
35 assert expected
[key
] == got
[key
]
38 @pytest.fixture(scope
="session")
39 def elogtool_path(request
):
40 exe
= request
.config
.option
.elogtool_path
41 assert os
.path
.exists(exe
)
45 @pytest.fixture(scope
="function")
46 def elogfile(tmp_path
):
48 tail_size
= 512 - header_size
51 # Magic (4 bytes) = "ELOG"
52 # Version (1 byte) = 1
54 # Reserved (2 bytes) = 0xffff
55 header
= struct
.pack("4sBBH", bytes("ELOG", "utf-8"), 1, 8, 0xffff)
57 # Fill the tail with EOL events.
58 tail
= bytes([ELOG_TYPE_EOL
] * tail_size
)
61 buf_path
= tmp_path
/ "elog_empty.bin"
62 with buf_path
.open("wb") as fd
:
69 def 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()
74 lines
= log
.splitlines()
75 lines
= [convert_to_event(s
.strip()) for s
in lines
]
79 def elog_clear(elogtool_path
: str, path
: str) -> None:
80 subprocess
.run([elogtool_path
, 'clear', '-f', path
], check
=True)
83 def 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)
88 def test_list_empty(elogtool_path
, elogfile
):
89 events
= elog_list(elogtool_path
, elogfile
)
90 assert len(events
) == 0
93 def test_clear_empty(elogtool_path
, elogfile
):
94 elog_clear(elogtool_path
, elogfile
)
95 events
= elog_list(elogtool_path
, elogfile
)
97 # Must have one event, the "Log area cleared" event.
98 assert len(events
) == 1
100 expected
= {"index": 0,
101 "desc": "Log area cleared",
102 # "0", since it was an empty elog buffer. No bytes were cleared.
104 compare_event(expected
, events
[0])
107 def test_clear_not_empty(elogtool_path
, elogfile
):
110 event_size
= ELOG_EVENT_HEADER_SIZE
+ data_size
+ ELOG_EVENT_CHECKSUM_SIZE
111 written_bytes
= tot_events
* event_size
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
)
120 # Must have one event, the "Log area cleared" event.
121 assert len(events
) == 1
123 expected
= {"index": 0,
124 "desc": "Log area cleared",
125 "data": str(written_bytes
)
127 compare_event(expected
, events
[0])
130 def 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
134 before
= datetime
.now() - timedelta(seconds
=1)
136 elog_add(elogtool_path
, elogfile
, ELOG_TYPE_SYSTEM_BOOT
,
137 boot_count
.to_bytes(4, "little"))
138 after
= datetime
.now()
140 events
= elog_list(elogtool_path
, elogfile
)
141 assert len(events
) == 1
144 expected
= {"index": 0,
145 "desc": "System boot",
146 "data": str(boot_count
)
148 compare_event(expected
, ev
)
150 assert before
< ev
["timestamp"] < after