Skip to content

Commit 678974b

Browse files
committed
Merge branch 'feature/enable_CI_target_tests' into 'master'
feat: Add pytest target tests Closes RDT-473, ESF-54, and ESF-53 See merge request espressif/esp-serial-flasher!116
2 parents 4c0a4f1 + 6e67989 commit 678974b

File tree

19 files changed

+480
-33
lines changed

19 files changed

+480
-33
lines changed

.gitlab-ci.yml

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ variables:
2222
ZEPHYR_SDK: https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.16.3/zephyr-sdk-0.16.3_linux-aarch64_minimal.tar.xz
2323
PI_PICO_SDK_REV: 1.5.1
2424
PI_PICO_SDK: https://github.com/raspberrypi/pico-sdk
25+
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
26+
PIP_EXTRA_INDEX_URL: "https://dl.espressif.com/pypi"
27+
28+
cache:
29+
paths:
30+
- "$CI_PROJECT_DIR/.cache/pip"
2531

2632
run_pre_commit:
2733
stage: pre_check
@@ -50,19 +56,32 @@ check_stub_source_correctness:
5056
- build
5157
- internet
5258
before_script:
53-
- pip install -U idf-build-apps
59+
- apt install ccache
60+
- pip install -U idf-build-apps --prefer-binary
5461
variables:
62+
IDF_CCACHE_ENABLE: 1
63+
CCACHE_DIR: "/cache/esf_ccache"
64+
CCACHE_SLOPPINESS: "time_macros"
5565
PEDANTIC_FLAGS: "-Werror -Wall -Wextra"
5666
EXTRA_CFLAGS: "${PEDANTIC_FLAGS}"
5767
EXTRA_CXXFLAGS: "${PEDANTIC_FLAGS}"
5868
BUILD_DIR: "build"
5969
script:
70+
# CCACHE_RECACHE Used when invalidating the current cache.
71+
# could be enabled by MR label "ccache:recache"
72+
- |
73+
if [ -n "${CI_MERGE_REQUEST_LABELS}" ] && echo "${CI_MERGE_REQUEST_LABELS}" | grep -q "ccache::recache"; then
74+
export CCACHE_RECACHE="1"
75+
echo "INFO: ccache recache enabled"
76+
fi
6077
- python -m idf_build_apps build -v -p .
6178
--recursive
6279
--exclude ./examples/binaries
6380
--config "sdkconfig.defaults*"
6481
--build-dir ${BUILD_DIR}
6582
--check-warnings
83+
# Show ccache statistics if enabled globally
84+
- test "$CI_CCACHE_STATS" == 1 && test -n "$(which ccache)" && ccache --show-stats -vv || true
6685

6786
build_idf_v4.3:
6887
image: espressif/idf:release-v4.3
@@ -71,20 +90,33 @@ build_idf_v4.3:
7190
- build
7291
- internet
7392
before_script:
74-
- pip install -U idf-build-apps
93+
- apt install ccache
94+
- pip install -U idf-build-apps --prefer-binary
7595
variables:
96+
IDF_CCACHE_ENABLE: 1
97+
CCACHE_DIR: "/cache/esf_ccache"
98+
CCACHE_SLOPPINESS: "time_macros"
7699
PEDANTIC_FLAGS: "-Werror -Wall -Wextra"
77100
EXTRA_CFLAGS: "${PEDANTIC_FLAGS}"
78101
EXTRA_CXXFLAGS: "${PEDANTIC_FLAGS}"
79102
BUILD_DIR: "build"
80103
script:
104+
# CCACHE_RECACHE Used when invalidating the current cache.
105+
# could be enabled by MR label "ccache:recache"
106+
- |
107+
if [ -n "${CI_MERGE_REQUEST_LABELS}" ] && echo "${CI_MERGE_REQUEST_LABELS}" | grep -q "ccache::recache"; then
108+
export CCACHE_RECACHE="1"
109+
echo "INFO: ccache recache enabled"
110+
fi
81111
- python -m idf_build_apps build -v -p .
82112
--recursive
83113
--exclude ./examples/binaries
84114
--target "esp32"
85115
--config "sdkconfig.defaults*"
86116
--build-dir ${BUILD_DIR}
87117
--check-warnings
118+
# Show ccache statistics if enabled globally
119+
- test "$CI_CCACHE_STATS" == 1 && test -n "$(which ccache)" && ccache --show-stats -vv || true
88120

