Merge branch 'ci/pytest_gdb_loadable_elf' into 'master'

ci: migrate ttfw jtag related test to pytest

Closes RDT-241, IDFCI-1149, and IDFCI-1337

See merge request espressif/esp-idf!20918
This commit is contained in:
Fu Hanxi
2022-12-09 10:16:02 +08:00
17 changed files with 430 additions and 425 deletions

View File

@@ -129,9 +129,52 @@
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
.build_pytest_no_jtag_template:
extends: .build_pytest_template
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
- run_cmd python tools/ci/ci_build_apps.py $TEST_DIR -v
-t $IDF_TARGET
-m \"not host_test and not jtag\"
--pytest-apps
--collect-size-info size_info.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
.build_pytest_jtag_template:
extends:
- .build_cmake_template
- .before_script_build_jobs
artifacts:
paths:
- "**/build*/size.json"
- "**/build*/build_log.txt"
- "**/build*/*.bin"
# upload to s3 server to save the artifacts size
# - "**/build*/*.map"
- "**/build*/*.elf" # need elf for gdb
- "**/build*/flasher_args.json"
- "**/build*/flash_project_args"
- "**/build*/config/sdkconfig.json"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- list_job_*.json
- size_info.txt
when: always
expire_in: 4 days
script:
# CI specific options start from "--collect-size-info xxx". could ignore when running locally
- run_cmd python tools/ci/ci_build_apps.py $TEST_DIR -v
-t $IDF_TARGET
-m \"not host_test and jtag\"
--pytest-apps
--collect-size-info size_info.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
build_pytest_examples_esp32:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32
parallel: 4
variables:
@@ -140,7 +183,7 @@ build_pytest_examples_esp32:
build_pytest_examples_esp32s2:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32s2
parallel: 3
variables:
@@ -149,7 +192,7 @@ build_pytest_examples_esp32s2:
build_pytest_examples_esp32s3:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32s3
parallel: 4
variables:
@@ -158,7 +201,7 @@ build_pytest_examples_esp32s3:
build_pytest_examples_esp32c3:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32c3
parallel: 3
variables:
@@ -167,7 +210,7 @@ build_pytest_examples_esp32c3:
build_pytest_examples_esp32c2:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32c2
variables:
IDF_TARGET: esp32c2
@@ -175,12 +218,20 @@ build_pytest_examples_esp32c2:
build_pytest_examples_esp32h4:
extends:
- .build_pytest_template
- .build_pytest_no_jtag_template
- .rules:build:example_test-esp32h4
variables:
IDF_TARGET: esp32h4
TEST_DIR: examples
build_pytest_examples_jtag: # for all targets
extends:
- .build_pytest_jtag_template
- .rules:build:example_test-esp32
variables:
IDF_TARGET: all
TEST_DIR: examples
build_pytest_components_esp32h4:
extends:
- .build_pytest_template

View File

@@ -38,6 +38,7 @@
--known-failure-cases-file known_failure_cases/known_failure_cases.txt
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
${PYTEST_EXTRA_FLAGS}
.pytest_examples_dir_template:
extends: .pytest_template
@@ -76,6 +77,17 @@ example_test_pytest_esp32_twai_network:
- build_pytest_examples_esp32
tags: [ esp32, twai_network ]
example_test_pytest_esp32_jtag:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32
needs:
- build_pytest_examples_jtag
tags: [ esp32, jtag ]
variables:
SETUP_TOOLS: "1" # need gdb openocd
PYTEST_EXTRA_FLAGS: "--log-cli-level DEBUG"
example_test_pytest_esp32s2_generic:
extends:
- .pytest_examples_dir_template
@@ -677,9 +689,10 @@ test_app_test_pytest_esp32_jtag:
- .rules:test:custom_test-esp32
needs:
- build_pytest_test_apps_esp32
tags: [ esp32, test_jtag_arm]
tags: [ esp32, jtag]
variables:
SETUP_TOOLS: "1" # need gdb
PYTEST_EXTRA_FLAGS: "--log-cli-level DEBUG"
test_app_test_pytest_esp32s2_generic:
extends:
@@ -908,15 +921,6 @@ example_test_007:
- ESP32
- Example_I2C_CCS811_SENSOR
example_test_009:
extends: .example_test_esp32_template
tags:
- ESP32
- test_jtag_arm
variables:
SETUP_TOOLS: "1"
PYTHON_VER: 3
example_test_011:
extends: .example_test_esp32_template
tags:
@@ -992,14 +996,6 @@ example_test_C6_GENERIC:
- .test_app_template
- .rules:test:custom_test-esp32s3
test_app_test_001:
extends: .test_app_esp32_template
tags:
- ESP32
- test_jtag_arm
variables:
SETUP_TOOLS: "1"
test_app_test_eth:
extends: .test_app_esp32_template
tags:

