test/panic: add gdbstub test configuration
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pygdbmi.gdbcontroller import GdbController
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import ttfw_idf
|
||||
from tiny_test_fw import Utility, TinyFW, DUT
|
||||
from tiny_test_fw.Utility import SearchCases, CaseConfig
|
||||
@@ -82,6 +84,7 @@ class PanicTestMixin(object):
|
||||
|
||||
def __enter__(self):
|
||||
self._raw_data = None
|
||||
self.gdb = None
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
@@ -89,7 +92,8 @@ class PanicTestMixin(object):
|
||||
with open(os.path.join(log_folder, "log_" + self.test_name + ".txt"), "w") as log_file:
|
||||
Utility.console_log("Writing output of {} to {}".format(self.test_name, log_file.name))
|
||||
log_file.write(self.get_raw_data())
|
||||
|
||||
if self.gdb:
|
||||
self.gdb.exit()
|
||||
self.close()
|
||||
|
||||
def get_raw_data(self):
|
||||
@@ -140,6 +144,90 @@ class PanicTestMixin(object):
|
||||
output_file_name = os.path.join(log_folder, "coredump_flash_result_" + self.test_name + ".txt")
|
||||
self._call_espcoredump(["--core-format", "raw"], coredump_file_name, output_file_name)
|
||||
|
||||
def start_gdb(self):
|
||||
"""
|
||||
Runs GDB and connects it to the "serial" port of the DUT.
|
||||
After this, the DUT expect methods can no longer be used to capture output.
|
||||
"""
|
||||
self.stop_receive()
|
||||
self._port_close()
|
||||
|
||||
Utility.console_log("Starting GDB...", "orange")
|
||||
self.gdb = GdbController(gdb_path=self.TOOLCHAIN_PREFIX + "gdb")
|
||||
|
||||
# pygdbmi logs to console by default, make it log to a file instead
|
||||
log_folder = self.app.get_log_folder(TEST_SUITE)
|
||||
pygdbmi_log_file_name = os.path.join(log_folder, "pygdbmi_log_" + self.test_name + ".txt")
|
||||
pygdbmi_logger = self.gdb.logger
|
||||
pygdbmi_logger.setLevel(logging.DEBUG)
|
||||
while pygdbmi_logger.hasHandlers():
|
||||
pygdbmi_logger.removeHandler(pygdbmi_logger.handlers[0])
|
||||
log_handler = logging.FileHandler(pygdbmi_log_file_name)
|
||||
log_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s: %(message)s"))
|
||||
pygdbmi_logger.addHandler(log_handler)
|
||||
|
||||
# Set up logging for GDB remote protocol
|
||||
gdb_remotelog_file_name = os.path.join(log_folder, "gdb_remote_log_" + self.test_name + ".txt")
|
||||
self.gdb.write("-gdb-set remotelogfile " + gdb_remotelog_file_name)
|
||||
|
||||
# Load the ELF file
|
||||
self.gdb.write("-file-exec-and-symbols {}".format(self.app.elf_file))
|
||||
|
||||
# Connect GDB to UART
|
||||
Utility.console_log("Connecting to GDB Stub...", "orange")
|
||||
self.gdb.write("-gdb-set serial baud 115200")
|
||||
responses = self.gdb.write("-target-select remote " + self.get_gdb_remote(), timeout_sec=3)
|
||||
|
||||
# Make sure we get the 'stopped' notification
|
||||
stop_response = self.find_gdb_response('stopped', 'notify', responses)
|
||||
if not stop_response:
|
||||
responses = self.gdb.write("-exec-interrupt", timeout_sec=3)
|
||||
stop_response = self.find_gdb_response('stopped', 'notify', responses)
|
||||
assert stop_response
|
||||
frame = stop_response["payload"]["frame"]
|
||||
if "file" not in frame:
|
||||
frame["file"] = "?"
|
||||
if "line" not in frame:
|
||||
frame["line"] = "?"
|
||||
Utility.console_log("Stopped in {func} at {addr} ({file}:{line})".format(**frame), "orange")
|
||||
|
||||
# Drain remaining responses
|
||||
self.gdb.get_gdb_response(raise_error_on_timeout=False)
|
||||
|
||||
def gdb_backtrace(self):
|
||||
"""
|
||||
Returns the list of stack frames for the current thread.
|
||||
Each frame is a dictionary, refer to pygdbmi docs for the format.
|
||||
"""
|
||||
assert self.gdb
|
||||
|
||||
responses = self.gdb.write("-stack-list-frames", timeout_sec=3)
|
||||
return self.find_gdb_response("done", "result", responses)["payload"]["stack"]
|
||||
|
||||
@staticmethod
|
||||
def match_backtrace(gdb_backtrace, expected_functions_list):
|
||||
"""
|
||||
Returns True if the function names listed in expected_functions_list match the backtrace
|
||||
given by gdb_backtrace argument. The latter is in the same format as returned by gdb_backtrace()
|
||||
function.
|
||||
"""
|
||||
return all([frame["func"] == expected_functions_list[i] for i, frame in enumerate(gdb_backtrace)])
|
||||
|
||||
@staticmethod
|
||||
def find_gdb_response(message, response_type, responses):
|
||||
"""
|
||||
Helper function which extracts one response from an array of GDB responses, filtering
|
||||
by message and type. Returned message is a dictionary, refer to pygdbmi docs for the format.
|
||||
"""
|
||||
def match_response(response):
|
||||
return (response["message"] == message and
|
||||
response["type"] == response_type)
|
||||
|
||||
filtered_responses = [r for r in responses if match_response(r)]
|
||||
if not filtered_responses:
|
||||
return None
|
||||
return filtered_responses[0]
|
||||
|
||||
|
||||
class ESP32PanicTestDUT(ttfw_idf.ESP32DUT, PanicTestMixin):
|
||||
def get_gdb_remote(self):
|
||||
@@ -170,8 +258,14 @@ def run_all(filename, case_filter=[]):
|
||||
test_cases = CaseConfig.Parser.apply_config(test_methods, None)
|
||||
tests_failed = []
|
||||
for case in test_cases:
|
||||
if case_filter and case.test_method.__name__ not in case_filter:
|
||||
continue
|
||||
test_name = case.test_method.__name__
|
||||
if case_filter:
|
||||
if case_filter[0].endswith("*"):
|
||||
if not test_name.startswith(case_filter[0][:-1]):
|
||||
continue
|
||||
else:
|
||||
if test_name not in case_filter:
|
||||
continue
|
||||
result = case.run()
|
||||
if not result:
|
||||
tests_failed.append(case)
|
||||
|
||||
Reference in New Issue
Block a user