Skip to content

Commit 4f63725

Browse files
fundakolkartben
authored andcommitted
twister: refactor twister_main to simplify the system tests
Refactored twister_main.py module to simplify the code and system tests. Removed the need to patch `sys.argv` in blackbox tests. Signed-off-by: Lukasz Fundakowski <[email protected]>
1 parent 654d794 commit 4f63725

File tree

7 files changed

+79
-182
lines changed

7 files changed

+79
-182
lines changed

scripts/pylib/twister/twisterlib/twister_main.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@
99
import shutil
1010
import sys
1111
import time
12+
from collections.abc import Sequence
1213

1314
import colorama
1415
from colorama import Fore
1516
from twisterlib.coverage import run_coverage
16-
from twisterlib.environment import TwisterEnv
17+
from twisterlib.environment import (
18+
TwisterEnv,
19+
add_parse_arguments,
20+
parse_arguments,
21+
python_version_guard,
22+
)
1723
from twisterlib.hardwaremap import HardwareMap
1824
from twisterlib.log_helper import close_logging, setup_logging
1925
from twisterlib.package import Artifacts
@@ -27,7 +33,7 @@ def init_color(colorama_strip):
2733
colorama.init(strip=colorama_strip)
2834

2935

30-
def twister(options: argparse.Namespace, default_options: argparse.Namespace):
36+
def twister(options: argparse.Namespace, default_options: argparse.Namespace) -> int:
3137
start_time = time.time()
3238

3339
# Configure color output
@@ -230,9 +236,17 @@ def twister(options: argparse.Namespace, default_options: argparse.Namespace):
230236
return 0
231237

232238

233-
def main(options: argparse.Namespace, default_options: argparse.Namespace):
239+
def main(argv: Sequence[str] | None = None) -> int:
240+
"""Main function to run twister."""
234241
try:
235-
return_code = twister(options, default_options)
242+
python_version_guard()
243+
244+
parser = add_parse_arguments()
245+
options = parse_arguments(parser, argv)
246+
default_options = parse_arguments(parser, [], on_init=False)
247+
return twister(options, default_options)
236248
finally:
237249
close_logging()
238-
return return_code
250+
if (os.name != "nt") and os.isatty(1):
251+
# (OS is not Windows) and (stdout is interactive)
252+
os.system("stty sane <&1")

scripts/tests/twister_blackbox/test_device.py

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,71 +6,42 @@
66
Blackbox tests for twister's command line functions related to test filtering.
77
"""
88

9-
import importlib
109
from unittest import mock
1110
import os
1211
import pytest
1312
import sys
1413
import re
1514

1615
# pylint: disable=no-name-in-module
17-
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
16+
from conftest import TEST_DATA, suite_filename_mock
1817
from twisterlib.testplan import TestPlan
18+
from twisterlib.twister_main import main as twister_main
1919

2020

2121
class TestDevice:
22-
TESTDATA_1 = [
23-
(
24-
1234,
25-
),
26-
(
27-
4321,
28-
),
29-
(
30-
1324,
31-
)
32-
]
33-
34-
@classmethod
35-
def setup_class(cls):
36-
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
37-
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
38-
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
39-
cls.twister_module = importlib.util.module_from_spec(cls.spec)
40-
41-
@classmethod
42-
def teardown_class(cls):
43-
pass
4422

4523
@pytest.mark.parametrize(
4624
'seed',
47-
TESTDATA_1,
48-
ids=[
49-
'seed 1234',
50-
'seed 4321',
51-
'seed 1324'
52-
],
25+
[1234, 4321, 1324],
5326
)
54-
5527
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
5628
def test_seed(self, capfd, out_path, seed):
5729
test_platforms = ['native_sim']
5830
path = os.path.join(TEST_DATA, 'tests', 'seed_native_sim')
59-
args = ['--no-detailed-test-id', '--outdir', out_path, '-i', '-T', path, '-vv',] + \
60-
['--seed', f'{seed[0]}'] + \
61-
[val for pair in zip(
62-
['-p'] * len(test_platforms), test_platforms
63-
) for val in pair]
6431

65-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
66-
pytest.raises(SystemExit) as sys_exit:
67-
self.loader.exec_module(self.twister_module)
32+
args = [
33+
'--no-detailed-test-id', '--outdir', out_path, '-i', '-T', path, '-vv',
34+
'--seed', f'{seed}',
35+
*[val for pair in zip(['-p'] * len(test_platforms), test_platforms) for val in pair]
36+
]
37+
38+
return_value = twister_main(args)
6839

6940
out, err = capfd.readouterr()
7041
sys.stdout.write(out)
7142
sys.stderr.write(err)
7243

73-
assert str(sys_exit.value) == '1'
44+
assert return_value == 1
7445

75-
expected_line = r'seed_native_sim.dummy\s+FAILED rc=1 \(native (\d+\.\d+)s/seed: {} <host>\)'.format(seed[0])
76-
assert re.search(expected_line, err)
46+
expected_line = r'seed_native_sim.dummy\s+FAILED rc=1 \(native (\d+\.\d+)s/seed: {} <host>\)'.format(seed)
47+
assert re.search(expected_line, err), f'Regex not found: r"{expected_line}"'

scripts/tests/twister_blackbox/test_disable.py

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
Blackbox tests for twister's command line functions related to disable features.
77
"""
88