89121
build_idf_v4.4:
90122
image: espressif/idf:release-v4.4
@@ -143,6 +175,11 @@ build_stm32:
143175
# IDF is not necessary for STM32 build, but this image is already used in another job
144176
# and it comes with a recent enough CMake version.
145177
image: espressif/idf:latest
178+
artifacts:
179+
paths:
180+
- "**/build*/*.bin"
181+
when: always
182+
expire_in: 3 days
146183
tags:
147184
- build
148185
- internet
@@ -194,8 +231,15 @@ build_pi_pico:
194231
tags:
195232
- build
196233
- internet
234+
artifacts:
235+
paths:
236+
- "**/build*/*.bin"
237+
- "**/build*/*.elf"
238+
- "**/build*/*.uf2"
239+
when: always
240+
expire_in: 3 days
197241
script:
198-
- git clone --single-branch --depth=1 -b ${PI_PICO_SDK_REV} ${PI_PICO_SDK}
242+
- git clone --recursive --single-branch --depth=1 -b ${PI_PICO_SDK_REV} ${PI_PICO_SDK}
199243
- export PICO_SDK_PATH=$(pwd)/pico-sdk
200244
- wget --no-verbose -O gcc-arm-none-eabi.tar.xz ${ARM_TOOLCHAIN_URL}
201245
- tar xf gcc-arm-none-eabi.tar.xz
@@ -207,7 +251,25 @@ build_pi_pico:
207251
- cmake ..
208252
- cmake --build .
209253

210-
run_tests:
254+
build_pi:
255+
stage: build
256+
image: dtcooper/raspberrypi-os:latest
257+
tags:
258+
- ESF-RPI-01
259+
before_script:
260+
- apt-get update
261+
- apt-get install -y cmake gcc g++ make pigpio
262+
script:
263+
- cd $CI_PROJECT_DIR/examples/raspberry_example
264+
- mkdir build && cd build
265+
- cmake .. && cmake --build .
266+
artifacts:
267+
paths:
268+
- "${CI_PROJECT_DIR}/examples/raspberry_example/build/raspberry_flasher"
269+
when: always
270+
expire_in: 3 days
271+
272+
test_qemu:
211273
stage: test
212274
image: ${CI_DOCKER_REGISTRY}/qemu:esp-develop-20191124
213275
tags:
@@ -218,6 +280,73 @@ run_tests:
218280
- export QEMU_PATH=/opt/qemu/bin/qemu-system-xtensa
219281
- ./run_qemu_test.sh
220282

