Lukasz Mrugala | 93a377e | 2024-02-06 14:44:26 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright (c) 2024 Intel Corporation |
| 3 | # |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | """ |
| 6 | Blackbox tests for twister's command line functions changing the output files. |
| 7 | """ |
| 8 | |
| 9 | import importlib |
| 10 | import re |
| 11 | import mock |
| 12 | import os |
| 13 | import shutil |
| 14 | import pytest |
| 15 | import sys |
| 16 | |
| 17 | from conftest import ZEPHYR_BASE, TEST_DATA, sample_filename_mock, testsuite_filename_mock |
| 18 | from 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) |
| 23 | class 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 |