Separate ignored from uncovered tests in coverage analysis

For historical reasons, the "ignored" tests in outcome analysis are not
actually ignored: they must not be covered, otherwise the script complains
about an unnecessary exception. In coverage analysis, rename this behavior
to "uncovered", and have "ignored" tests be actually ignored. In driver test
parity analysis, which is now only done in the 3.6 LTS branch, keep the
historical behavior

Consuming branches are currently defining `IGNORED_TESTS` with the
expectation that the test cases must be uncovered. They will need to rename
their definition to `UNCOVERED_TESTS`.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
Gilles Peskine
2026-04-05 20:29:58 +02:00
parent 1404bcbb8e
commit 30d14d340e
+25 -8
View File
@@ -220,20 +220,30 @@ class CoverageTask(Task):
"""Analyze test coverage.""" """Analyze test coverage."""
# Test cases whose suite and description are matched by an entry in # Test cases whose suite and description are matched by an entry in
# IGNORED_TESTS are expected to be never executed. # UNCOVERED_TESTS are expected to be never executed.
# Tests matched by IGNORED_TESTS are ignored entierly.
# All other test cases are expected to be executed at least once. # All other test cases are expected to be executed at least once.
UNCOVERED_TESTS: TestCaseSetDescription = {}
IGNORED_TESTS: TestCaseSetDescription = {} IGNORED_TESTS: TestCaseSetDescription = {}
def __init__(self, options) -> None: def __init__(self, options) -> None:
super().__init__(options) super().__init__(options)
self.full_coverage = options.full_coverage #type: bool self.full_coverage = options.full_coverage #type: bool
self.uncovered_tests = TestCaseSet(self.UNCOVERED_TESTS)
self.ignored_tests = TestCaseSet(self.IGNORED_TESTS) self.ignored_tests = TestCaseSet(self.IGNORED_TESTS)
@staticmethod @staticmethod
def section_name() -> str: def section_name() -> str:
return "Analyze coverage" return "Analyze coverage"
def note_ignored_test(self, results: Results,
test_suite: str, test_description: str) -> None:
# pylint: disable=no-self-use # derived classes may need self
"""This method runs for each test case that's available and ignored."""
results.info('Test case was ignored: {};{}',
test_suite, test_description)
def run(self, results: Results, outcomes: Outcomes) -> None: def run(self, results: Results, outcomes: Outcomes) -> None:
"""Check that all available test cases are executed at least once.""" """Check that all available test cases are executed at least once."""
# Make sure that the generated data files are present (and up-to-date). # Make sure that the generated data files are present (and up-to-date).
@@ -254,21 +264,26 @@ class CoverageTask(Task):
for comp_outcomes in outcomes.values()) for comp_outcomes in outcomes.values())
(test_suite, test_description) = suite_case.split(';') (test_suite, test_description) = suite_case.split(';')
ignored = self.ignored_tests.contains(test_suite, test_description) ignored = self.ignored_tests.contains(test_suite, test_description)
if ignored:
self.note_ignored_test(results, test_suite, test_description)
if not hit and not ignored: uncovered = self.uncovered_tests.contains(test_suite, test_description)
if not hit and not uncovered:
if self.full_coverage: if self.full_coverage:
results.error('Test case not executed: {}', suite_case) results.error('Test case not executed: {}', suite_case)
else: else:
results.warning('Test case not executed: {}', suite_case) results.warning('Test case not executed: {}', suite_case)
elif hit and ignored: elif hit and uncovered:
# If a test case is no longer always skipped, we should remove # If a test case is no longer always skipped, we should remove
# it from the ignore list. # it from the ignore list.
if self.full_coverage: if self.full_coverage:
results.error('Test case was executed but marked as ignored for coverage: {}', results.error(
suite_case) 'Test case was executed but marked as uncovered for coverage: {}',
suite_case)
else: else:
results.warning('Test case was executed but marked as ignored for coverage: {}', results.warning(
suite_case) 'Test case was executed but marked as uncovered for coverage: {}',
suite_case)
class DriverVSReference(Task): class DriverVSReference(Task):
@@ -289,7 +304,9 @@ class DriverVSReference(Task):
DRIVER = '' DRIVER = ''
# Ignored test suites (without the test_suite_ prefix). # Ignored test suites (without the test_suite_ prefix).
IGNORED_SUITES = [] #type: typing.List[str] IGNORED_SUITES = [] #type: typing.List[str]
# Ignored test cases. Despite the name, these test case are not
# completely ignored: they must be skipped by drivers, indicating
# a spurious entry.
IGNORED_TESTS: TestCaseSetDescription = {} IGNORED_TESTS: TestCaseSetDescription = {}
def __init__(self, options) -> None: def __init__(self, options) -> None: