Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,5 @@ jobs:
- uses: actions/upload-artifact@v4
if: failure()
with:
name: logs-${{ python-version }}
name: logs-${{ matrix.python-version }}
path: logs.zip
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ def copy_fixtures(testdir: Testdir):
yield


@pytest.fixture()
def copy_mock_esp_idf(testdir: Testdir):
esp_idf = os.path.join(os.path.dirname(__file__), 'tests', 'esp-idf')
for item in os.listdir(esp_idf):
if os.path.isfile(os.path.join(esp_idf, item)):
shutil.copy(os.path.join(esp_idf, item), os.path.join(str(testdir.tmpdir), item))
else:
shutil.copytree(os.path.join(esp_idf, item), os.path.join(str(testdir.tmpdir), item))

yield


@pytest.fixture(autouse=True)
def cache_file_remove(cache_dir):
yield
Expand Down
63 changes: 63 additions & 0 deletions docs/usages/markers.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
##################
Addition Markers
##################

``pytest-embedded`` provides additional markers to enhance testing functionality.

*****************
``skip_if_soc``
*****************

The ``skip_if_soc`` marker allows you to skip tests based on the ``soc_caps`` (system-on-chip capabilities) of a target device. These capabilities are defined in the ``esp-idf``. For example, for the ESP32, you can reference them in the ``soc_caps.h`` file: `soc_caps.h <https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/soc_caps.h>`_.

Use Case
========

Imagine you have multiple targets, such as ``[esp32, esp32c3, ..., esp32s4]``. However, you may want to skip tests for chips that do not support specific features.

The ``skip_if_soc`` marker simplifies this by allowing you to define conditions based on the ``soc_caps`` property of your chip. This enables dynamic filtering of targets without the need for manual target-specific logic.

Examples
========

Here are examples of how to use ``skip_if_soc`` with different conditions:

**Condition 1**: A boolean expression such as ``SOC_ULP_SUPPORTED != 1 and SOC_UART_NUM != 3``. This skips tests for chips that:

- Do not support the ``low power mode`` feature (``SOC_ULP_SUPPORTED != 1``).
- **And** have a UART number other than 3 (``SOC_UART_NUM != 3``).

.. code:: python

@pytest.mark.skip_if_soc("SOC_ULP_SUPPORTED != 1 and SOC_UART_NUM != 3")
@pytest.mark.parametrize("target", ["esp32", "esp32s2", "esp32c3"], indirect=True)
def test_template_first_condition():
pass

----

**Condition 2**: A boolean expression such as ``SOC_ULP_SUPPORTED != 1 or SOC_UART_NUM != 3``. This skips tests for chips that:

- Either do not support the ``low power mode`` feature (``SOC_ULP_SUPPORTED != 1``).
- **Or** have a UART number other than 3 (``SOC_UART_NUM != 3``).

.. code:: python

@pytest.mark.skip_if_soc("SOC_ULP_SUPPORTED != 1 or SOC_UART_NUM != 3")
@pytest.mark.parametrize("target", ["esp32", "esp32s2", "esp32c3"], indirect=True)
def test_template_second_condition():
pass

----

**Condition 3**: You can use a shortcut to apply this condition to all ESP-IDF supported targets (assuming ``IDF_PATH`` is set).

.. code:: python

import pytest
from esp_bool_parser.constants import SUPPORTED_TARGETS

@pytest.mark.skip_if_soc("SOC_ULP_SUPPORTED != 1")
@pytest.mark.parametrize("target", SUPPORTED_TARGETS, indirect=True)
def test_template():
pass
1 change: 1 addition & 0 deletions pytest-embedded-idf/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ requires-python = ">=3.7"
dependencies = [
"pytest-embedded~=1.12.1",
"esp-idf-panic-decoder",
"esp-bool-parser>=0.1.2,<1"
]