View File

@@ -109,7 +109,7 @@ ENV_MARKERS = {
'MSPI_F8R8': 'runner with Octal Flash and Octal PSRAM',
'MSPI_F4R8': 'runner with Quad Flash and Octal PSRAM',
'MSPI_F4R4': 'runner with Quad Flash and Quad PSRAM',
'test_jtag_arm': 'runner where the chip is accessible through JTAG as well',
'jtag': 'runner where the chip is accessible through JTAG as well',
'adc': 'ADC related tests should run on adc runners',
'xtal32k': 'Runner with external 32k crystal connected',
'no32kXtal': 'Runner with no external 32k crystal connected',
@@ -203,6 +203,11 @@ def get_target_marker_from_expr(markexpr: str) -> str:
############
# Fixtures #
############
@pytest.fixture(scope='session')
def idf_path() -> str:
return os.path.dirname(__file__)
@pytest.fixture(scope='session', autouse=True)
def session_tempdir() -> str:
_tmpdir = os.path.join(

View File

@@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import os
import shutil
import tempfile
import typing as t
from itertools import zip_longest
import pytest
from pytest_embedded_idf import IdfDut
TEMP_DIR = tempfile.mkdtemp()
HOST_FILE_NAME = 'host_file.txt'
HOST_FILE_PATH = os.path.join(os.path.dirname(__file__), 'data', HOST_FILE_NAME)
@pytest.fixture(autouse=True)
def prepare() -> t.Generator[None, None, None]:
shutil.copyfile(HOST_FILE_PATH, os.path.join(TEMP_DIR, HOST_FILE_NAME))
yield
shutil.rmtree(TEMP_DIR, ignore_errors=True)
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize(
'embedded_services, no_gdb, openocd_cli_args',
[
pytest.param(
'esp,idf,jtag',
'y',
f'-c \'set ESP_SEMIHOST_BASEDIR "{TEMP_DIR}"\' -f board/esp32-wrover-kit-3.3v.cfg',
marks=[pytest.mark.esp32],
),
],
indirect=True,
)
def test_semihost_vfs(dut: IdfDut) -> None:
dut.expect_exact('example: Switch to semihosted stdout')
dut.expect_exact('example: Switched back to UART stdout')
dut.expect_exact('example: Wrote 2798 bytes')
dut.expect_exact('====================== HOST DATA START =========================')
with open(HOST_FILE_PATH) as f:
for line in f:
if line.strip():
dut.expect_exact(line.strip())
dut.expect_exact('====================== HOST DATA END =========================')
dut.expect_exact('example: Read 6121 bytes')
with open(os.path.join(TEMP_DIR, 'esp32_stdout.txt')) as f:
def expected_content() -> t.Iterator[str]:
yield 'example: Switched to semihosted stdout'
for i in range(100):
yield 'Semihosted stdout write {}'.format(i)
yield 'example: Switch to UART stdout'
for actual, expected in zip_longest(f, expected_content(), fillvalue='-'):
if expected not in actual: # "in" used because of the printed ASCII color codes
raise RuntimeError('"{}" != "{}"'.format(expected, actual.strip()))

View File

@@ -1,59 +0,0 @@
import os
import shutil
import tempfile
from io import open
import ttfw_idf
try:
from itertools import izip_longest as zip_longest
except ImportError:
# Python 3
from itertools import zip_longest
@ttfw_idf.idf_example_test(env_tag='test_jtag_arm')
def test_examples_semihost_vfs(env, extra_data):
rel_project_path = os.path.join('examples', 'storage', 'semihost_vfs')
dut = env.get_dut('semihost_vfs', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
host_file_name = 'host_file.txt'
try:
temp_dir = tempfile.mkdtemp()
host_file_path = os.path.join(proj_path, 'data', host_file_name)
shutil.copyfile(host_file_path, os.path.join(temp_dir, host_file_name))
cfg_cmds = ['set ESP_SEMIHOST_BASEDIR "{}"'.format(temp_dir)]
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target, cfg_cmds=cfg_cmds):
dut.start_app()
dut.expect_all('example: Switch to semihosted stdout',
'example: Switched back to UART stdout',
'example: Wrote 2798 bytes',
'====================== HOST DATA START =========================',
timeout=20)
with open(host_file_path) as f:
file_content = [line.strip() for line in f]
dut.expect_all(*file_content, timeout=20)
dut.expect_all('====================== HOST DATA END =========================',
'example: Read 6121 bytes',
timeout=5)
with open(os.path.join(temp_dir, 'esp32_stdout.txt')) as f:
def expected_content():
yield 'example: Switched to semihosted stdout'
for i in range(100):
yield 'Semihosted stdout write {}'.format(i)
yield 'example: Switch to UART stdout'
for actual, expected in zip_longest(f, expected_content(), fillvalue='-'):
if expected not in actual: # "in" used because of the printed ASCII color codes
raise RuntimeError('"{}" != "{}"'.format(expected, actual.strip()))
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
if __name__ == '__main__':
test_examples_semihost_vfs()

View File

@@ -1,53 +0,0 @@
from __future__ import unicode_literals
import os
import re
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='test_jtag_arm')
def test_examples_app_trace_to_host(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'app_trace_to_host')
dut = env.get_dut('app_trace_to_host', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
oocd_log_path = os.path.join(proj_path, 'openocd.log')
with ttfw_idf.OCDBackend(oocd_log_path, dut.app.target) as ocd:
dut.start_app()
dut.expect_all('example: Enabling ADC1 on channel 6 / GPIO34.',
'example: Enabling CW generator on DAC channel 0 / GPIO25',
'example: Sampling ADC and sending data to the host...',
re.compile(r'example: Collected \d+ samples in 20 ms.'),
'example: Sampling ADC and sending data to the UART...',
re.compile(r'example: Sample:\d, Value:\d+'),
re.compile(r'example: Collected \d+ samples in 20 ms.'),
timeout=20)
ocd.apptrace_start('file://adc.log 0 9000 5 0 0')
ocd.apptrace_wait_stop(tmo=30)
with open(oocd_log_path) as oocd_log:
cores = 1 if dut.app.get_sdkconfig().get('CONFIG_FREERTOS_UNICORE', '').replace('"','') == 'y' else 2
params_str = 'App trace params: from {} cores'.format(cores)
for line in oocd_log:
if params_str in line:
break
else:
raise RuntimeError('"{}" could not be found in {}'.format(params_str, oocd_log_path))
with ttfw_idf.CustomProcess(' '.join([os.path.join(idf_path, 'tools/esp_app_trace/logtrace_proc.py'),
'adc.log',
os.path.join(dut.app.binary_path, 'app_trace_to_host.elf')]),
logfile='logtrace_proc.log') as logtrace:
logtrace.pexpect_proc.expect_exact('Parse trace file')
logtrace.pexpect_proc.expect_exact('Parsing completed.')
logtrace.pexpect_proc.expect_exact('====================================================================')
logtrace.pexpect_proc.expect(re.compile(r'example: Sample:\d+, Value:\d+'))
logtrace.pexpect_proc.expect_exact('====================================================================')
logtrace.pexpect_proc.expect(re.compile(r'Log records count: \d+'))
if __name__ == '__main__':
test_examples_app_trace_to_host()

View File

@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import os
import re
import time
import pexpect
import pytest
from pytest_embedded.log import DuplicateStdoutPopen, MessageQueue
from pytest_embedded_idf import IdfDut
from pytest_embedded_jtag import OpenOcd
def apptrace_wait_stop(openocd: OpenOcd, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = openocd.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize(
'embedded_services, no_gdb',
[
('esp,idf,jtag', 'y'),
],
indirect=True,
)
def test_examples_app_trace_to_host(msg_queue: MessageQueue, dut: IdfDut, openocd: OpenOcd, idf_path: str) -> None:
dut.expect_exact('example: Enabling ADC1 on channel 6 / GPIO34.')
dut.expect_exact('example: Enabling CW generator on DAC channel 0 / GPIO25')
dut.expect_exact('example: Sampling ADC and sending data to the host...')
dut.expect(r'example: Collected \d+ samples in 20 ms.')
dut.expect_exact('example: Sampling ADC and sending data to the UART...')
dut.expect(r'example: Sample:\d, Value:\d+')
dut.expect(r'example: Collected \d+ samples in 20 ms.')
assert 'Targets connected.' in dut.openocd.write('esp apptrace start file://adc.log 0 9000 5 0 0')
apptrace_wait_stop(dut.openocd)
with open(openocd._logfile) as oocd_log: # pylint: disable=protected-access
cores = 1 if dut.app.sdkconfig.get('CONFIG_FREERTOS_UNICORE') == 'y' else 2
params_str = 'App trace params: from {} cores'.format(cores)
for line in oocd_log:
if params_str in line:
break
else:
raise RuntimeError(
'"{}" could not be found in {}'.format(params_str, openocd._logfile) # pylint: disable=protected-access
)
DuplicateStdoutPopen(
msg_queue,
[
os.path.join(idf_path, 'tools', 'esp_app_trace', 'logtrace_proc.py'),
'adc.log',
os.path.join(dut.app.elf_file),
],
)
dut.expect_exact('Parse trace file')
dut.expect_exact('Parsing completed.')
dut.expect_exact('====================================================================')
dut.expect(re.compile(rb'example: Sample:\d+, Value:\d+'))
dut.expect_exact('====================================================================')
dut.expect(re.compile(rb'Log records count: \d+'))

View File

@@ -1,66 +0,0 @@
from __future__ import unicode_literals
import os
import time
import ttfw_idf
from ttfw_idf import Utility
@ttfw_idf.idf_example_test(env_tag='test_jtag_arm')
def test_examples_gcov(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'gcov')
dut = env.get_dut('gcov', rel_project_path)
idf_path = dut.app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
openocd_cmd_log = os.path.join(proj_path, 'openocd_cmd.log')
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target) as oocd:
dut.start_app()
def expect_counter_output(loop, timeout=10):
dut.expect_all('blink_dummy_func: Counter = {}'.format(loop),
'some_dummy_func: Counter = {}'.format(loop * 2),
timeout=timeout)
expect_counter_output(0, timeout=20)
dut.expect('Ready to dump GCOV data...', timeout=5)
def dump_coverage(cmd):
try:
response = oocd.cmd_exec(cmd)
with open(openocd_cmd_log, 'a') as f:
f.write(response)
assert all(x in response for x in ['Targets connected.',
'gcov_example_main.c.gcda',
'gcov_example_func.c.gcda',
'some_funcs.c.gcda',
'Targets disconnected.',
])
except AssertionError:
# Print what is happening with DUT. Limit the size if it is in loop and generating output.
Utility.console_log(dut.read(size=1000))
raise
# Test two hard-coded dumps
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
expect_counter_output(1)
dut.expect('Ready to dump GCOV data...', timeout=5)
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
for i in range(2, 6):
expect_counter_output(i)
for _ in range(3):
time.sleep(1)
# Test instant run-time dump
dump_coverage('esp gcov')
if __name__ == '__main__':
test_examples_gcov()

View File

@@ -0,0 +1,72 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import os.path
import time
import pytest
from pytest_embedded_idf import IdfDut
from pytest_embedded_jtag import OpenOcd
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize(
'embedded_services, no_gdb',
[
('esp,idf,jtag', 'y'),
],
indirect=True,
)
def test_gcov(dut: IdfDut, openocd: OpenOcd) -> None:
# create the generated .gcda folder, otherwise would have error: failed to open file.
# normally this folder would be created via `idf.py build`. but in CI the non-related files would not be preserved
os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir'), exist_ok=True)
os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'sample', 'CMakeFiles', '__idf_sample.dir'), exist_ok=True)
def expect_counter_output(loop: int, timeout: int = 10) -> None:
dut.expect_exact(
[f'blink_dummy_func: Counter = {loop}', f'some_dummy_func: Counter = {loop * 2}'],
expect_all=True,
timeout=timeout,
)
expect_counter_output(0)
dut.expect('Ready to dump GCOV data...', timeout=5)
def dump_coverage(cmd: str) -> None:
response = openocd.write(cmd)
expect_lines = [
'Targets connected.',
'gcov_example_main.c.gcda',
'gcov_example_func.c.gcda',
'some_funcs.c.gcda',
'Targets disconnected.',
]
for line in response.splitlines():
for expect in expect_lines[:]:
if expect in line:
if expect.endswith('.gcda'): # check file exists
file_path = line.split()[-1].strip("'")
assert os.path.isfile(file_path)
expect_lines.remove(expect)
assert len(expect_lines) == 0
# Test two hard-coded dumps
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
expect_counter_output(1)
dut.expect('Ready to dump GCOV data...', timeout=5)
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
for i in range(2, 6):
expect_counter_output(i)
for _ in range(3):
time.sleep(1)
# Test instant run-time dump
dump_coverage('esp gcov')

