Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(junit-merger): add flag to preserve Python test cases and "is_unity_case" attribute #324

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
4 changes: 2 additions & 2 deletions pytest-embedded-idf/pytest_embedded_idf/unity_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,9 +787,9 @@ def get_merge_data(test_cases_attr: t.List[t.Dict]) -> t.Dict:
continue

if k not in output:
output[k] = [f'[dut-{ind}]: {val}']
output[k] = [val]
else:
output[k].append(f'[dut-{ind}]: {val}')
output[k].append(val)

for k, val in output.items():
if k in ('file', 'line'):
Expand Down
90 changes: 90 additions & 0 deletions pytest-embedded-idf/tests/test_idf.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,3 +897,93 @@ def test_erase_all_with_port_cache_case2(dut):
)

result.assert_outcomes(passed=2)


def test_no_preserve_python_tests(testdir):
testdir.makepyfile(r"""
def test_python_case(dut):
dut.run_all_single_board_cases(name=["normal_case1", "multiple_stages_test"])
""")

testdir.runpytest(
'-s',
'--embedded-services', 'esp,idf',
'--app-path', os.path.join(testdir.tmpdir, 'unit_test_app_esp32'),
'--log-cli-level', 'DEBUG',
'--junitxml', 'report.xml',
)

junit_report = ET.parse('report.xml').getroot()[0]

assert junit_report.attrib['tests'] == '2'
for testcase in junit_report.findall('testcase'):
assert testcase.attrib['is_unity_case'] == '1'

def test_preserve_python_tests(testdir):
testdir.makepyfile(r"""
def test_python_case(dut):
dut.run_all_single_board_cases(name=["normal_case1", "multiple_stages_test"])
""")

testdir.runpytest(
'-s',
'--embedded-services', 'esp,idf',
'--app-path', os.path.join(testdir.tmpdir, 'unit_test_app_esp32'),
'--log-cli-level', 'DEBUG',
'--junitxml', 'report.xml',
'--unity-test-report-mode', 'merge',
)

junit_report = ET.parse('report.xml').getroot()[0]

assert junit_report.attrib['tests'] == '2'
assert junit_report[0].attrib['is_unity_case'] == '0'
for testcase in junit_report[1:]:
assert testcase.attrib['is_unity_case'] == '1'


def test_preserve_python_tests_with_failures(testdir):
testdir.makepyfile(r"""
def test_python_case(dut):
dut.run_all_single_board_cases(name=["normal_case1", "normal_case2"])
""")

testdir.runpytest(
'-s',
'--embedded-services', 'esp,idf',
'--app-path', os.path.join(testdir.tmpdir, 'unit_test_app_esp32'),
'--log-cli-level', 'DEBUG',
'--junitxml', 'report.xml',
'--unity-test-report-mode', 'merge',
)

junit_report = ET.parse('report.xml').getroot()[0]

assert junit_report.attrib['failures'] == '1'
assert junit_report[0].attrib['is_unity_case'] == '0' # Python test case is preserved
assert junit_report[1].attrib['is_unity_case'] == '1' # C test case
assert junit_report[1].find('failure') is None # normal_case1 passed
assert junit_report[2].attrib['is_unity_case'] == '1'
assert junit_report[2].find('failure') is not None # normal_case2 failed


def test_python_func_attribute(testdir):
testdir.makepyfile(r"""
def test_python_case(dut):
dut.run_all_single_board_cases(name=["normal_case1", "multiple_stages_test"])
""")

testdir.runpytest(
'-s',
'--embedded-services', 'esp,idf',
'--app-path', os.path.join(testdir.tmpdir, 'unit_test_app_esp32'),
'--log-cli-level', 'DEBUG',
'--junitxml', 'report.xml',
'--unity-test-report-mode', 'merge',
)

junit_report = ET.parse('report.xml').getroot()[0]

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
16 changes: 14 additions & 2 deletions pytest-embedded/pytest_embedded/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
wokwi_gn,
)
from .log import MessageQueue, PexpectProcess
from .unity import JunitMerger, escape_illegal_xml_chars
from .unity import JunitMerger, UnityTestReportMode, escape_illegal_xml_chars
from .utils import (
SERVICE_LIB_NAMES,
ClassCliOptions,
Expand Down Expand Up @@ -111,6 +111,16 @@ def pytest_addoption(parser):
help='y/yes/true for True and n/no/false for False. '
'Set to True to prettify XML junit report. (Default: False)',
)
parser.addoption(
'--unity-test-report-mode',
choices=[UnityTestReportMode.REPLACE.value, UnityTestReportMode.MERGE.value],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
choices=[UnityTestReportMode.REPLACE.value, UnityTestReportMode.MERGE.value],
choices=[v.value for v in UnityTestReportMode],

default=UnityTestReportMode.REPLACE.value,
help=(
'Specify the behavior for handling Unity test cases in the main JUnit report. '
"'merge' includes them alongside the parent Python test case. "
"'replace' substitutes the parent Python test case with Unity test cases (default)."
),
)

# supports parametrization
base_group.addoption('--root-logdir', help='set session-based root log dir. (Default: system temp folder)')
Expand Down Expand Up @@ -1183,7 +1193,9 @@ def unity_tester(dut: t.Union['IdfDut', t.Tuple['IdfDut']]) -> t.Optional['CaseT


def pytest_configure(config: Config) -> None:
config.stash[_junit_merger_key] = JunitMerger(config.option.xmlpath)
config.stash[_junit_merger_key] = JunitMerger(
config.option.xmlpath, config.getoption('unity_test_report_mode', default='replace')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missed one

)
config.stash[_junit_report_path_key] = config.option.xmlpath

config.stash[_pytest_embedded_key] = PytestEmbedded(
Expand Down
14 changes: 12 additions & 2 deletions pytest-embedded/pytest_embedded/unity.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ class TestFormat(enum.Enum):
FIXTURE = 1


class UnityTestReportMode(str, enum.Enum):
REPLACE = 'replace'
MERGE = 'merge'


class TestCase:
def __init__(self, name: str, result: str, **kwargs):
self.name = name
Expand Down Expand Up @@ -206,8 +211,9 @@ class JunitMerger:
SUB_JUNIT_FILENAME = 'dut.xml'
# multi-dut junit reports should be dut-[INDEX].xml

def __init__(self, main_junit: Optional[str]) -> None:
def __init__(self, main_junit: Optional[str], unity_test_report_mode: Optional[str] = None) -> None:
self.junit_path = main_junit
self.unity_test_report_mode = unity_test_report_mode or UnityTestReportMode.REPLACE.value

self._junit = None

Expand Down Expand Up @@ -291,9 +297,13 @@ def merge(self, junit_files: List[str]):
raise ValueError(f'Could\'t find test case {test_case_name}, dumped into "debug.xml" for debugging')

junit_case_is_fail = junit_case.find('failure') is not None
junit_parent.remove(junit_case)

junit_case.attrib['is_unity_case'] = '0'
if self.unity_test_report_mode == UnityTestReportMode.REPLACE.value:
junit_parent.remove(junit_case)

for case in merging_cases:
case.attrib['is_unity_case'] = '1'
junit_parent.append(case)

junit_parent.attrib['errors'] = self._int_add(
Expand Down
Loading