[project.optional-dependencies]
Expand Down
75 changes: 75 additions & 0 deletions pytest-embedded-idf/tests/test_idf.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,3 +987,78 @@ def test_python_case(dut):
assert junit_report[0].attrib['is_unity_case'] == '0' # Python test case
for testcase in junit_report[1:]:
assert testcase.attrib['is_unity_case'] == '1' # Other test cases

def test_esp_bool_parser_returned_values(testdir, copy_mock_esp_idf, monkeypatch): # noqa: ARG001
monkeypatch.setenv('IDF_PATH', str(testdir))
from esp_bool_parser import SOC_HEADERS, SUPPORTED_TARGETS
assert SOC_HEADERS == {
'esp32': {'SOC_A': 0, 'SOC_B': 1, 'SOC_C': 0},
'esp32s2': {'SOC_A': 0, 'SOC_B': 0, 'SOC_C': 0},
'esp32c3': {'SOC_A': 1, 'SOC_B': 1, 'SOC_C': 1},
'esp32s3': {'SOC_A': 1, 'SOC_B': 0, 'SOC_C': 1},
'esp32c2': {'SOC_A': 0, 'SOC_B': 1, 'SOC_C': 0},
'esp32c6': {'SOC_A': 1, 'SOC_B': 0, 'SOC_C': 0},
'esp32h2': {'SOC_A': 0, 'SOC_B': 1, 'SOC_C': 1},
'esp32p4': {'SOC_A': 0, 'SOC_B': 0, 'SOC_C': 1},
'linux': {},
'esp32c5': {'SOC_A': 1, 'SOC_B': 1, 'SOC_C': 0},
'esp32c61': {'SOC_A': 0, 'SOC_B': 0, 'SOC_C': 1},
'esp32h21': {'SOC_A': 0, 'SOC_B': 0, 'SOC_C': 0}
}
assert SUPPORTED_TARGETS == ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2', 'esp32p4']


def test_skip_if_soc(testdir, copy_mock_esp_idf, monkeypatch): # noqa: ARG001
monkeypatch.setenv('IDF_PATH', str(testdir))
from esp_bool_parser import SOC_HEADERS, SUPPORTED_TARGETS

def run_test_for_condition(condition, condition_func):
to_skip = sum([1 for t in SUPPORTED_TARGETS if condition_func(SOC_HEADERS[t])])
to_pass = len(SUPPORTED_TARGETS) - to_skip
testdir.makepyfile(f"""
import pytest
from esp_bool_parser.constants import SUPPORTED_TARGETS

@pytest.mark.skip_if_soc("{condition}")
@pytest.mark.parametrize('target', SUPPORTED_TARGETS, indirect=True)
def test_skip_if_for_condition():
pass
""")

result = testdir.runpytest('-s', '--embedded-services', 'esp,idf')
result.assert_outcomes(passed=to_pass, skipped=to_skip)


for c, cf in [
('SOC_A == 1', lambda h: h['SOC_A'] == 1),
('SOC_A == 1 or SOC_B == 1', lambda h: h['SOC_A'] == 1 or h['SOC_B'] == 1),
('SOC_A == 1 and SOC_B == 1', lambda h: h['SOC_A'] == 1 and h['SOC_B'] == 1),
('SOC_A == 1 or SOC_B == 1 and SOC_C == 1', lambda h: h['SOC_A'] == 1 or (h['SOC_B'] == 1 and h['SOC_C'] == 1)),
('SOC_A == 1 and SOC_B == 0 or SOC_C == 1 ', lambda h: (h['SOC_A'] == 1 and h['SOC_B'] == 0) or h['SOC_C'] == 1), # noqa: E501
]:
run_test_for_condition(c, cf)


def test_skip_if_soc_target_in_args(testdir, copy_mock_esp_idf, monkeypatch): # noqa: ARG001
monkeypatch.setenv('IDF_PATH', str(testdir))

def run_pytest_with_target(target):
count = len(target.split('|'))
return testdir.runpytest( '--embedded-services', 'esp,idf', '--target', target, '--count', count)