View File

@@ -1,66 +0,0 @@
from __future__ import unicode_literals
import os
import re
import tempfile
import time
from io import open
import debug_backend
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='test_jtag_arm')
def test_examples_sysview_tracing(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'sysview_tracing')
dut = env.get_dut('sysview_tracing', rel_project_path)
proj_path = os.path.join(dut.app.idf_path, rel_project_path)
elf_path = os.path.join(dut.app.binary_path, 'sysview_tracing.elf')
def get_temp_file():
with tempfile.NamedTemporaryFile(delete=False) as f:
return f.name
try:
tempfiles = [get_temp_file(), get_temp_file()]
with open(os.path.join(proj_path, 'gdbinit')) as f_in, open(tempfiles[0], 'w') as f_out:
new_content = f_in.read()
# localhost connection issue occurs in docker unless:
new_content = new_content.replace(':3333', '127.0.0.1:3333', 1)
new_content = new_content.replace('file:///tmp/sysview_example.svdat', 'file://{}'.format(tempfiles[1]), 1)
f_out.write(new_content)
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target) as oocd:
dut.start_app()
def dut_expect_task_event():
dut.expect(re.compile(r'example: Task\[0x3[0-9A-Fa-f]+\]: received event \d+'), timeout=30)
dut_expect_task_event()
gdb_log = os.path.join(proj_path, 'gdb.log')
gdb_workdir = os.path.join(proj_path, 'main')
with ttfw_idf.GDBBackend(gdb_log, elf_path, dut.app.target, tempfiles[0], gdb_workdir) as p:
p.gdb.wait_target_state(debug_backend.TARGET_STATE_RUNNING)
stop_reason = p.gdb.wait_target_state(debug_backend.TARGET_STATE_STOPPED)
assert stop_reason == debug_backend.TARGET_STOP_REASON_BP, 'STOP reason: {}'.format(stop_reason)
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
dut_expect_task_event()
# Do a sleep while sysview samples are captured.
time.sleep(3)
# GDBMI isn't responding now to any commands, therefore, the following command is issued to openocd
oocd.cmd_exec('esp sysview stop')
finally:
for x in tempfiles:
try:
os.unlink(x)
except Exception:
pass
if __name__ == '__main__':
test_examples_sysview_tracing()

