Skip to content

Commit e11aeca

Browse files
golowanownashif
authored andcommitted
twister: fix Ztest C++ test names extraction from ELF
Fix Ztest test function name extraction from ELF symbols for C++ compiled binaries where symbol names need additional 'demangling' to match with corresponding test names. The `c++filt` utility (part of binutils) is called for demangling when it is needed. Twister test suite extension and adjustment. Signed-off-by: Dmitrii Golovanov <[email protected]>
1 parent 18451bc commit e11aeca

File tree

21 files changed

+288
-35
lines changed

21 files changed

+288
-35
lines changed

scripts/pylib/twister/twisterlib/runner.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,10 @@ def __init__(self, instance: TestInstance, env: TwisterEnv, jobserver, **kwargs)
814814
self.env = env
815815
self.duts = None
816816

817+
@property
818+
def trace(self) -> bool:
819+
return self.options.verbose > 2
820+
817821
def log_info(self, filename, inline_logs, log_testcases=False):
818822
filename = os.path.abspath(os.path.realpath(filename))
819823
if inline_logs:
@@ -1087,6 +1091,18 @@ def process(self, pipeline, done, message, lock, results):
10871091
self.instance.reason = reason
10881092
self.instance.add_missing_case_status(TwisterStatus.BLOCK, reason)
10891093