9-
import importlib
109
import pytest
1110
from unittest import mock
1211
import os
1312
import sys
1413
import re
1514

1615
# pylint: disable=no-name-in-module
17-
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
16+
from conftest import TEST_DATA, suite_filename_mock
1817
from twisterlib.testplan import TestPlan
18+
from twisterlib.twister_main import main as twister_main
1919

2020

2121
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
@@ -41,30 +41,17 @@ class TestDisable:
4141
os.path.join(TEST_DATA, 'tests', 'always_warning'),
4242
['qemu_x86'],
4343
'--disable-warnings-as-errors',
44-
'0'
44+
0
4545
),
4646
(
4747
os.path.join(TEST_DATA, 'tests', 'always_warning'),
4848
['qemu_x86'],
4949
'-v',
50-
'1'
50+
1
5151
),
5252
]
5353

5454

55-
@classmethod
56-
def setup_class(cls):
57-
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
58-
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
59-
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
60-
cls.twister_module = importlib.util.module_from_spec(cls.spec)
61-
62-
63-
@classmethod
64-
def teardown_class(cls):
65-
pass
66-
67-
6855
@pytest.mark.parametrize(
6956
'test_path, test_platforms, flag, expected, expected_none',
7057
TESTDATA_1,
@@ -82,15 +69,14 @@ def test_disable_suite_name_check(self, capfd, out_path, test_path, test_platfor
8269
['-p'] * len(test_platforms), test_platforms
8370
) for val in pair]
8471

85-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
86-
pytest.raises(SystemExit) as sys_exit:
87-
self.loader.exec_module(self.twister_module)
72+
73+
return_value = twister_main(args)
8874

8975
out, err = capfd.readouterr()
9076
sys.stdout.write(out)
9177
sys.stderr.write(err)
9278

93-
assert str(sys_exit.value) == '0'
79+
assert return_value == 0
9480
if expected_none:
9581
assert re.search(expected[0], err) is None, f"Not expected string in log: {expected[0]}"
9682
assert re.search(expected[1], err) is None, f"Not expected: {expected[1]}"
@@ -117,13 +103,12 @@ def test_disable_warnings_as_errors(self, capfd, out_path, test_path, test_platf
117103
['-p'] * len(test_platforms), test_platforms
118104
) for val in pair]
119105

120-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
121-
pytest.raises(SystemExit) as sys_exit:
122-
self.loader.exec_module(self.twister_module)
106+
107+
return_value = twister_main(args)
123108

124109
out, err = capfd.readouterr()
125110
sys.stdout.write(out)
126111
sys.stderr.write(err)
127112

128-
assert str(sys_exit.value) == expected_exit_code, \
129-
f"Twister return not expected ({expected_exit_code}) exit code: ({sys_exit.value})"
113+
assert return_value == expected_exit_code, \
114+
f"Twister return not expected ({expected_exit_code}) exit code: ({return_value})"

scripts/tests/twister_blackbox/test_testlist.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,17 @@
66
Blackbox tests for twister's command line functions related to saving and loading a testlist.
77
"""
88

9-
import importlib
109
from unittest import mock
1110
import os
12-
import pytest
13-
import sys
1411
import json
1512

1613
# pylint: disable=no-name-in-module
17-
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock, clear_log_in_test
14+
from conftest import TEST_DATA, suite_filename_mock, clear_log_in_test
1815
from twisterlib.testplan import TestPlan
16+
from twisterlib.twister_main import main as twister_main
1917

2018

2119
class TestTestlist:
22-
@classmethod
23-
def setup_class(cls):
24-
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
25-
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
26-
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
27-
cls.twister_module = importlib.util.module_from_spec(cls.spec)
28-
29-
@classmethod
30-
def teardown_class(cls):
31-
pass
3220

3321
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
3422
def test_save_tests(self, out_path):
@@ -42,11 +30,7 @@ def test_save_tests(self, out_path):
4230
) for val in pair]
4331

4432
# Save agnostics tests
45-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
46-
pytest.raises(SystemExit) as sys_exit:
47-
self.loader.exec_module(self.twister_module)
48-
49-
assert str(sys_exit.value) == '0'
33+
assert twister_main(args) == 0
5034

5135
clear_log_in_test()
5236

@@ -58,11 +42,7 @@ def test_save_tests(self, out_path):
5842
['-p'] * len(test_platforms), test_platforms
5943
) for val in pair]
6044

61-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
62-
pytest.raises(SystemExit) as sys_exit:
63-
self.loader.exec_module(self.twister_module)
64-
65-
assert str(sys_exit.value) == '0'
45+
assert twister_main(args) == 0
6646

6747
with open(os.path.join(out_path, 'testplan.json')) as f:
6848
j = json.load(f)

scripts/tests/twister_blackbox/test_testplan.py

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,21 @@
66
Blackbox tests for twister's command line functions - those requiring testplan.json
77
"""
88