283+
.test_template:
284+
stage: test
285+
image: debian:latest
286+
before_script:
287+
- apt-get update
288+
- apt-get install -y python3 python3-pip libffi-dev
289+
- pip install -r $CI_PROJECT_DIR/test/requirements_test.txt --break-system-packages --prefer-binary
290+
- pip install esptool --break-system-packages --prefer-binary
291+
artifacts:
292+
paths:
293+
- "${CI_PROJECT_DIR}/examples/pytest_embedded_log/"
294+
when: always
295+
expire_in: 1 week
296+
297+
test_esp1:
298+
extends: .test_template
299+
tags:
300+
- ESF-RPI-02
301+
parallel:
302+
matrix:
303+
- CI_BUILD_FOLDER:
304+
- "build_stable"
305+
- "build_master"
306+
script:
307+
- cd $CI_PROJECT_DIR
308+
- pytest --target=esp32s3 --port=/dev/serial_ports/ESP32S3_ESP32C3 -k 'not test_esp32_spi_load_ram_example'
309+
310+
test_esp2:
311+
extends: .test_template
312+
tags:
313+
- ESF-RPI-01
314+
parallel:
315+
matrix:
316+
- CI_BUILD_FOLDER:
317+
- "build_stable"
318+
- "build_master"
319+
script:
320+
- cd $CI_PROJECT_DIR
321+
- pytest --target=esp32 --port=/dev/serial_ports/ESP32_ESP32
322+
- pytest --target=esp32s3 --port=/dev/serial_ports/ESP32S3_ESP32C3 -k 'not test_esp32_usb_cdc_acm_example'
323+
324+
test_stm32:
325+
extends: .test_template
326+
tags:
327+
- ESF-RPI-03
328+
script:
329+
- pytest --target=stm32 --port=/dev/serial_ports/STM32_ESP32
330+
331+
test_pi_pico:
332+
extends: .test_template
333+
tags:
334+
- ESF-RPI-03
335+
script:
336+
- pytest --target=pi_pico --port=/dev/serial_ports/pico_ESP32
337+
338+
test_rpi:
339+
stage: test
340+
image: dtcooper/raspberrypi-os:latest
341+
tags:
342+
- ESF-RPI-01
343+
before_script:
344+
- apt-get update
345+
- apt-get install -y pigpio python3 python3-pip libffi-dev
346+
- pip install -r $CI_PROJECT_DIR/test/requirements_test.txt --break-system-packages --prefer-binary
347+
script:
348+
- pytest --target=raspberry
349+
221350
push_to_the_components_registry:
222351
stage: deploy
223352
image: python:3.11-bookworm

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,11 @@ repos:
2424
hooks:
2525
- id: conventional-precommit-linter
2626
stages: [commit-msg]
27+
- repo: https://github.com/astral-sh/ruff-pre-commit
28+
rev: v0.1.14
29+
hooks:
30+
- id: ruff # Runs ruff linter
31+
args: [--fix, --exit-non-zero-on-fix] # --fix for fixing errors
32+
types: [python]
33+
- id: ruff-format
34+
types: [python]

cmake/gen_stub_sources.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"esp32c6.json", # ESP32C6_CHIP
4141
]
4242

43+
4344
def read_stub_json(json_file):
4445
stub = json.load(json_file)
4546
entry = stub["entry"]
@@ -83,7 +84,10 @@ def read_stub_json(json_file):
8384

8485
if __name__ == "__main__":
8586
# .h and .c file templates
86-
with open(h_template_path, "r") as h_template_file, open(c_template_path, "r") as c_template_file:
87+
with (
88+
open(h_template_path, "r") as h_template_file,
89+
open(c_template_path, "r") as c_template_file,
90+
):
8791
h_template = h_template_file.read()
8892
c_template = c_template_file.read()
8993

@@ -112,7 +116,9 @@ def read_stub_json(json_file):
112116
with open(f"{stub_override_path}/{file_to_download}") as file_path:
113117
cfile.write(read_stub_json(file_path))
114118
else:
115-
with urllib.request.urlopen(f"{stub_download_url}/v{stub_version}/{file_to_download}") as url:
119+
with urllib.request.urlopen(
120+
f"{stub_download_url}/v{stub_version}/{file_to_download}"
121+
) as url:
116122
cfile.write(read_stub_json(url))
117123

118124
cfile.write("};\n" "\n" "#endif\n")

