Skip to content

Commit

Permalink
Merge pull request #327 from horw/feat/skip_if_soc_marker
Browse files Browse the repository at this point in the history
feat: added skip_if_soc marker for idf target
  • Loading branch information
hfudev authored Jan 16, 2025
2 parents e299b26 + 5f6e5f6 commit 48bb1b4
Show file tree
Hide file tree
Showing 19 changed files with 232 additions and 2 deletions.
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']

0 comments on commit 48bb1b4

Please sign in to comment.