View File

@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import re
import time
import pexpect.fdpexpect
import pytest
from pytest_embedded_idf import IdfDut
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize(
'embedded_services',
[
'esp,idf,jtag',
],
indirect=True,
)
def test_examples_sysview_tracing(dut: IdfDut) -> None:
def dut_expect_task_event() -> None:
dut.expect(re.compile(rb'example: Task\[0x3[0-9A-Fa-f]+\]: received event \d+'), timeout=30)
dut.gdb.write('mon reset halt')
dut.gdb.write('flushregs')
dut.gdb.write('b app_main')
dut.gdb.write('commands', non_blocking=True)
dut.gdb.write(
'mon esp sysview start file:///tmp/sysview_example0.svdat file:///tmp/sysview_example1.svdat', non_blocking=True
)
dut.gdb.write('c', non_blocking=True)
dut.gdb.write('end')
dut.gdb.write('c', non_blocking=True)
time.sleep(1) # to avoid EOF file error
with open(dut.gdb._logfile) as fr: # pylint: disable=protected-access
gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno())
gdb_pexpect_proc.expect('Thread 2 "main" hit Breakpoint 1, app_main ()')
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
dut_expect_task_event()
# Do a sleep while sysview samples are captured.
time.sleep(3)
# GDB isn't responding now to any commands, therefore, the following command is issued to openocd
dut.openocd.write('esp sysview stop')