9-
import importlib
109
from unittest import mock
1110
import os
1211
import pytest
13-
import sys
1412
import json
1513

1614
# pylint: disable=no-name-in-module
17-
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
15+
from conftest import TEST_DATA, suite_filename_mock
1816
from twisterlib.testplan import TestPlan
1917
from twisterlib.error import TwisterRuntimeError
18+
from twisterlib.twister_main import main as twister_main
2019

2120

2221
class TestTestPlan:
2322
TESTDATA_1 = [
24-
('dummy.agnostic.group2.a2_tests.assert1', SystemExit, 4),
23+
('dummy.agnostic.group2.a2_tests.assert1', None, 4),
2524
(
2625
os.path.join('scripts', 'tests', 'twister_blackbox', 'test_data', 'tests',
2726
'dummy', 'agnostic', 'group1', 'subgroup1',
@@ -39,17 +38,6 @@ class TestTestPlan:
3938
(False, 7),
4039
]
4140

42-
@classmethod
43-
def setup_class(cls):
44-
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
45-
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
46-
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
47-
cls.twister_module = importlib.util.module_from_spec(cls.spec)
48-
49-
@classmethod
50-
def teardown_class(cls):
51-
pass
52-
5341
@pytest.mark.parametrize(
5442
'test, expected_exception, expected_subtest_count',
5543
TESTDATA_1,
@@ -65,24 +53,21 @@ def test_subtest(self, out_path, test, expected_exception, expected_subtest_coun
6553
['-p'] * len(test_platforms), test_platforms
6654
) for val in pair]
6755

68-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
69-
pytest.raises(expected_exception) as exc:
70-
self.loader.exec_module(self.twister_module)
71-
72-
if expected_exception != SystemExit:
73-
assert True
74-
return
75-
76-
with open(os.path.join(out_path, 'testplan.json')) as f:
77-
j = json.load(f)
78-
filtered_j = [
79-
(ts['platform'], ts['name'], tc['identifier']) \
80-
for ts in j['testsuites'] \
81-
for tc in ts['testcases'] if 'reason' not in tc
82-
]
83-
84-
assert str(exc.value) == '0'
85-
assert len(filtered_j) == expected_subtest_count
56+
if expected_exception:
57+
with pytest.raises(expected_exception):
58+
twister_main(args)
59+
else:
60+
return_value = twister_main(args)
61+
with open(os.path.join(out_path, 'testplan.json')) as f:
62+
j = json.load(f)
63+
filtered_j = [
64+
(ts['platform'], ts['name'], tc['identifier']) \
65+
for ts in j['testsuites'] \
66+
for tc in ts['testcases'] if 'reason' not in tc
67+
]
68+
69+
assert return_value == 0
70+
assert len(filtered_j) == expected_subtest_count
8671

8772
@pytest.mark.parametrize(
8873
'filter, expected_count',
@@ -98,11 +83,8 @@ def test_filter(self, out_path, filter, expected_count):
9883
['-p'] * len(test_platforms), test_platforms
9984
) for val in pair]
10085

101-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
102-
pytest.raises(SystemExit) as exc:
103-
self.loader.exec_module(self.twister_module)
86+
assert twister_main(args) == 0
10487

105-
assert str(exc.value) == '0'
10688
import pprint
10789
with open(os.path.join(out_path, 'testplan.json')) as f:
10890
j = json.load(f)
@@ -132,11 +114,7 @@ def test_integration(self, out_path, integration, expected_count):
132114
['-p'] * len(test_platforms), test_platforms
133115
) for val in pair]
134116

135-
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
136-
pytest.raises(SystemExit) as exc:
137-
self.loader.exec_module(self.twister_module)
138-
139-
assert str(exc.value) == '0'
117+
assert twister_main(args) == 0
140118

141119
with open(os.path.join(out_path, 'testplan.json')) as f:
142120
j = json.load(f)

0 commit comments

Comments
 (0)