elogtool: add pytest for elogtool
This CL adds a python test for elogtool. It tests the basic functionality of elogtool: list, clear and add. A future CL will include more complex tests. BUG=b:172210863 TEST=pytest elogtool_test.py Change-Id: If1241ad070d1c690c84f5ca61c0487ba27c2a287 Signed-off-by: Ricardo Quesada <ricardoq@google.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/57869 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Jack Rosenthal <jrosenth@chromium.org> Reviewed-by: Patrick Georgi <pgeorgi@google.com>
This commit is contained in:
parent
6c8008283c
commit
e1f392ea34
|
@ -0,0 +1,26 @@
|
||||||
|
# CBFSTool tests
|
||||||
|
|
||||||
|
To run the tests do `pytest name_of_the_file.py`. E.g:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pytest elogtool_test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Pytest
|
||||||
|
|
||||||
|
Requires `pytest`. To install it do:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pip install --user pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Binaries
|
||||||
|
|
||||||
|
Make sure that you have compiled the cbfstool binaries before running the test. e.g:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cd $COREBOOT_SRC/util/cbfstool
|
||||||
|
$ make
|
||||||
|
```
|
|
@ -0,0 +1,12 @@
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
here = pathlib.Path(__file__).parent
|
||||||
|
parser.addoption(
|
||||||
|
"--elogtool-path",
|
||||||
|
type=pathlib.Path,
|
||||||
|
default=(here / ".." / "elogtool").resolve(),
|
||||||
|
)
|
|
@ -0,0 +1,150 @@
|
||||||
|
#!/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
|
Loading…
Reference in New Issue