Merge branch 'feat/lift_restriction_on_pygdbmi' into 'master'

ci: lift restriction on pygdbmi in panic test

See merge request espressif/esp-idf!18692
This commit is contained in:
Roland Dobai 2022-07-01 14:25:44 +08:00
commit 6db4361565
2 changed files with 83 additions and 13 deletions

View File

@ -3,7 +3,6 @@
# pylint: disable=W0621 # redefined-outer-name # pylint: disable=W0621 # redefined-outer-name
import hashlib
import logging import logging
import os import os
import subprocess import subprocess
@ -14,17 +13,11 @@ import pexpect
import pytest import pytest
from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
from pygdbmi.gdbcontroller import GdbController, GdbTimeoutError, NoGdbProcessError from pygdbmi.gdbcontroller import GdbController
from pytest_embedded_idf.app import IdfApp from pytest_embedded_idf.app import IdfApp
from pytest_embedded_idf.dut import IdfDut from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.serial import IdfSerial from pytest_embedded_idf.serial import IdfSerial
from utils import NoGdbProcessError, attach_logger, quote_string, sha256, verify_valid_gdb_subprocess
def sha256(file: str) -> str:
res = hashlib.sha256()
with open(file, 'rb') as fr:
res.update(fr.read())
return res.hexdigest()
class PanicTestDut(IdfDut): class PanicTestDut(IdfDut):
@ -157,11 +150,21 @@ class PanicTestDut(IdfDut):
Runs GDB and connects it to the "serial" port of the DUT. 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. After this, the DUT expect methods can no longer be used to capture output.
""" """
self.gdb = GdbController(gdb_path=self.toolchain_prefix + 'gdb') gdb_path = self.toolchain_prefix + 'gdb'
try:
from pygdbmi.constants import GdbTimeoutError
default_gdb_args = ['--nx', '--quiet', '--interpreter=mi2']
gdb_command = [gdb_path] + default_gdb_args
self.gdb = GdbController(command=gdb_command)
pygdbmi_logger = attach_logger()
except ImportError:
# fallback for pygdbmi<0.10.0.0.
from pygdbmi.gdbcontroller import GdbTimeoutError
self.gdb = GdbController(gdb_path=gdb_path)
pygdbmi_logger = self.gdb.logger
# pygdbmi logs to console by default, make it log to a file instead # pygdbmi logs to console by default, make it log to a file instead
pygdbmi_log_file_name = os.path.join(self.logdir, 'pygdbmi_log.txt') pygdbmi_log_file_name = os.path.join(self.logdir, 'pygdbmi_log.txt')
pygdbmi_logger = self.gdb.logger
pygdbmi_logger.setLevel(logging.DEBUG) pygdbmi_logger.setLevel(logging.DEBUG)
while pygdbmi_logger.hasHandlers(): while pygdbmi_logger.hasHandlers():
pygdbmi_logger.removeHandler(pygdbmi_logger.handlers[0]) pygdbmi_logger.removeHandler(pygdbmi_logger.handlers[0])
@ -170,15 +173,23 @@ class PanicTestDut(IdfDut):
logging.Formatter('%(asctime)s %(levelname)s: %(message)s') logging.Formatter('%(asctime)s %(levelname)s: %(message)s')
) )
pygdbmi_logger.addHandler(log_handler) pygdbmi_logger.addHandler(log_handler)
try:
gdb_command = self.gdb.command
except AttributeError:
# fallback for pygdbmi < 0.10
gdb_command = self.gdb.cmd
logging.info('Running command: %s', self.gdb.get_subprocess_cmd()) logging.info(f'Running command: "{" ".join(quote_string(c) for c in gdb_command)}"')
for _ in range(10): for _ in range(10):
try: try:
# GdbController creates a process with subprocess.Popen(). Is it really running? It is probable that # GdbController creates a process with subprocess.Popen(). Is it really running? It is probable that
# an RPI under high load will get non-responsive during creating a lot of processes. # an RPI under high load will get non-responsive during creating a lot of processes.
if not hasattr(self.gdb, 'verify_valid_gdb_subprocess'):
# for pygdbmi >= 0.10.0.0
verify_valid_gdb_subprocess(self.gdb.gdb_process)
resp = self.gdb.get_gdb_response( resp = self.gdb.get_gdb_response(
timeout_sec=10 timeout_sec=10
) # calls verify_valid_gdb_subprocess() internally ) # calls verify_valid_gdb_subprocess() internally for pygdbmi < 0.10.0.0
# it will be interesting to look up this response if the next GDB command fails (times out) # it will be interesting to look up this response if the next GDB command fails (times out)
logging.info('GDB response: %s', resp) logging.info('GDB response: %s', resp)
break # success break # success

View File

@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import hashlib
import logging
import re
import time
from subprocess import Popen
class NoGdbProcessError(ValueError):
"""Raise when trying to interact with gdb subprocess, but it does not exist.
It may have been killed and removed, or failed to initialize for some reason."""
pass
def sha256(file: str) -> str:
res = hashlib.sha256()
with open(file, 'rb') as fr:
res.update(fr.read())
return res.hexdigest()
def quote_string(string: str) -> str:
"""Return a shell-escaped version of the string *string*."""
_find_unsafe = re.compile(r'[^\w@%+=:,./-]', re.ASCII).search
if not string:
return "''"
if _find_unsafe(string) is None:
return string
# use single quotes, and put single quotes into double quotes
# the string $'b is then quoted as '$'"'"'b'
return "'" + string.replace("'", "'\"'\"'") + "'"
def verify_valid_gdb_subprocess(gdb_process: Popen) -> None:
"""Verify there is a process object, and that it is still running.
Raise NoGdbProcessError if either of the above are not true."""
if not gdb_process:
raise NoGdbProcessError('gdb process is not attached')
elif gdb_process.poll() is not None:
raise NoGdbProcessError(
'gdb process has already finished with return code: %s'
% str(gdb_process.poll())
)
def attach_logger() -> logging.Logger:
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
unique_number = time.time()
logger = logging.getLogger(__name__ + '.' + str(unique_number))
logger.propagate = False
logger.setLevel(logging.ERROR)
logger.addHandler(handler)
return logger