testdir.makepyfile("""
import pytest

@pytest.mark.skip_if_soc("SOC_A == 1")
def test_from_args():
pass

""")

results = [
(run_pytest_with_target('auto'), {'passed': 1, 'failed': 0, 'skipped': 0}),
(run_pytest_with_target('esp32|esp32'), {'passed': 1, 'failed': 0, 'skipped': 0}),
]

for result, expected in results:
result.assert_outcomes(**expected)
38 changes: 37 additions & 1 deletion pytest-embedded/pytest_embedded/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import subprocess
import tempfile
import typing as t
import warnings
import xml.dom.minidom
from collections import Counter
from operator import itemgetter
Expand Down Expand Up @@ -1204,6 +1205,7 @@ def pytest_configure(config: Config) -> None:
add_target_as_marker=_str_bool(config.getoption('add_target_as_marker', False)),
)
config.pluginmanager.register(config.stash[_pytest_embedded_key])
config.addinivalue_line('markers', 'skip_if_soc')


def pytest_unconfigure(config: Config) -> None:
Expand Down Expand Up @@ -1252,7 +1254,41 @@ def _duplicate_items(items: t.List[_T]) -> t.List[_T]:
return duplicates

@pytest.hookimpl(hookwrapper=True, trylast=True)
def pytest_collection_modifyitems(self, items: t.List[Function]):
def pytest_collection_modifyitems(self, config: Config, items: t.List[Function]):
for item in items:
skip_marker = item.get_closest_marker('skip_if_soc')
if not skip_marker:
continue
if 'idf' not in map(str.strip, config.getoption('embedded_services').split(',')):
raise ValueError("'skip_if_soc' marker must be used with the 'idf' embedded service.")

from esp_bool_parser import parse_bool_expr

target = config.getoption('--target', None)
if hasattr(item, 'callspec'):
target = item.callspec.params.get('target', None)
if target == 'auto' or not isinstance(target, str):
warnings.warn(
f"Ignoring pytest.mark.skip_if_soc for test item '{item.originalname}': "
"Ensure that 'target' is included in the test's "
"@pytest.mark.parametrize when using 'skip_if_soc', "
'or provide the --target argument '
"when running tests (excluding 'auto' and multi-DUT configurations)."
)
continue
if '|' in target:
warnings.warn(
'Ignoring pytest.mark.skip_if_soc, '
"because multi-DUT tests do not support the 'skip_if_soc' marker. "
'Please adjust the test setup accordingly.'
)
continue

stm = parse_bool_expr(skip_marker.args[0])
if stm.get_value(target, ''):
reason = f'Filtered by {skip_marker.args[0]}, for {target}.'
item.add_marker(pytest.mark.skip(reason=reason))

if self.add_target_as_marker:
for item in items:
item_target = item.callspec.getparam('target')
Expand Down
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 1
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32c2/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 1
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32c3/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 1
#define SOC_B 1
#define SOC_C 1
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32c5/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 1
#define SOC_B 1
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32c6/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 1
#define SOC_B 0
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32c61/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 0
#define SOC_C 1
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32h2/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 1
#define SOC_C 1
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32h21/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 0
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32p4/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 0
#define SOC_C 1
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32s2/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 0
#define SOC_B 0
#define SOC_C 0
3 changes: 3 additions & 0 deletions tests/esp-idf/components/soc/esp32s3/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#define SOC_A 1
#define SOC_B 0
#define SOC_C 1
5 changes: 5 additions & 0 deletions tests/esp-idf/tools/cmake/version.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
set(IDF_VERSION_MAJOR 5)
set(IDF_VERSION_MINOR 5)
set(IDF_VERSION_PATCH 0)

set(ENV{IDF_VERSION} "${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}.${IDF_VERSION_PATCH}")
5 changes: 5 additions & 0 deletions tests/esp-idf/tools/idf_py_actions/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2', 'esp32p4']
PREVIEW_TARGETS = ['linux', 'esp32c5', 'esp32c61', 'esp32h21']
Loading