diff options
Diffstat (limited to 'poky/meta/lib/oeqa')
45 files changed, 1319 insertions, 371 deletions
diff --git a/poky/meta/lib/oeqa/core/runner.py b/poky/meta/lib/oeqa/core/runner.py index a86a706bd9..b683d9b80a 100644 --- a/poky/meta/lib/oeqa/core/runner.py +++ b/poky/meta/lib/oeqa/core/runner.py @@ -357,7 +357,7 @@ class OETestResultJSONHelper(object): os.makedirs(write_dir, exist_ok=True) test_results = self._get_existing_testresults_if_available(write_dir) test_results[result_id] = {'configuration': configuration, 'result': test_result} - json_testresults = json.dumps(test_results, sort_keys=True, indent=4) + json_testresults = json.dumps(test_results, sort_keys=True, indent=1) self._write_file(write_dir, self.testresult_filename, json_testresults) if has_bb: bb.utils.unlockfile(lf) diff --git a/poky/meta/lib/oeqa/core/target/serial.py b/poky/meta/lib/oeqa/core/target/serial.py new file mode 100644 index 0000000000..7c2cd8b248 --- /dev/null +++ b/poky/meta/lib/oeqa/core/target/serial.py @@ -0,0 +1,315 @@ +# +# SPDX-License-Identifier: MIT +# + +import base64 +import logging +import os +from threading import Lock +from . import OETarget + +class OESerialTarget(OETarget): + + def __init__(self, logger, target_ip, server_ip, server_port=0, + timeout=300, serialcontrol_cmd=None, serialcontrol_extra_args=None, + serialcontrol_ps1=None, serialcontrol_connect_timeout=None, + machine=None, **kwargs): + if not logger: + logger = logging.getLogger('target') + logger.setLevel(logging.INFO) + filePath = os.path.join(os.getcwd(), 'remoteTarget.log') + fileHandler = logging.FileHandler(filePath, 'w', 'utf-8') + formatter = logging.Formatter( + '%(asctime)s.%(msecs)03d %(levelname)s: %(message)s', + '%H:%M:%S') + fileHandler.setFormatter(formatter) + logger.addHandler(fileHandler) + + super(OESerialTarget, self).__init__(logger) + + if serialcontrol_ps1: + self.target_ps1 = serialcontrol_ps1 + elif machine: + # fallback to a default value which assumes root@machine + self.target_ps1 = f'root@{machine}:.*# ' + else: + raise ValueError("Unable to determine shell command prompt (PS1) format.") + + if not serialcontrol_cmd: + raise ValueError("Unable to determine serial control command.") + + if serialcontrol_extra_args: + self.connection_script = f'{serialcontrol_cmd} {serialcontrol_extra_args}' + else: + self.connection_script = serialcontrol_cmd + + if serialcontrol_connect_timeout: + self.connect_timeout = serialcontrol_connect_timeout + else: + self.connect_timeout = 10 # default to 10s connection timeout + + self.default_command_timeout = timeout + self.ip = target_ip + self.server_ip = server_ip + self.server_port = server_port + self.conn = None + self.mutex = Lock() + + def start(self, **kwargs): + pass + + def stop(self, **kwargs): + pass + + def get_connection(self): + if self.conn is None: + self.conn = SerialConnection(self.connection_script, + self.target_ps1, + self.connect_timeout, + self.default_command_timeout) + + return self.conn + + def run(self, cmd, timeout=None): + """ + Runs command on target over the provided serial connection. + The first call will open the connection, and subsequent + calls will re-use the same connection to send new commands. + + command: Command to run on target. + timeout: <value>: Kill command after <val> seconds. + None: Kill command default value seconds. + 0: No timeout, runs until return. + """ + # Lock needed to avoid multiple threads running commands concurrently + # A serial connection can only be used by one caller at a time + with self.mutex: + conn = self.get_connection() + + self.logger.debug(f"[Running]$ {cmd}") + # Run the command, then echo $? to get the command's return code + try: + output = conn.run_command(cmd, timeout) + status = conn.run_command("echo $?") + self.logger.debug(f" [stdout]: {output}") + self.logger.debug(f" [ret code]: {status}\n\n") + except SerialTimeoutException as e: + self.logger.debug(e) + output = "" + status = 255 + + # Return to $HOME after each command to simulate a stateless SSH connection + conn.run_command('cd "$HOME"') + + return (int(status), output) + + def copyTo(self, localSrc, remoteDst): + """ + Copies files by converting them to base 32, then transferring + the ASCII text to the target, and decoding it in place on the + target. + + On a 115k baud serial connection, this method transfers at + roughly 30kbps. + """ + with open(localSrc, 'rb') as file: + data = file.read() + + b32 = base64.b32encode(data).decode('utf-8') + + # To avoid shell line limits, send a chunk at a time + SPLIT_LEN = 512 + lines = [b32[i:i+SPLIT_LEN] for i in range(0, len(b32), SPLIT_LEN)] + + with self.mutex: + conn = self.get_connection() + + filename = os.path.basename(localSrc) + TEMP = f'/tmp/{filename}.b32' + + # Create or empty out the temp file + conn.run_command(f'echo -n "" > {TEMP}') + + for line in lines: + conn.run_command(f'echo -n {line} >> {TEMP}') + + # Check to see whether the remoteDst is a directory + is_directory = conn.run_command(f'[[ -d {remoteDst} ]]; echo $?') + if int(is_directory) == 0: + # append the localSrc filename to the end of remoteDst + remoteDst = os.path.join(remoteDst, filename) + + conn.run_command(f'base32 -d {TEMP} > {remoteDst}') + conn.run_command(f'rm {TEMP}') + + return 0, 'Success' + + def copyFrom(self, remoteSrc, localDst): + """ + Copies files by converting them to base 32 on the target, then + transferring the ASCII text to the host. That text is then + decoded here and written out to the destination. + + On a 115k baud serial connection, this method transfers at + roughly 30kbps. + """ + with self.mutex: + b32 = self.get_connection().run_command(f'base32 {remoteSrc}') + + data = base64.b32decode(b32.replace('\r\n', '')) + + # If the local path is a directory, get the filename from + # the remoteSrc path and append it to localDst + if os.path.isdir(localDst): + filename = os.path.basename(remoteSrc) + localDst = os.path.join(localDst, filename) + + with open(localDst, 'wb') as file: + file.write(data) + + return 0, 'Success' + + def copyDirTo(self, localSrc, remoteDst): + """ + Copy recursively localSrc directory to remoteDst in target. + """ + + for root, dirs, files in os.walk(localSrc): + # Create directories in the target as needed + for d in dirs: + tmpDir = os.path.join(root, d).replace(localSrc, "") + newDir = os.path.join(remoteDst, tmpDir.lstrip("/")) + cmd = "mkdir -p %s" % newDir + self.run(cmd) + + # Copy files into the target + for f in files: + tmpFile = os.path.join(root, f).replace(localSrc, "") + dstFile = os.path.join(remoteDst, tmpFile.lstrip("/")) + srcFile = os.path.join(root, f) + self.copyTo(srcFile, dstFile) + + def deleteFiles(self, remotePath, files): + """ + Deletes files in target's remotePath. + """ + + cmd = "rm" + if not isinstance(files, list): + files = [files] + + for f in files: + cmd = "%s %s" % (cmd, os.path.join(remotePath, f)) + + self.run(cmd) + + def deleteDir(self, remotePath): + """ + Deletes target's remotePath directory. + """ + + cmd = "rmdir %s" % remotePath + self.run(cmd) + + def deleteDirStructure(self, localPath, remotePath): + """ + Delete recursively localPath structure directory in target's remotePath. + + This function is useful to delete a package that is installed in the + device under test (DUT) and the host running the test has such package + extracted in tmp directory. + + Example: + pwd: /home/user/tmp + tree: . + └── work + ├── dir1 + │ └── file1 + └── dir2 + + localpath = "/home/user/tmp" and remotepath = "/home/user" + + With the above variables this function will try to delete the + directory in the DUT in this order: + /home/user/work/dir1/file1 + /home/user/work/dir1 (if dir is empty) + /home/user/work/dir2 (if dir is empty) + /home/user/work (if dir is empty) + """ + + for root, dirs, files in os.walk(localPath, topdown=False): + # Delete files first + tmpDir = os.path.join(root).replace(localPath, "") + remoteDir = os.path.join(remotePath, tmpDir.lstrip("/")) + self.deleteFiles(remoteDir, files) + + # Remove dirs if empty + for d in dirs: + tmpDir = os.path.join(root, d).replace(localPath, "") + remoteDir = os.path.join(remotePath, tmpDir.lstrip("/")) + self.deleteDir(remoteDir) + +class SerialTimeoutException(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class SerialConnection: + + def __init__(self, script, target_prompt, connect_timeout, default_command_timeout): + import pexpect # limiting scope to avoid build dependency + self.prompt = target_prompt + self.connect_timeout = connect_timeout + self.default_command_timeout = default_command_timeout + self.conn = pexpect.spawn('/bin/bash', ['-c', script], encoding='utf8') + self._seek_to_clean_shell() + # Disable echo to avoid the need to parse the outgoing command + self.run_command('stty -echo') + + def _seek_to_clean_shell(self): + """ + Attempts to find a clean shell, meaning it is clear and + ready to accept a new command. This is necessary to ensure + the correct output is captured from each command. + """ + import pexpect # limiting scope to avoid build dependency + # Look for a clean shell + # Wait a short amount of time for the connection to finish + pexpect_code = self.conn.expect([self.prompt, pexpect.TIMEOUT], + timeout=self.connect_timeout) + + # if a timeout occurred, send an empty line and wait for a clean shell + if pexpect_code == 1: + # send a newline to clear and present the shell + self.conn.sendline("") + pexpect_code = self.conn.expect(self.prompt) + + def run_command(self, cmd, timeout=None): + """ + Runs command on target over the provided serial connection. + Returns any output on the shell while the command was run. + + command: Command to run on target. + timeout: <value>: Kill command after <val> seconds. + None: Kill command default value seconds. + 0: No timeout, runs until return. + """ + import pexpect # limiting scope to avoid build dependency + # Convert from the OETarget defaults to pexpect timeout values + if timeout is None: + timeout = self.default_command_timeout + elif timeout == 0: + timeout = None # passing None to pexpect is infinite timeout + + self.conn.sendline(cmd) + pexpect_code = self.conn.expect([self.prompt, pexpect.TIMEOUT], timeout=timeout) + + # check for timeout + if pexpect_code == 1: + self.conn.send('\003') # send Ctrl+C + self._seek_to_clean_shell() + raise SerialTimeoutException(f'Timeout executing: {cmd} after {timeout}s') + + return self.conn.before.removesuffix('\r\n') + diff --git a/poky/meta/lib/oeqa/core/target/ssh.py b/poky/meta/lib/oeqa/core/target/ssh.py index 09cdd14c75..d473469384 100644 --- a/poky/meta/lib/oeqa/core/target/ssh.py +++ b/poky/meta/lib/oeqa/core/target/ssh.py @@ -55,14 +55,14 @@ class OESSHTarget(OETarget): def stop(self, **kwargs): pass - def _run(self, command, timeout=None, ignore_status=True): + def _run(self, command, timeout=None, ignore_status=True, raw=False): """ Runs command in target using SSHProcess. """ self.logger.debug("[Running]$ %s" % " ".join(command)) starttime = time.time() - status, output = SSHCall(command, self.logger, timeout) + status, output = SSHCall(command, self.logger, timeout, raw) self.logger.debug("[Command returned '%d' after %.2f seconds]" "" % (status, time.time() - starttime)) @@ -72,7 +72,7 @@ class OESSHTarget(OETarget): return (status, output) - def run(self, command, timeout=None, ignore_status=True): + def run(self, command, timeout=None, ignore_status=True, raw=False): """ Runs command in target. @@ -91,7 +91,7 @@ class OESSHTarget(OETarget): else: processTimeout = self.timeout - status, output = self._run(sshCmd, processTimeout, ignore_status) + status, output = self._run(sshCmd, processTimeout, ignore_status, raw) self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output)) return (status, output) @@ -206,7 +206,7 @@ class OESSHTarget(OETarget): remoteDir = os.path.join(remotePath, tmpDir.lstrip("/")) self.deleteDir(remoteDir) -def SSHCall(command, logger, timeout=None, **opts): +def SSHCall(command, logger, timeout=None, raw=False, **opts): def run(): nonlocal output @@ -265,7 +265,7 @@ def SSHCall(command, logger, timeout=None, **opts): else: output_raw = process.communicate()[0] - output = output_raw.decode('utf-8', errors='ignore') + output = output_raw if raw else output_raw.decode('utf-8', errors='ignore') logger.debug('Data from SSH call:\n%s' % output.rstrip()) # timout or not, make sure process exits and is not hanging @@ -292,7 +292,7 @@ def SSHCall(command, logger, timeout=None, **opts): options = { "stdout": subprocess.PIPE, - "stderr": subprocess.STDOUT, + "stderr": subprocess.STDOUT if not raw else None, "stdin": None, "shell": False, "bufsize": -1, @@ -320,4 +320,4 @@ def SSHCall(command, logger, timeout=None, **opts): logger.debug('Something went wrong, killing SSH process') raise - return (process.returncode, output.rstrip()) + return (process.returncode, output if raw else output.rstrip()) diff --git a/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt b/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt index 2c0bd9a247..9c2677c4cf 100644 --- a/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt +++ b/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt @@ -1,2 +1,8 @@ # These should be reviewed to see if they are still needed cacheinfo: Failed to find cpu0 device node + +# 6.10 restructures sysctl registration such that mips +# registers an empty table and generates harmless warnings: +# failed when register_sysctl_sz sched_fair_sysctls to kernel +# failed when register_sysctl_sz sched_core_sysctls to kernel +failed when register_sysctl_sz sched diff --git a/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt b/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt index b0c0fc9ddf..143db40d63 100644 --- a/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt +++ b/poky/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt @@ -13,6 +13,14 @@ FBIOPUT_VSCREENINFO failed, double buffering disabled # pci 0000:00:00.0: [Firmware Bug]: reg 0x20: invalid BAR (can't size) # pci 0000:00:00.0: [Firmware Bug]: reg 0x24: invalid BAR (can't size) invalid BAR (can't size) +# 6.10+ the invalid BAR warnings are of this format: +# pci 0000:00:00.0: [Firmware Bug]: BAR 0: invalid; can't size +# pci 0000:00:00.0: [Firmware Bug]: BAR 1: invalid; can't size +# pci 0000:00:00.0: [Firmware Bug]: BAR 2: invalid; can't size +# pci 0000:00:00.0: [Firmware Bug]: BAR 3: invalid; can't size +# pci 0000:00:00.0: [Firmware Bug]: BAR 4: invalid; can't size +# pci 0000:00:00.0: [Firmware Bug]: BAR 5: invalid; can't size +invalid; can't size # These should be reviewed to see if they are still needed wrong ELF class diff --git a/poky/meta/lib/oeqa/runtime/cases/parselogs.py b/poky/meta/lib/oeqa/runtime/cases/parselogs.py index 6966923c94..47c77fccd5 100644 --- a/poky/meta/lib/oeqa/runtime/cases/parselogs.py +++ b/poky/meta/lib/oeqa/runtime/cases/parselogs.py @@ -34,7 +34,7 @@ class ParseLogsTest(OERuntimeTestCase): log_locations = ["/var/log/", "/var/log/dmesg", "/tmp/dmesg_output.log"] # The keywords that identify error messages in the log files - errors = ["error", "cannot", "can't", "failed"] + errors = ["error", "cannot", "can't", "failed", "---[ cut here ]---", "No irq handler for vector"] # A list of error messages that should be ignored ignore_errors = [] diff --git a/poky/meta/lib/oeqa/runtime/cases/scp.py b/poky/meta/lib/oeqa/runtime/cases/scp.py index ee97b8ef66..364264369a 100644 --- a/poky/meta/lib/oeqa/runtime/cases/scp.py +++ b/poky/meta/lib/oeqa/runtime/cases/scp.py @@ -25,7 +25,7 @@ class ScpTest(OERuntimeTestCase): os.remove(cls.tmp_path) @OETestDepends(['ssh.SSHTest.test_ssh']) - @OEHasPackage(['openssh-scp']) + @OEHasPackage({'openssh-scp', 'openssh-sftp-server'}) def test_scp_file(self): dst = '/tmp/test_scp_file' diff --git a/poky/meta/lib/oeqa/runtime/cases/ssh.py b/poky/meta/lib/oeqa/runtime/cases/ssh.py index cdbef59500..89d64430e5 100644 --- a/poky/meta/lib/oeqa/runtime/cases/ssh.py +++ b/poky/meta/lib/oeqa/runtime/cases/ssh.py @@ -4,6 +4,9 @@ # SPDX-License-Identifier: MIT # +import time +import signal + from oeqa.runtime.case import OERuntimeTestCase from oeqa.core.decorator.depends import OETestDepends from oeqa.runtime.decorator.package import OEHasPackage @@ -13,12 +16,22 @@ class SSHTest(OERuntimeTestCase): @OETestDepends(['ping.PingTest.test_ping']) @OEHasPackage(['dropbear', 'openssh-sshd']) def test_ssh(self): - (status, output) = self.target.run('sleep 20', timeout=2) - msg='run() timed out but return code was zero.' - self.assertNotEqual(status, 0, msg=msg) - (status, output) = self.target.run('uname -a') - self.assertEqual(status, 0, msg='SSH Test failed: %s' % output) - (status, output) = self.target.run('cat /etc/controllerimage') - msg = "This isn't the right image - /etc/controllerimage " \ - "shouldn't be here %s" % output - self.assertEqual(status, 1, msg=msg) + for i in range(5): + status, output = self.target.run("uname -a", timeout=30) + if status == 0: + break + elif status == 255 or status == -signal.SIGTERM: + # ssh returns 255 only if a ssh error occurs. This could + # be an issue with "Connection refused" because the port + # isn't open yet, and this could check explicitly for that + # here. However, let's keep it simple and just retry for + # all errors a limited amount of times with a sleep to + # give it time for the port to open. + # We sometimes see -15 (SIGTERM) on slow emulation machines too, likely + # from boot/init not being 100% complete, retry for these too. + time.sleep(5) + continue + else: + self.fail("uname failed with \"%s\" (exit code %s)" % (output, status)) + if status != 0: + self.fail("ssh failed with \"%s\" (exit code %s)" % (output, status)) diff --git a/poky/meta/lib/oeqa/runtime/cases/systemd.py b/poky/meta/lib/oeqa/runtime/cases/systemd.py index 80fdae240a..640f28abe9 100644 --- a/poky/meta/lib/oeqa/runtime/cases/systemd.py +++ b/poky/meta/lib/oeqa/runtime/cases/systemd.py @@ -150,12 +150,21 @@ class SystemdServiceTests(SystemdTest): t_thread.start() time.sleep(1) - status, output = self.target.run('pidof sleep') + status, sleep_pid = self.target.run('pidof sleep') # cause segfault on purpose - self.target.run('kill -SEGV %s' % output) - self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % output) + self.target.run('kill -SEGV %s' % sleep_pid) + self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % sleep_pid) - (status, output) = self.target.run('coredumpctl info') + # Give some time to systemd-coredump@.service to process the coredump + for x in range(20): + status, output = self.target.run('coredumpctl list %s' % sleep_pid) + if status == 0: + break + time.sleep(1) + else: + self.fail("Timed out waiting for coredump creation") + + (status, output) = self.target.run('coredumpctl info %s' % sleep_pid) self.assertEqual(status, 0, msg='MiniDebugInfo Test failed: %s' % output) self.assertEqual('sleep_for_duration (busybox.nosuid' in output or 'xnanosleep (sleep.coreutils' in output, True, msg='Call stack is missing minidebuginfo symbols (functions shown as "n/a"): %s' % output) diff --git a/poky/meta/lib/oeqa/runtime/context.py b/poky/meta/lib/oeqa/runtime/context.py index cb7227a8df..daabc44910 100644 --- a/poky/meta/lib/oeqa/runtime/context.py +++ b/poky/meta/lib/oeqa/runtime/context.py @@ -8,6 +8,7 @@ import os import sys from oeqa.core.context import OETestContext, OETestContextExecutor +from oeqa.core.target.serial import OESerialTarget from oeqa.core.target.ssh import OESSHTarget from oeqa.core.target.qemu import OEQemuTarget @@ -60,7 +61,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): runtime_group = self.parser.add_argument_group('runtime options') runtime_group.add_argument('--target-type', action='store', - default=self.default_target_type, choices=['simpleremote', 'qemu'], + default=self.default_target_type, choices=['simpleremote', 'qemu', 'serial'], help="Target type of device under test, default: %s" \ % self.default_target_type) runtime_group.add_argument('--target-ip', action='store', @@ -108,6 +109,8 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): target = OESSHTarget(logger, target_ip, server_ip, **kwargs) elif target_type == 'qemu': target = OEQemuTarget(logger, server_ip, **kwargs) + elif target_type == 'serial': + target = OESerialTarget(logger, target_ip, server_ip, **kwargs) else: # XXX: This code uses the old naming convention for controllers and # targets, the idea it is to leave just targets as the controller @@ -203,8 +206,15 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): super(OERuntimeTestContextExecutor, self)._process_args(logger, args) + td = self.tc_kwargs['init']['td'] + target_kwargs = {} + target_kwargs['machine'] = td.get("MACHINE") or None target_kwargs['qemuboot'] = args.qemu_boot + target_kwargs['serialcontrol_cmd'] = td.get("TEST_SERIALCONTROL_CMD") or None + target_kwargs['serialcontrol_extra_args'] = td.get("TEST_SERIALCONTROL_EXTRA_ARGS") or "" + target_kwargs['serialcontrol_ps1'] = td.get("TEST_SERIALCONTROL_PS1") or None + target_kwargs['serialcontrol_connect_timeout'] = td.get("TEST_SERIALCONTROL_CONNECT_TIMEOUT") or None self.tc_kwargs['init']['target'] = \ OERuntimeTestContextExecutor.getTarget(args.target_type, diff --git a/poky/meta/lib/oeqa/sdk/cases/autotools.py b/poky/meta/lib/oeqa/sdk/cases/autotools.py index 848e9392ec..4bac28f04d 100644 --- a/poky/meta/lib/oeqa/sdk/cases/autotools.py +++ b/poky/meta/lib/oeqa/sdk/cases/autotools.py @@ -7,6 +7,7 @@ import os import tempfile import subprocess +import unittest from oeqa.sdk.case import OESDKTestCase from oeqa.utils.subprocesstweak import errors_have_output @@ -16,6 +17,11 @@ class AutotoolsTest(OESDKTestCase): """ Check that autotools will cross-compile correctly. """ + def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("AutotoolsTest class: SDK doesn't contain a supported C library") + def test_cpio(self): with tempfile.TemporaryDirectory(prefix="cpio-", dir=self.tc.sdk_dir) as testdir: tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftp.gnu.org/gnu/cpio/cpio-2.15.tar.gz") diff --git a/poky/meta/lib/oeqa/sdk/cases/cmake.py b/poky/meta/lib/oeqa/sdk/cases/cmake.py index db7d826a38..cb0944ee99 100644 --- a/poky/meta/lib/oeqa/sdk/cases/cmake.py +++ b/poky/meta/lib/oeqa/sdk/cases/cmake.py @@ -19,6 +19,10 @@ class CMakeTest(OESDKTestCase): """ def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("CMakeTest class: SDK doesn't contain a supported C library") + if not (self.tc.hasHostPackage("nativesdk-cmake") or self.tc.hasHostPackage("cmake-native")): raise unittest.SkipTest("CMakeTest: needs cmake") diff --git a/poky/meta/lib/oeqa/sdk/cases/gcc.py b/poky/meta/lib/oeqa/sdk/cases/gcc.py index fc28b9c3d4..e810d2c42b 100644 --- a/poky/meta/lib/oeqa/sdk/cases/gcc.py +++ b/poky/meta/lib/oeqa/sdk/cases/gcc.py @@ -26,6 +26,10 @@ class GccCompileTest(OESDKTestCase): os.path.join(self.tc.sdk_dir, f)) def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("GccCompileTest class: SDK doesn't contain a supported C library") + machine = self.td.get("MACHINE") if not (self.tc.hasHostPackage("packagegroup-cross-canadian-%s" % machine) or self.tc.hasHostPackage("^gcc-", regex=True)): diff --git a/poky/meta/lib/oeqa/sdk/cases/gtk3.py b/poky/meta/lib/oeqa/sdk/cases/gtk3.py index c329c4bb86..8f60d5e7da 100644 --- a/poky/meta/lib/oeqa/sdk/cases/gtk3.py +++ b/poky/meta/lib/oeqa/sdk/cases/gtk3.py @@ -18,6 +18,10 @@ class GTK3Test(OESDKTestCase): Test that autotools and GTK+ 3 compiles correctly. """ def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("GTK3Test class: SDK doesn't contain a supported C library") + if not (self.tc.hasTargetPackage("gtk+3", multilib=True) or \ self.tc.hasTargetPackage("libgtk-3.0", multilib=True)): raise unittest.SkipTest("GalculatorTest class: SDK don't support gtk+3") diff --git a/poky/meta/lib/oeqa/sdk/cases/kmod.py b/poky/meta/lib/oeqa/sdk/cases/kmod.py new file mode 100644 index 0000000000..9e8fdbcd40 --- /dev/null +++ b/poky/meta/lib/oeqa/sdk/cases/kmod.py @@ -0,0 +1,41 @@ +# +# Copyright OpenEmbedded Contributors +# +# SPDX-License-Identifier: MIT +# + +import os +import subprocess +import tempfile +import unittest + +from oeqa.sdk.case import OESDKTestCase +from oeqa.utils.subprocesstweak import errors_have_output +errors_have_output() + +class KernelModuleTest(OESDKTestCase): + """ + Test that out-of-tree kernel modules build. + """ + + def setUp(self): + if not self.tc.hasTargetPackage("kernel-devsrc"): + raise unittest.SkipTest("KernelModuleTest needs kernel-devsrc") + + # These targets need to be built before kernel modules can be built. + self._run("make -j -C $OECORE_TARGET_SYSROOT/usr/src/kernel prepare scripts") + + + def test_cryptodev(self): + with tempfile.TemporaryDirectory(prefix="cryptodev", dir=self.tc.sdk_dir) as testdir: + git_url = "https://github.com/cryptodev-linux/cryptodev-linux" + # This is a knnown-good commit post-1.13 that builds with kernel 6.7+ + git_sha = "bb8bc7cf60d2c0b097c8b3b0e807f805b577a53f" + + sourcedir = os.path.join(testdir, "cryptodev-linux") + subprocess.check_output(["git", "clone", git_url, sourcedir], stderr=subprocess.STDOUT) + self.assertTrue(os.path.isdir(sourcedir)) + subprocess.check_output(["git", "-C", sourcedir, "checkout", git_sha], stderr=subprocess.STDOUT) + + self._run("make -C %s V=1 KERNEL_DIR=$OECORE_TARGET_SYSROOT/usr/src/kernel" % sourcedir) + self.check_elf(os.path.join(sourcedir, "cryptodev.ko")) diff --git a/poky/meta/lib/oeqa/sdk/cases/makefile.py b/poky/meta/lib/oeqa/sdk/cases/makefile.py index 2ff54ce25f..e1e2484820 100644 --- a/poky/meta/lib/oeqa/sdk/cases/makefile.py +++ b/poky/meta/lib/oeqa/sdk/cases/makefile.py @@ -5,6 +5,7 @@ # import os, tempfile, subprocess +import unittest from oeqa.sdk.case import OESDKTestCase from oeqa.utils.subprocesstweak import errors_have_output errors_have_output() @@ -13,6 +14,11 @@ class MakefileTest(OESDKTestCase): """ Test that "plain" compilation works, using just $CC $CFLAGS etc. """ + def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("MakefileTest class: SDK doesn't contain a supported C library") + def test_lzip(self): with tempfile.TemporaryDirectory(prefix="lzip", dir=self.tc.sdk_dir) as testdir: tarball = self.fetch(testdir, self.td["DL_DIR"], "http://downloads.yoctoproject.org/mirror/sources/lzip-1.19.tar.gz") diff --git a/poky/meta/lib/oeqa/sdk/cases/meson.py b/poky/meta/lib/oeqa/sdk/cases/meson.py index be53df204a..1edf78720a 100644 --- a/poky/meta/lib/oeqa/sdk/cases/meson.py +++ b/poky/meta/lib/oeqa/sdk/cases/meson.py @@ -18,6 +18,10 @@ class MesonTest(OESDKTestCase): Test that Meson builds correctly. """ def setUp(self): + libc = self.td.get("TCLIBC") + if libc in [ 'newlib' ]: + raise unittest.SkipTest("MesonTest class: SDK doesn't contain a supported C library") + if not (self.tc.hasHostPackage("nativesdk-meson") or self.tc.hasHostPackage("meson-native")): raise unittest.SkipTest("MesonTest: needs meson") diff --git a/poky/meta/lib/oeqa/selftest/cases/bbclasses.py b/poky/meta/lib/oeqa/selftest/cases/bbclasses.py new file mode 100644 index 0000000000..10545ebe65 --- /dev/null +++ b/poky/meta/lib/oeqa/selftest/cases/bbclasses.py @@ -0,0 +1,106 @@ +# +# Copyright OpenEmbedded Contributors +# +# SPDX-License-Identifier: MIT +# + +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import get_bb_vars, bitbake + +class Systemd(OESelftestTestCase): + """ + Tests related to the systemd bbclass. + """ + + def getVars(self, recipe): + self.bb_vars = get_bb_vars( + [ + 'BPN', + 'D', + 'INIT_D_DIR', + 'prefix', + 'systemd_system_unitdir', + 'sysconfdir', + ], + recipe, + ) + + def fileExists(self, filename): + self.assertExists(filename.format(**self.bb_vars)) + + def fileNotExists(self, filename): + self.assertNotExists(filename.format(**self.bb_vars)) + + def test_systemd_in_distro(self): + """ + Summary: Verify that no sysvinit files are installed when the + systemd distro feature is enabled, but sysvinit is not. + Expected: Systemd service file exists, but /etc does not. + Product: OE-Core + Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com> + """ + + self.write_config(""" +DISTRO_FEATURES:append = " systemd usrmerge" +DISTRO_FEATURES:remove = "sysvinit" +VIRTUAL-RUNTIME_init_manager = "systemd" +""") + bitbake("systemd-only systemd-and-sysvinit -c install") + + self.getVars("systemd-only") + self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service") + + self.getVars("systemd-and-sysvinit") + self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service") + self.fileNotExists("{D}{sysconfdir}") + + def test_systemd_and_sysvinit_in_distro(self): + """ + Summary: Verify that both systemd and sysvinit files are installed + when both the systemd and sysvinit distro features are + enabled. + Expected: Systemd service file and sysvinit initscript exist. + Product: OE-Core + Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com> + """ + + self.write_config(""" +DISTRO_FEATURES:append = " systemd sysvinit usrmerge" +VIRTUAL-RUNTIME_init_manager = "systemd" +""") + bitbake("systemd-only systemd-and-sysvinit -c install") + + self.getVars("systemd-only") + self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service") + + self.getVars("systemd-and-sysvinit") + self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service") + self.fileExists("{D}{INIT_D_DIR}/{BPN}") + + def test_sysvinit_in_distro(self): + """ + Summary: Verify that no systemd service files are installed when the + sysvinit distro feature is enabled, but systemd is not. + Expected: The systemd service file does not exist, nor does /usr. + The sysvinit initscript exists. + Product: OE-Core + Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com> + """ + + self.write_config(""" +DISTRO_FEATURES:remove = "systemd" +DISTRO_FEATURES:append = " sysvinit usrmerge" +VIRTUAL-RUNTIME_init_manager = "sysvinit" +""") + bitbake("systemd-only systemd-and-sysvinit -c install") + + self.getVars("systemd-only") + self.fileNotExists("{D}{systemd_system_unitdir}/{BPN}.service") + self.fileNotExists("{D}{prefix}") + self.fileNotExists("{D}{sysconfdir}") + self.fileExists("{D}") + + self.getVars("systemd-and-sysvinit") + self.fileNotExists("{D}{systemd_system_unitdir}/{BPN}.service") + self.fileNotExists("{D}{prefix}") + self.fileExists("{D}{INIT_D_DIR}/{BPN}") diff --git a/poky/meta/lib/oeqa/selftest/cases/binutils.py b/poky/meta/lib/oeqa/selftest/cases/binutils.py index 1688eabe4e..5ff263d342 100644 --- a/poky/meta/lib/oeqa/selftest/cases/binutils.py +++ b/poky/meta/lib/oeqa/selftest/cases/binutils.py @@ -33,7 +33,7 @@ class BinutilsCrossSelfTest(OESelftestTestCase, OEPTestResultTestCase): features.append('CHECK_TARGETS = "{0}"'.format(suite)) self.write_config("\n".join(features)) - recipe = "binutils-cross-testsuite" + recipe = "binutils-testsuite" bb_vars = get_bb_vars(["B", "TARGET_SYS", "T"], recipe) builddir, target_sys, tdir = bb_vars["B"], bb_vars["TARGET_SYS"], bb_vars["T"] diff --git a/poky/meta/lib/oeqa/selftest/cases/buildoptions.py b/poky/meta/lib/oeqa/selftest/cases/buildoptions.py index 31dafaa9c5..423c31e189 100644 --- a/poky/meta/lib/oeqa/selftest/cases/buildoptions.py +++ b/poky/meta/lib/oeqa/selftest/cases/buildoptions.py @@ -84,7 +84,7 @@ class SanityOptionsTest(OESelftestTestCase): self.write_config("INHERIT:remove = \"report-error\"") if "packages-list" not in get_bb_var("ERROR_QA"): - self.append_config("ERROR_QA:append = \" packages-list\"") + self.append_config("ERROR_QA:append:pn-xcursor-transparent-theme = \" packages-list\"") self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') self.add_command_to_tearDown('bitbake -c clean xcursor-transparent-theme') @@ -94,8 +94,8 @@ class SanityOptionsTest(OESelftestTestCase): self.assertTrue(line and line.startswith("ERROR:"), msg=res.output) self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output)) self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') - self.append_config('ERROR_QA:remove = "packages-list"') - self.append_config('WARN_QA:append = " packages-list"') + self.append_config('ERROR_QA:remove:pn-xcursor-transparent-theme = "packages-list"') + self.append_config('WARN_QA:append:pn-xcursor-transparent-theme = " packages-list"') res = bitbake("xcursor-transparent-theme -f -c package") self.delete_recipeinc('xcursor-transparent-theme') line = self.getline(res, "QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors.") @@ -173,8 +173,8 @@ class BuildhistoryTests(BuildhistoryBase): data = load_bh(os.path.join(history_dir, 'hicolor-icon-theme-dev', 'latest')) if 'FILELIST' in data: - self.assertEqual(data['FILELIST'], '') - self.assertEqual(int(data['PKGSIZE']), 0) + self.assertEqual(data['FILELIST'], '/usr/share/pkgconfig/default-icon-theme.pc') + self.assertGreater(int(data['PKGSIZE']), 0) class ArchiverTest(OESelftestTestCase): def test_arch_work_dir_and_export_source(self): diff --git a/poky/meta/lib/oeqa/selftest/cases/cve_check.py b/poky/meta/lib/oeqa/selftest/cases/cve_check.py index 60cecd1328..3dd3e89d3e 100644 --- a/poky/meta/lib/oeqa/selftest/cases/cve_check.py +++ b/poky/meta/lib/oeqa/selftest/cases/cve_check.py @@ -72,6 +72,54 @@ class CVECheck(OESelftestTestCase): self.assertEqual(convert_cve_version("6.2_rc8"), "6.2-rc8") self.assertEqual(convert_cve_version("6.2_rc31"), "6.2-rc31") + def test_product_match(self): + from oe.cve_check import has_cve_product_match + + status = {} + status["detail"] = "ignored" + status["vendor"] = "*" + status["product"] = "*" + status["description"] = "" + status["mapping"] = "" + + self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), True) + self.assertEqual(has_cve_product_match(status, "*:*"), True) + self.assertEqual(has_cve_product_match(status, "some_product"), True) + self.assertEqual(has_cve_product_match(status, "glibc"), True) + self.assertEqual(has_cve_product_match(status, "glibca"), True) + self.assertEqual(has_cve_product_match(status, "aglibc"), True) + self.assertEqual(has_cve_product_match(status, "*"), True) + self.assertEqual(has_cve_product_match(status, "aglibc glibc test:test"), True) + + status["product"] = "glibc" + self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), False) + # The CPE in the recipe must be defined, no * accepted + self.assertEqual(has_cve_product_match(status, "*:*"), False) + self.assertEqual(has_cve_product_match(status, "*"), False) + self.assertEqual(has_cve_product_match(status, "some_product"), False) + self.assertEqual(has_cve_product_match(status, "glibc"), True) + self.assertEqual(has_cve_product_match(status, "glibca"), False) + self.assertEqual(has_cve_product_match(status, "aglibc"), False) + self.assertEqual(has_cve_product_match(status, "some_vendor:glibc"), True) + self.assertEqual(has_cve_product_match(status, "some_vendor:glibc test"), True) + self.assertEqual(has_cve_product_match(status, "test some_vendor:glibc"), True) + + status["vendor"] = "glibca" + status["product"] = "glibc" + self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), False) + # The CPE in the recipe must be defined, no * accepted + self.assertEqual(has_cve_product_match(status, "*:*"), False) + self.assertEqual(has_cve_product_match(status, "*"), False) + self.assertEqual(has_cve_product_match(status, "some_product"), False) + self.assertEqual(has_cve_product_match(status, "glibc"), False) + self.assertEqual(has_cve_product_match(status, "glibca"), False) + self.assertEqual(has_cve_product_match(status, "aglibc"), False) + self.assertEqual(has_cve_product_match(status, "some_vendor:glibc"), False) + self.assertEqual(has_cve_product_match(status, "glibca:glibc"), True) + self.assertEqual(has_cve_product_match(status, "test:test glibca:glibc"), True) + self.assertEqual(has_cve_product_match(status, "test glibca:glibc"), True) + self.assertEqual(has_cve_product_match(status, "glibca:glibc test"), True) + def test_recipe_report_json(self): config = """ @@ -217,9 +265,10 @@ CVE_CHECK_REPORT_PATCHED = "1" # m4 CVE should not be in logrotate self.assertNotIn("CVE-2008-1687", found_cves) # logrotate has both Patched and Ignored CVEs + detail = "version-not-in-range" self.assertIn("CVE-2011-1098", found_cves) self.assertEqual(found_cves["CVE-2011-1098"]["status"], "Patched") - self.assertEqual(len(found_cves["CVE-2011-1098"]["detail"]), 0) + self.assertEqual(found_cves["CVE-2011-1098"]["detail"], detail) self.assertEqual(len(found_cves["CVE-2011-1098"]["description"]), 0) detail = "not-applicable-platform" description = "CVE is debian, gentoo or SUSE specific on the way logrotate was installed/used" diff --git a/poky/meta/lib/oeqa/selftest/cases/devtool.py b/poky/meta/lib/oeqa/selftest/cases/devtool.py index 432d9c9a67..8e709944a8 100644 --- a/poky/meta/lib/oeqa/selftest/cases/devtool.py +++ b/poky/meta/lib/oeqa/selftest/cases/devtool.py @@ -317,7 +317,7 @@ class DevtoolBase(DevtoolTestCase): cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n' % cls.original_sstate) - cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687"\n') + cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yoctoproject.org:8686"\n') @classmethod def tearDownClass(cls): @@ -2017,7 +2017,7 @@ class DevtoolUpgradeTests(DevtoolBase): newlines = f.readlines() self.assertEqual(desiredlines, newlines) - def test_devtool_upgrade_recipe_update_extra_tasks(self): + def test_devtool_upgrade_recipe_upgrade_extra_tasks(self): # Check preconditions self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') self.track_for_cleanup(self.workspacedir) diff --git a/poky/meta/lib/oeqa/selftest/cases/distrodata.py b/poky/meta/lib/oeqa/selftest/cases/distrodata.py index ad952c004b..7771a42e2b 100644 --- a/poky/meta/lib/oeqa/selftest/cases/distrodata.py +++ b/poky/meta/lib/oeqa/selftest/cases/distrodata.py @@ -20,10 +20,10 @@ class Distrodata(OESelftestTestCase): feature = 'LICENSE_FLAGS_ACCEPTED += " commercial"\n' self.write_config(feature) - pkgs = oe.recipeutils.get_recipe_upgrade_status() + pkggroups = oe.recipeutils.get_recipe_upgrade_status() - regressed_failures = [pkg[0] for pkg in pkgs if pkg[1] == 'UNKNOWN_BROKEN'] - regressed_successes = [pkg[0] for pkg in pkgs if pkg[1] == 'KNOWN_BROKEN'] + regressed_failures = [pkg['pn'] for pkgs in pkggroups for pkg in pkgs if pkg['status'] == 'UNKNOWN_BROKEN'] + regressed_successes = [pkg['pn'] for pkgs in pkggroups for pkg in pkgs if pkg['status'] == 'KNOWN_BROKEN'] msg = "" if len(regressed_failures) > 0: msg = msg + """ @@ -55,7 +55,7 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re return False def is_maintainer_exception(entry): - exceptions = ["musl", "newlib", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data", + exceptions = ["musl", "newlib", "picolibc", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data", "cve-update-nvd2-native",] for i in exceptions: if i in entry: @@ -115,3 +115,15 @@ The list of oe-core recipes with maintainers is empty. This may indicate that th self.fail(""" Unable to find recipes for the following entries in maintainers.inc: """ + "\n".join(['%s' % i for i in missing_recipes])) + + def test_common_include_recipes(self): + """ + Summary: Test that obtaining recipes that share includes between them returns a sane result + Expected: At least cmake and qemu entries are present in the output + Product: oe-core + Author: Alexander Kanavin <alex.kanavin@gmail.com> + """ + recipes = oe.recipeutils.get_common_include_recipes() + + self.assertIn({'qemu-system-native', 'qemu', 'qemu-native'}, recipes) + self.assertIn({'cmake-native', 'cmake'}, recipes) diff --git a/poky/meta/lib/oeqa/selftest/cases/fitimage.py b/poky/meta/lib/oeqa/selftest/cases/fitimage.py index 347c065377..0b5f4602fb 100644 --- a/poky/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/poky/meta/lib/oeqa/selftest/cases/fitimage.py @@ -11,6 +11,51 @@ import re class FitImageTests(OESelftestTestCase): + def _setup_uboot_tools_native(self): + """build u-boot-tools-native and return RECIPE_SYSROOT_NATIVE""" + bitbake("u-boot-tools-native -c addto_recipe_sysroot") + return get_bb_var('RECIPE_SYSROOT_NATIVE', 'u-boot-tools-native') + + def _verify_fit_image_signature(self, uboot_tools_sysroot_native, fitimage_path, dtb_path, conf_name=None): + """Verify the signature of a fit contfiguration + + The fit_check_sign utility from u-boot-tools-native is called. + uboot-fit_check_sign -f fitImage -k $dtb_name -c conf-$dtb_name + """ + fit_check_sign_path = os.path.join(uboot_tools_sysroot_native, 'usr', 'bin', 'uboot-fit_check_sign') + cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path) + if conf_name: + cmd += ' -c %s' % conf_name + result = runCmd(cmd) + self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output) + self.assertIn("Signature check OK", result.output) + + @staticmethod + def _find_string_in_bin_file(file_path, search_string): + """find stings in a binary file + + Shell equivalent: strings "$1" | grep "$2" | wc -l + return number of matches + """ + found_positions = 0 + with open(file_path, 'rb') as file: + byte = file.read(1) + current_position = 0 + current_match = 0 + while byte: + char = byte.decode('ascii', errors='ignore') + if char == search_string[current_match]: + current_match += 1 + if current_match == len(search_string): + found_positions += 1 + current_match = 0 + else: + current_match = 0 + current_position += 1 + byte = file.read(1) + return found_positions + + def test_fit_image(self): """ Summary: Check if FIT image and Image Tree Source (its) are built @@ -53,10 +98,8 @@ FIT_DESC = "A model description" fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "fitImage-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME'])) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) # Check that the type, load address, entrypoint address and default # values for kernel and ramdisk in Image Tree Source are as expected. @@ -108,19 +151,21 @@ FIT_DESC = "A model description" Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon work by Usama Arif <usama.arif@arm.com> """ + a_comment = "a smart comment" config = """ # Enable creation of fitImage MACHINE = "beaglebone-yocto" KERNEL_IMAGETYPES += " fitImage " -KERNEL_CLASSES = " kernel-fitimage test-mkimage-wrapper " +KERNEL_CLASSES = " kernel-fitimage " UBOOT_SIGN_ENABLE = "1" FIT_GENERATE_KEYS = "1" UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" FIT_SIGN_INDIVIDUAL = "1" -UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" -""" +UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'" +""" % a_comment + self.write_config(config) # fitImage is created as part of linux recipe @@ -133,10 +178,8 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "fitImage-%s.bin" % (bb_vars['KERNEL_FIT_LINK_NAME'])) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) req_itspaths = [ ['/', 'images', 'kernel-1'], @@ -195,10 +238,8 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" self.assertEqual(value, reqvalue) # Dump the image to see if it really got signed - bitbake("u-boot-tools-native -c addto_recipe_sysroot") - result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') - recipe_sysroot_native = result.output.split('=')[1].strip('"') - dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') + uboot_tools_sysroot_native = self._setup_uboot_tools_native() + dumpimage_path = os.path.join(uboot_tools_sysroot_native, 'usr', 'bin', 'dumpimage') result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) in_signed = None signed_sections = {} @@ -224,17 +265,15 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" value = values.get('Sign value', None) self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) - # Check for UBOOT_MKIMAGE_SIGN_ARGS - result = runCmd('bitbake -e virtual/kernel | grep ^T=') - tempdir = result.output.split('=', 1)[1].strip().strip('') - result = runCmd('grep "a smart comment" %s/run.do_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN_ARGS value did not get used') + # Search for the string passed to mkimage: 1 kernel + 3 DTBs + config per DTB = 7 sections + # Looks like mkimage supports to add a comment but does not support to read it back. + found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment) + self.assertEqual(found_comments, 7, "Expected 7 signed and commented section in the fitImage.") - # Check for evidence of test-mkimage-wrapper class - result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') - result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') + # Verify the signature for all configurations = DTBs + for dtb in ['am335x-bone.dtb', 'am335x-boneblack.dtb', 'am335x-bonegreen.dtb']: + self._verify_fit_image_signature(uboot_tools_sysroot_native, fitimage_path, + os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], dtb), 'conf-' + dtb) def test_uboot_fit_image(self): """ @@ -287,10 +326,8 @@ FIT_SIGN_INDIVIDUAL = "1" fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % (machine,)) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) # Check that the type, load address, entrypoint address and default # values for kernel and ramdisk in Image Tree Source are as expected. @@ -351,7 +388,6 @@ UBOOT_ENTRYPOINT = "0x80080000" UBOOT_FIT_DESC = "A model description" KERNEL_IMAGETYPES += " fitImage " KERNEL_CLASSES = " kernel-fitimage " -INHERIT += "test-mkimage-wrapper" UBOOT_SIGN_ENABLE = "1" FIT_GENERATE_KEYS = "1" UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" @@ -372,10 +408,8 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'" fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % (machine,)) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) # Check that the type, load address, entrypoint address and default # values for kernel and ramdisk in Image Tree Source are as expected. @@ -425,6 +459,7 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'" work by Paul Eggleton <paul.eggleton@microsoft.com> and Usama Arif <usama.arif@arm.com> """ + a_comment = "a smart U-Boot comment" config = """ # There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set @@ -434,7 +469,6 @@ SPL_BINARY = "MLO" # The kernel-fitimage class is a dependency even if we're only # creating/signing the U-Boot fitImage KERNEL_CLASSES = " kernel-fitimage" -INHERIT += "test-mkimage-wrapper" # Enable creation and signing of the U-Boot fitImage UBOOT_FITIMAGE_ENABLE = "1" SPL_SIGN_ENABLE = "1" @@ -446,17 +480,17 @@ UBOOT_LOADADDRESS = "0x80000000" UBOOT_DTB_LOADADDRESS = "0x82000000" UBOOT_ARCH = "arm" SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" -SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'" +SPL_MKIMAGE_SIGN_ARGS = "-c '%s'" UBOOT_EXTLINUX = "0" UBOOT_FIT_GENERATE_KEYS = "1" UBOOT_FIT_HASH_ALG = "sha256" -""" +""" % a_comment + self.write_config(config) # The U-Boot fitImage is created as part of the U-Boot recipe bitbake("virtual/bootloader") - image_type = "core-image-minimal" deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') machine = get_bb_var('MACHINE') fitimage_its_path = os.path.join(deploy_dir_image, @@ -464,10 +498,8 @@ UBOOT_FIT_HASH_ALG = "sha256" fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % (machine,)) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) req_itspaths = [ ['/', 'images', 'uboot'], @@ -516,10 +548,8 @@ UBOOT_FIT_HASH_ALG = "sha256" self.assertEqual(value, reqvalue) # Dump the image to see if it really got signed - bitbake("u-boot-tools-native -c addto_recipe_sysroot") - result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') - recipe_sysroot_native = result.output.split('=')[1].strip('"') - dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') + uboot_tools_sysroot_native = self._setup_uboot_tools_native() + dumpimage_path = os.path.join(uboot_tools_sysroot_native, 'usr', 'bin', 'dumpimage') result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) in_signed = None signed_sections = {} @@ -542,16 +572,14 @@ UBOOT_FIT_HASH_ALG = "sha256" self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) # Check for SPL_MKIMAGE_SIGN_ARGS - result = runCmd('bitbake -e virtual/bootloader | grep ^T=') - tempdir = result.output.split('=', 1)[1].strip().strip('') - result = runCmd('grep "a smart U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used') + # Looks like mkimage supports to add a comment but does not support to read it back. + found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment) + self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.") + + # Verify the signature + self._verify_fit_image_signature(uboot_tools_sysroot_native, fitimage_path, + os.path.join(deploy_dir_image, 'u-boot-spl.dtb')) - # Check for evidence of test-mkimage-wrapper class - result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') - result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') def test_sign_cascaded_uboot_fit_image(self): """ @@ -573,6 +601,7 @@ UBOOT_FIT_HASH_ALG = "sha256" work by Paul Eggleton <paul.eggleton@microsoft.com> and Usama Arif <usama.arif@arm.com> """ + a_comment = "a smart cascaded U-Boot comment" config = """ # There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at # least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set @@ -588,7 +617,7 @@ UBOOT_DTB_BINARY = "u-boot.dtb" UBOOT_ENTRYPOINT = "0x80000000" UBOOT_LOADADDRESS = "0x80000000" UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" -UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded Kernel comment'" +UBOOT_MKIMAGE_SIGN_ARGS = "-c '%s'" UBOOT_DTB_LOADADDRESS = "0x82000000" UBOOT_ARCH = "arm" SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" @@ -598,20 +627,18 @@ UBOOT_FIT_GENERATE_KEYS = "1" UBOOT_FIT_HASH_ALG = "sha256" KERNEL_IMAGETYPES += " fitImage " KERNEL_CLASSES = " kernel-fitimage " -INHERIT += "test-mkimage-wrapper" UBOOT_SIGN_ENABLE = "1" FIT_GENERATE_KEYS = "1" UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" FIT_SIGN_INDIVIDUAL = "1" -""" +""" % a_comment self.write_config(config) # The U-Boot fitImage is created as part of the U-Boot recipe bitbake("virtual/bootloader") - image_type = "core-image-minimal" deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') machine = get_bb_var('MACHINE') fitimage_its_path = os.path.join(deploy_dir_image, @@ -619,10 +646,8 @@ FIT_SIGN_INDIVIDUAL = "1" fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % (machine,)) - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) req_itspaths = [ ['/', 'images', 'uboot'], @@ -671,10 +696,8 @@ FIT_SIGN_INDIVIDUAL = "1" self.assertEqual(value, reqvalue) # Dump the image to see if it really got signed - bitbake("u-boot-tools-native -c addto_recipe_sysroot") - result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=') - recipe_sysroot_native = result.output.split('=')[1].strip('"') - dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage') + uboot_tools_sysroot_native = self._setup_uboot_tools_native() + dumpimage_path = os.path.join(uboot_tools_sysroot_native, 'usr', 'bin', 'dumpimage') result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path)) in_signed = None signed_sections = {} @@ -697,17 +720,13 @@ FIT_SIGN_INDIVIDUAL = "1" self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section) # Check for SPL_MKIMAGE_SIGN_ARGS - result = runCmd('bitbake -e virtual/bootloader | grep ^T=') - tempdir = result.output.split('=', 1)[1].strip().strip('') - result = runCmd('grep "a smart cascaded U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used') - - # Check for evidence of test-mkimage-wrapper class - result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work') - result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True) - self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work') + # Looks like mkimage supports to add a comment but does not support to read it back. + found_comments = FitImageTests._find_string_in_bin_file(fitimage_path, a_comment) + self.assertEqual(found_comments, 2, "Expected 2 signed and commented section in the fitImage.") + # Verify the signature + self._verify_fit_image_signature(uboot_tools_sysroot_native, fitimage_path, + os.path.join(deploy_dir_image, 'u-boot-spl.dtb')) def test_initramfs_bundle(self): @@ -755,24 +774,24 @@ FIT_HASH_ALG = "sha256" # fitImage is created as part of linux recipe bitbake("virtual/kernel") - image_type = get_bb_var('INITRAMFS_IMAGE') - deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') - machine = get_bb_var('MACHINE') - fitimage_its_path = os.path.join(deploy_dir_image, - "fitImage-its-%s-%s-%s" % (image_type, machine, machine)) - fitimage_path = os.path.join(deploy_dir_image,"fitImage") - - self.assertTrue(os.path.exists(fitimage_its_path), - "%s image tree source doesn't exist" % (fitimage_its_path)) - self.assertTrue(os.path.exists(fitimage_path), - "%s FIT image doesn't exist" % (fitimage_path)) + bb_vars = get_bb_vars([ + 'DEPLOY_DIR_IMAGE', + 'FIT_HASH_ALG', + 'FIT_KERNEL_COMP_ALG', + 'INITRAMFS_IMAGE', + 'MACHINE', + 'UBOOT_ARCH', + 'UBOOT_ENTRYPOINT', + 'UBOOT_LOADADDRESS', + 'UBOOT_MKIMAGE_KERNEL_TYPE' + ], + 'virtual/kernel') + fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], + "fitImage-its-%s-%s-%s" % (bb_vars['INITRAMFS_IMAGE'], bb_vars['MACHINE'], bb_vars['MACHINE'])) + fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],"fitImage") - kernel_load = str(get_bb_var('UBOOT_LOADADDRESS')) - kernel_entry = str(get_bb_var('UBOOT_ENTRYPOINT')) - kernel_type = str(get_bb_var('UBOOT_MKIMAGE_KERNEL_TYPE')) - kernel_compression = str(get_bb_var('FIT_KERNEL_COMP_ALG')) - uboot_arch = str(get_bb_var('UBOOT_ARCH')) - fit_hash_alg = str(get_bb_var('FIT_HASH_ALG')) + self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) + self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path)) its_file = open(fitimage_its_path) @@ -782,31 +801,31 @@ FIT_HASH_ALG = "sha256" 'kernel-1 {', 'description = "Linux kernel";', 'data = /incbin/("linux.bin");', - 'type = "' + kernel_type + '";', - 'arch = "' + uboot_arch + '";', + 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";', + 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";', 'os = "linux";', - 'compression = "' + kernel_compression + '";', - 'load = <' + kernel_load + '>;', - 'entry = <' + kernel_entry + '>;', + 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', + 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;', + 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;', 'hash-1 {', - 'algo = "' + fit_hash_alg +'";', + 'algo = "' + str(bb_vars['FIT_HASH_ALG']) +'";', '};', '};' ] node_str = exp_node_lines[0] - test_passed = False - print ("checking kernel node\n") + self.assertIn(node_str, its_lines) - if node_str in its_lines: - node_start_idx = its_lines.index(node_str) - node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))] - if node == exp_node_lines: - print("kernel node verified") - else: - self.assertTrue(test_passed == True,"kernel node does not match expectation") + node_start_idx = its_lines.index(node_str) + node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))] + + # Remove the absolute path. This refers to WORKDIR which is not always predictable. + re_data = re.compile(r'^data = /incbin/\(.*/linux\.bin"\);$') + node = [re.sub(re_data, 'data = /incbin/("linux.bin");', cfg_str) for cfg_str in node] + + self.assertEqual(node, exp_node_lines, "kernel node does not match expectation") rx_configs = re.compile("^conf-.*") its_configs = list(filter(rx_configs.match, its_lines)) @@ -822,25 +841,14 @@ FIT_HASH_ALG = "sha256" node = its_lines[cfg_start_idx:line_idx] print("checking configuration " + cfg_str.rstrip(" {")) - rx_desc_line = re.compile("^description.*1 Linux kernel.*") - if len(list(filter(rx_desc_line.match, node))) != 1: - self.assertTrue(test_passed == True,"kernel keyword not found in the description line") - break - else: - print("kernel keyword found in the description line") + rx_desc_line = re.compile(r'^description = ".*Linux kernel.*') + self.assertEqual(len(list(filter(rx_desc_line.match, node))), 1, "kernel keyword not found in the description line") - if 'kernel = "kernel-1";' not in node: - self.assertTrue(test_passed == True,"kernel line not found") - break - else: - print("kernel line found") + self.assertIn('kernel = "kernel-1";', node) - rx_sign_line = re.compile("^sign-images.*kernel.*") - if len(list(filter(rx_sign_line.match, node))) != 1: - self.assertTrue(test_passed == True,"kernel hash not signed") - break - else: - print("kernel hash signed") + rx_sign_line = re.compile(r'^sign-images = .*kernel.*') + self.assertEqual(len(list(filter(rx_sign_line.match, node))), 1, "kernel hash not signed") - test_passed = True - self.assertTrue(test_passed == True,"Initramfs bundle test success") + # Verify the signature + uboot_tools_sysroot_native = self._setup_uboot_tools_native() + self._verify_fit_image_signature(uboot_tools_sysroot_native, fitimage_path, os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], 'am335x-bone.dtb')) diff --git a/poky/meta/lib/oeqa/selftest/cases/gcc.py b/poky/meta/lib/oeqa/selftest/cases/gcc.py index 89360178fe..1bda29a72b 100644 --- a/poky/meta/lib/oeqa/selftest/cases/gcc.py +++ b/poky/meta/lib/oeqa/selftest/cases/gcc.py @@ -37,7 +37,7 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase): features = [] features.append('MAKE_CHECK_TARGETS = "{0}"'.format(" ".join(targets))) if ssh is not None: - features.append('TOOLCHAIN_TEST_TARGET = "ssh"') + features.append('TOOLCHAIN_TEST_TARGET = "linux-ssh"') features.append('TOOLCHAIN_TEST_HOST = "{0}"'.format(ssh)) features.append('TOOLCHAIN_TEST_HOST_USER = "root"') features.append('TOOLCHAIN_TEST_HOST_PORT = "22"') @@ -83,6 +83,8 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase): # validate that SSH is working status, _ = qemu.run("uname") self.assertEqual(status, 0) + qemu.run('echo "MaxStartups 75:30:100" >> /etc/ssh/sshd_config') + qemu.run('service sshd restart') return self.run_check(*args, ssh=qemu.ip, **kwargs) diff --git a/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py b/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py index dc88c222bd..94d01ba116 100644 --- a/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py +++ b/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py @@ -250,12 +250,7 @@ USERADD_GID_TABLES += "files/static-group" DISTRO_FEATURES:append = " pam opengl wayland" # Switch to systemd -DISTRO_FEATURES:append = " systemd usrmerge" -VIRTUAL-RUNTIME_init_manager = "systemd" -VIRTUAL-RUNTIME_initscripts = "" -VIRTUAL-RUNTIME_syslog = "" -VIRTUAL-RUNTIME_login_manager = "shadow-base" -DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit" +INIT_MANAGER = "systemd" # Replace busybox PREFERRED_PROVIDER_virtual/base-utils = "packagegroup-core-base-utils" @@ -319,7 +314,7 @@ SKIP_RECIPE[busybox] = "Don't build this" """ config = """ DISTRO_FEATURES:append = " api-documentation" -CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc" +CORE_IMAGE_EXTRA_INSTALL = "man-pages" """ self.write_config(config) bitbake("core-image-minimal") @@ -330,7 +325,7 @@ CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc" self.assertEqual(status, 1, 'Failed to run apropos: %s' % (output)) self.assertIn("iso_8859_15", output) - # This manpage is provided by kmod - status, output = qemu.run_serial("man --pager=cat modprobe") + # This manpage is provided by man-pages + status, output = qemu.run_serial("man --pager=cat intro") self.assertEqual(status, 1, 'Failed to run man: %s' % (output)) - self.assertIn("force-modversion", output) + self.assertIn("introduction to user commands", output) diff --git a/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py b/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py index f4af67a239..be5484bca4 100644 --- a/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py +++ b/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py @@ -114,7 +114,7 @@ INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*" def test_bash_and_license(self): self.disable_class("create-spdx") - self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " & SomeLicense"') + self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " & SomeLicense"\nERROR_QA:remove:pn-bash = "license-exists"') error_msg = "ERROR: core-image-minimal-1.0-r0 do_rootfs: Package bash cannot be installed into the image because it has incompatible license(s): GPL-3.0-or-later" result = bitbake('core-image-minimal', ignore_status=True) @@ -123,12 +123,12 @@ INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*" def test_bash_or_license(self): self.disable_class("create-spdx") - self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " | SomeLicense"') + self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " | SomeLicense"\nERROR_QA:remove:pn-bash = "license-exists"\nERROR_QA:remove:pn-core-image-minimal = "license-file-missing"') bitbake('core-image-minimal') def test_bash_license_exceptions(self): - self.write_config(self.default_config() + '\nINCOMPATIBLE_LICENSE_EXCEPTIONS:pn-core-image-minimal = "bash:GPL-3.0-or-later"') + self.write_config(self.default_config() + '\nINCOMPATIBLE_LICENSE_EXCEPTIONS:pn-core-image-minimal = "bash:GPL-3.0-or-later"\nERROR_QA:remove:pn-core-image-minimal = "license-exception"') bitbake('core-image-minimal') diff --git a/poky/meta/lib/oeqa/selftest/cases/locales.py b/poky/meta/lib/oeqa/selftest/cases/locales.py index 4ca8ffb7aa..ac4888ef66 100644 --- a/poky/meta/lib/oeqa/selftest/cases/locales.py +++ b/poky/meta/lib/oeqa/selftest/cases/locales.py @@ -14,7 +14,7 @@ class LocalesTest(OESelftestTestCase): features = [] features.append('EXTRA_IMAGE_FEATURES = "empty-root-password allow-empty-password allow-root-login"') features.append('IMAGE_INSTALL:append = " glibc-utils localedef"') - features.append('GLIBC_GENERATE_LOCALES = "en_US.UTF-8 fr_FR.UTF-8"') + features.append('GLIBC_GENERATE_LOCALES = "en_US.UTF-8 fr_FR.UTF-8 en_US.ISO-8859-1 de_DE.UTF-8 fr_FR.ISO-8859-1 zh_HK.BIG5-HKSCS tr_TR.UTF-8"') features.append('IMAGE_LINGUAS:append = " en-us fr-fr"') if binary_enabled: features.append('ENABLE_BINARY_LOCALE_GENERATION = "1"') diff --git a/poky/meta/lib/oeqa/selftest/cases/meta_ide.py b/poky/meta/lib/oeqa/selftest/cases/meta_ide.py index ffe0d2604d..5a17ca52ea 100644 --- a/poky/meta/lib/oeqa/selftest/cases/meta_ide.py +++ b/poky/meta/lib/oeqa/selftest/cases/meta_ide.py @@ -20,8 +20,8 @@ class MetaIDE(OESelftestTestCase): bitbake('meta-ide-support') bitbake('build-sysroots -c build_native_sysroot') bitbake('build-sysroots -c build_target_sysroot') - bb_vars = get_bb_vars(['MULTIMACH_TARGET_SYS', 'DEPLOY_DIR_IMAGE', 'COREBASE']) - cls.environment_script = 'environment-setup-%s' % bb_vars['MULTIMACH_TARGET_SYS'] + bb_vars = get_bb_vars(['MACHINE_ARCH', 'TARGET_VENDOR', 'TARGET_OS', 'DEPLOY_DIR_IMAGE', 'COREBASE']) + cls.environment_script = 'environment-setup-%s%s-%s' % (bb_vars['MACHINE_ARCH'], bb_vars['TARGET_VENDOR'], bb_vars['TARGET_OS']) cls.deploydir = bb_vars['DEPLOY_DIR_IMAGE'] cls.environment_script_path = '%s/%s' % (cls.deploydir, cls.environment_script) cls.corebasedir = bb_vars['COREBASE'] diff --git a/poky/meta/lib/oeqa/selftest/cases/oescripts.py b/poky/meta/lib/oeqa/selftest/cases/oescripts.py index fcfe54af74..bfbc33b08d 100644 --- a/poky/meta/lib/oeqa/selftest/cases/oescripts.py +++ b/poky/meta/lib/oeqa/selftest/cases/oescripts.py @@ -175,7 +175,7 @@ class OEListPackageconfigTests(OESelftestTestCase): def test_packageconfig_flags_option_all(self): results = runCmd('%s/contrib/list-packageconfig-flags.py -a' % self.scripts_dir) expected_endlines = [] - expected_endlines.append("pinentry-1.3.0") + expected_endlines.append("pinentry-1.3.1") expected_endlines.append("PACKAGECONFIG ncurses") expected_endlines.append("PACKAGECONFIG[qt] --enable-pinentry-qt, --disable-pinentry-qt, qtbase-native qtbase") expected_endlines.append("PACKAGECONFIG[gtk2] --enable-pinentry-gtk2, --disable-pinentry-gtk2, gtk+ glib-2.0") diff --git a/poky/meta/lib/oeqa/selftest/cases/overlayfs.py b/poky/meta/lib/oeqa/selftest/cases/overlayfs.py index e31063567b..580fbdcb9c 100644 --- a/poky/meta/lib/oeqa/selftest/cases/overlayfs.py +++ b/poky/meta/lib/oeqa/selftest/cases/overlayfs.py @@ -5,7 +5,7 @@ # from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import bitbake, runqemu +from oeqa.utils.commands import bitbake, runqemu, get_bb_vars from oeqa.core.decorator import OETestTag from oeqa.core.decorator.data import skipIfNotMachine @@ -466,6 +466,45 @@ IMAGE_INSTALL:append = " overlayfs-user" line = getline_qemu(output, "Read-only file system") self.assertTrue(line, msg=output) + @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently") + def test_postinst_on_target_for_read_only_rootfs(self): + """ + Summary: The purpose of this test case is to verify that post-installation + on target scripts are executed even if using read-only rootfs when + read-only-rootfs-delayed-postinsts is set + Expected: The test files are created on first boot + """ + + import oe.path + + vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal") + sysconfdir = vars["sysconfdir"] + self.assertIsNotNone(sysconfdir) + # Need to use oe.path here as sysconfdir starts with / + targettestdir = os.path.join(sysconfdir, "postinst-test") + + config = self.get_working_config() + + args = { + 'OVERLAYFS_INIT_OPTION': "", + 'OVERLAYFS_ETC_USE_ORIG_INIT_NAME': 1, + 'OVERLAYFS_ROOTFS_TYPE': "ext4", + 'OVERLAYFS_ETC_CREATE_MOUNT_DIRS': 1 + } + + # read-only-rootfs is already set in get_working_config() + config += 'EXTRA_IMAGE_FEATURES += "read-only-rootfs-delayed-postinsts"\n' + config += 'CORE_IMAGE_EXTRA_INSTALL = "postinst-delayed-b"\n' + + self.write_config(config.format(**args)) + + res = bitbake('core-image-minimal') + + with runqemu('core-image-minimal', image_fstype='wic') as qemu: + for filename in ("rootfs", "delayed-a", "delayed-b"): + status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename)) + self.assertIn("found", output, "%s was not present on boot" % filename) + def get_working_config(self): return """ # Use systemd as init manager diff --git a/poky/meta/lib/oeqa/selftest/cases/package.py b/poky/meta/lib/oeqa/selftest/cases/package.py index 1aa6c03f8a..38ed7173fe 100644 --- a/poky/meta/lib/oeqa/selftest/cases/package.py +++ b/poky/meta/lib/oeqa/selftest/cases/package.py @@ -103,11 +103,37 @@ class PackageTests(OESelftestTestCase): dest = get_bb_var('PKGDEST', 'selftest-hardlink') bindir = get_bb_var('bindir', 'selftest-hardlink') + libdir = get_bb_var('libdir', 'selftest-hardlink') + libexecdir = get_bb_var('libexecdir', 'selftest-hardlink') def checkfiles(): # Recipe creates 4 hardlinked files, there is a copy in package/ and a copy in packages-split/ # so expect 8 in total. self.assertEqual(os.stat(dest + "/selftest-hardlink" + bindir + "/hello1").st_nlink, 8) + self.assertEqual(os.stat(dest + "/selftest-hardlink" + libexecdir + "/hello3").st_nlink, 8) + + # Check dbg version + # 2 items, a copy in both package/packages-split so 4 + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + bindir + "/.debug/hello1").st_nlink, 4) + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello1").st_nlink, 4) + + # Even though the libexecdir name is 'hello3' or 'hello4', that isn't the debug target name + self.assertEqual(os.path.exists(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello3"), False) + self.assertEqual(os.path.exists(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello4"), False) + + # Check the staticdev libraries + # 101 items, a copy in both package/packages-split so 202 + self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-25.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-50.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-75.a").st_nlink, 202) + + # Check static dbg + # 101 items, a copy in both package/packages-split so 202 + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-25.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-50.a").st_nlink, 202) + self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-75.a").st_nlink, 202) # Test a sparse file remains sparse sparsestat = os.stat(dest + "/selftest-hardlink" + bindir + "/sparsetest") diff --git a/poky/meta/lib/oeqa/selftest/cases/picolibc.py b/poky/meta/lib/oeqa/selftest/cases/picolibc.py new file mode 100644 index 0000000000..e40b4fc3d3 --- /dev/null +++ b/poky/meta/lib/oeqa/selftest/cases/picolibc.py @@ -0,0 +1,18 @@ +# +# Copyright OpenEmbedded Contributors +# +# SPDX-License-Identifier: MIT +# + +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import bitbake, get_bb_var + +class PicolibcTest(OESelftestTestCase): + + def test_picolibc(self): + compatible_machines = ['qemuarm', 'qemuarm64', 'qemuriscv32', 'qemuriscv64'] + machine = get_bb_var('MACHINE') + if machine not in compatible_machines: + self.skipTest('This test only works with machines : %s' % ' '.join(compatible_machines)) + self.write_config('TCLIBC = "picolibc"') + bitbake("picolibc-helloworld") diff --git a/poky/meta/lib/oeqa/selftest/cases/recipetool.py b/poky/meta/lib/oeqa/selftest/cases/recipetool.py index 42202b7831..f742dd4d64 100644 --- a/poky/meta/lib/oeqa/selftest/cases/recipetool.py +++ b/poky/meta/lib/oeqa/selftest/cases/recipetool.py @@ -1068,6 +1068,7 @@ class RecipetoolTests(RecipetoolBase): d = DataConnectorCopy d.getVar = Mock(return_value=commonlicdir) + d.expand = Mock(side_effect=lambda x: x) srctree = tempfile.mkdtemp(prefix='recipetoolqa') self.track_for_cleanup(srctree) diff --git a/poky/meta/lib/oeqa/selftest/cases/reproducible.py b/poky/meta/lib/oeqa/selftest/cases/reproducible.py index 97a9c3da90..92266ab66a 100644 --- a/poky/meta/lib/oeqa/selftest/cases/reproducible.py +++ b/poky/meta/lib/oeqa/selftest/cases/reproducible.py @@ -16,8 +16,6 @@ import os import datetime exclude_packages = [ - 'rust-rustdoc', - 'rust-dbg' ] def is_excluded(package): @@ -135,7 +133,8 @@ class ReproducibleTests(OESelftestTestCase): max_report_size = 250 * 1024 * 1024 # targets are the things we want to test the reproducibility of - targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'core-image-weston', 'world'] + # Have to add the virtual targets manually for now as builds may or may not include them as they're exclude from world + targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'core-image-weston', 'world', 'virtual/librpc', 'virtual/libsdl2', 'virtual/crypt'] # sstate targets are things to pull from sstate to potentially cut build/debugging time sstate_targets = [] @@ -178,12 +177,8 @@ class ReproducibleTests(OESelftestTestCase): self.sstate_targets = bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS'].split() self.extraresults = {} - self.extraresults.setdefault('reproducible.rawlogs', {})['log'] = '' self.extraresults.setdefault('reproducible', {}).setdefault('files', {}) - def append_to_log(self, msg): - self.extraresults['reproducible.rawlogs']['log'] += msg - def compare_packages(self, reference_dir, test_dir, diffutils_sysroot): result = PackageCompareResults(self.oeqa_reproducible_excluded_packages) @@ -210,7 +205,7 @@ class ReproducibleTests(OESelftestTestCase): def write_package_list(self, package_class, name, packages): self.extraresults['reproducible']['files'].setdefault(package_class, {})[name] = [ - {'reference': p.reference, 'test': p.test} for p in packages] + p.reference.split("/./")[1] for p in packages] def copy_file(self, source, dest): bb.utils.mkdirhier(os.path.dirname(dest)) @@ -275,9 +270,13 @@ class ReproducibleTests(OESelftestTestCase): os.chmod(save_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) self.logger.info('Non-reproducible packages will be copied to %s', save_dir) + # The below bug shows that a few reproducible issues are depends on build dir path length. + # https://bugzilla.yoctoproject.org/show_bug.cgi?id=15554 + # So, the reproducibleA & reproducibleB directories are changed to reproducibleA & reproducibleB-extended to have different size. + vars_A = self.do_test_build('reproducibleA', self.build_from_sstate) - vars_B = self.do_test_build('reproducibleB', False) + vars_B = self.do_test_build('reproducibleB-extended', False) # NOTE: The temp directories from the reproducible build are purposely # kept after the build so it can be diffed for debugging. @@ -296,8 +295,6 @@ class ReproducibleTests(OESelftestTestCase): self.logger.info('Reproducibility summary for %s: %s' % (c, result)) - self.append_to_log('\n'.join("%s: %s" % (r.status, r.test) for r in result.total)) - self.write_package_list(package_class, 'missing', result.missing) self.write_package_list(package_class, 'different', result.different) self.write_package_list(package_class, 'different_excluded', result.different_excluded) @@ -332,7 +329,7 @@ class ReproducibleTests(OESelftestTestCase): # Copy jquery to improve the diffoscope output usability self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js')) - run_diffoscope('reproducibleA', 'reproducibleB', package_html_dir, max_report_size=self.max_report_size, + run_diffoscope('reproducibleA', 'reproducibleB-extended', package_html_dir, max_report_size=self.max_report_size, native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir) if fails: diff --git a/poky/meta/lib/oeqa/selftest/cases/retain.py b/poky/meta/lib/oeqa/selftest/cases/retain.py new file mode 100644 index 0000000000..892be45857 --- /dev/null +++ b/poky/meta/lib/oeqa/selftest/cases/retain.py @@ -0,0 +1,241 @@ +# Tests for retain.bbclass +# +# Copyright OpenEmbedded Contributors +# +# SPDX-License-Identifier: MIT +# + +import os +import glob +import fnmatch +import oe.path +import shutil +import tarfile +from oeqa.utils.commands import bitbake, get_bb_vars +from oeqa.selftest.case import OESelftestTestCase + +class Retain(OESelftestTestCase): + + def test_retain_always(self): + """ + Summary: Test retain class with RETAIN_DIRS_ALWAYS + Expected: Archive written to RETAIN_OUTDIR when build of test recipe completes + Product: oe-core + Author: Paul Eggleton <paul.eggleton@microsoft.com> + """ + + test_recipe = 'quilt-native' + + features = 'INHERIT += "retain"\n' + features += 'RETAIN_DIRS_ALWAYS = "${T}"\n' + self.write_config(features) + + bitbake('-c clean %s' % test_recipe) + + bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR']) + retain_outdir = bb_vars['RETAIN_OUTDIR'] or '' + tmpdir = bb_vars['TMPDIR'] + if len(retain_outdir) < 5: + self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir) + if not oe.path.is_path_parent(tmpdir, retain_outdir): + self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir)) + try: + shutil.rmtree(retain_outdir) + except FileNotFoundError: + pass + + bitbake(test_recipe) + if not glob.glob(os.path.join(retain_outdir, '%s_temp_*.tar.gz' % test_recipe)): + self.fail('No output archive for %s created' % test_recipe) + + + def test_retain_failure(self): + """ + Summary: Test retain class default behaviour + Expected: Archive written to RETAIN_OUTDIR only when build of test + recipe fails, and archive contents are as expected + Product: oe-core + Author: Paul Eggleton <paul.eggleton@microsoft.com> + """ + + test_recipe_fail = 'error' + + features = 'INHERIT += "retain"\n' + self.write_config(features) + + bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR', 'RETAIN_DIRS_ALWAYS', 'RETAIN_DIRS_GLOBAL_ALWAYS']) + if bb_vars['RETAIN_DIRS_ALWAYS']: + self.fail('RETAIN_DIRS_ALWAYS is set, this interferes with the test') + if bb_vars['RETAIN_DIRS_GLOBAL_ALWAYS']: + self.fail('RETAIN_DIRS_GLOBAL_ALWAYS is set, this interferes with the test') + retain_outdir = bb_vars['RETAIN_OUTDIR'] or '' + tmpdir = bb_vars['TMPDIR'] + if len(retain_outdir) < 5: + self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir) + if not oe.path.is_path_parent(tmpdir, retain_outdir): + self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir)) + + try: + shutil.rmtree(retain_outdir) + except FileNotFoundError: + pass + + bitbake('-c clean %s' % test_recipe_fail) + + if os.path.exists(retain_outdir): + retain_dirlist = os.listdir(retain_outdir) + if retain_dirlist: + self.fail('RETAIN_OUTDIR should be empty without failure, contents:\n%s' % '\n'.join(retain_dirlist)) + + result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True) + if result.status == 0: + self.fail('Build of %s did not fail as expected' % test_recipe_fail) + + archives = glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % test_recipe_fail)) + if not archives: + self.fail('No output archive for %s created' % test_recipe_fail) + if len(archives) > 1: + self.fail('More than one archive for %s created' % test_recipe_fail) + for archive in archives: + found = False + archive_prefix = os.path.basename(archive).split('.tar')[0] + expected_prefix_start = '%s_workdir' % test_recipe_fail + if not archive_prefix.startswith(expected_prefix_start): + self.fail('Archive %s name does not start with expected prefix "%s"' % (os.path.basename(archive), expected_prefix_start)) + with tarfile.open(archive) as tf: + for ti in tf: + if not fnmatch.fnmatch(ti.name, '%s/*' % archive_prefix): + self.fail('File without tarball-named subdirectory within tarball %s: %s' % (os.path.basename(archive), ti.name)) + if ti.name.endswith('/temp/log.do_compile'): + found = True + if not found: + self.fail('Did not find log.do_compile in output archive %s' % os.path.basename(archive)) + + + def test_retain_global(self): + """ + Summary: Test retain class RETAIN_DIRS_GLOBAL_* behaviour + Expected: Ensure RETAIN_DIRS_GLOBAL_ALWAYS always causes an + archive to be created, and RETAIN_DIRS_GLOBAL_FAILURE + only causes an archive to be created on failure. + Also test archive naming (with : character) as an + added bonus. + Product: oe-core + Author: Paul Eggleton <paul.eggleton@microsoft.com> + """ + + test_recipe = 'quilt-native' + test_recipe_fail = 'error' + + features = 'INHERIT += "retain"\n' + features += 'RETAIN_DIRS_GLOBAL_ALWAYS = "${LOG_DIR};prefix=buildlogs"\n' + features += 'RETAIN_DIRS_GLOBAL_FAILURE = "${STAMPS_DIR}"\n' + self.write_config(features) + + bitbake('-c clean %s' % test_recipe) + + bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR', 'STAMPS_DIR']) + retain_outdir = bb_vars['RETAIN_OUTDIR'] or '' + tmpdir = bb_vars['TMPDIR'] + if len(retain_outdir) < 5: + self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir) + if not oe.path.is_path_parent(tmpdir, retain_outdir): + self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir)) + try: + shutil.rmtree(retain_outdir) + except FileNotFoundError: + pass + + # Test success case + bitbake(test_recipe) + if not glob.glob(os.path.join(retain_outdir, 'buildlogs_*.tar.gz')): + self.fail('No output archive for LOG_DIR created') + stamps_dir = bb_vars['STAMPS_DIR'] + if glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % os.path.basename(stamps_dir))): + self.fail('Output archive for STAMPS_DIR created when it should not have been') + + # Test failure case + result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True) + if result.status == 0: + self.fail('Build of %s did not fail as expected' % test_recipe_fail) + if not glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % os.path.basename(stamps_dir))): + self.fail('Output archive for STAMPS_DIR not created') + if len(glob.glob(os.path.join(retain_outdir, 'buildlogs_*.tar.gz'))) != 2: + self.fail('Should be exactly two buildlogs archives in output dir') + + + def test_retain_misc(self): + """ + Summary: Test retain class with RETAIN_ENABLED and RETAIN_TARBALL_SUFFIX + Expected: Archive written to RETAIN_OUTDIR only when RETAIN_ENABLED is set + and archive contents are as expected. Also test archive naming + (with : character) as an added bonus. + Product: oe-core + Author: Paul Eggleton <paul.eggleton@microsoft.com> + """ + + test_recipe_fail = 'error' + + features = 'INHERIT += "retain"\n' + features += 'RETAIN_DIRS_ALWAYS = "${T}"\n' + features += 'RETAIN_ENABLED = "0"\n' + self.write_config(features) + + bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR']) + retain_outdir = bb_vars['RETAIN_OUTDIR'] or '' + tmpdir = bb_vars['TMPDIR'] + if len(retain_outdir) < 5: + self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir) + if not oe.path.is_path_parent(tmpdir, retain_outdir): + self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir)) + + try: + shutil.rmtree(retain_outdir) + except FileNotFoundError: + pass + + bitbake('-c clean %s' % test_recipe_fail) + result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True) + if result.status == 0: + self.fail('Build of %s did not fail as expected' % test_recipe_fail) + + if os.path.exists(retain_outdir) and os.listdir(retain_outdir): + self.fail('RETAIN_OUTDIR should be empty with RETAIN_ENABLED = "0"') + + features = 'INHERIT += "retain"\n' + features += 'RETAIN_DIRS_ALWAYS = "${T};prefix=recipelogs"\n' + features += 'RETAIN_TARBALL_SUFFIX = "${DATETIME}-testsuffix.tar.bz2"\n' + features += 'RETAIN_ENABLED = "1"\n' + self.write_config(features) + + result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True) + if result.status == 0: + self.fail('Build of %s did not fail as expected' % test_recipe_fail) + + archives = glob.glob(os.path.join(retain_outdir, '%s_*-testsuffix.tar.bz2' % test_recipe_fail)) + if not archives: + self.fail('No output archive for %s created' % test_recipe_fail) + if len(archives) != 2: + self.fail('Two archives for %s expected, but %d exist' % (test_recipe_fail, len(archives))) + recipelogs_found = False + workdir_found = False + for archive in archives: + contents_found = False + archive_prefix = os.path.basename(archive).split('.tar')[0] + if archive_prefix.startswith('%s_recipelogs' % test_recipe_fail): + recipelogs_found = True + if archive_prefix.startswith('%s_workdir' % test_recipe_fail): + workdir_found = True + with tarfile.open(archive, 'r:bz2') as tf: + for ti in tf: + if not fnmatch.fnmatch(ti.name, '%s/*' % (archive_prefix)): + self.fail('File without tarball-named subdirectory within tarball %s: %s' % (os.path.basename(archive), ti.name)) + if ti.name.endswith('/log.do_compile'): + contents_found = True + if not contents_found: + # Both archives should contain this file + self.fail('Did not find log.do_compile in output archive %s' % os.path.basename(archive)) + if not recipelogs_found: + self.fail('No archive with expected "recipelogs" prefix found') + if not workdir_found: + self.fail('No archive with expected "workdir" prefix found') diff --git a/poky/meta/lib/oeqa/selftest/cases/runtime_test.py b/poky/meta/lib/oeqa/selftest/cases/runtime_test.py index 13aa5f16c9..27090ae5cd 100644 --- a/poky/meta/lib/oeqa/selftest/cases/runtime_test.py +++ b/poky/meta/lib/oeqa/selftest/cases/runtime_test.py @@ -310,10 +310,7 @@ class Postinst(OESelftestTestCase): features += 'IMAGE_FEATURES += "package-management empty-root-password"\n' features += 'PACKAGE_CLASSES = "%s"\n' % classes if init_manager == "systemd": - features += 'DISTRO_FEATURES:append = " systemd usrmerge"\n' - features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n' - features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n' - features += 'VIRTUAL-RUNTIME_initscripts = ""\n' + features += 'INIT_MANAGER = "systemd"\n' self.write_config(features) bitbake('core-image-minimal') diff --git a/poky/meta/lib/oeqa/selftest/cases/rust.py b/poky/meta/lib/oeqa/selftest/cases/rust.py index 4ccbe9867b..cbe6366f75 100644 --- a/poky/meta/lib/oeqa/selftest/cases/rust.py +++ b/poky/meta/lib/oeqa/selftest/cases/rust.py @@ -66,132 +66,45 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase): # bless: First runs rustfmt to format the codebase, # then runs tidy checks. exclude_list = [ - 'compiler/rustc', - 'compiler/rustc_interface/src/tests.rs', - 'library/panic_abort', - 'library/panic_unwind', - 'library/test/src/stats/tests.rs', - 'src/bootstrap/builder/tests.rs', + 'src/bootstrap', 'src/doc/rustc', 'src/doc/rustdoc', 'src/doc/unstable-book', 'src/librustdoc', 'src/rustdoc-json-types', 'src/tools/compiletest/src/common.rs', + 'src/tools/jsondoclint', 'src/tools/lint-docs', + 'src/tools/replace-version-placeholder', 'src/tools/rust-analyzer', 'src/tools/rustdoc-themes', - 'src/tools/tidy', + 'src/tools/rust-installer', + 'src/tools/suggest-tests', + 'src/tools/tidy/src/', 'tests/assembly/asm/aarch64-outline-atomics.rs', 'tests/codegen/abi-main-signature-32bit-c-int.rs', - 'tests/codegen/abi-repr-ext.rs', - 'tests/codegen/abi-x86-interrupt.rs', - 'tests/codegen/branch-protection.rs', - 'tests/codegen/catch-unwind.rs', - 'tests/codegen/cf-protection.rs', - 'tests/codegen/enum-bounds-check-derived-idx.rs', - 'tests/codegen/force-unwind-tables.rs', - 'tests/codegen/intrinsic-no-unnamed-attr.rs', - 'tests/codegen/issues/issue-103840.rs', - 'tests/codegen/issues/issue-47278.rs', - 'tests/codegen/issues/issue-73827-bounds-check-index-in-subexpr.rs', - 'tests/codegen/lifetime_start_end.rs', - 'tests/codegen/local-generics-in-exe-internalized.rs', - 'tests/codegen/match-unoptimized.rs', - 'tests/codegen/noalias-rwlockreadguard.rs', - 'tests/codegen/non-terminate/nonempty-infinite-loop.rs', - 'tests/codegen/noreturn-uninhabited.rs', - 'tests/codegen/repr-transparent-aggregates-3.rs', - 'tests/codegen/riscv-abi/call-llvm-intrinsics.rs', - 'tests/codegen/riscv-abi/riscv64-lp64f-lp64d-abi.rs', - 'tests/codegen/riscv-abi/riscv64-lp64d-abi.rs', - 'tests/codegen/sse42-implies-crc32.rs', + 'tests/codegen/i128-x86-align.rs', + 'tests/codegen/issues/issue-122805.rs', 'tests/codegen/thread-local.rs', - 'tests/codegen/uninit-consts.rs', - 'tests/pretty/raw-str-nonexpr.rs', + 'tests/mir-opt/', 'tests/run-make', 'tests/run-make-fulldeps', 'tests/rustdoc', 'tests/rustdoc-json', 'tests/rustdoc-js-std', - 'tests/rustdoc-ui/cfg-test.rs', - 'tests/rustdoc-ui/check-cfg-test.rs', - 'tests/rustdoc-ui/display-output.rs', - 'tests/rustdoc-ui/doc-comment-multi-line-attr.rs', - 'tests/rustdoc-ui/doc-comment-multi-line-cfg-attr.rs', - 'tests/rustdoc-ui/doc-test-doctest-feature.rs', - 'tests/rustdoc-ui/doctest-multiline-crate-attribute.rs', - 'tests/rustdoc-ui/doctest-output.rs', - 'tests/rustdoc-ui/doc-test-rustdoc-feature.rs', - 'tests/rustdoc-ui/failed-doctest-compile-fail.rs', - 'tests/rustdoc-ui/issue-80992.rs', - 'tests/rustdoc-ui/issue-91134.rs', - 'tests/rustdoc-ui/nocapture-fail.rs', - 'tests/rustdoc-ui/nocapture.rs', - 'tests/rustdoc-ui/no-run-flag.rs', - 'tests/rustdoc-ui/run-directory.rs', - 'tests/rustdoc-ui/test-no_std.rs', - 'tests/rustdoc-ui/test-type.rs', - 'tests/rustdoc/unit-return.rs', 'tests/ui/abi/stack-probes-lto.rs', 'tests/ui/abi/stack-probes.rs', - 'tests/ui/array-slice-vec/subslice-patterns-const-eval-match.rs', - 'tests/ui/asm/x86_64/sym.rs', - 'tests/ui/associated-type-bounds/fn-apit.rs', - 'tests/ui/associated-type-bounds/fn-dyn-apit.rs', - 'tests/ui/associated-type-bounds/fn-wrap-apit.rs', + 'tests/ui/codegen/mismatched-data-layouts.rs', 'tests/ui/debuginfo/debuginfo-emit-llvm-ir-and-split-debuginfo.rs', - 'tests/ui/drop/dynamic-drop.rs', - 'tests/ui/empty_global_asm.rs', - 'tests/ui/functions-closures/fn-help-with-err.rs', - 'tests/ui/linkage-attr/issue-10755.rs', - 'tests/ui/macros/restricted-shadowing-legacy.rs', + 'tests/ui-fulldeps/', 'tests/ui/process/nofile-limit.rs', - 'tests/ui/process/process-panic-after-fork.rs', - 'tests/ui/process/process-sigpipe.rs', - 'tests/ui/simd/target-feature-mixup.rs', 'tests/ui/structs-enums/multiple-reprs.rs', - 'src/tools/jsondoclint', - 'src/tools/replace-version-placeholder', - 'tests/codegen/abi-efiapi.rs', - 'tests/codegen/abi-sysv64.rs', - 'tests/codegen/align-byval.rs', - 'tests/codegen/align-fn.rs', - 'tests/codegen/asm-powerpc-clobbers.rs', - 'tests/codegen/async-fn-debug-awaitee-field.rs', - 'tests/codegen/binary-search-index-no-bound-check.rs', - 'tests/codegen/call-metadata.rs', - 'tests/codegen/debug-column.rs', - 'tests/codegen/debug-limited.rs', - 'tests/codegen/debuginfo-generic-closure-env-names.rs', - 'tests/codegen/drop.rs', - 'tests/codegen/dst-vtable-align-nonzero.rs', - 'tests/codegen/enable-lto-unit-splitting.rs', - 'tests/codegen/enum/enum-u128.rs', - 'tests/codegen/fn-impl-trait-self.rs', - 'tests/codegen/inherit_overflow.rs', - 'tests/codegen/inline-function-args-debug-info.rs', - 'tests/codegen/intrinsics/mask.rs', - 'tests/codegen/intrinsics/transmute-niched.rs', - 'tests/codegen/issues/issue-73258.rs', - 'tests/codegen/issues/issue-75546.rs', - 'tests/codegen/issues/issue-77812.rs', - 'tests/codegen/issues/issue-98156-const-arg-temp-lifetime.rs', - 'tests/codegen/llvm-ident.rs', - 'tests/codegen/mainsubprogram.rs', - 'tests/codegen/move-operands.rs', - 'tests/codegen/repr/transparent-mips64.rs', - 'tests/mir-opt/', - 'tests/rustdoc-json', - 'tests/rustdoc-ui/doc-test-rustdoc-feature.rs', - 'tests/rustdoc-ui/no-run-flag.rs', - 'tests/ui-fulldeps/', - 'tests/ui/numbers-arithmetic/u128.rs' + 'tidyselftest' ] exclude_fail_tests = " ".join([" --exclude " + item for item in exclude_list]) # Add exclude_fail_tests with other test arguments - testargs = exclude_fail_tests + " --doc --no-fail-fast --bless" + testargs = exclude_fail_tests + " --no-fail-fast --bless" # wrap the execution with a qemu instance. # Tests are run with 512 tasks in parallel to execute all tests very quickly diff --git a/poky/meta/lib/oeqa/selftest/cases/spdx.py b/poky/meta/lib/oeqa/selftest/cases/spdx.py index 7685a81e7f..be595babb3 100644 --- a/poky/meta/lib/oeqa/selftest/cases/spdx.py +++ b/poky/meta/lib/oeqa/selftest/cases/spdx.py @@ -6,21 +6,26 @@ import json import os +import textwrap +from pathlib import Path from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import bitbake, get_bb_var, runCmd +from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd -class SPDXCheck(OESelftestTestCase): +class SPDX22Check(OESelftestTestCase): @classmethod def setUpClass(cls): - super(SPDXCheck, cls).setUpClass() + super().setUpClass() bitbake("python3-spdx-tools-native") bitbake("-c addto_recipe_sysroot python3-spdx-tools-native") def check_recipe_spdx(self, high_level_dir, spdx_file, target_name): - config = """ -INHERIT += "create-spdx" -""" + config = textwrap.dedent( + """\ + INHERIT:remove = "create-spdx" + INHERIT += "create-spdx-2.2" + """ + ) self.write_config(config) deploy_dir = get_bb_var("DEPLOY_DIR") @@ -29,7 +34,9 @@ INHERIT += "create-spdx" # qemux86-64 creates the directory qemux86_64 machine_dir = machine_var.replace("-", "_") - full_file_path = os.path.join(deploy_dir, "spdx", spdx_version, machine_dir, high_level_dir, spdx_file) + full_file_path = os.path.join( + deploy_dir, "spdx", spdx_version, machine_dir, high_level_dir, spdx_file + ) try: os.remove(full_file_path) @@ -44,8 +51,13 @@ INHERIT += "create-spdx" self.assertNotEqual(report, None) self.assertNotEqual(report["SPDXID"], None) - python = os.path.join(get_bb_var('STAGING_BINDIR', 'python3-spdx-tools-native'), 'nativepython3') - validator = os.path.join(get_bb_var('STAGING_BINDIR', 'python3-spdx-tools-native'), 'pyspdxtools') + python = os.path.join( + get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"), + "nativepython3", + ) + validator = os.path.join( + get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"), "pyspdxtools" + ) result = runCmd("{} {} -i {}".format(python, validator, filename)) self.assertExists(full_file_path) @@ -53,3 +65,106 @@ INHERIT += "create-spdx" def test_spdx_base_files(self): self.check_recipe_spdx("packages", "base-files.spdx.json", "base-files") + + +class SPDX3CheckBase(object): + """ + Base class for checking SPDX 3 based tests + """ + + def check_spdx_file(self, filename): + import oe.spdx30 + + self.assertExists(filename) + + # Read the file + objset = oe.spdx30.SHACLObjectSet() + with open(filename, "r") as f: + d = oe.spdx30.JSONLDDeserializer() + d.read(f, objset) + + return objset + + def check_recipe_spdx(self, target_name, spdx_path, *, task=None, extraconf=""): + config = textwrap.dedent( + f"""\ + INHERIT:remove = "create-spdx" + INHERIT += "{self.SPDX_CLASS}" + {extraconf} + """ + ) + self.write_config(config) + + if task: + bitbake(f"-c {task} {target_name}") + else: + bitbake(target_name) + + filename = spdx_path.format( + **get_bb_vars( + [ + "DEPLOY_DIR_IMAGE", + "DEPLOY_DIR_SPDX", + "MACHINE", + "MACHINE_ARCH", + "SDKMACHINE", + "SDK_DEPLOY", + "SPDX_VERSION", + "TOOLCHAIN_OUTPUTNAME", + ], + target_name, + ) + ) + + return self.check_spdx_file(filename) + + def check_objset_missing_ids(self, objset): + if objset.missing_ids: + self.assertTrue( + False, + "The following SPDXIDs are unresolved:\n " + + "\n ".join(objset.missing_ids), + ) + + +class SPDX30Check(SPDX3CheckBase, OESelftestTestCase): + SPDX_CLASS = "create-spdx-3.0" + + def test_base_files(self): + self.check_recipe_spdx( + "base-files", + "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/base-files.spdx.json", + ) + + def test_core_image_minimal(self): + objset = self.check_recipe_spdx( + "core-image-minimal", + "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json", + ) + + # Document should be fully linked + self.check_objset_missing_ids(objset) + + def test_core_image_minimal_sdk(self): + objset = self.check_recipe_spdx( + "core-image-minimal", + "{SDK_DEPLOY}/{TOOLCHAIN_OUTPUTNAME}.spdx.json", + task="populate_sdk", + ) + + # Document should be fully linked + self.check_objset_missing_ids(objset) + + def test_baremetal_helloworld(self): + objset = self.check_recipe_spdx( + "baremetal-helloworld", + "{DEPLOY_DIR_IMAGE}/baremetal-helloworld-image-{MACHINE}.spdx.json", + extraconf=textwrap.dedent( + """\ + TCLIBC = "baremetal" + """ + ), + ) + + # Document should be fully linked + self.check_objset_missing_ids(objset) diff --git a/poky/meta/lib/oeqa/selftest/cases/sstatetests.py b/poky/meta/lib/oeqa/selftest/cases/sstatetests.py index 94ad6e38b6..fa0172dd6d 100644 --- a/poky/meta/lib/oeqa/selftest/cases/sstatetests.py +++ b/poky/meta/lib/oeqa/selftest/cases/sstatetests.py @@ -378,7 +378,6 @@ class SStateHashSameSigs(SStateBase): self.write_config(""" MACHINE = "qemux86" TMPDIR = "${TOPDIR}/tmp-sstatesamehash" -TCLIBCAPPEND = "" BUILD_ARCH = "x86_64" BUILD_OS = "linux" SDKMACHINE = "x86_64" @@ -390,7 +389,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" self.write_config(""" MACHINE = "qemux86" TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" -TCLIBCAPPEND = "" BUILD_ARCH = "i686" BUILD_OS = "linux" SDKMACHINE = "i686" @@ -426,7 +424,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -TCLIBCAPPEND = \"\" NATIVELSBSTRING = \"DistroA\" BB_SIGNATURE_HANDLER = "OEBasicHash" """) @@ -434,7 +431,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" bitbake("core-image-weston -S none") self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -TCLIBCAPPEND = \"\" NATIVELSBSTRING = \"DistroB\" BB_SIGNATURE_HANDLER = "OEBasicHash" """) @@ -463,17 +459,17 @@ class SStateHashSameSigs2(SStateBase): configA = """ TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86-64\" BB_SIGNATURE_HANDLER = "OEBasicHash" """ #OLDEST_KERNEL is arch specific so set to a different value here for testing configB = """ TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -TCLIBCAPPEND = \"\" MACHINE = \"qemuarm\" OLDEST_KERNEL = \"3.3.0\" BB_SIGNATURE_HANDLER = "OEBasicHash" +ERROR_QA:append = " somenewoption" +WARN_QA:append = " someotheroption" """ self.sstate_common_samesigs(configA, configB, allarch=True) @@ -484,7 +480,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" configA = """ TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86-64\" require conf/multilib.conf MULTILIBS = \"multilib:lib32\" @@ -493,7 +488,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" """ configB = """ TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -TCLIBCAPPEND = \"\" MACHINE = \"qemuarm\" require conf/multilib.conf MULTILIBS = \"\" @@ -511,7 +505,6 @@ class SStateHashSameSigs3(SStateBase): self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86\" require conf/multilib.conf MULTILIBS = "multilib:lib32" @@ -522,7 +515,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" bitbake("world meta-toolchain -S none") self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86copy\" require conf/multilib.conf MULTILIBS = "multilib:lib32" @@ -559,7 +551,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86\" require conf/multilib.conf MULTILIBS = "multilib:lib32" @@ -570,7 +561,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" bitbake("binutils-native -S none") self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86copy\" BB_SIGNATURE_HANDLER = "OEBasicHash" """) @@ -598,7 +588,6 @@ class SStateHashSameSigs4(SStateBase): self.write_config(""" TMPDIR = "${TOPDIR}/tmp-sstatesamehash" -TCLIBCAPPEND = "" BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}" PARALLEL_MAKE = "-j 1" DL_DIR = "${TOPDIR}/download1" @@ -613,7 +602,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" bitbake("world meta-toolchain -S none") self.write_config(""" TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" -TCLIBCAPPEND = "" BB_NUMBER_THREADS = "${@oe.utils.cpu_count()+1}" PARALLEL_MAKE = "-j 2" DL_DIR = "${TOPDIR}/download2" @@ -724,7 +712,6 @@ class SStateFindSiginfo(SStateBase): """ self.write_config(""" TMPDIR = \"${TOPDIR}/tmp-sstates-findsiginfo\" -TCLIBCAPPEND = \"\" MACHINE = \"qemux86-64\" require conf/multilib.conf MULTILIBS = "multilib:lib32" @@ -933,8 +920,7 @@ class SStateCheckObjectPresence(SStateBase): # these get influnced by IMAGE_FSTYPES tweaks in yocto-autobuilder-helper's config.json (on x86-64) # additionally, they depend on noexec (thus, absent stamps) package, install, etc. image tasks, # which makes tracing other changes difficult - exceptions += ["{}.*create_spdx".format(t) for t in targets.split()] - exceptions += ["{}.*create_runtime_spdx".format(t) for t in targets.split()] + exceptions += ["{}.*create_.*spdx".format(t) for t in targets.split()] output_l = output.splitlines() for l in output_l: @@ -977,7 +963,7 @@ class SStateMirrors(SStateCheckObjectPresence): self.config_sstate(True) self.append_config(""" MACHINE = "{}" -BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687" +BB_HASHSERVE_UPSTREAM = "hashserv.yoctoproject.org:8686" SSTATE_MIRRORS ?= "file://.* http://cdn.jsdelivr.net/yocto/sstate/all/PATH;downloadfilename=PATH" """.format(machine)) else: @@ -992,12 +978,10 @@ MACHINE = "{}" def test_cdn_mirror_qemux86_64(self): exceptions = [] - self.run_test("qemux86-64", "core-image-minimal core-image-full-cmdline core-image-sato-sdk", exceptions, ignore_errors = True) self.run_test("qemux86-64", "core-image-minimal core-image-full-cmdline core-image-sato-sdk", exceptions) def test_cdn_mirror_qemuarm64(self): exceptions = [] - self.run_test("qemuarm64", "core-image-minimal core-image-full-cmdline core-image-sato-sdk", exceptions, ignore_errors = True) self.run_test("qemuarm64", "core-image-minimal core-image-full-cmdline core-image-sato-sdk", exceptions) def test_local_cache_qemux86_64(self): diff --git a/poky/meta/lib/oeqa/selftest/context.py b/poky/meta/lib/oeqa/selftest/context.py index 99186175e5..acc3b073bd 100644 --- a/poky/meta/lib/oeqa/selftest/context.py +++ b/poky/meta/lib/oeqa/selftest/context.py @@ -117,8 +117,11 @@ class OESelftestTestContext(OETestContext): newbblayers += 'BBLAYERS = "%s"\n' % ' '.join(bblayers_abspath) f.write(newbblayers) + # Rewrite builddir paths seen in environment variables for e in os.environ: - if builddir + "/" in os.environ[e]: + # Rewrite paths that absolutely point inside builddir + # (e.g $builddir/conf/ would be rewritten but not $builddir/../bitbake/) + if builddir + "/" in os.environ[e] and builddir + "/" in os.path.abspath(os.environ[e]): os.environ[e] = os.environ[e].replace(builddir + "/", newbuilddir + "/") if os.environ[e].endswith(builddir): os.environ[e] = os.environ[e].replace(builddir, newbuilddir) diff --git a/poky/meta/lib/oeqa/utils/__init__.py b/poky/meta/lib/oeqa/utils/__init__.py index 53bdcbf266..e03f7e33bb 100644 --- a/poky/meta/lib/oeqa/utils/__init__.py +++ b/poky/meta/lib/oeqa/utils/__init__.py @@ -96,4 +96,10 @@ def get_json_result_dir(d): custom_json_result_dir = d.getVar("OEQA_JSON_RESULT_DIR") if custom_json_result_dir: json_result_dir = custom_json_result_dir - return json_result_dir
\ No newline at end of file + return json_result_dir + +def get_artefact_dir(d): + custom_json_result_dir = d.getVar("OEQA_ARTEFACT_DIR") + if custom_json_result_dir: + return custom_json_result_dir + return os.path.join(d.getVar("LOG_DIR"), 'oeqa-artefacts') diff --git a/poky/meta/lib/oeqa/utils/gitarchive.py b/poky/meta/lib/oeqa/utils/gitarchive.py index 10cb267dfa..a826646059 100644 --- a/poky/meta/lib/oeqa/utils/gitarchive.py +++ b/poky/meta/lib/oeqa/utils/gitarchive.py @@ -67,7 +67,7 @@ def git_commit_data(repo, data_dir, branch, message, exclude, notes, log): # Remove files that are excluded if exclude: - repo.run_cmd(['rm', '--cached'] + [f for f in exclude], env_update) + repo.run_cmd(['rm', '--cached', '--ignore-unmatch'] + [f for f in exclude], env_update) tree = repo.run_cmd('write-tree', env_update) @@ -202,6 +202,8 @@ def gitarchive(data_dir, git_dir, no_create, bare, commit_msg_subject, commit_ms log.info("Pushing data to remote") data_repo.run_cmd(cmd) + return tag_name + # Container class for tester revisions TestedRev = namedtuple('TestedRev', 'commit commit_number tags') diff --git a/poky/meta/lib/oeqa/utils/postactions.py b/poky/meta/lib/oeqa/utils/postactions.py index ecdddd2d40..8f787838b9 100644 --- a/poky/meta/lib/oeqa/utils/postactions.py +++ b/poky/meta/lib/oeqa/utils/postactions.py @@ -7,23 +7,20 @@ # Run a set of actions after tests. The runner provides internal data # dictionary as well as test context to any action to run. -from oeqa.utils import get_json_result_dir - -def create_artifacts_directory(d, tc): - import shutil - - local_artifacts_dir = os.path.join(get_json_result_dir(d), "artifacts") - if os.path.isdir(local_artifacts_dir): - shutil.rmtree(local_artifacts_dir) - - os.makedirs(local_artifacts_dir) +import datetime +import io +import os +import stat +import subprocess +import tempfile +from oeqa.utils import get_artefact_dir ################################################################## # Host/target statistics ################################################################## -def get_target_disk_usage(d, tc): - output_file = os.path.join(get_json_result_dir(d), "artifacts", "target_disk_usage.txt") +def get_target_disk_usage(d, tc, artifacts_list, outputdir): + output_file = os.path.join(outputdir, "target_disk_usage.txt") try: (status, output) = tc.target.run('df -h') with open(output_file, 'w') as f: @@ -32,10 +29,10 @@ def get_target_disk_usage(d, tc): except Exception as e: bb.warn(f"Can not get target disk usage: {e}") -def get_host_disk_usage(d, tc): +def get_host_disk_usage(d, tc, artifacts_list, outputdir): import subprocess - output_file = os.path.join(get_json_result_dir(d), "artifacts", "host_disk_usage.txt") + output_file = os.path.join(outputdir, "host_disk_usage.txt") try: with open(output_file, 'w') as f: output = subprocess.run(['df', '-hl'], check=True, text=True, stdout=f, env={}) @@ -61,25 +58,21 @@ def get_artifacts_list(target, raw_list): return result -def retrieve_test_artifacts(target, artifacts_list, target_dir): - local_artifacts_dir = os.path.join(target_dir, "artifacts") - for artifact_path in artifacts_list: - if not os.path.isabs(artifact_path): - bb.warn(f"{artifact_path} is not an absolute path") - continue - try: - dest_dir = os.path.join(local_artifacts_dir, os.path.dirname(artifact_path[1:])) - os.makedirs(dest_dir, exist_ok=True) - target.copyFrom(artifact_path, dest_dir) - except Exception as e: - bb.warn(f"Can not retrieve {artifact_path} from test target: {e}") - -def list_and_fetch_failed_tests_artifacts(d, tc): - artifacts_list = get_artifacts_list(tc.target, d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS")) +def list_and_fetch_failed_tests_artifacts(d, tc, artifacts_list, outputdir): + artifacts_list = get_artifacts_list(tc.target, artifacts_list) if not artifacts_list: bb.warn("Could not load artifacts list, skip artifacts retrieval") - else: - retrieve_test_artifacts(tc.target, artifacts_list, get_json_result_dir(d)) + return + try: + cmd = "tar zcf - " + " ".join(artifacts_list) + (status, output) = tc.target.run(cmd, raw = True) + if status != 0 or not output: + raise Exception("Error while fetching compressed artifacts") + archive_name = os.path.join(outputdir, "tests_artifacts.tar.gz") + with open(archive_name, "wb") as f: + f.write(output) + except Exception as e: + bb.warn(f"Can not retrieve artifacts from test target: {e}") ################################################################## @@ -87,12 +80,22 @@ def list_and_fetch_failed_tests_artifacts(d, tc): ################################################################## def run_failed_tests_post_actions(d, tc): + artifacts = d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS") + # Allow all the code to be disabled by having no artifacts set, e.g. for systems with no ssh support + if not artifacts: + return + + outputdir = get_artefact_dir(d) + os.makedirs(outputdir, exist_ok=True) + datestr = datetime.datetime.now().strftime('%Y%m%d') + outputdir = tempfile.mkdtemp(prefix='oeqa-target-artefacts-%s-' % datestr, dir=outputdir) + os.chmod(outputdir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + post_actions=[ - create_artifacts_directory, list_and_fetch_failed_tests_artifacts, get_target_disk_usage, get_host_disk_usage ] for action in post_actions: - action(d, tc) + action(d, tc, artifacts, outputdir) diff --git a/poky/meta/lib/oeqa/utils/qemurunner.py b/poky/meta/lib/oeqa/utils/qemurunner.py index cda43aad8c..f1c2d2b5c9 100644 --- a/poky/meta/lib/oeqa/utils/qemurunner.py +++ b/poky/meta/lib/oeqa/utils/qemurunner.py @@ -519,7 +519,6 @@ class QemuRunner: except Exception as e: self.logger.warning('Extra log data exception %s' % repr(e)) data = None - self.thread.serial_lock.release() return False with self.thread.serial_lock: @@ -822,10 +821,12 @@ class LoggingThread(threading.Thread): self.logfunc(data, ".stdout") elif self.serialsock and self.serialsock.fileno() == fd: if self.serial_lock.acquire(blocking=False): - data = self.recv(1024, self.serialsock) - self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) - self.logfunc(data, ".2") - self.serial_lock.release() + try: + data = self.recv(1024, self.serialsock) + self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) + self.logfunc(data, ".2") + finally: + self.serial_lock.release() else: serial_registered = False poll.unregister(self.serialsock.fileno()) |