1094+
def demangle(self, symbol_name):
1095+
if symbol_name[:2] == '_Z':
1096+
try:
1097+
cpp_filt = subprocess.run('c++filt', input=symbol_name, text=True, check=True,
1098+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1099+
if self.trace:
1100+
logger.debug(f"Demangle: '{symbol_name}'==>'{cpp_filt.stdout}'")
1101+
return cpp_filt.stdout.strip()
1102+
except Exception as e:
1103+
logger.error(f"Failed to demangle '{symbol_name}': {e}")
1104+
return symbol_name
1105+
10901106
def determine_testcases(self, results):
10911107
yaml_testsuite_name = self.instance.testsuite.id
10921108
logger.debug(f"Determine test cases for test suite: {yaml_testsuite_name}")
@@ -1102,19 +1118,22 @@ def determine_testcases(self, results):
11021118
for sym in section.iter_symbols():
11031119
# It is only meant for new ztest fx because only new ztest fx exposes test functions
11041120
# precisely.
1105-
1121+
m_ = new_ztest_unit_test_regex.search(sym.name)
1122+
if not m_:
1123+
continue
1124+
# Demangle C++ symbols
1125+
m_ = new_ztest_unit_test_regex.search(self.demangle(sym.name))
1126+
if not m_:
1127+
continue
11061128
# The 1st capture group is new ztest suite name.
11071129
# The 2nd capture group is new ztest unit test name.
1108-
matches = new_ztest_unit_test_regex.findall(sym.name)
1109-
if matches:
1110-
for m in matches:
1111-
new_ztest_suite = m[0]
1112-
if new_ztest_suite not in self.instance.testsuite.ztest_suite_names:
1113-
logger.warning(f"Unexpected Ztest suite '{new_ztest_suite}' "
1114-
f"not present in: {self.instance.testsuite.ztest_suite_names}")
1115-
test_func_name = m[1].replace("test_", "", 1)
1116-
testcase_id = f"{yaml_testsuite_name}.{new_ztest_suite}.{test_func_name}"
1117-
detected_cases.append(testcase_id)
1130+
new_ztest_suite = m_[1]
1131+
if new_ztest_suite not in self.instance.testsuite.ztest_suite_names:
1132+
logger.warning(f"Unexpected Ztest suite '{new_ztest_suite}' "
1133+
f"not present in: {self.instance.testsuite.ztest_suite_names}")
1134+
test_func_name = m_[2].replace("test_", "", 1)
1135+
testcase_id = f"{yaml_testsuite_name}.{new_ztest_suite}.{test_func_name}"
1136+
detected_cases.append(testcase_id)
11181137

11191138
if detected_cases:
11201139
logger.debug(f"Detected Ztest cases: [{', '.join(detected_cases)}] in {elf_file}")

scripts/tests/twister/test_runner.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def mocked_instance(tmp_path):
5656
def mocked_env():
5757
env = mock.Mock()
5858
options = mock.Mock()
59+
options.verbose = 2
5960
env.options = options
6061
return env
6162

@@ -1571,6 +1572,24 @@ def mock_determine_testcases(res):
15711572
('dummy_id.dummy_suite2_name.dummy_name2')
15721573
]
15731574
),
1575+
(
1576+
[
1577+
'z_ztest_unit_test__dummy_suite2_name__test_dummy_name2',
1578+
'z_ztest_unit_test__bad_suite3_name_no_test',
1579+
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name4E',
1580+
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_bad_name1E',
1581+
'_ZN12_GLOBAL__N_1L51z_ztest_unit_test_dummy_suite3_name__test_bad_name2E',
1582+
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name5E',
1583+
'_ZN15foobarnamespaceL54z_ztest_unit_test__dummy_suite3_name__test_dummy_name6E',
1584+
],
1585+
[
1586+
('dummy_id.dummy_suite2_name.dummy_name2'),
1587+
('dummy_id.dummy_suite3_name.dummy_name4'),
1588+
('dummy_id.dummy_suite3_name.bad_name1E'),
1589+
('dummy_id.dummy_suite3_name.dummy_name5'),
1590+
('dummy_id.dummy_suite3_name.dummy_name6'),
1591+
]
1592+
),
15741593
(
15751594
['no match'],
15761595
[]
@@ -1580,10 +1599,11 @@ def mock_determine_testcases(res):
15801599
@pytest.mark.parametrize(
15811600
'symbols_names, added_tcs',
15821601
TESTDATA_7,
1583-
ids=['two hits, one miss', 'nothing']
1602+
ids=['two hits, one miss', 'demangle', 'nothing']
15841603
)
15851604
def test_projectbuilder_determine_testcases(
15861605
mocked_jobserver,
1606+
mocked_env,
15871607
symbols_names,
15881608
added_tcs
15891609
):
@@ -1603,9 +1623,8 @@ def test_projectbuilder_determine_testcases(
16031623
instance_mock.testcases = []
16041624
instance_mock.testsuite.id = 'dummy_id'
16051625
instance_mock.testsuite.ztest_suite_names = []
1606-
env_mock = mock.Mock()
16071626

1608-
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
1627+
pb = ProjectBuilder(instance_mock, mocked_env, mocked_jobserver)
16091628

16101629
with mock.patch('twisterlib.runner.ELFFile', elf_mock), \
16111630
mock.patch('builtins.open', mock.mock_open()):

scripts/tests/twister_blackbox/test_data/test_config.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ levels:
66
description: >
77
A plan to be used verifying basic features
88
adds:
9-
- dummy.agnostic.*
9+
- dummy.agnostic\..*
1010
- name: acceptance
1111
description: >
1212
More coverage
1313
adds:
14-
- dummy.*
14+
- dummy.agnostic\..*
15+
- dummy.device\..*
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(integration)
6+
7+
FILE(GLOB app_sources src/*.cpp)
8+
target_sources(app PRIVATE ${app_sources})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_ZTEST=y
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (c) 2023-2024 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
9+
10+
// global namespace
11+
12+
ZTEST_SUITE(a1_1_tests, NULL, NULL, NULL, NULL, NULL);
13+
14+
/**
15+
* @brief Test Asserts
16+
*
17+
* This test verifies various assert macros provided by ztest.
18+
*
19+
*/
20+
ZTEST(a1_1_tests, test_assert)
21+
{
22+
zassert_true(1, "1 was false");
23+
zassert_false(0, "0 was true");
24+
zassert_is_null(NULL, "NULL was not NULL");
25+
zassert_not_null("foo", "\"foo\" was NULL");
26+
zassert_equal(1, 1, "1 was not equal to 1");
27+
zassert_equal_ptr(NULL, NULL, "NULL was not equal to NULL");
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests:
2+
dummy.agnostic_cpp.group1.subgroup1:
3+
platform_allow:
4+
- native_sim
5+
integration_platforms:
6+
- native_sim
7+
tags:
8+
- agnostic
9+
- cpp
10+
- subgrouped
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(integration)
6+
7+
FILE(GLOB app_sources src/*.cpp)
8+
target_sources(app PRIVATE ${app_sources})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_ZTEST=y
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2023-2024 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
9+
// global namespace
10+
11+
ZTEST_SUITE(a1_2_tests, NULL, NULL, NULL, NULL, NULL);
12+
13+
/**
14+
* @brief Test Asserts
15+
*
16+
* This test verifies various assert macros provided by ztest.
17+
*
18+
*/
19+
ZTEST(a1_2_tests, test_assert)
20+
{
21+
zassert_true(1, "1 was false");
22+
zassert_false(0, "0 was true");
23+
zassert_is_null(NULL, "NULL was not NULL");
24+
zassert_not_null("foo", "\"foo\" was NULL");
25+
zassert_equal(1, 1, "1 was not equal to 1");
26+
zassert_equal_ptr(NULL, NULL, "NULL was not equal to NULL");
27+
}

0 commit comments

Comments
 (0)