View File

@@ -1,64 +0,0 @@
from __future__ import unicode_literals
import os
import re
import tempfile
from io import open
import debug_backend
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag='test_jtag_arm')
def test_examples_sysview_tracing_heap_log(env, extra_data):
rel_project_path = os.path.join('examples', 'system', 'sysview_tracing_heap_log')
dut = env.get_dut('sysview_tracing_heap_log', rel_project_path)
proj_path = os.path.join(dut.app.idf_path, rel_project_path)
elf_path = os.path.join(dut.app.binary_path, 'sysview_tracing_heap_log.elf')
def get_temp_file():
with tempfile.NamedTemporaryFile(delete=False) as f:
return f.name
try:
tempfiles = [get_temp_file(), get_temp_file()]
with open(os.path.join(proj_path, 'gdbinit')) as f_in, open(tempfiles[0], 'w') as f_out:
new_content = f_in.read()
# localhost connection issue occurs in docker unless:
new_content = new_content.replace(':3333', '127.0.0.1:3333', 1)
new_content = new_content.replace('file:///tmp/heap_log.svdat', 'file://{}'.format(tempfiles[1]), 1)
f_out.write(new_content)
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target):
dut.start_app()
dut.expect('esp_apptrace: Initialized TRAX on CPU0')
gdb_log = os.path.join(proj_path, 'gdb.log')
gdb_workdir = os.path.join(proj_path, 'main')
with ttfw_idf.GDBBackend(gdb_log, elf_path, dut.app.target, tempfiles[0], gdb_workdir) as p:
for _ in range(2): # There are two breakpoints
p.gdb.wait_target_state(debug_backend.TARGET_STATE_RUNNING)
stop_reason = p.gdb.wait_target_state(debug_backend.TARGET_STATE_STOPPED)
assert stop_reason == debug_backend.TARGET_STOP_REASON_BP, 'STOP reason: {}'.format(stop_reason)
# dut has been restarted by gdb since the last dut.expect()
dut.expect('esp_apptrace: Initialized TRAX on CPU0')
with ttfw_idf.CustomProcess(' '.join([os.path.join(dut.app.idf_path, 'tools/esp_app_trace/sysviewtrace_proc.py'),
'-p',
'-b', elf_path,
tempfiles[1]]),
logfile='sysviewtrace_proc.log') as sysviewtrace:
sysviewtrace.pexpect_proc.expect(re.compile(r'Found \d+ leaked bytes in \d+ blocks.'), timeout=120)
finally:
for x in tempfiles:
try:
os.unlink(x)
except Exception:
pass
if __name__ == '__main__':
test_examples_sysview_tracing_heap_log()

View File

@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import os.path
import time
import pexpect.fdpexpect
import pytest
from pytest_embedded_idf import IdfDut
TEMP_FILE = os.path.join(os.path.dirname(__file__), 'heap_log.svdat')
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize('embedded_services', [
'esp,idf,jtag',
], indirect=True)
def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
dut.gdb.write('mon reset halt')
dut.gdb.write('flushregs')
dut.gdb.write('tb heap_trace_start')
dut.gdb.write('commands', non_blocking=True)
dut.gdb.write(f'mon esp sysview start file://{TEMP_FILE}', non_blocking=True)
dut.gdb.write('c', non_blocking=True)
dut.gdb.write('end')
dut.gdb.write('tb heap_trace_stop')
dut.gdb.write('commands', non_blocking=True)
dut.gdb.write('mon esp sysview stop', non_blocking=True)
dut.gdb.write('end')
dut.gdb.write('c', non_blocking=True)
dut.expect('esp_apptrace: Initialized TRAX on CPU0')
time.sleep(1) # make sure that the sysview file has been generated
with pexpect.spawn(' '.join([os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'),
'-p',
'-b', dut.app.elf_file,
TEMP_FILE])) as sysviewtrace:
sysviewtrace.expect(r'Found \d+ leaked bytes in \d+ blocks.', timeout=120)
with open(dut.gdb._logfile) as fr: # pylint: disable=protected-access
gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno())
gdb_pexpect_proc.expect_exact(
'Thread 2 "main" hit Temporary breakpoint 1, heap_trace_start (mode_param=HEAP_TRACE_ALL)', timeout=10)
gdb_pexpect_proc.expect_exact('Thread 2 "main" hit Temporary breakpoint 2, heap_trace_stop ()', timeout=10)

