blob: 0376769781841a03974d5cd511673e343ddd29a6 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2024 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
"""
Blackbox tests for twister's command line functions changing the output files.
"""
import importlib
import re
import mock
import os
import shutil
import pytest
import sys
import tarfile
# pylint: disable=no-name-in-module
from conftest import ZEPHYR_BASE, TEST_DATA, sample_filename_mock, testsuite_filename_mock
from twisterlib.testplan import TestPlan
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
@mock.patch.object(TestPlan, 'SAMPLE_FILENAME', sample_filename_mock)
class TestOutfile:
@classmethod
def setup_class(cls):
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
cls.twister_module = importlib.util.module_from_spec(cls.spec)
@classmethod
def teardown_class(cls):
pass
@pytest.mark.parametrize(
'flag_section, clobber, expect_straggler',
[
([], True, False),
(['--clobber-output'], False, False),
(['--no-clean'], False, True),
(['--clobber-output', '--no-clean'], False, True),
],
ids=['clobber', 'do not clobber', 'do not clean', 'do not clobber, do not clean']
)
def test_clobber_output(self, out_path, flag_section, clobber, expect_straggler):
test_platforms = ['qemu_x86', 'intel_adl_crb']
path = os.path.join(TEST_DATA, 'tests', 'dummy')
args = ['-i', '--outdir', out_path, '-T', path, '-y'] + \
flag_section + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
# We create an empty 'blackbox-out' to trigger the clobbering
os.mkdir(os.path.join(out_path))
# We want to have a single straggler to check for
straggler_name = 'atavi.sm'
straggler_path = os.path.join(out_path, straggler_name)
open(straggler_path, 'a').close()
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'
expected_dirs = ['blackbox-out']
if clobber:
expected_dirs += ['blackbox-out.1']
current_dirs = os.listdir(os.path.normpath(os.path.join(out_path, '..')))
print(current_dirs)
assert sorted(current_dirs) == sorted(expected_dirs)
out_contents = os.listdir(os.path.join(out_path))
print(out_contents)
if expect_straggler:
assert straggler_name in out_contents
else:
assert straggler_name not in out_contents
def test_runtime_artifact_cleanup(self, out_path):
test_platforms = ['qemu_x86', 'intel_adl_crb']
path = os.path.join(TEST_DATA, 'samples', 'hello_world')
args = ['-i', '--outdir', out_path, '-T', path] + \
['--runtime-artifact-cleanup'] + \
[] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'
relpath = os.path.relpath(path, ZEPHYR_BASE)
sample_path = os.path.join(out_path, 'qemu_x86_atom', relpath, 'sample.basic.helloworld')
listdir = os.listdir(sample_path)
zephyr_listdir = os.listdir(os.path.join(sample_path, 'zephyr'))
expected_contents = ['CMakeFiles', 'handler.log', 'build.ninja', 'CMakeCache.txt',
'zephyr', 'build.log']
expected_zephyr_contents = ['.config']
assert all([content in expected_zephyr_contents for content in zephyr_listdir]), \
'Cleaned zephyr directory has unexpected files.'
assert all([content in expected_contents for content in listdir]), \
'Cleaned directory has unexpected files.'
def test_short_build_path(self, out_path):
test_platforms = ['qemu_x86']
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2')
# twister_links dir does not exist in a dry run.
args = ['-i', '--outdir', out_path, '-T', path] + \
['--short-build-path'] + \
['--ninja'] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
relative_test_path = os.path.relpath(path, ZEPHYR_BASE)
test_result_path = os.path.join(out_path, 'qemu_x86_atom',
relative_test_path, 'dummy.agnostic.group2')
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'
with open(os.path.join(out_path, 'twister.log')) as f:
twister_log = f.read()
pattern_running = r'Running\s+cmake\s+on\s+(?P<full_path>[\\\/].*)\s+for\s+qemu_x86/atom\s*\n'
res_running = re.search(pattern_running, twister_log)
assert res_running
# Spaces, forward slashes, etc. in the path as well as CMake peculiarities
# require us to forgo simple RegExes.
pattern_calling_line = r'Calling cmake: [^\n]+$'
res_calling = re.search(pattern_calling_line, twister_log[res_running.end():], re.MULTILINE)
calling_line = res_calling.group()
# HIGHLY DANGEROUS pattern!
# If the checked text is not CMake flags only, it is exponential!
# Where N is the length of non-flag space-delimited text section.
flag_pattern = r'(?:\S+(?: \\)?)+- '
cmake_path = shutil.which('cmake')
if not cmake_path:
assert False, 'CMake not found.'
cmake_call_section = r'^Calling cmake: ' + re.escape(cmake_path)
calling_line = re.sub(cmake_call_section, '', calling_line)
calling_line = calling_line[::-1]
flag_iterable = re.finditer(flag_pattern, calling_line)
for match in flag_iterable:
reversed_flag = match.group()
flag = reversed_flag[::-1]
# Build flag
if flag.startswith(' -B'):
flag_value = flag[3:]
build_filename = os.path.basename(os.path.normpath(flag_value))
unshortened_build_path = os.path.join(test_result_path, build_filename)
assert flag_value != unshortened_build_path, 'Build path unchanged.'
assert len(flag_value) < len(unshortened_build_path), 'Build path not shortened.'
# Pipe flag
if flag.startswith(' -DQEMU_PIPE='):
flag_value = flag[13:]
pipe_filename = os.path.basename(os.path.normpath(flag_value))
unshortened_pipe_path = os.path.join(test_result_path, pipe_filename)
assert flag_value != unshortened_pipe_path, 'Pipe path unchanged.'
assert len(flag_value) < len(unshortened_pipe_path), 'Pipe path not shortened.'
def test_prep_artifacts_for_testing(self, out_path):
test_platforms = ['qemu_x86', 'intel_adl_crb']
path = os.path.join(TEST_DATA, 'samples', 'hello_world')
relative_test_path = os.path.relpath(path, ZEPHYR_BASE)
zephyr_out_path = os.path.join(out_path, 'qemu_x86_atom', relative_test_path,
'sample.basic.helloworld', 'zephyr')
args = ['-i', '--outdir', out_path, '-T', path] + \
['--prep-artifacts-for-testing'] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'
zephyr_artifact_list = os.listdir(zephyr_out_path)
# --build-only and normal run leave more files than --prep-artifacts-for-testing
# However, the cost of testing that this leaves less seems to outweigh the benefits.
# So we'll only check for the most important artifact.
assert 'zephyr.elf' in zephyr_artifact_list
def test_package_artifacts(self, out_path):
test_platforms = ['qemu_x86']
path = os.path.join(TEST_DATA, 'samples', 'hello_world')
package_name = 'PACKAGE'
package_path = os.path.join(out_path, package_name)
args = ['-i', '--outdir', out_path, '-T', path] + \
['--package-artifacts', package_path] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'
# Check whether we have something as basic as zephyr.elf file
with tarfile.open(package_path, "r") as tar:
assert any([path.endswith('zephyr.elf') for path in tar.getnames()])
# Delete everything but for the package
for clean_up in os.listdir(os.path.join(out_path)):
if not clean_up.endswith(package_name):
clean_up_path = os.path.join(out_path, clean_up)
if os.path.isfile(clean_up_path):
os.remove(clean_up_path)
else:
shutil.rmtree(os.path.join(out_path, clean_up))
# Unpack the package
with tarfile.open(package_path, "r") as tar:
tar.extractall(path=out_path)
# Why does package.py put files inside the out_path folder?
# It forces us to move files up one directory after extraction.
file_names = os.listdir(os.path.join(out_path, os.path.basename(out_path)))
for file_name in file_names:
shutil.move(os.path.join(out_path, os.path.basename(out_path), file_name), out_path)
args = ['-i', '--outdir', out_path, '-T', path] + \
['--test-only'] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
pytest.raises(SystemExit) as sys_exit:
self.loader.exec_module(self.twister_module)
assert str(sys_exit.value) == '0'