blob: cb04fc1f1e71e61ac960927051bbe99ca3ca7461 [file]
#!/usr/bin/env python3
# Copyright 2024 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Tests for snapshot processing."""
import base64
import textwrap
import unittest
from pw_log.proto import log_pb2
from pw_metric_proto import metric_service_pb2
from pw_tokenizer import detokenize, tokens
from pw_snapshot.processor import process_snapshot
from pw_snapshot_protos import snapshot_pb2
_SNAPSHOT_HEADER = """
____ _ __ _____ _ _____ ____ _____ __ ______ ______
/ __ \\ | / / / ___// | / / | / __ \\/ ___// / / / __ \\/_ __/
/ /_/ / | /| / / \\__ \\/ |/ / /| | / /_/ /\\__ \\/ /_/ / / / / / /
/ ____/| |/ |/ / ___/ / /| / ___ |/ ____/___/ / __ / /_/ / / /
/_/ |__/|__/____/____/_/ |_/_/ |_/_/ /____/_/ /_/\\____/ /_/
/_____/
"""
_RISCV_EXPECTED_SNAPSHOT = (
_SNAPSHOT_HEADER
+ """
Snapshot capture reason:
Example Reason
Reason token: 0x6d617845
Project name: example_project
Device: hyper-fast-gshoe
CPU Arch: RV32I
Device FW version: gShoe-debug-1.2.1-6f23412b+
Snapshot UUID: 00000001
All registers:
mepc 0x20000001
mcause 0x20000002
mstatus 0x20000003
"""
)
_ARM_EXPECTED_SNAPSHOT = (
_SNAPSHOT_HEADER
+ """
Snapshot capture reason:
Example Reason
Reason token: 0x6d617845
Project name: example_project
Device: hyper-fast-gshoe
CPU Arch: ARMV7M
Device FW version: gShoe-debug-1.2.1-6f23412b+
Snapshot UUID: 00000001
Exception caused by a unknown exception.
No active Crash Fault Status Register (CFSR) fields.
All registers:
pc 0x20000001
lr 0x20000002
psr 0x20000003
"""
)
_TOKEN_DB = tokens.Database()
def _make_token(string: str) -> int:
"""Tokenizes the given string and adds it to the database.
Returns: The token value.
"""
token = tokens.pw_tokenizer_65599_hash(string)
_TOKEN_DB.add([tokens.TokenizedStringEntry(token, string)])
return token
_LOG_MESSAGE_TOKEN = _make_token("A tokenized log message!")
_BATTERY_TOKEN = _make_token("battery")
_INTERRUPTS_TOKEN = _make_token("interrupts")
_SPI0_TOKEN = _make_token("spi0")
_TEMPERATURE_TOKEN = _make_token("temperature")
_UART0_TOKEN = _make_token("uart0")
_VOLTAGE_TOKEN = _make_token("voltage")
_RAM_TOKEN = _make_token("t-ram")
_TEST_LOGS = [
log_pb2.LogEntry(message=b"Basic"),
log_pb2.LogEntry(
message=_LOG_MESSAGE_TOKEN.to_bytes(length=4, byteorder="little"),
),
log_pb2.LogEntry(
message=b"Hello, world!",
line_level=(1234 << 3) | 4,
timestamp=2745587123456,
module=b"MYMOD",
file=b"dispatcher.c",
thread=b"Dispatcher",
),
]
_TEST_METRICS = [
metric_service_pb2.Metric(
token_path=[_BATTERY_TOKEN, _TEMPERATURE_TOKEN],
as_float=38.0,
),
metric_service_pb2.Metric(
token_path=[_BATTERY_TOKEN, _VOLTAGE_TOKEN],
as_float=2.5,
),
metric_service_pb2.Metric(
token_path=[_INTERRUPTS_TOKEN, _SPI0_TOKEN],
as_int=11234,
),
metric_service_pb2.Metric(
token_path=[_INTERRUPTS_TOKEN, _UART0_TOKEN],
as_int=22345,
),
]
class ProcessorTest(unittest.TestCase):
"""Tests that the metadata processor produces expected results."""
maxDiff = None
def test_riscv_process_snapshot(self):
"""Test processing snapshot of a RISCV CPU"""
snapshot = base64.b64decode(
"ggFYCg5FeGFtcGxlIFJlYXNvbhoPZXhhbXBs"
"ZV9wcm9qZWN0IhtnU2hvZS1kZWJ1Zy0xLjIu"
"MS02ZjIzNDEyYisyEGh5cGVyLWZhc3QtZ3No"
"b2U6BAAAAAFABaIBEgiBgICAAhCCgICAAhiD"
"gICAAg=="
)
output = process_snapshot(snapshot)
self.assertEqual(output, _RISCV_EXPECTED_SNAPSHOT)
def test_cortexm_process_snapshot(self):
"""Test processing snapshot of a ARM CPU"""
snapshot = base64.b64decode(
"ggFYCg5FeGFtcGxlIFJlYXNvbhoPZXhhbXBsZV9wc"
"m9qZWN0IhtnU2hvZS1kZWJ1Zy0xLjIuMS02ZjIzND"
"EyYisyEGh5cGVyLWZhc3QtZ3Nob2U6BAAAAAFAAqI"
"BEgiBgICAAhCCgICAAhiDgICAAg=="
)
output = process_snapshot(snapshot)
self.assertEqual(output, _ARM_EXPECTED_SNAPSHOT)
def test_process_snapshot_with_logs(self):
snapshot = snapshot_pb2.Snapshot(logs=_TEST_LOGS)
expected_output = _SNAPSHOT_HEADER + textwrap.dedent(
"""
Logs:
Basic
A tokenized log message!
ERR MYMOD 00:45:45.587123 Hello, world! dispatcher.c:1234
"""
)
detokenizer = detokenize.Detokenizer(_TOKEN_DB)
output = process_snapshot(
snapshot.SerializeToString(), detokenizer=detokenizer
)
self.assertEqual(output, expected_output)
def test_process_snapshot_with_no_log_processor(self):
"""Verify process_snapshot() supports process_logs=None."""
snapshot = snapshot_pb2.Snapshot(logs=_TEST_LOGS)
expected_output = _SNAPSHOT_HEADER
output = process_snapshot(
snapshot.SerializeToString(),
process_logs=None,
)
self.assertEqual(output, expected_output)
def test_process_snapshot_with_metrics(self):
snapshot = snapshot_pb2.Snapshot(metrics=_TEST_METRICS)
expected_output = _SNAPSHOT_HEADER + textwrap.dedent(
"""
Metrics:
{
"battery": {
"temperature": 38.0,
"voltage": 2.5
},
"interrupts": {
"spi0": 11234,
"uart0": 22345
}
}
"""
)
detokenizer = detokenize.Detokenizer(_TOKEN_DB)
output = process_snapshot(
snapshot.SerializeToString(), detokenizer=detokenizer
)
self.assertEqual(output, expected_output)
def test_process_snapshot_with_memory_regions(self):
snapshot = snapshot_pb2.Snapshot(
memory_regions=[
snapshot_pb2.MemoryRegion(
name=b"main_ram",
address=0x20000000,
data=b"\x01\x02\x03\x04\x05\x06\x07\x08",
),
snapshot_pb2.MemoryRegion(
address=0x08000000,
data=b"\xde\xad\xbe\xef",
),
]
)
expected_output = _SNAPSHOT_HEADER + textwrap.dedent(
"""
Memory Regions:
20000000 (main_ram) (8 bytes)
08000000 (4 bytes)
"""
)
output = process_snapshot(snapshot.SerializeToString())
self.assertEqual(output, expected_output)
def test_process_snapshot_with_memory_region_processor(self):
snapshot = snapshot_pb2.Snapshot(
memory_regions=[
snapshot_pb2.MemoryRegion(
name=b"main_ram",
address=0x20000000,
data=b"\x01\x02\x03\x04\x05\x06\x07\x08",
),
]
)
def mock_processor(region: snapshot_pb2.MemoryRegion) -> str:
return f"Processed {len(region.data)} bytes at {region.address:08x}"
expected_output = _SNAPSHOT_HEADER + textwrap.dedent(
"""
Memory Regions:
20000000 (main_ram) (8 bytes)
Processed 8 bytes at 20000000
"""
)
output = process_snapshot(
snapshot.SerializeToString(), memory_region_processor=mock_processor
)
self.assertEqual(output, expected_output)
def test_process_snapshot_with_tokenized_memory_regions(self):
snapshot = snapshot_pb2.Snapshot(
memory_regions=[
snapshot_pb2.MemoryRegion(
name=_RAM_TOKEN.to_bytes(length=4, byteorder="little"),
address=0x30000000,
data=b"\x01\x02",
),
]
)
expected_output = _SNAPSHOT_HEADER + textwrap.dedent(
"""
Memory Regions:
30000000 (t-ram) (2 bytes)
"""
)
detokenizer = detokenize.Detokenizer(_TOKEN_DB)
output = process_snapshot(
snapshot.SerializeToString(), detokenizer=detokenizer
)
self.assertEqual(output, expected_output)
if __name__ == '__main__':
unittest.main()