View File

@@ -3,6 +3,7 @@
pytest-embedded-serial-esp
pytest-embedded-idf
pytest-embedded-jtag
pytest-embedded-qemu
pytest-rerunfailures
pytest-timeout

View File

@@ -1,14 +1,14 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import os
import re
import signal
import subprocess
import sys
import pexpect
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf import IdfDut
try:
from idf_py_actions.debug_ext import get_openocd_arguments
@@ -18,36 +18,38 @@ except ModuleNotFoundError:
@pytest.mark.supported_targets
@pytest.mark.test_jtag_arm
def test_idf_gdb(dut: Dut) -> None:
@pytest.mark.jtag
def test_idf_gdb(dut: IdfDut) -> None:
# Need to wait a moment to connect via OpenOCD after the hard reset happened.
# Along with this check that app runs ok
dut.expect('Hello world!')
# Don't need to have output from UART any more
# Don't need to have output from UART anymore
dut.serial.stop_redirect_thread()
with open(os.path.join(dut.logdir, 'ocd.txt'), 'w') as ocd_log:
ocd = subprocess.Popen(f'openocd {get_openocd_arguments(dut.target)}', stdout=ocd_log, stderr=ocd_log, shell=True)
cmd = ['openocd', *get_openocd_arguments(dut.target).split()]
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if openocd_scripts:
cmd.extend(['-s', openocd_scripts])
logging.info('Running %s', cmd)
ocd = subprocess.Popen(cmd, stdout=ocd_log, stderr=ocd_log)
try:
gdb_env = os.environ.copy()
gdb_env['ESP_IDF_GDB_TESTING'] = '1'
with open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, \
pexpect.spawn(f'idf.py -B {dut.app.binary_path} gdb --batch',
env=gdb_env,
timeout=60,
logfile=gdb_log,
encoding='utf-8',
codec_errors='ignore') as p:
pexpect.spawn(f'idf.py -B {dut.app.binary_path} gdb --batch',
env=gdb_env,
timeout=60,
logfile=gdb_log,
encoding='utf-8',
codec_errors='ignore') as p:
p.expect(re.compile(r'add symbol table from file.*bootloader.elf'))
p.expect(re.compile(r'add symbol table from file.*rom.elf'))
p.expect_exact('hit Temporary breakpoint 1, app_main ()')
finally:
try:
ocd.send_signal(signal.SIGINT)
ocd.communicate(timeout=15)
except subprocess.TimeoutExpired:
ocd.kill()
ocd.communicate()
ocd.terminate()
ocd.kill()