examples/conftest.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import logging
2+
import os
3+
import sys
4+
from datetime import datetime
5+
from typing import List, Optional
6+
7+
import pytest
8+
from pytest_embedded.plugin import multi_dut_fixture
9+
10+
11+
def pytest_configure(config):
12+
target = config.getoption("--target")
13+
14+
if "stm32" in target:
15+
config.option.embedded_services = "serial"
16+
elif "raspberry" in target:
17+
pass
18+
elif "pi_pico" in target:
19+
config.option.embedded_services = "serial"
20+
else:
21+
config.option.embedded_services = "esp,idf"
22+
23+
24+
def pytest_collection_modifyitems(
25+
session: pytest.Session, config: pytest.Config, items: List[pytest.Item]
26+
):
27+
"""Modify test collection based on selected target"""
28+
target = config.getoption("--target")
29+
30+
# Filter the test cases
31+
filtered_items = []
32+
for item in items:
33+
# filter by target
34+
all_markers = [marker.name for marker in item.iter_markers()]
35+
if target not in all_markers:
36+
continue
37+
38+
filtered_items.append(item)
39+
items[:] = filtered_items
40+
41+
42+
@pytest.fixture(scope="session", autouse=True)
43+
def session_tempdir() -> str:
44+
_tmpdir = os.path.join(
45+
os.path.dirname(__file__),
46+
"pytest_embedded_log",
47+
datetime.now().strftime("%Y-%m-%d_%H-%M-%S"),
48+
)
49+
os.makedirs(_tmpdir, exist_ok=True)
50+
return _tmpdir
51+
52+
53+
@pytest.fixture
54+
@multi_dut_fixture
55+
def build_dir(
56+
request: pytest.FixtureRequest, app_path: str, target: Optional[str]
57+
) -> str:
58+
"""
59+
Check local build dir and return the valid one
60+
61+
Returns:
62+
valid build directory
63+
"""
64+
check_dirs = []
65+
build_folder = os.getenv("CI_BUILD_FOLDER")
66+
if build_folder is not None:
67+
check_dirs.append(build_folder)
68+
check_dirs.append("build")
69+
70+
for check_dir in check_dirs:
71+
binary_path = os.path.join(app_path, check_dir)
72+
if os.path.isdir(binary_path):
73+
logging.info(f"find valid binary path: {binary_path}")
74+
return check_dir
75+
76+
logging.warning(
77+
"checking binary path: %s... missing... try another place", binary_path
78+
)
79+
80+
logging.error("no build dir valid. Please build the binary and run pytest again.")
81+
sys.exit(1)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import pytest
2+
from pytest_embedded import Dut
3+
4+
5+
@pytest.mark.esp32
6+
def test_esp32_example(dut: Dut) -> None:
7+
for i in range(3):
8+
dut.expect("Finished programming")
9+
dut.expect("Hello world!")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import pytest
2+
from pytest_embedded import Dut
3+
4+
5+
@pytest.mark.esp32
6+
def test_esp32_load_ram_example(dut: Dut) -> None:
7+
dut.expect("Finished loading")
8+
dut.expect("Hello world!")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import pytest
2+
from pytest_embedded import Dut
3+
4+
5+
@pytest.mark.esp32s3
6+
def test_esp32_spi_load_ram_example(dut: Dut) -> None:
7+
dut.expect("Finished loading")
8+
dut.expect("Hello world!")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pytest
2+
from pytest_embedded import Dut
3+
4+
5+
@pytest.mark.esp32
6+
def test_esp32_stub_example(dut: Dut) -> None:
7+
dut.expect("Finished programming")
8+
for i in range(3):
9+
dut.expect("Flash verified")
10+
dut.expect("Hello world!")

examples/esp32_usb_cdc_acm_example/main/main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ void app_main(void)
9696
device_disconnected_sem = xSemaphoreCreateBinary();
9797
assert(device_disconnected_sem);
9898

99+
// Delay as target may not be ready to accept commands immediately after connection
100+
loader_port_delay_ms(100);
101+
99102
/* The ESP32-S3 ignores the line coding set commands,
100103
so we don't set the higher baudrate argument */
101104
if (connect_to_target(0) == ESP_LOADER_SUCCESS) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import pytest
2+
from pytest_embedded import Dut
3+
4+
5+
@pytest.mark.esp32s3
6+
def test_esp32_usb_cdc_acm_example(dut: Dut) -> None:
7+
dut.expect("Finished programming")
8+
for i in range(3):
9+
dut.expect("Flash verified")

0 commit comments

Comments
 (0)