blob: 5e019d672fc954079a263039dacfdb0fbe1706d9 [file] [log] [blame]
Lukasz Mrugala93a377e2024-02-06 14:44:26 +00001#!/usr/bin/env python3
2# Copyright (c) 2024 Intel Corporation
3#
4# SPDX-License-Identifier: Apache-2.0
5"""
6Blackbox tests for twister's command line functions changing the output files.
7"""
8
9import importlib
10import re
11import mock
12import os
13import shutil
14import pytest
15import sys
16
17from conftest import ZEPHYR_BASE, TEST_DATA, sample_filename_mock, testsuite_filename_mock
18from twisterlib.testplan import TestPlan
19
20
21@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
22@mock.patch.object(TestPlan, 'SAMPLE_FILENAME', sample_filename_mock)
23class TestOutfile:
24 @classmethod
25 def setup_class(cls):
26 apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
27 cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
28 cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
29 cls.twister_module = importlib.util.module_from_spec(cls.spec)
30
31 @classmethod
32 def teardown_class(cls):
33 pass
34
35 @pytest.mark.parametrize(
36 'flag_section, clobber, expect_straggler',
37 [
38 ([], True, False),
39 (['--clobber-output'], False, False),
40 (['--no-clean'], False, True),
41 (['--clobber-output', '--no-clean'], False, True),
42 ],
43 ids=['clobber', 'do not clobber', 'do not clean', 'do not clobber, do not clean']
44 )
45 def test_clobber_output(self, out_path, flag_section, clobber, expect_straggler):
46 test_platforms = ['qemu_x86', 'frdm_k64f']
47 path = os.path.join(TEST_DATA, 'tests', 'dummy')
48 args = ['-i', '--outdir', out_path, '-T', path, '-y'] + \
49 flag_section + \
50 [val for pair in zip(
51 ['-p'] * len(test_platforms), test_platforms
52 ) for val in pair]
53
54 # We create an empty 'blackbox-out' to trigger the clobbering
55 os.mkdir(os.path.join(out_path))
56 # We want to have a single straggler to check for
57 straggler_name = 'atavi.sm'
58 straggler_path = os.path.join(out_path, straggler_name)
59 open(straggler_path, 'a').close()
60
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'
66
67 expected_dirs = ['blackbox-out']
68 if clobber:
69 expected_dirs += ['blackbox-out.1']
70 current_dirs = os.listdir(os.path.normpath(os.path.join(out_path, '..')))
71 print(current_dirs)
72 assert sorted(current_dirs) == sorted(expected_dirs)
73
74 out_contents = os.listdir(os.path.join(out_path))
75 print(out_contents)
76 if expect_straggler:
77 assert straggler_name in out_contents
78 else:
79 assert straggler_name not in out_contents
80
81 def test_runtime_artifact_cleanup(self, out_path):
82 test_platforms = ['qemu_x86', 'frdm_k64f']
83 path = os.path.join(TEST_DATA, 'samples', 'hello_world')
84 args = ['-i', '--outdir', out_path, '-T', path] + \
85 ['--runtime-artifact-cleanup'] + \
86 [] + \
87 [val for pair in zip(
88 ['-p'] * len(test_platforms), test_platforms
89 ) for val in pair]
90
91 with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
92 pytest.raises(SystemExit) as sys_exit:
93 self.loader.exec_module(self.twister_module)
94
95 assert str(sys_exit.value) == '0'
96
97 relpath = os.path.relpath(path, ZEPHYR_BASE)
98 sample_path = os.path.join(out_path, 'qemu_x86', relpath, 'sample.basic.helloworld')
99 listdir = os.listdir(sample_path)
100 zephyr_listdir = os.listdir(os.path.join(sample_path, 'zephyr'))
101
102 expected_contents = ['CMakeFiles', 'handler.log', 'build.ninja', 'CMakeCache.txt',
103 'zephyr', 'build.log']
104 expected_zephyr_contents = ['.config']
105
106 assert all([content in expected_zephyr_contents for content in zephyr_listdir]), \
107 'Cleaned zephyr directory has unexpected files.'
108 assert all([content in expected_contents for content in listdir]), \
109 'Cleaned directory has unexpected files.'
110
111 def test_short_build_path(self, out_path):
112 test_platforms = ['qemu_x86']
113 path = os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2')
114 # twister_links dir does not exist in a dry run.
115 args = ['-i', '--outdir', out_path, '-T', path] + \
116 ['--short-build-path'] + \
117 ['--ninja'] + \
118 [val for pair in zip(
119 ['-p'] * len(test_platforms), test_platforms
120 ) for val in pair]
121
122 relative_test_path = os.path.relpath(path, ZEPHYR_BASE)
123 test_result_path = os.path.join(out_path, 'qemu_x86',
124 relative_test_path, 'dummy.agnostic.group2')
125
126 with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
127 pytest.raises(SystemExit) as sys_exit:
128 self.loader.exec_module(self.twister_module)
129
130 assert str(sys_exit.value) == '0'
131
132 with open(os.path.join(out_path, 'twister.log')) as f:
133 twister_log = f.read()
134
135 pattern_running = r'Running\s+cmake\s+on\s+(?P<full_path>[\\\/].*)\s+for\s+qemu_x86\s*\n'
136 res_running = re.search(pattern_running, twister_log)
137 assert res_running
138
139 # Spaces, forward slashes, etc. in the path as well as CMake peculiarities
140 # require us to forgo simple RegExes.
141 pattern_calling_line = r'Calling cmake: [^\n]+$'
142 res_calling = re.search(pattern_calling_line, twister_log[res_running.end():], re.MULTILINE)
143 calling_line = res_calling.group()
144
145 # HIGHLY DANGEROUS pattern!
146 # If the checked text is not CMake flags only, it is exponential!
147 # Where N is the length of non-flag space-delimited text section.
148 flag_pattern = r'(?:\S+(?: \\)?)+- '
149 cmake_path = shutil.which('cmake')
150 if not cmake_path:
151 assert False, 'Cmake not found.'
152
153 cmake_call_section = r'^Calling cmake: ' + re.escape(cmake_path)
154 calling_line = re.sub(cmake_call_section, '', calling_line)
155 calling_line = calling_line[::-1]
156 flag_iterable = re.finditer(flag_pattern, calling_line)
157
158 for match in flag_iterable:
159 reversed_flag = match.group()
160 flag = reversed_flag[::-1]
161
162 # Build flag
163 if flag.startswith(' -B'):
164 flag_value = flag[3:]
165 build_filename = os.path.basename(os.path.normpath(flag_value))
166 unshortened_build_path = os.path.join(test_result_path, build_filename)
167 assert flag_value != unshortened_build_path, 'Build path unchanged.'
168 assert len(flag_value) < len(unshortened_build_path), 'Build path not shortened.'
169
170 # Pipe flag
171 if flag.startswith(' -DQEMU_PIPE='):
172 flag_value = flag[13:]
173 pipe_filename = os.path.basename(os.path.normpath(flag_value))
174 unshortened_pipe_path = os.path.join(test_result_path, pipe_filename)
175 assert flag_value != unshortened_pipe_path, 'Pipe path unchanged.'
176 assert len(flag_value) < len(unshortened_pipe_path), 'Pipe path not shortened.'
177
178 def test_prep_artifacts_for_testing(self, out_path):
179 test_platforms = ['qemu_x86', 'frdm_k64f']
180 path = os.path.join(TEST_DATA, 'samples', 'hello_world')
181 relative_test_path = os.path.relpath(path, ZEPHYR_BASE)
182 zephyr_out_path = os.path.join(out_path, 'qemu_x86', relative_test_path,
183 'sample.basic.helloworld', 'zephyr')
184 args = ['-i', '--outdir', out_path, '-T', path] + \
185 ['--prep-artifacts-for-testing'] + \
186 [val for pair in zip(
187 ['-p'] * len(test_platforms), test_platforms
188 ) for val in pair]
189
190 with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
191 pytest.raises(SystemExit) as sys_exit:
192 self.loader.exec_module(self.twister_module)
193
194 assert str(sys_exit.value) == '0'
195
196 zephyr_artifact_list = os.listdir(zephyr_out_path)
197
198 # --build-only and normal run leave more files than --prep-artifacts-for-testing
199 # However, the cost of testing that this leaves less seems to outweigh the benefits.
200 # So we'll only check for the most important artifact.
201 assert 'zephyr.elf' in zephyr_artifact_list