View File

@@ -1,74 +0,0 @@
from __future__ import unicode_literals
import os
import threading
import time
import debug_backend
import pexpect
import serial
import ttfw_idf
from tiny_test_fw import Utility
class SerialThread(object):
def run(self, log_path, exit_event):
with serial.Serial(os.getenv('ESPPORT', '/dev/ttyUSB1'), 115200) as ser, open(log_path, 'wb') as f:
while True:
f.write(ser.read(ser.in_waiting))
if exit_event.is_set():
break
time.sleep(1)
def __init__(self, log_path):
self.exit_event = threading.Event()
self.t = threading.Thread(target=self.run, args=(log_path, self.exit_event,))
self.t.start()
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.exit_event.set()
self.t.join(60)
if self.t.is_alive():
Utility.console_log('The pyserial thread is still alive', 'O')
@ttfw_idf.idf_custom_test(env_tag='test_jtag_arm', group='test-apps')
def test_app_loadable_elf(env, extra_data):
rel_project_path = os.path.join('tools', 'test_apps', 'system', 'gdb_loadable_elf')
app_files = ['gdb_loadable_elf.elf']
target = 'esp32'
app = ttfw_idf.LoadableElfTestApp(rel_project_path, app_files, target=target)
idf_path = app.get_sdk_path()
proj_path = os.path.join(idf_path, rel_project_path)
elf_path = os.path.join(app.binary_path, 'gdb_loadable_elf.elf')
esp_log_path = os.path.join(proj_path, 'esp.txt')
with SerialThread(esp_log_path):
openocd_log = os.path.join(proj_path, 'openocd.txt')
gdb_log = os.path.join(proj_path, 'gdb.txt')
gdb_init = os.path.join(proj_path, 'gdbinit_' + target)
gdb_dir = os.path.join(proj_path, 'main')
with ttfw_idf.OCDBackend(openocd_log, app.target):
with ttfw_idf.GDBBackend(gdb_log, elf_path, app.target, gdb_init, gdb_dir) as p:
def wait_for_breakpoint():
p.gdb.wait_target_state(debug_backend.TARGET_STATE_RUNNING)
stop_reason = p.gdb.wait_target_state(debug_backend.TARGET_STATE_STOPPED)
assert stop_reason == debug_backend.TARGET_STOP_REASON_BP, 'STOP reason: {}'.format(stop_reason)
wait_for_breakpoint()
p.gdb.add_bp('esp_restart')
p.gdb.exec_continue()
wait_for_breakpoint()
if pexpect.run('grep "Restarting now." {}'.format(esp_log_path), withexitstatus=True)[1]:
raise RuntimeError('Expected output from ESP was not received')
if __name__ == '__main__':
test_app_loadable_elf()

View File

@@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded_idf import IdfDut
@pytest.mark.parametrize('offset', [
pytest.param('0x40007d54', marks=[pytest.mark.esp32]),
# pytest.param('0x4000f6e2', marks=[pytest.mark.esp32s2]),
# pytest.param('0x40047654', marks=[pytest.mark.esp32c3]),
])
@pytest.mark.parametrize('embedded_services, skip_autoflash, erase_all', [
('esp,idf,jtag', 'y', 'y'),
], indirect=True)
@pytest.mark.jtag
def test_loadable_elf(dut: IdfDut, offset: str) -> None:
dut.gdb.write('mon reset halt')
dut.gdb.write(f'thb *{offset}')
assert 'Temporary breakpoint 1, 0x40007d54' in dut.gdb.write('c')
dut.gdb.write('load')
dut.gdb.write('tb app_main')
assert 'Thread 2 "main" hit Temporary breakpoint 2, app_main ()' in dut.gdb.write('c')
dut.gdb.write('tb esp_restart')
assert 'Thread 2 "main" hit Temporary breakpoint 3, esp_restart ()' in dut.gdb.write('c')
dut.expect_exact('Restarting now.')