summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/buildtools-docs/cases/README (renamed from meta/lib/oeqa/sdk/buildtools-docs-cases/README)0
-rw-r--r--meta/lib/oeqa/buildtools-docs/cases/build.py (renamed from meta/lib/oeqa/sdk/buildtools-docs-cases/build.py)0
-rw-r--r--meta/lib/oeqa/buildtools/cases/README (renamed from meta/lib/oeqa/sdk/buildtools-cases/README)0
-rw-r--r--meta/lib/oeqa/buildtools/cases/build.py (renamed from meta/lib/oeqa/sdk/buildtools-cases/build.py)0
-rw-r--r--meta/lib/oeqa/buildtools/cases/gcc.py (renamed from meta/lib/oeqa/sdk/buildtools-cases/gcc.py)0
-rw-r--r--meta/lib/oeqa/buildtools/cases/https.py (renamed from meta/lib/oeqa/sdk/buildtools-cases/https.py)4
-rw-r--r--meta/lib/oeqa/buildtools/cases/sanity.py (renamed from meta/lib/oeqa/sdk/buildtools-cases/sanity.py)0
-rw-r--r--meta/lib/oeqa/core/case.py8
-rw-r--r--meta/lib/oeqa/core/context.py7
-rw-r--r--meta/lib/oeqa/core/decorator/data.py12
-rw-r--r--meta/lib/oeqa/core/runner.py2
-rw-r--r--meta/lib/oeqa/core/target/__init__.py1
-rw-r--r--meta/lib/oeqa/core/target/qemu.py2
-rw-r--r--meta/lib/oeqa/core/target/serial.py315
-rw-r--r--meta/lib/oeqa/core/target/ssh.py62
-rw-r--r--meta/lib/oeqa/core/tests/common.py1
-rw-r--r--meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml2
-rw-r--r--meta/lib/oeqa/files/test.go7
-rw-r--r--meta/lib/oeqa/manual/crops.json294
-rw-r--r--meta/lib/oeqa/manual/eclipse-plugin.json322
-rw-r--r--meta/lib/oeqa/runtime/case.py18
-rw-r--r--meta/lib/oeqa/runtime/cases/apt.py9
-rw-r--r--meta/lib/oeqa/runtime/cases/buildcpio.py4
-rw-r--r--meta/lib/oeqa/runtime/cases/buildlzip.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py21
-rw-r--r--meta/lib/oeqa/runtime/cases/go.py68
-rw-r--r--meta/lib/oeqa/runtime/cases/logrotate.py36
-rw-r--r--meta/lib/oeqa/runtime/cases/ltp.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt17
-rw-r--r--meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt8
-rw-r--r--meta/lib/oeqa/runtime/cases/parselogs.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/ping.py10
-rw-r--r--meta/lib/oeqa/runtime/cases/scp.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/skeletoninit.py3
-rw-r--r--meta/lib/oeqa/runtime/cases/ssh.py34
-rw-r--r--meta/lib/oeqa/runtime/cases/stap.py11
-rw-r--r--meta/lib/oeqa/runtime/cases/systemd.py23
-rw-r--r--meta/lib/oeqa/runtime/cases/uki.py16
-rw-r--r--meta/lib/oeqa/runtime/cases/weston.py2
-rw-r--r--meta/lib/oeqa/runtime/context.py12
-rw-r--r--meta/lib/oeqa/sdk/case.py70
-rw-r--r--meta/lib/oeqa/sdk/cases/assimp.py45
-rw-r--r--meta/lib/oeqa/sdk/cases/autotools.py52
-rw-r--r--meta/lib/oeqa/sdk/cases/buildcpio.py37
-rw-r--r--meta/lib/oeqa/sdk/cases/buildepoxy.py44
-rw-r--r--meta/lib/oeqa/sdk/cases/buildgalculator.py46
-rw-r--r--meta/lib/oeqa/sdk/cases/cmake.py51
-rw-r--r--meta/lib/oeqa/sdk/cases/gcc.py4
-rw-r--r--meta/lib/oeqa/sdk/cases/go.py107
-rw-r--r--meta/lib/oeqa/sdk/cases/gtk3.py40
-rw-r--r--meta/lib/oeqa/sdk/cases/kmod.py43
-rw-r--r--meta/lib/oeqa/sdk/cases/makefile.py (renamed from meta/lib/oeqa/sdk/cases/buildlzip.py)34
-rw-r--r--meta/lib/oeqa/sdk/cases/manifest.py26
-rw-r--r--meta/lib/oeqa/sdk/cases/maturin.py57
-rw-r--r--meta/lib/oeqa/sdk/cases/meson.py72
-rw-r--r--meta/lib/oeqa/sdk/cases/perl.py5
-rw-r--r--meta/lib/oeqa/sdk/cases/python.py5
-rw-r--r--meta/lib/oeqa/sdk/cases/rust.py3
-rw-r--r--meta/lib/oeqa/sdk/context.py15
-rw-r--r--meta/lib/oeqa/sdk/testsdk.py25
-rw-r--r--meta/lib/oeqa/sdkext/cases/devtool.py7
-rw-r--r--meta/lib/oeqa/sdkext/context.py4
-rw-r--r--meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt2
-rw-r--r--meta/lib/oeqa/sdkext/testsdk.py3
-rw-r--r--meta/lib/oeqa/selftest/cases/archiver.py18
-rw-r--r--meta/lib/oeqa/selftest/cases/barebox.py44
-rw-r--r--meta/lib/oeqa/selftest/cases/bbclasses.py106
-rw-r--r--meta/lib/oeqa/selftest/cases/bblayers.py96
-rw-r--r--meta/lib/oeqa/selftest/cases/bblock.py4
-rw-r--r--meta/lib/oeqa/selftest/cases/bbtests.py21
-rw-r--r--meta/lib/oeqa/selftest/cases/binutils.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/buildhistory.py63
-rw-r--r--meta/lib/oeqa/selftest/cases/buildoptions.py55
-rw-r--r--meta/lib/oeqa/selftest/cases/containerimage.py3
-rw-r--r--meta/lib/oeqa/selftest/cases/cve_check.py256
-rw-r--r--meta/lib/oeqa/selftest/cases/debuginfod.py14
-rw-r--r--meta/lib/oeqa/selftest/cases/devtool.py291
-rw-r--r--meta/lib/oeqa/selftest/cases/distrodata.py22
-rw-r--r--meta/lib/oeqa/selftest/cases/efibootpartition.py11
-rw-r--r--meta/lib/oeqa/selftest/cases/esdk.py6
-rw-r--r--meta/lib/oeqa/selftest/cases/fetch.py16
-rw-r--r--meta/lib/oeqa/selftest/cases/fitimage.py2125
-rw-r--r--meta/lib/oeqa/selftest/cases/gcc.py9
-rw-r--r--meta/lib/oeqa/selftest/cases/gdbserver.py3
-rw-r--r--meta/lib/oeqa/selftest/cases/imagefeatures.py28
-rw-r--r--meta/lib/oeqa/selftest/cases/incompatible_lic.py9
-rw-r--r--meta/lib/oeqa/selftest/cases/layerappend.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/liboe.py37
-rw-r--r--meta/lib/oeqa/selftest/cases/locales.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/meta_ide.py12
-rw-r--r--meta/lib/oeqa/selftest/cases/minidebuginfo.py16
-rw-r--r--meta/lib/oeqa/selftest/cases/oescripts.py30
-rw-r--r--meta/lib/oeqa/selftest/cases/overlayfs.py41
-rw-r--r--meta/lib/oeqa/selftest/cases/package.py26
-rw-r--r--meta/lib/oeqa/selftest/cases/picolibc.py18
-rw-r--r--meta/lib/oeqa/selftest/cases/recipetool.py266
-rw-r--r--meta/lib/oeqa/selftest/cases/recipeutils.py4
-rw-r--r--meta/lib/oeqa/selftest/cases/reproducible.py111
-rw-r--r--meta/lib/oeqa/selftest/cases/retain.py241
-rw-r--r--meta/lib/oeqa/selftest/cases/runqemu.py33
-rw-r--r--meta/lib/oeqa/selftest/cases/runtime_test.py11
-rw-r--r--meta/lib/oeqa/selftest/cases/rust.py146
-rw-r--r--meta/lib/oeqa/selftest/cases/sdk.py39
-rw-r--r--meta/lib/oeqa/selftest/cases/signing.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/spdx.py256
-rw-r--r--meta/lib/oeqa/selftest/cases/sstatetests.py208
-rw-r--r--meta/lib/oeqa/selftest/cases/toolchain.py71
-rw-r--r--meta/lib/oeqa/selftest/cases/uboot.py98
-rw-r--r--meta/lib/oeqa/selftest/cases/uki.py141
-rw-r--r--meta/lib/oeqa/selftest/cases/wic.py534
-rw-r--r--meta/lib/oeqa/selftest/cases/yoctotestresultsquerytests.py4
-rw-r--r--meta/lib/oeqa/selftest/context.py17
-rw-r--r--meta/lib/oeqa/targetcontrol.py7
-rw-r--r--meta/lib/oeqa/utils/__init__.py8
-rw-r--r--meta/lib/oeqa/utils/commands.py54
-rw-r--r--meta/lib/oeqa/utils/gitarchive.py6
-rw-r--r--meta/lib/oeqa/utils/metadata.py5
-rw-r--r--meta/lib/oeqa/utils/postactions.py74
-rw-r--r--meta/lib/oeqa/utils/qemurunner.py40
-rw-r--r--meta/lib/oeqa/utils/sshcontrol.py6
-rw-r--r--meta/lib/oeqa/utils/subprocesstweak.py13
-rw-r--r--meta/lib/oeqa/utils/testexport.py10
122 files changed, 5432 insertions, 2495 deletions
diff --git a/meta/lib/oeqa/sdk/buildtools-docs-cases/README b/meta/lib/oeqa/buildtools-docs/cases/README
index f8edbc7dad..f8edbc7dad 100644
--- a/meta/lib/oeqa/sdk/buildtools-docs-cases/README
+++ b/meta/lib/oeqa/buildtools-docs/cases/README
diff --git a/meta/lib/oeqa/sdk/buildtools-docs-cases/build.py b/meta/lib/oeqa/buildtools-docs/cases/build.py
index 6e3ee94292..6e3ee94292 100644
--- a/meta/lib/oeqa/sdk/buildtools-docs-cases/build.py
+++ b/meta/lib/oeqa/buildtools-docs/cases/build.py
diff --git a/meta/lib/oeqa/sdk/buildtools-cases/README b/meta/lib/oeqa/buildtools/cases/README
index d4f20faa9f..d4f20faa9f 100644
--- a/meta/lib/oeqa/sdk/buildtools-cases/README
+++ b/meta/lib/oeqa/buildtools/cases/README
diff --git a/meta/lib/oeqa/sdk/buildtools-cases/build.py b/meta/lib/oeqa/buildtools/cases/build.py
index c85c32496b..c85c32496b 100644
--- a/meta/lib/oeqa/sdk/buildtools-cases/build.py
+++ b/meta/lib/oeqa/buildtools/cases/build.py
diff --git a/meta/lib/oeqa/sdk/buildtools-cases/gcc.py b/meta/lib/oeqa/buildtools/cases/gcc.py
index a62c4d0bc4..a62c4d0bc4 100644
--- a/meta/lib/oeqa/sdk/buildtools-cases/gcc.py
+++ b/meta/lib/oeqa/buildtools/cases/gcc.py
diff --git a/meta/lib/oeqa/sdk/buildtools-cases/https.py b/meta/lib/oeqa/buildtools/cases/https.py
index 4525e3d758..98f27e5994 100644
--- a/meta/lib/oeqa/sdk/buildtools-cases/https.py
+++ b/meta/lib/oeqa/buildtools/cases/https.py
@@ -15,8 +15,8 @@ class HTTPTests(OESDKTestCase):
15 """ 15 """
16 16
17 def test_wget(self): 17 def test_wget(self):
18 self._run('env -i wget --debug --output-document /dev/null https://yoctoproject.org/connectivity.html') 18 self._run('env -i wget --debug --output-document /dev/null https://www.yoctoproject.org/connectivity.html')
19 19
20 def test_python(self): 20 def test_python(self):
21 # urlopen() returns a file-like object on success and throws an exception otherwise 21 # urlopen() returns a file-like object on success and throws an exception otherwise
22 self._run('python3 -c \'import urllib.request; urllib.request.urlopen("https://yoctoproject.org/connectivity.html")\'') 22 self._run('python3 -c \'import urllib.request; urllib.request.urlopen("https://www.yoctoproject.org/connectivity.html")\'')
diff --git a/meta/lib/oeqa/sdk/buildtools-cases/sanity.py b/meta/lib/oeqa/buildtools/cases/sanity.py
index a55d456656..a55d456656 100644
--- a/meta/lib/oeqa/sdk/buildtools-cases/sanity.py
+++ b/meta/lib/oeqa/buildtools/cases/sanity.py
diff --git a/meta/lib/oeqa/core/case.py b/meta/lib/oeqa/core/case.py
index bc4446a938..ad5524a714 100644
--- a/meta/lib/oeqa/core/case.py
+++ b/meta/lib/oeqa/core/case.py
@@ -5,6 +5,7 @@
5# 5#
6 6
7import base64 7import base64
8import os
8import zlib 9import zlib
9import unittest 10import unittest
10 11
@@ -57,6 +58,13 @@ class OETestCase(unittest.TestCase):
57 d.tearDownDecorator() 58 d.tearDownDecorator()
58 self.tearDownMethod() 59 self.tearDownMethod()
59 60
61 def assertFileExists(self, filename, msg=None):
62 """
63 Test that filename exists. If it does not, the test will fail.
64 """
65 if not os.path.exists(filename):
66 self.fail(msg or "%s does not exist" % filename)
67
60class OEPTestResultTestCase: 68class OEPTestResultTestCase:
61 """ 69 """
62 Mix-in class to provide functions to make interacting with extraresults for 70 Mix-in class to provide functions to make interacting with extraresults for
diff --git a/meta/lib/oeqa/core/context.py b/meta/lib/oeqa/core/context.py
index 9313271f58..46de9135c1 100644
--- a/meta/lib/oeqa/core/context.py
+++ b/meta/lib/oeqa/core/context.py
@@ -179,9 +179,16 @@ class OETestContextExecutor(object):
179 else: 179 else:
180 self.tc_kwargs['init']['td'] = {} 180 self.tc_kwargs['init']['td'] = {}
181 181
182 # Run image specific TEST_SUITE like testimage.bbclass by default
183 test_suites = self.tc_kwargs['init']['td'].get("TEST_SUITES")
184 if test_suites:
185 test_suites = test_suites.split()
186
182 if args.run_tests: 187 if args.run_tests:
183 self.tc_kwargs['load']['modules'] = args.run_tests 188 self.tc_kwargs['load']['modules'] = args.run_tests
184 self.tc_kwargs['load']['modules_required'] = args.run_tests 189 self.tc_kwargs['load']['modules_required'] = args.run_tests
190 elif test_suites:
191 self.tc_kwargs['load']['modules'] = test_suites
185 else: 192 else:
186 self.tc_kwargs['load']['modules'] = [] 193 self.tc_kwargs['load']['modules'] = []
187 194
diff --git a/meta/lib/oeqa/core/decorator/data.py b/meta/lib/oeqa/core/decorator/data.py
index 5444b2cb75..0daf46334f 100644
--- a/meta/lib/oeqa/core/decorator/data.py
+++ b/meta/lib/oeqa/core/decorator/data.py
@@ -228,3 +228,15 @@ class skipIfNotArch(OETestDecorator):
228 arch = self.case.td['HOST_ARCH'] 228 arch = self.case.td['HOST_ARCH']
229 if arch not in self.archs: 229 if arch not in self.archs:
230 self.case.skipTest('Test skipped on %s' % arch) 230 self.case.skipTest('Test skipped on %s' % arch)
231
232@registerDecorator
233class skipIfNotBuildArch(OETestDecorator):
234 """
235 Skip test if BUILD_ARCH is not present in the tuple specified.
236 """
237
238 attrs = ('archs',)
239 def setUpDecorator(self):
240 arch = self.case.td['BUILD_ARCH']
241 if arch not in self.archs:
242 self.case.skipTest('Test skipped on %s' % arch)
diff --git a/meta/lib/oeqa/core/runner.py b/meta/lib/oeqa/core/runner.py
index a86a706bd9..b683d9b80a 100644
--- a/meta/lib/oeqa/core/runner.py
+++ b/meta/lib/oeqa/core/runner.py
@@ -357,7 +357,7 @@ class OETestResultJSONHelper(object):
357 os.makedirs(write_dir, exist_ok=True) 357 os.makedirs(write_dir, exist_ok=True)
358 test_results = self._get_existing_testresults_if_available(write_dir) 358 test_results = self._get_existing_testresults_if_available(write_dir)
359 test_results[result_id] = {'configuration': configuration, 'result': test_result} 359 test_results[result_id] = {'configuration': configuration, 'result': test_result}
360 json_testresults = json.dumps(test_results, sort_keys=True, indent=4) 360 json_testresults = json.dumps(test_results, sort_keys=True, indent=1)
361 self._write_file(write_dir, self.testresult_filename, json_testresults) 361 self._write_file(write_dir, self.testresult_filename, json_testresults)
362 if has_bb: 362 if has_bb:
363 bb.utils.unlockfile(lf) 363 bb.utils.unlockfile(lf)
diff --git a/meta/lib/oeqa/core/target/__init__.py b/meta/lib/oeqa/core/target/__init__.py
index 1382aa9b52..177f648fe3 100644
--- a/meta/lib/oeqa/core/target/__init__.py
+++ b/meta/lib/oeqa/core/target/__init__.py
@@ -10,6 +10,7 @@ class OETarget(object):
10 10
11 def __init__(self, logger, *args, **kwargs): 11 def __init__(self, logger, *args, **kwargs):
12 self.logger = logger 12 self.logger = logger
13 self.runner = None
13 14
14 @abstractmethod 15 @abstractmethod
15 def start(self): 16 def start(self):
diff --git a/meta/lib/oeqa/core/target/qemu.py b/meta/lib/oeqa/core/target/qemu.py
index d93b3ac94a..769a6fec7e 100644
--- a/meta/lib/oeqa/core/target/qemu.py
+++ b/meta/lib/oeqa/core/target/qemu.py
@@ -15,7 +15,7 @@ from collections import defaultdict
15from .ssh import OESSHTarget 15from .ssh import OESSHTarget
16from oeqa.utils.qemurunner import QemuRunner 16from oeqa.utils.qemurunner import QemuRunner
17 17
18supported_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] 18supported_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic', 'wic.zst', 'ext3.zst', 'ext4.zst']
19 19
20class OEQemuTarget(OESSHTarget): 20class OEQemuTarget(OESSHTarget):
21 def __init__(self, logger, server_ip, timeout=300, user='root', 21 def __init__(self, logger, server_ip, timeout=300, user='root',
diff --git a/meta/lib/oeqa/core/target/serial.py b/meta/lib/oeqa/core/target/serial.py
new file mode 100644
index 0000000000..7c2cd8b248
--- /dev/null
+++ b/meta/lib/oeqa/core/target/serial.py
@@ -0,0 +1,315 @@
1#
2# SPDX-License-Identifier: MIT
3#
4
5import base64
6import logging
7import os
8from threading import Lock
9from . import OETarget
10
11class OESerialTarget(OETarget):
12
13 def __init__(self, logger, target_ip, server_ip, server_port=0,
14 timeout=300, serialcontrol_cmd=None, serialcontrol_extra_args=None,
15 serialcontrol_ps1=None, serialcontrol_connect_timeout=None,
16 machine=None, **kwargs):
17 if not logger:
18 logger = logging.getLogger('target')
19 logger.setLevel(logging.INFO)
20 filePath = os.path.join(os.getcwd(), 'remoteTarget.log')
21 fileHandler = logging.FileHandler(filePath, 'w', 'utf-8')
22 formatter = logging.Formatter(
23 '%(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
24 '%H:%M:%S')
25 fileHandler.setFormatter(formatter)
26 logger.addHandler(fileHandler)
27
28 super(OESerialTarget, self).__init__(logger)
29
30 if serialcontrol_ps1:
31 self.target_ps1 = serialcontrol_ps1
32 elif machine:
33 # fallback to a default value which assumes root@machine
34 self.target_ps1 = f'root@{machine}:.*# '
35 else:
36 raise ValueError("Unable to determine shell command prompt (PS1) format.")
37
38 if not serialcontrol_cmd:
39 raise ValueError("Unable to determine serial control command.")
40
41 if serialcontrol_extra_args:
42 self.connection_script = f'{serialcontrol_cmd} {serialcontrol_extra_args}'
43 else:
44 self.connection_script = serialcontrol_cmd
45
46 if serialcontrol_connect_timeout:
47 self.connect_timeout = serialcontrol_connect_timeout
48 else:
49 self.connect_timeout = 10 # default to 10s connection timeout
50
51 self.default_command_timeout = timeout
52 self.ip = target_ip
53 self.server_ip = server_ip
54 self.server_port = server_port
55 self.conn = None
56 self.mutex = Lock()
57
58 def start(self, **kwargs):
59 pass
60
61 def stop(self, **kwargs):
62 pass
63
64 def get_connection(self):
65 if self.conn is None:
66 self.conn = SerialConnection(self.connection_script,
67 self.target_ps1,
68 self.connect_timeout,
69 self.default_command_timeout)
70
71 return self.conn
72
73 def run(self, cmd, timeout=None):
74 """
75 Runs command on target over the provided serial connection.
76 The first call will open the connection, and subsequent
77 calls will re-use the same connection to send new commands.
78
79 command: Command to run on target.
80 timeout: <value>: Kill command after <val> seconds.
81 None: Kill command default value seconds.
82 0: No timeout, runs until return.
83 """
84 # Lock needed to avoid multiple threads running commands concurrently
85 # A serial connection can only be used by one caller at a time
86 with self.mutex:
87 conn = self.get_connection()
88
89 self.logger.debug(f"[Running]$ {cmd}")
90 # Run the command, then echo $? to get the command's return code
91 try:
92 output = conn.run_command(cmd, timeout)
93 status = conn.run_command("echo $?")
94 self.logger.debug(f" [stdout]: {output}")
95 self.logger.debug(f" [ret code]: {status}\n\n")
96 except SerialTimeoutException as e:
97 self.logger.debug(e)
98 output = ""
99 status = 255
100
101 # Return to $HOME after each command to simulate a stateless SSH connection
102 conn.run_command('cd "$HOME"')
103
104 return (int(status), output)
105
106 def copyTo(self, localSrc, remoteDst):
107 """
108 Copies files by converting them to base 32, then transferring
109 the ASCII text to the target, and decoding it in place on the
110 target.
111
112 On a 115k baud serial connection, this method transfers at
113 roughly 30kbps.
114 """
115 with open(localSrc, 'rb') as file:
116 data = file.read()
117
118 b32 = base64.b32encode(data).decode('utf-8')
119
120 # To avoid shell line limits, send a chunk at a time
121 SPLIT_LEN = 512
122 lines = [b32[i:i+SPLIT_LEN] for i in range(0, len(b32), SPLIT_LEN)]
123
124 with self.mutex:
125 conn = self.get_connection()
126
127 filename = os.path.basename(localSrc)
128 TEMP = f'/tmp/{filename}.b32'
129
130 # Create or empty out the temp file
131 conn.run_command(f'echo -n "" > {TEMP}')
132
133 for line in lines:
134 conn.run_command(f'echo -n {line} >> {TEMP}')
135
136 # Check to see whether the remoteDst is a directory
137 is_directory = conn.run_command(f'[[ -d {remoteDst} ]]; echo $?')
138 if int(is_directory) == 0:
139 # append the localSrc filename to the end of remoteDst
140 remoteDst = os.path.join(remoteDst, filename)
141
142 conn.run_command(f'base32 -d {TEMP} > {remoteDst}')
143 conn.run_command(f'rm {TEMP}')
144
145 return 0, 'Success'
146
147 def copyFrom(self, remoteSrc, localDst):
148 """
149 Copies files by converting them to base 32 on the target, then
150 transferring the ASCII text to the host. That text is then
151 decoded here and written out to the destination.
152
153 On a 115k baud serial connection, this method transfers at
154 roughly 30kbps.
155 """
156 with self.mutex:
157 b32 = self.get_connection().run_command(f'base32 {remoteSrc}')
158
159 data = base64.b32decode(b32.replace('\r\n', ''))
160
161 # If the local path is a directory, get the filename from
162 # the remoteSrc path and append it to localDst
163 if os.path.isdir(localDst):
164 filename = os.path.basename(remoteSrc)
165 localDst = os.path.join(localDst, filename)
166
167 with open(localDst, 'wb') as file:
168 file.write(data)
169
170 return 0, 'Success'
171
172 def copyDirTo(self, localSrc, remoteDst):
173 """
174 Copy recursively localSrc directory to remoteDst in target.
175 """
176
177 for root, dirs, files in os.walk(localSrc):
178 # Create directories in the target as needed
179 for d in dirs:
180 tmpDir = os.path.join(root, d).replace(localSrc, "")
181 newDir = os.path.join(remoteDst, tmpDir.lstrip("/"))
182 cmd = "mkdir -p %s" % newDir
183 self.run(cmd)
184
185 # Copy files into the target
186 for f in files:
187 tmpFile = os.path.join(root, f).replace(localSrc, "")
188 dstFile = os.path.join(remoteDst, tmpFile.lstrip("/"))
189 srcFile = os.path.join(root, f)
190 self.copyTo(srcFile, dstFile)
191
192 def deleteFiles(self, remotePath, files):
193 """
194 Deletes files in target's remotePath.
195 """
196
197 cmd = "rm"
198 if not isinstance(files, list):
199 files = [files]
200
201 for f in files:
202 cmd = "%s %s" % (cmd, os.path.join(remotePath, f))
203
204 self.run(cmd)
205
206 def deleteDir(self, remotePath):
207 """
208 Deletes target's remotePath directory.
209 """
210
211 cmd = "rmdir %s" % remotePath
212 self.run(cmd)
213
214 def deleteDirStructure(self, localPath, remotePath):
215 """
216 Delete recursively localPath structure directory in target's remotePath.
217
218 This function is useful to delete a package that is installed in the
219 device under test (DUT) and the host running the test has such package
220 extracted in tmp directory.
221
222 Example:
223 pwd: /home/user/tmp
224 tree: .
225 └── work
226 ├── dir1
227 │   └── file1
228 └── dir2
229
230 localpath = "/home/user/tmp" and remotepath = "/home/user"
231
232 With the above variables this function will try to delete the
233 directory in the DUT in this order:
234 /home/user/work/dir1/file1
235 /home/user/work/dir1 (if dir is empty)
236 /home/user/work/dir2 (if dir is empty)
237 /home/user/work (if dir is empty)
238 """
239
240 for root, dirs, files in os.walk(localPath, topdown=False):
241 # Delete files first
242 tmpDir = os.path.join(root).replace(localPath, "")
243 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
244 self.deleteFiles(remoteDir, files)
245
246 # Remove dirs if empty
247 for d in dirs:
248 tmpDir = os.path.join(root, d).replace(localPath, "")
249 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
250 self.deleteDir(remoteDir)
251
252class SerialTimeoutException(Exception):
253 def __init__(self, msg):
254 self.msg = msg
255 def __str__(self):
256 return self.msg
257
258class SerialConnection:
259
260 def __init__(self, script, target_prompt, connect_timeout, default_command_timeout):
261 import pexpect # limiting scope to avoid build dependency
262 self.prompt = target_prompt
263 self.connect_timeout = connect_timeout
264 self.default_command_timeout = default_command_timeout
265 self.conn = pexpect.spawn('/bin/bash', ['-c', script], encoding='utf8')
266 self._seek_to_clean_shell()
267 # Disable echo to avoid the need to parse the outgoing command
268 self.run_command('stty -echo')
269
270 def _seek_to_clean_shell(self):
271 """
272 Attempts to find a clean shell, meaning it is clear and
273 ready to accept a new command. This is necessary to ensure
274 the correct output is captured from each command.
275 """
276 import pexpect # limiting scope to avoid build dependency
277 # Look for a clean shell
278 # Wait a short amount of time for the connection to finish
279 pexpect_code = self.conn.expect([self.prompt, pexpect.TIMEOUT],
280 timeout=self.connect_timeout)
281
282 # if a timeout occurred, send an empty line and wait for a clean shell
283 if pexpect_code == 1:
284 # send a newline to clear and present the shell
285 self.conn.sendline("")
286 pexpect_code = self.conn.expect(self.prompt)
287
288 def run_command(self, cmd, timeout=None):
289 """
290 Runs command on target over the provided serial connection.
291 Returns any output on the shell while the command was run.
292
293 command: Command to run on target.
294 timeout: <value>: Kill command after <val> seconds.
295 None: Kill command default value seconds.
296 0: No timeout, runs until return.
297 """
298 import pexpect # limiting scope to avoid build dependency
299 # Convert from the OETarget defaults to pexpect timeout values
300 if timeout is None:
301 timeout = self.default_command_timeout
302 elif timeout == 0:
303 timeout = None # passing None to pexpect is infinite timeout
304
305 self.conn.sendline(cmd)
306 pexpect_code = self.conn.expect([self.prompt, pexpect.TIMEOUT], timeout=timeout)
307
308 # check for timeout
309 if pexpect_code == 1:
310 self.conn.send('\003') # send Ctrl+C
311 self._seek_to_clean_shell()
312 raise SerialTimeoutException(f'Timeout executing: {cmd} after {timeout}s')
313
314 return self.conn.before.removesuffix('\r\n')
315
diff --git a/meta/lib/oeqa/core/target/ssh.py b/meta/lib/oeqa/core/target/ssh.py
index 09cdd14c75..0ac3ae4388 100644
--- a/meta/lib/oeqa/core/target/ssh.py
+++ b/meta/lib/oeqa/core/target/ssh.py
@@ -55,24 +55,28 @@ class OESSHTarget(OETarget):
55 def stop(self, **kwargs): 55 def stop(self, **kwargs):
56 pass 56 pass
57 57
58 def _run(self, command, timeout=None, ignore_status=True): 58 def _run(self, command, timeout=None, ignore_status=True, raw=False, ignore_ssh_fails=False):
59 """ 59 """
60 Runs command in target using SSHProcess. 60 Runs command in target using SSHProcess.
61 """ 61 """
62 self.logger.debug("[Running]$ %s" % " ".join(command)) 62 self.logger.debug("[Running]$ %s" % " ".join(command))
63 63
64 starttime = time.time() 64 starttime = time.time()
65 status, output = SSHCall(command, self.logger, timeout) 65 status, output = SSHCall(command, self.logger, timeout, raw)
66 self.logger.debug("[Command returned '%d' after %.2f seconds]" 66 self.logger.debug("[Command returned '%d' after %.2f seconds]"
67 "" % (status, time.time() - starttime)) 67 "" % (status, time.time() - starttime))
68 68
69 if status and not ignore_status: 69 if status == 255 and not ignore_ssh_fails:
70 raise AssertionError("ssh exited with status '255' for command "
71 "'%s': this is likely an SSH failure\n%s"
72 % (command, output))
73 elif status and not ignore_status:
70 raise AssertionError("Command '%s' returned non-zero exit " 74 raise AssertionError("Command '%s' returned non-zero exit "
71 "status %d:\n%s" % (command, status, output)) 75 "status %d:\n%s" % (command, status, output))
72 76
73 return (status, output) 77 return (status, output)
74 78
75 def run(self, command, timeout=None, ignore_status=True): 79 def run(self, command, timeout=None, ignore_status=True, raw=False, ignore_ssh_fails=False):
76 """ 80 """
77 Runs command in target. 81 Runs command in target.
78 82
@@ -91,8 +95,11 @@ class OESSHTarget(OETarget):
91 else: 95 else:
92 processTimeout = self.timeout 96 processTimeout = self.timeout
93 97
94 status, output = self._run(sshCmd, processTimeout, ignore_status) 98 status, output = self._run(sshCmd, processTimeout, ignore_status, raw, ignore_ssh_fails)
95 self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output)) 99 if len(output) > (64 * 1024):
100 self.logger.debug('Command: %s\nStatus: %d Output length: %s\n' % (command, status, len(output)))
101 else:
102 self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output))
96 103
97 return (status, output) 104 return (status, output)
98 105
@@ -206,22 +213,23 @@ class OESSHTarget(OETarget):
206 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/")) 213 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
207 self.deleteDir(remoteDir) 214 self.deleteDir(remoteDir)
208 215
209def SSHCall(command, logger, timeout=None, **opts): 216def SSHCall(command, logger, timeout=None, raw=False, **opts):
210 217
211 def run(): 218 def run():
212 nonlocal output 219 nonlocal output
213 nonlocal process 220 nonlocal process
214 output_raw = b'' 221 output_raw = bytearray()
215 starttime = time.time() 222 starttime = time.time()
223 progress = time.time()
216 process = subprocess.Popen(command, **options) 224 process = subprocess.Popen(command, **options)
217 has_timeout = False 225 has_timeout = False
226 appendline = None
218 if timeout: 227 if timeout:
219 endtime = starttime + timeout 228 endtime = starttime + timeout
220 eof = False 229 eof = False
221 os.set_blocking(process.stdout.fileno(), False) 230 os.set_blocking(process.stdout.fileno(), False)
222 while not has_timeout and not eof: 231 while not has_timeout and not eof:
223 try: 232 try:
224 logger.debug('Waiting for process output: time: %s, endtime: %s' % (time.time(), endtime))
225 if select.select([process.stdout], [], [], 5)[0] != []: 233 if select.select([process.stdout], [], [], 5)[0] != []:
226 # wait a bit for more data, tries to avoid reading single characters 234 # wait a bit for more data, tries to avoid reading single characters
227 time.sleep(0.2) 235 time.sleep(0.2)
@@ -229,9 +237,9 @@ def SSHCall(command, logger, timeout=None, **opts):
229 if not data: 237 if not data:
230 eof = True 238 eof = True
231 else: 239 else:
232 output_raw += data 240 output_raw.extend(data)
233 # ignore errors to capture as much as possible 241 # ignore errors to capture as much as possible
234 logger.debug('Partial data from SSH call:\n%s' % data.decode('utf-8', errors='ignore')) 242 #logger.debug('Partial data from SSH call:\n%s' % data.decode('utf-8', errors='ignore'))
235 endtime = time.time() + timeout 243 endtime = time.time() + timeout
236 except InterruptedError: 244 except InterruptedError:
237 logger.debug('InterruptedError') 245 logger.debug('InterruptedError')
@@ -244,6 +252,10 @@ def SSHCall(command, logger, timeout=None, **opts):
244 logger.debug('SSHCall has timeout! Time: %s, endtime: %s' % (time.time(), endtime)) 252 logger.debug('SSHCall has timeout! Time: %s, endtime: %s' % (time.time(), endtime))
245 has_timeout = True 253 has_timeout = True
246 254
255 if time.time() >= (progress + 60):
256 logger.debug('Waiting for process output at time: %s with datasize: %s' % (time.time(), len(output_raw)))
257 progress = time.time()
258
247 process.stdout.close() 259 process.stdout.close()
248 260
249 # process hasn't returned yet 261 # process hasn't returned yet
@@ -256,17 +268,29 @@ def SSHCall(command, logger, timeout=None, **opts):
256 logger.debug('OSError when killing process') 268 logger.debug('OSError when killing process')
257 pass 269 pass
258 endtime = time.time() - starttime 270 endtime = time.time() - starttime
259 lastline = ("\nProcess killed - no output for %d seconds. Total" 271 appendline = ("\nProcess killed - no output for %d seconds. Total"
260 " running time: %d seconds." % (timeout, endtime)) 272 " running time: %d seconds." % (timeout, endtime))
261 logger.debug('Received data from SSH call:\n%s ' % lastline) 273 logger.debug('Received data from SSH call:\n%s ' % appendline)
262 output += lastline
263 process.wait() 274 process.wait()
264 275
276 if raw:
277 output = bytes(output_raw)
278 if appendline:
279 output += bytes(appendline, "utf-8")
280 else:
281 output = output_raw.decode('utf-8', errors='ignore')
282 if appendline:
283 output += appendline
265 else: 284 else:
266 output_raw = process.communicate()[0] 285 output = output_raw = process.communicate()[0]
286 if not raw:
287 output = output_raw.decode('utf-8', errors='ignore')
267 288
268 output = output_raw.decode('utf-8', errors='ignore') 289 if len(output) < (64 * 1024):
269 logger.debug('Data from SSH call:\n%s' % output.rstrip()) 290 if output.rstrip():
291 logger.debug('Data from SSH call:\n%s' % output.rstrip())
292 else:
293 logger.debug('No output from SSH call')
270 294
271 # timout or not, make sure process exits and is not hanging 295 # timout or not, make sure process exits and is not hanging
272 if process.returncode == None: 296 if process.returncode == None:
@@ -292,7 +316,7 @@ def SSHCall(command, logger, timeout=None, **opts):
292 316
293 options = { 317 options = {
294 "stdout": subprocess.PIPE, 318 "stdout": subprocess.PIPE,
295 "stderr": subprocess.STDOUT, 319 "stderr": subprocess.STDOUT if not raw else None,
296 "stdin": None, 320 "stdin": None,
297 "shell": False, 321 "shell": False,
298 "bufsize": -1, 322 "bufsize": -1,
@@ -320,4 +344,4 @@ def SSHCall(command, logger, timeout=None, **opts):
320 logger.debug('Something went wrong, killing SSH process') 344 logger.debug('Something went wrong, killing SSH process')
321 raise 345 raise
322 346
323 return (process.returncode, output.rstrip()) 347 return (process.returncode, output if raw else output.rstrip())
diff --git a/meta/lib/oeqa/core/tests/common.py b/meta/lib/oeqa/core/tests/common.py
index 88cc758ad3..bcc4fde632 100644
--- a/meta/lib/oeqa/core/tests/common.py
+++ b/meta/lib/oeqa/core/tests/common.py
@@ -9,7 +9,6 @@ import os
9 9
10import unittest 10import unittest
11import logging 11import logging
12import os
13 12
14logger = logging.getLogger("oeqa") 13logger = logging.getLogger("oeqa")
15logger.setLevel(logging.INFO) 14logger.setLevel(logging.INFO)
diff --git a/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml b/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml
index de95025e86..a78ada2593 100644
--- a/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml
+++ b/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml
@@ -14,7 +14,7 @@ crate-type = ["cdylib"]
14rand = "0.8.4" 14rand = "0.8.4"
15 15
16[dependencies.pyo3] 16[dependencies.pyo3]
17version = "0.19.0" 17version = "0.24.1"
18# "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8 18# "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8
19features = ["abi3-py38"] 19features = ["abi3-py38"]
20 20
diff --git a/meta/lib/oeqa/files/test.go b/meta/lib/oeqa/files/test.go
new file mode 100644
index 0000000000..9ca9302654
--- /dev/null
+++ b/meta/lib/oeqa/files/test.go
@@ -0,0 +1,7 @@
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("Hello from Go!")
7}
diff --git a/meta/lib/oeqa/manual/crops.json b/meta/lib/oeqa/manual/crops.json
deleted file mode 100644
index 5cfa653843..0000000000
--- a/meta/lib/oeqa/manual/crops.json
+++ /dev/null
@@ -1,294 +0,0 @@
1[
2 {
3 "test": {
4 "@alias": "crops-default.crops-default.sdkext_eSDK_devtool_build_make",
5 "author": [
6 {
7 "email": "francisco.j.pedraza.gonzalez@intel.com",
8 "name": "francisco.j.pedraza.gonzalez@intel.com"
9 }
10 ],
11 "execution": {
12 "1": {
13 "action": "IMPORTANT NOTE: The firsts 5 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\n\n",
14 "expected_results": ""
15 },
16 "2": {
17 "action": " Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
18 "expected_results": ""
19 },
20 "3": {
21 "action": "Create the following tree of files <crops-esdk-workdir-workspace>/sdkext/files/myapp <crops-esdk-workdir-workspace>/sdkext/files/myapp_cmake \n\n\n",
22 "expected_results": ""
23 },
24 "4": {
25 "action": " Create the following files withing the myapp directory myapp.c and the Makefile. Write the following inside of each file: \n---------------------------------------- \nMakefile should contain \n\nall: myapp \n\nmyapp: myapp.o \n\t$(CC) $(LDFLAGS) $< -o $@ \n\nmyapp.o: myapp.c \n\t$(CC) $(CFLAGS) -c $< -o $@ \n\nclean: \n\trm -rf myapp.o myapp \n\n----------------------------- \nmyapp.c shold contain \n\n\n#include <stdio.h> \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n \n\treturn 0; \n} \n------------------------------------ \n\n",
26 "expected_results": "be sure that the indentations on the makefile are tabs not spaces. \n\n"
27 },
28 "5": {
29 "action": " Create the following files within the myapp_cmake directory CMakeLists.txt and myapp.c. Write the following inside each file: \n\n------------------------------------ \nCMakeLists.txt should contain: \n\ncmake_minimum_required (VERSION 2.6) \nproject (myapp) \n# The version number. \nset (myapp_VERSION_MAJOR 1) \nset (myapp_VERSION_MINOR 0) \n\n# add the executable \nadd_executable (myapp myapp.c) \n\ninstall(TARGETS myapp \nRUNTIME DESTINATION bin) \n\n------------------------------------------ \nmyapp.c should contain: \n\n#include <stdio.h> \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n\n\treturn 0; \n} \n------------------------------------------------- \n\n",
30 "expected_results": "Be sure that the indentations on CMakeLists.txt is tabs not spaces."
31 },
32 "6": {
33 "action": " source environment-setup-i586-poky-linux \n\n",
34 "expected_results": "This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n\n"
35 },
36 "7": {
37 "action": " run command which devtool \n\n",
38 "expected_results": "this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n "
39 },
40 "8": {
41 "action": "devtool add myapp <directory>(this is myapp dir) \n\n\n",
42 "expected_results": "The directory you should input is the myapp directory. This should automatically create the recipe myapp.bb under <crops-esdk-workdir-workspace>/recipes/myapp/myapp.bb"
43 },
44 "9": {
45 "action": " devtool build myapp \n\n",
46 "expected_results": "This should compile an image"
47 },
48 "10": {
49 "action": " devtool reset myapp ",
50 "expected_results": "This cleans sysroot of the myapp recipe, but it leaves the source tree intact. meaning it does not erase."
51 }
52 },
53 "summary": "sdkext_eSDK_devtool_build_make"
54 }
55 },
56 {
57 "test": {
58 "@alias": "crops-default.crops-default.sdkext_devtool_build_esdk_package",
59 "author": [
60 {
61 "email": "francisco.j.pedraza.gonzalez@intel.com",
62 "name": "francisco.j.pedraza.gonzalez@intel.com"
63 }
64 ],
65 "execution": {
66 "1": {
67 "action": "IMPORTANT NOTE: The firsts 5 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\n\n",
68 "expected_results": ""
69 },
70 "2": {
71 "action": " Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
72 "expected_results": ""
73 },
74 "3": {
75 "action": " Create the following tree of files <crops-esdk-workdir-workspace>/sdkext/files/myapp/ \n <crops-esdk-workdir-workspace>/sdkext/files/myapp_cmake \n\n",
76 "expected_results": ""
77 },
78 "4": {
79 "action": " Create the following files withing the myapp directory myapp.c and the Makefile. Write the following inside of each file: \n---------------------------------------- \nMakefile should contain \n\nall: myapp \n\nmyapp: myapp.o \n\t$(CC) $(LDFLAGS) $< -o $@ \n\nmyapp.o: myapp.c \n\t$(CC) $(CFLAGS) -c $< -o $@ \n\nclean: \n\trm -rf myapp.o myapp \n\n----------------------------- \nmyapp.c shold contain \n\n#include <stdio.h> \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n \n\treturn 0; \n} \n------------------------------------ \n\n",
80 "expected_results": "be sure that the indentations on the makefile are tabs not spaces. \n\n"
81 },
82 "5": {
83 "action": " Create the following files within the myapp_cmake directory CMakeLists.txt and myapp.c. Write the following inside each file: \n\n------------------------------------ \nCMakeLists.txt should contain: \n\ncmake_minimum_required (VERSION 2.6) \nproject (myapp) \n# The version number. \nset (myapp_VERSION_MAJOR 1) \nset (myapp_VERSION_MINOR 0) \n\n# add the executable \nadd_executable (myapp myapp.c) \n\ninstall(TARGETS myapp \nRUNTIME DESTINATION bin) \n\n------------------------------------------ \nmyapp.c should contain: \n\n#include<stdio.h> \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n\n\treturn 0; \n} \n------------------------------------------------- \n\n",
84 "expected_results": "Be sure that the indentations on CMakeLists.txt is tabs not spaces. \n\n"
85 },
86 "6": {
87 "action": " source environment-setup-i586-poky-linux \n\n",
88 "expected_results": "This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n\n"
89 },
90 "7": {
91 "action": " run command which devtool \n\n",
92 "expected_results": " this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n"
93 },
94 "8": {
95 "action": " devtool add myapp <directory> (this is myapp dir) \n\n",
96 "expected_results": " The directory you should input is the myapp directory. This should automatically create the recipe myapp.bb under <crops-esdk-workdir-workspace>/recipes/myapp/myapp.bb \n\n"
97 },
98 "9": {
99 "action": " devtool package myapp \n\n",
100 "expected_results": " you should expect a package creation of myapp and it should be under the /tmp/deploy/ \n\n"
101 },
102 "10": {
103 "action": " devtool reset myapp ",
104 "expected_results": "This cleans sysroot of the myapp recipe, but it leaves the source tree intact. meaning it does not erase.\n</package_format>"
105 }
106 },
107 "summary": "sdkext_devtool_build_esdk_package"
108 }
109 },
110 {
111 "test": {
112 "@alias": "crops-default.crops-default.sdkext_devtool_build_cmake",
113 "author": [
114 {
115 "email": "francisco.j.pedraza.gonzalez@intel.com",
116 "name": "francisco.j.pedraza.gonzalez@intel.com"
117 }
118 ],
119 "execution": {
120 "1": {
121 "action": "IMPORTANT NOTE: The firsts 5 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\n\n",
122 "expected_results": ""
123 },
124 "2": {
125 "action": " Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
126 "expected_results": ""
127 },
128 "3": {
129 "action": " Create the following tree of files <crops-esdk-workdir-workspace>/sdkext/files/myapp \n <crops-esdk-workdir-workspace>/sdkext/files/myapp_cmake \n\n",
130 "expected_results": ""
131 },
132 "4": {
133 "action": " Create the following files withing the myapp directory myapp.c and the Makefile. Write the following inside of each file: \n---------------------------------------- \nMakefile should contain \n\nall: myapp \n\nmyapp: myapp.o \n\t$(CC) $(LDFLAGS) $< -o $@ \n\nmyapp.o: myapp.c \n\t$(CC) $(CFLAGS) -c $< -o $@ \n\nclean: \n\trm -rf myapp.o myapp \n\n----------------------------- \nmyapp.c shold contain \n\n#include <stdio.h> \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n \n\treturn 0; \n} \n------------------------------------ \n\n",
134 "expected_results": "be sure that the indentations on the makefile are tabs not spaces. \n\n"
135 },
136 "5": {
137 "action": " Create the following files within the myapp_cmake directory CMakeLists.txt and myapp.c. Write the following inside each file: \n\n------------------------------------ \nCMakeLists.txt should contain: \n\ncmake_minimum_required (VERSION 2.6) \nproject (myapp) \n# The version number. \nset (myapp_VERSION_MAJOR 1) \nset (myapp_VERSION_MINOR 0) \n\n# add the executable \nadd_executable (myapp myapp.c) \n\ninstall(TARGETS myapp \nRUNTIME DESTINATION bin) \n\n------------------------------------------ \nmyapp.c should contain: \n\n#include \n\nint \nmain(int argc, char *argv[]) \n{ \n\tprintf(\"Hello world\\n\"); \n\n\treturn 0; \n} \n------------------------------------------------- \n\n",
138 "expected_results": "Be sure that the indentations on CMakeLists.txt is tabs not spaces. \n\n"
139 },
140 "6": {
141 "action": " source environment-setup-i586-poky-linux \n\n",
142 "expected_results": "This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n\n"
143 },
144 "7": {
145 "action": " run command which devtool \n\n",
146 "expected_results": "this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n"
147 },
148 "8": {
149 "action": " devtool add myapp <directory> (this is myapp_cmake dir) \n\n",
150 "expected_results": "The directory you should input is the myapp_cmake directory. This should automatically create the recipe myapp.bb under <crops-esdk-workdir-workspace>/recipes/myapp/myapp.bb \n\n"
151 },
152 "9": {
153 "action": " devtool build myapp \n\n",
154 "expected_results": "This should compile an image \n\n"
155 },
156 "10": {
157 "action": " devtool reset myapp ",
158 "expected_results": "This cleans sysroot of the myapp recipe, but it leaves the source tree intact. meaning it does not erase. "
159 }
160 },
161 "summary": "sdkext_devtool_build_cmake"
162 }
163 },
164 {
165 "test": {
166 "@alias": "crops-default.crops-default.sdkext_extend_autotools_recipe_creation",
167 "author": [
168 {
169 "email": "francisco.j.pedraza.gonzalez@intel.com",
170 "name": "francisco.j.pedraza.gonzalez@intel.com"
171 }
172 ],
173 "execution": {
174 "1": {
175 "action": "IMPORTANT NOTE: The firsts 2 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\n\n",
176 "expected_results": ""
177 },
178 "2": {
179 "action": "Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
180 "expected_results": ""
181 },
182 "3": {
183 "action": " source environment-setup-i586-poky-linux \n\n",
184 "expected_results": " This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n\n"
185 },
186 "4": {
187 "action": "run command which devtool \n\n",
188 "expected_results": "this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n"
189 },
190 "5": {
191 "action": "devtool sdk-install -s libxml2 \n\n",
192 "expected_results": "this should install libxml2 \n\n"
193 },
194 "6": {
195 "action": "devtool add librdfa https://github.com/rdfa/librdfa \n\n",
196 "expected_results": "This should automatically create the recipe librdfa.bb under /recipes/librdfa/librdfa.bb \n\n"
197 },
198 "7": {
199 "action": "devtool build librdfa \n\n",
200 "expected_results": "This should compile \n\n"
201 },
202 "8": {
203 "action": "devtool reset librdfa ",
204 "expected_results": "This cleans sysroot of the librdfa recipe, but it leaves the source tree intact. meaning it does not erase."
205 }
206 },
207 "summary": "sdkext_extend_autotools_recipe_creation"
208 }
209 },
210 {
211 "test": {
212 "@alias": "crops-default.crops-default.sdkext_devtool_kernelmodule",
213 "author": [
214 {
215 "email": "francisco.j.pedraza.gonzalez@intel.com",
216 "name": "francisco.j.pedraza.gonzalez@intel.com"
217 }
218 ],
219 "execution": {
220 "1": {
221 "action": "IMPORTANT NOTE: The firsts 2 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\n",
222 "expected_results": ""
223 },
224 "2": {
225 "action": " Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
226 "expected_results": ""
227 },
228 "3": {
229 "action": "source environment-setup-i586-poky-linux \n\n",
230 "expected_results": "This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n \n"
231 },
232 "4": {
233 "action": "run command which devtool \n\n",
234 "expected_results": "this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n"
235 },
236 "5": {
237 "action": "devtool add kernel-module-hello-world https://git.yoctoproject.org/git/kernel-module-hello-world \n\n",
238 "expected_results": "This should automatically create the recipe kernel-module-hello-world.bb under <crops-esdk-workdir-workspace>/recipes/kernel-module-hello-world/kernel-module-hello-world.bb "
239 },
240 "6": {
241 "action": "devtool build kernel-module-hello-world \n\n",
242 "expected_results": "This should compile an image \n\n"
243 },
244 "7": {
245 "action": "devtool reset kernel-module-hello-world ",
246 "expected_results": "This cleans sysroot of the kernel-module-hello-world recipe, but it leaves the source tree intact. meaning it does not erase."
247 }
248 },
249 "summary": "sdkext_devtool_kernelmodule"
250 }
251 },
252 {
253 "test": {
254 "@alias": "crops-default.crops-default.sdkext_recipes_for_nodejs",
255 "author": [
256 {
257 "email": "francisco.j.pedraza.gonzalez@intel.com",
258 "name": "francisco.j.pedraza.gonzalez@intel.com"
259 }
260 ],
261 "execution": {
262 "1": {
263 "action": "IMPORTANT NOTE: The firsts 2 steps refer to configuration of the environment to run the rest of the steps. These only apply for CROPS-eSDK. \n\n\nlets say variable npm = npm://registry.npmjs.org;name=winston;version=2.2.0 \n\n",
264 "expected_results": ""
265 },
266 "2": {
267 "action": "Initiate your Crops-esdk environment as it says in wiki https://github.com/crops/docker-win-mac-docs/wiki \n\n",
268 "expected_results": ""
269 },
270 "3": {
271 "action": "source environment-setup-i586-poky-linux \n\n",
272 "expected_results": "This should output a message that says SDK environment now set up; additionally you may now run devtool to perform development tasks etc etc ... \n\n"
273 },
274 "4": {
275 "action": "run command which devtool \n\n",
276 "expected_results": "this should output the directory of the devtool script and it should be within the sdk workdir you are working in. \n\n"
277 },
278 "5": {
279 "action": " 4a) git clone git://git.openembedded.org/meta-openembedded in layers/build directory \n \n4b) Add meta-openembedded/meta-oe in bblayer.conf as mentioned below: ${SDKBASEMETAPATH}/layers/build/meta-openembedded/meta-oe \\ \n\n4c) devtool add \"npm://registry.npmjs.org;name=npm;version=2.2.0\" \n\n",
280 "expected_results": " This should automatically create the recipe npm.bb under /recipes/npm/npm.bb \n\n"
281 },
282 "6": {
283 "action": "devtool build npm \n\n",
284 "expected_results": "This should compile an image \n\n"
285 },
286 "7": {
287 "action": " devtool reset npm",
288 "expected_results": "This cleans sysroot of the npm recipe, but it leaves the source tree intact. meaning it does not erase."
289 }
290 },
291 "summary": "sdkext_recipes_for_nodejs"
292 }
293 }
294]
diff --git a/meta/lib/oeqa/manual/eclipse-plugin.json b/meta/lib/oeqa/manual/eclipse-plugin.json
deleted file mode 100644
index 6c110d0656..0000000000
--- a/meta/lib/oeqa/manual/eclipse-plugin.json
+++ /dev/null
@@ -1,322 +0,0 @@
1[
2 {
3 "test": {
4 "@alias": "eclipse-plugin.eclipse-plugin.support_SSH_connection_to_Target",
5 "author": [
6 {
7 "email": "ee.peng.yeoh@intel.com",
8 "name": "ee.peng.yeoh@intel.com"
9 }
10 ],
11 "execution": {
12 "1": {
13 "action": "In Eclipse, swich to Remote System Explorer to create a connention baseed on SSH, input the remote target IP address as the Host name, make sure disable the proxy in Window->Preferences->General->Network Connection, set Direct as Active Provider field. ",
14 "expected_results": "the connection based on SSH could be set up."
15 },
16 "2": {
17 "action": "Configure connection from Eclipse: Run->Run Configurations->C/C++ Remote Application\\ ->New Connection->General->SSH Only ",
18 "expected_results": ""
19 },
20 "3": {
21 "action": "Then right click to connect, input the user ID and password. ",
22 "expected_results": ""
23 },
24 "4": {
25 "action": "expand the connection, it will show the Sftp Files etc. \nNOTE. Might need to change dropbear to openssh and add the packagegroup-core-eclipse-debug recipe",
26 "expected_results": ""
27 }
28 },
29 "summary": "support_SSH_connection_to_Target"
30 }
31 },
32 {
33 "test": {
34 "@alias": "eclipse-plugin.eclipse-plugin.Launch_QEMU_from_Eclipse",
35 "author": [
36 {
37 "email": "ee.peng.yeoh@intel.com",
38 "name": "ee.peng.yeoh@intel.com"
39 }
40 ],
41 "execution": {
42 "1": {
43 "action": "Set the Yocto ADT's toolchain root location, sysroot location and kernel, in the menu Window -> Preferences -> Yocto ADT. \n \n",
44 "expected_results": ""
45 },
46 "2": {
47 "action": "wget https://downloads.yoctoproject.org/releases/yocto/yocto-$VERSION/machines/qemu/qemux86/ (ex:core-image-sato-sdk-qemux86-date-rootfs-tar-bz2) \nsource /opt/poky/version/environment-setup-i585-poky-linux \n\nExtract qemu with runqemu-extract-sdk /home/user/file(ex.core-image-sato-sdk-qemux86.bz2) \n/home/user/qemux86-sato-sdk \n\n",
48 "expected_results": " Qemu can be lauched normally."
49 },
50 "3": {
51 "action": "(a)Point to the Toolchain: \n \nIf you are using a stand-alone pre-built toolchain, you should be pointing to the /opt/poky/{test-version} directory as Toolchain Root Location. This is the default location for toolchains installed by the ADT Installer or by hand. If ADT is installed in other location, use that location as Toolchain location.\nIf you are using a system-derived toolchain, the path you provide for the Toolchain Root Location field is the Yocto Project's build directory. \n \n E.g:/home/user/yocto/poky/build \n",
52 "expected_results": ""
53 },
54 "4": {
55 "action": "(b)Specify the Sysroot Location: \nSysroot Location is the location where the root filesystem for the target hardware is created on the development system by the ADT Installer (SYSROOT in step 2 of the case ADT installer Installation). \n \n Local : e.g: /home/user/qemux86-sato-sdk \nUsing ADT : e.g :/home/user/test-yocto/qemux86 \n\n",
56 "expected_results": ""
57 },
58 "5": {
59 "action": "(c)Select the Target Architecture: \n \nThe target architecture is the type of hardware you are going to use or emulate. Use the pull-down Target Architecture menu to make your selection. \n \n\n",
60 "expected_results": ""
61 },
62 "6": {
63 "action": "(d) QEMU: \nSelect this option if you will be using the QEMU emulator. Specify the Kernel matching the QEMU architecture you are using. \n wget https://downloads.yoctoproject.org/releases/yocto/yocto-$VERSION/machines/qemu/qemux86/bzImage-qemux86.bin \n e.g: /home/$USER/yocto/adt-installer/download_image/bzImage-qemux86.bin \n\n",
64 "expected_results": ""
65 },
66 "7": {
67 "action": "(e) select OK to save the settings. \n\n\n1: In the Eclipse toolbar, expose the Run -> External Tools menu. Your image should appear as a selectable menu item. \n2: Select your image in the navigation pane to launch the emulator in a new window. \n3: If needed, enter your host root password in the shell window at the prompt. This sets up a Tap 0 connection needed for running in user-space NFS mode. \n",
68 "expected_results": ""
69 }
70 },
71 "summary": "Launch_QEMU_from_Eclipse"
72 }
73 },
74 {
75 "test": {
76 "@alias": "eclipse-plugin.eclipse-plugin.Relocatable_SDK_-_C_-_Build_Hello_World_ANSI_C_Autotools_Project",
77 "author": [
78 {
79 "email": "ee.peng.yeoh@intel.com",
80 "name": "ee.peng.yeoh@intel.com"
81 }
82 ],
83 "execution": {
84 "1": {
85 "action": "Launch a QEMU of target environment.(Reference to case \"ADT - Launch qemu by eclipse\") ",
86 "expected_results": ""
87 },
88 "2": {
89 "action": "Select File -> New -> Project.",
90 "expected_results": ""
91 },
92 "3": {
93 "action": "Double click C/C++.",
94 "expected_results": ""
95 },
96 "4": {
97 "action": "Click C or C++ Project to create the project.",
98 "expected_results": ""
99 },
100 "5": {
101 "action": "Expand Yocto ADT Project.",
102 "expected_results": ""
103 },
104 "6": {
105 "action": "Select Hello World ANSI C Autotools Project.",
106 "expected_results": ""
107 },
108 "7": {
109 "action": "Put a name in the Project name. Do not use hyphens as part of the name. \n \n",
110 "expected_results": ""
111 },
112 "8": {
113 "action": "Click Next.",
114 "expected_results": ""
115 },
116 "9": {
117 "action": "Add information in the Author and Copyright notice fields. \n1",
118 "expected_results": ""
119 },
120 "10": {
121 "action": "Click Finish. \n1",
122 "expected_results": ""
123 },
124 "11": {
125 "action": "If the \"open perspective\" prompt appears, click \"Yes\" so that you open the C/C++ perspective. \n1",
126 "expected_results": ""
127 },
128 "12": {
129 "action": "In the Project Explorer window, right click the project -> Reconfigure project. \n1",
130 "expected_results": ""
131 },
132 "13": {
133 "action": "In the Project Explorer window, right click the project -> Build project. \n1",
134 "expected_results": "Under the Project files, a new folder appears called Binaries. This indicates that the compilation have been successful and the project binary have been created. \n"
135 },
136 "14": {
137 "action": "Right click it again and Run as -> Run Configurations. \n\t\t\tUnder Run Configurations expand \"C/C++ Remote Application\". A configuration for the current project should appear. Clicking it will display the configuration settings. \n\t\t\tin \"C/C++ Application\" field input Remote Absolute File path for C/C++ Application. e.g.: /home/root/myapplication \n\t\t\tIn \"Connection\" drop-down list make sure a TCF connection is set up for your target. If not, create a new one by clicking the New button. \n1",
138 "expected_results": "step 14 to step 16 -> Build succeed and the console outputs Hello world, you can also check the output on target."
139 },
140 "15": {
141 "action": "After all settings are done, select the Run button on the bottom right corner \n\n1",
142 "expected_results": ""
143 },
144 "16": {
145 "action": "Repeat the steps 14-15, but instead of using Run Configurations use Debug Configurations: \nRight click it again and Debug as -> Debug Configurations \nUnder Debug Configurations expand \"C/C++ Remote Application\". A configuration for the current project should appear. Clicking it will display the configuration settings. \nin \"C/C++ Application\" field input Remote Absolute File path for C/C++ Application.\ne.g.: /home/root/myapplication \nIn \"Connection\" drop-down list make sure a TCF connection is set up for your target. If not, create a new one by clicking the New button \n1",
146 "expected_results": ""
147 },
148 "17": {
149 "action": "After all settings are done, select the Debug button on the bottom right corner",
150 "expected_results": ""
151 }
152 },
153 "summary": "Relocatable_SDK_-_C_-_Build_Hello_World_ANSI_C_Autotools_Project"
154 }
155 },
156 {
157 "test": {
158 "@alias": "eclipse-plugin.eclipse-plugin.Relocatable_SDK_-_C++_-_Build_Hello_World_C++_Autotools_project",
159 "author": [
160 {
161 "email": "ee.peng.yeoh@intel.com",
162 "name": "ee.peng.yeoh@intel.com"
163 }
164 ],
165 "execution": {
166 "1": {
167 "action": "Launch a QEMU of target environment.(Reference to case \"ADT - Launch qemu by eclipse\") ",
168 "expected_results": ""
169 },
170 "2": {
171 "action": "Select File -> New -> Project. ",
172 "expected_results": ""
173 },
174 "3": {
175 "action": "Double click C/C++. ",
176 "expected_results": ""
177 },
178 "4": {
179 "action": "Click C or C++ Project to create the project. ",
180 "expected_results": ""
181 },
182 "5": {
183 "action": "Expand Yocto ADT Project. ",
184 "expected_results": ""
185 },
186 "6": {
187 "action": "Select Hello World ANSI C++ Autotools Project. ",
188 "expected_results": ""
189 },
190 "7": {
191 "action": "Put a name in the Project name. Do not use hyphens as part of the name. \n \n",
192 "expected_results": ""
193 },
194 "8": {
195 "action": "Click Next.",
196 "expected_results": ""
197 },
198 "9": {
199 "action": "Add information in the Author and Copyright notice fields.",
200 "expected_results": ""
201 },
202 "10": {
203 "action": "Click Finish. \n1",
204 "expected_results": ""
205 },
206 "11": {
207 "action": "If the \"open perspective\" prompt appears, click \"Yes\" so that you open the C/C++ perspective. \n1",
208 "expected_results": ""
209 },
210 "12": {
211 "action": "In the Project Explorer window, right click the project -> Reconfigure project. \n1",
212 "expected_results": ""
213 },
214 "13": {
215 "action": "In the Project Explorer window, right click the project -> Build project. \n\n1",
216 "expected_results": "under the Project files, a new folder appears called Binaries. This indicates that the compilation have been successful and the project binary have been created. \n"
217 },
218 "14": {
219 "action": "Right click it again and Run as -> Run Configurations. \n\t\t\tUnder Run Configurations expand \"C/C++ Remote Application\". A configuration for the current project should appear. Clicking it will display the configuration settings. \n\t\t\tin \"C/C++ Application\" field input Remote Absolute File path for C/C++ Application. e.g.: /home/root/myapplication \n\t\t\tIn \"Connection\" drop-down list make sure a TCF connection is set up for your target. If not, create a new one by clicking the New button. \n1",
220 "expected_results": "step 14 to step 16 -> Build succeed and the console outputs Hello world, you can also check the output on target."
221 },
222 "15": {
223 "action": "After all settings are done, select the Run button on the bottom right corner \n\n1",
224 "expected_results": ""
225 },
226 "16": {
227 "action": "Repeat the steps 14-15, but instead of using Run Configurations use Debug Configurations: \n\t\tRight click it again and Debug as -> Debug Configurations \n\t\tUnder Debug Configurations expand \"C/C++ Remote Application\". A configuration for the current project should appear. Clicking it will display the configuration settings. \n\t\tin \"C/C++ Application\" field input Remote Absolute File path for C/C++ Application. \n\t\te.g.: /home/root/myapplication \n\t\tIn \"Connection\" drop-down list make sure a TCF connection is set up for your target. If not, create a new one by clicking the New button \n1",
228 "expected_results": ""
229 },
230 "17": {
231 "action": "After all settings are done, select the Debug button on the bottom right corner",
232 "expected_results": ""
233 }
234 },
235 "summary": "Relocatable_SDK_-_C++_-_Build_Hello_World_C++_Autotools_project"
236 }
237 },
238 {
239 "test": {
240 "@alias": "eclipse-plugin.eclipse-plugin.Build_Eclipse_Plugin_from_source",
241 "author": [
242 {
243 "email": "laurentiu.serban@intel.com",
244 "name": "laurentiu.serban@intel.com"
245 }
246 ],
247 "execution": {
248 "1": {
249 "action": "Clone eclipse-poky source. \n \n - git clone git://git.yoctoproject.org/eclipse-poky \n\n",
250 "expected_results": "Eclipse plugin is successfully installed \n\nDocumentation is there. For example if you have release yocto-2.0.1 you will found on https://downloads.yoctoproject.org/releases/yocto/yocto-2.0.1/eclipse-plugin/mars/ archive with documentation like org.yocto.doc-development-$date.zip \n \n"
251 },
252 "2": {
253 "action": "Checkout correct tag. \n\n - git checkout <eclipse-version>/<yocto-version> \n\n",
254 "expected_results": "After plugin is build you must have 4 archive in foder scripts from eclipse-poky: \n - org.yocto.bc - mars-master-$date.zip \n - org.yocto.doc - mars-master-$date.zip --> documentation \n - org.yocto.sdk - mars-master-$date.zip \n - org.yocto.sdk - mars-master-$date.-archive.zip --> plugin "
255 },
256 "3": {
257 "action": "Move to scripts/ folder. \n\n",
258 "expected_results": ""
259 },
260 "4": {
261 "action": "Run ./setup.sh \n\n",
262 "expected_results": ""
263 },
264 "5": {
265 "action": "When the script finishes, it prompts a command to issue to build the plugin. It should look similar to the following: \n\n$ ECLIPSE_HOME=/eclipse-poky/scripts/eclipse ./build.sh /&1 | tee -a build.log \n\nHere, the three arguments to the build script are tag name, branch for documentation and release name. \n\n",
266 "expected_results": ""
267 },
268 "6": {
269 "action": "On an eclipse without the Yocto Plugin, select \"Install New Software\" from Help pull-down menu \n\n",
270 "expected_results": ""
271 },
272 "7": {
273 "action": "Select Add and from the dialog choose Archive... Look for the *archive.zip file that was built previously with the build.sh script. Click OK. \n\n",
274 "expected_results": ""
275 },
276 "8": {
277 "action": "Select all components and proceed with Installation of plugin. Restarting eclipse might be required.\n",
278 "expected_results": ""
279 }
280 },
281 "summary": "Build_Eclipse_Plugin_from_source"
282 }
283 },
284 {
285 "test": {
286 "@alias": "eclipse-plugin.eclipse-plugin.Eclipse_Poky_installation_and_setup",
287 "author": [
288 {
289 "email": "ee.peng.yeoh@intel.com",
290 "name": "ee.peng.yeoh@intel.com"
291 }
292 ],
293 "execution": {
294 "1": {
295 "action": "Install SDK \n\ta)Download https://autobuilder.yocto.io/pub/releases//toolchain/x86_64/poky-glibc-x86_64-core-\timage-sato-i586-toolchain-.sh \n\tb)Run the SDK installer and accept the default installation directory ",
296 "expected_results": ""
297 },
298 "2": {
299 "action": "Install \"Eclipse IDE for C/C++ Developers\" Oxygen release (4.7.0) \n\ta) Go to https://www.eclipse.org/downloads/packages/all, click \"Oxygen R\" \n\tb) Click to download the build for your OS \n\tc) Click \"Download\" button to download from a mirror \n\td) Run \"tar xf\" to extract the downloaded archive ",
300 "expected_result": ""
301 },
302 "3": {
303 "action": "Install \"Eclipse IDE for C/C++ Developers\" Oxygen release (4.7.0) (Continue) \n\te) Run \"eclipse/eclipse\" to start Eclipse \n\tf) Optional step for host machine within Intel network: In Eclipse workbench window, go to \"Window\" menu -> \"Preferences...\". \n\tg) In \"Preferences\" dialog, go to \"General\" -> \"Network Connections\", set \"Active Provider\" to \"Manual\". In \"Proxy \tentries\" table, select HTTP and click \"Edit\" and enter host \"proxy-chain.intel.com\" port 911, click OK. Repeat for HTTPS with port 912 \nClick OK to close \"Preferences\" dialog. \n\th) Go to \"File\" menu -> \"Restart\" to restart Eclipse for proxy settings to take effect. ",
304 "expected_result": ""
305 },
306 "4": {
307 "action": "Install Eclipse Poky plugins \n\ta) Download https://autobuilder.yocto.io/pub/releases/<yocto-version>/eclipse-plugin/<eclipse-version>/org.yocto.sdk-development-<date>-archive.zip \n\tb) In Eclipse workbench window, go to \"Help\" menu -> \"Install New Software...\" \n\tc) In \"Install\" dialog, click \"Add...\" button \n\td) In \"Add Repository\" dialog, enter \"Eclipse Poky\" for (repository) Name, click \"Archive...\" ",
308 "expected_results": ""
309 },
310 "5": {
311 "action": "Install Eclipse Poky plugins (continue) \n\te) In \"Repository archive\" browse dialog, select the downloaded Eclipse Poky repository archive \n\tf) Back in \"Add Repository\" dialog, click \"OK\" \n\tg) Back in \"Install\" dialog, make sure \"Work with:\" is set to \"Eclipse Poky\" repository, tick \"Yocto Project \tDocumentation Plug-in\" and \"Yocto Project SDK Plug-in\", click \"Next >\" and verify plugins/features name/version, \tclick \"Next >\" and accept license agreement, click \"Finish\" \n\th) If \"Security Warning\" dialog appears, click \"OK\" to install unsigned content. \n\ti) In \"Software Updates\" dialog, click \"Yes\" to restart Eclipse to complete Eclipse Poky plugins installation. ",
312 "expected_results": ""
313 },
314 "6": {
315 "action": "Setup Eclipse Poky to use SDK \n\ta) In Eclipse workbench window, go to \"Window\" menu -> \"Preferences\". \n\tb) In \"Preferences\" window, go to \"Yocto Project SDK\", in \"Cross Compiler Options\" frame, select \"Standalone pre-\tbuilt toolchain\". ",
316 "expected_results": "Eclipse Poky plugins installed and running successfully, e.g. observe that \"Yocto Project Tools\" menu is available on Eclipse workbench window."
317 }
318 },
319 "summary": "Eclipse_Poky_installation_and_setup"
320 }
321 }
322]
diff --git a/meta/lib/oeqa/runtime/case.py b/meta/lib/oeqa/runtime/case.py
index f036982e1f..2a47771a3d 100644
--- a/meta/lib/oeqa/runtime/case.py
+++ b/meta/lib/oeqa/runtime/case.py
@@ -4,6 +4,9 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7import os
8import subprocess
9import time
7from oeqa.core.case import OETestCase 10from oeqa.core.case import OETestCase
8from oeqa.utils.package_manager import install_package, uninstall_package 11from oeqa.utils.package_manager import install_package, uninstall_package
9 12
@@ -18,3 +21,18 @@ class OERuntimeTestCase(OETestCase):
18 def tearDown(self): 21 def tearDown(self):
19 super(OERuntimeTestCase, self).tearDown() 22 super(OERuntimeTestCase, self).tearDown()
20 uninstall_package(self) 23 uninstall_package(self)
24
25def run_network_serialdebug(runner):
26 if not runner:
27 return
28 status, output = runner.run_serial("ip addr")
29 print("ip addr on target: %s %s" % (output, status))
30 status, output = runner.run_serial("ping -c 1 %s" % self.target.server_ip)
31 print("ping on target for %s: %s %s" % (self.target.server_ip, output, status))
32 status, output = runner.run_serial("ping -c 1 %s" % self.target.ip)
33 print("ping on target for %s: %s %s" % (self.target.ip, output, status))
34 # Have to use a full path for netstat which isn't in HOSTTOOLS
35 subprocess.call(["/usr/bin/netstat", "-tunape"])
36 subprocess.call(["/usr/bin/netstat", "-ei"])
37 subprocess.call(["ps", "-awx"], shell=True)
38 print("PID: %s %s" % (str(os.getpid()), time.time()))
diff --git a/meta/lib/oeqa/runtime/cases/apt.py b/meta/lib/oeqa/runtime/cases/apt.py
index 8000645843..c6b62987f1 100644
--- a/meta/lib/oeqa/runtime/cases/apt.py
+++ b/meta/lib/oeqa/runtime/cases/apt.py
@@ -54,7 +54,14 @@ class AptRepoTest(AptTest):
54 def setup_key(self): 54 def setup_key(self):
55 # the key is found on the target /etc/pki/packagefeed-gpg/ 55 # the key is found on the target /etc/pki/packagefeed-gpg/
56 # named PACKAGEFEED-GPG-KEY-poky-branch 56 # named PACKAGEFEED-GPG-KEY-poky-branch
57 self.target.run('cd %s; apt-key add P*' % ('/etc/pki/packagefeed-gpg')) 57 # copy it to /etc/apt/keyrings/PACKAGEFEED-GPG-KEY-poky-branch.asc, and
58 # set it as the signing key for the repos
59 cmd = "KEY_FILE_PATH=`realpath /etc/pki/packagefeed-gpg/P*`; "
60 cmd += "KEY_FILE_NAME=`basename $KEY_FILE_PATH`; "
61 cmd += "mkdir -p /etc/apt/keyrings; "
62 cmd += "cp $KEY_FILE_PATH /etc/apt/keyrings/${KEY_FILE_NAME}.asc; "
63 cmd += 'sed -i "s|^deb |deb \[signed-by=/etc/apt/keyrings/${KEY_FILE_NAME}.asc\] |g" /etc/apt/sources.list'
64 self.target.run(cmd)
58 65
59 @skipIfNotFeature('package-management', 66 @skipIfNotFeature('package-management',
60 'Test requires package-management to be in IMAGE_FEATURES') 67 'Test requires package-management to be in IMAGE_FEATURES')
diff --git a/meta/lib/oeqa/runtime/cases/buildcpio.py b/meta/lib/oeqa/runtime/cases/buildcpio.py
index 7be734cb4f..0c9c57a3cb 100644
--- a/meta/lib/oeqa/runtime/cases/buildcpio.py
+++ b/meta/lib/oeqa/runtime/cases/buildcpio.py
@@ -29,6 +29,6 @@ class BuildCpioTest(OERuntimeTestCase):
29 @OEHasPackage(['autoconf']) 29 @OEHasPackage(['autoconf'])
30 def test_cpio(self): 30 def test_cpio(self):
31 self.project.download_archive() 31 self.project.download_archive()
32 self.project.run_configure() 32 self.project.run_configure(configure_args="CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration'")
33 self.project.run_make() 33 self.project.run_make(make_args="CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration'")
34 self.project.run_install() 34 self.project.run_install()
diff --git a/meta/lib/oeqa/runtime/cases/buildlzip.py b/meta/lib/oeqa/runtime/cases/buildlzip.py
index 44f4f1be71..921a0bca61 100644
--- a/meta/lib/oeqa/runtime/cases/buildlzip.py
+++ b/meta/lib/oeqa/runtime/cases/buildlzip.py
@@ -15,7 +15,7 @@ class BuildLzipTest(OERuntimeTestCase):
15 @classmethod 15 @classmethod
16 def setUpClass(cls): 16 def setUpClass(cls):
17 uri = 'http://downloads.yoctoproject.org/mirror/sources' 17 uri = 'http://downloads.yoctoproject.org/mirror/sources'
18 uri = '%s/lzip-1.19.tar.gz' % uri 18 uri = '%s/lzip-1.25.tar.gz' % uri
19 cls.project = TargetBuildProject(cls.tc.target, 19 cls.project = TargetBuildProject(cls.tc.target,
20 uri, 20 uri,
21 dl_dir = cls.tc.td['DL_DIR']) 21 dl_dir = cls.tc.td['DL_DIR'])
diff --git a/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py b/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py
index eac8f2d082..c3be60f006 100644
--- a/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py
+++ b/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py
@@ -9,26 +9,7 @@ from oeqa.core.decorator.data import skipIfQemu
9 9
10class Ethernet_Test(OERuntimeTestCase): 10class Ethernet_Test(OERuntimeTestCase):
11 11
12 def set_ip(self, x):
13 x = x.split(".")
14 sample_host_address = '150'
15 x[3] = sample_host_address
16 x = '.'.join(x)
17 return x
18
19 @skipIfQemu() 12 @skipIfQemu()
20 @OETestDepends(['ssh.SSHTest.test_ssh'])
21 def test_set_virtual_ip(self):
22 (status, output) = self.target.run("ifconfig eth0 | grep 'inet ' | awk '{print $2}'")
23 self.assertEqual(status, 0, msg='Failed to get ip address. Make sure you have an ethernet connection on your device, output: %s' % output)
24 original_ip = output
25 virtual_ip = self.set_ip(original_ip)
26
27 (status, output) = self.target.run("ifconfig eth0:1 %s netmask 255.255.255.0 && sleep 2 && ping -c 5 %s && ifconfig eth0:1 down" % (virtual_ip,virtual_ip))
28 self.assertEqual(status, 0, msg='Failed to create virtual ip address, output: %s' % output)
29
30 @skipIfQemu()
31 @OETestDepends(['ethernet_ip_connman.Ethernet_Test.test_set_virtual_ip'])
32 def test_get_ip_from_dhcp(self): 13 def test_get_ip_from_dhcp(self):
33 (status, output) = self.target.run("connmanctl services | grep -E '*AO Wired|*AR Wired' | awk '{print $3}'") 14 (status, output) = self.target.run("connmanctl services | grep -E '*AO Wired|*AR Wired' | awk '{print $3}'")
34 self.assertEqual(status, 0, msg='No wired interfaces are detected, output: %s' % output) 15 self.assertEqual(status, 0, msg='No wired interfaces are detected, output: %s' % output)
@@ -39,4 +20,4 @@ class Ethernet_Test(OERuntimeTestCase):
39 default_gateway = output 20 default_gateway = output
40 21
41 (status, output) = self.target.run("connmanctl config %s --ipv4 dhcp && sleep 2 && ping -c 5 %s" % (wired_interfaces,default_gateway)) 22 (status, output) = self.target.run("connmanctl config %s --ipv4 dhcp && sleep 2 && ping -c 5 %s" % (wired_interfaces,default_gateway))
42 self.assertEqual(status, 0, msg='Failed to get dynamic IP address via DHCP in connmand, output: %s' % output) \ No newline at end of file 23 self.assertEqual(status, 0, msg='Failed to get dynamic IP address via DHCP in connmand, output: %s' % output)
diff --git a/meta/lib/oeqa/runtime/cases/go.py b/meta/lib/oeqa/runtime/cases/go.py
index 39a80f4dca..fc7959b5f4 100644
--- a/meta/lib/oeqa/runtime/cases/go.py
+++ b/meta/lib/oeqa/runtime/cases/go.py
@@ -4,10 +4,78 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7import os
7from oeqa.runtime.case import OERuntimeTestCase 8from oeqa.runtime.case import OERuntimeTestCase
8from oeqa.core.decorator.depends import OETestDepends 9from oeqa.core.decorator.depends import OETestDepends
9from oeqa.runtime.decorator.package import OEHasPackage 10from oeqa.runtime.decorator.package import OEHasPackage
10 11
12class GoCompileTest(OERuntimeTestCase):
13
14 @classmethod
15 def setUp(cls):
16 dst = '/tmp/'
17 src = os.path.join(cls.tc.files_dir, 'test.go')
18 cls.tc.target.copyTo(src, dst)
19
20 @classmethod
21 def tearDown(cls):
22 files = '/tmp/test.go /tmp/test'
23 cls.tc.target.run('rm %s' % files)
24 dirs = '/tmp/hello-go'
25 cls.tc.target.run('rm -r %s' % dirs)
26
27 @OETestDepends(['ssh.SSHTest.test_ssh'])
28 @OEHasPackage('go')
29 @OEHasPackage('go-runtime')
30 @OEHasPackage('go-runtime-dev')
31 @OEHasPackage('openssh-scp')
32 def test_go_compile(self):
33 # Check if go is available
34 status, output = self.target.run('which go')
35 if status != 0:
36 self.skipTest('go command not found, output: %s' % output)
37
38 # Compile the simple Go program
39 status, output = self.target.run('go build -o /tmp/test /tmp/test.go')
40 msg = 'go compile failed, output: %s' % output
41 self.assertEqual(status, 0, msg=msg)
42
43 # Run the compiled program
44 status, output = self.target.run('/tmp/test')
45 msg = 'running compiled file failed, output: %s' % output
46 self.assertEqual(status, 0, msg=msg)
47
48 @OETestDepends(['ssh.SSHTest.test_ssh'])
49 @OEHasPackage('go')
50 @OEHasPackage('go-runtime')
51 @OEHasPackage('go-runtime-dev')
52 @OEHasPackage('openssh-scp')
53 def test_go_module(self):
54 # Check if go is available
55 status, output = self.target.run('which go')
56 if status != 0:
57 self.skipTest('go command not found, output: %s' % output)
58
59 # Create a simple Go module
60 status, output = self.target.run('mkdir -p /tmp/hello-go')
61 msg = 'mkdir failed, output: %s' % output
62 self.assertEqual(status, 0, msg=msg)
63
64 # Copy the existing test.go file to the module
65 status, output = self.target.run('cp /tmp/test.go /tmp/hello-go/main.go')
66 msg = 'copying test.go failed, output: %s' % output
67 self.assertEqual(status, 0, msg=msg)
68
69 # Build the module
70 status, output = self.target.run('cd /tmp/hello-go && go build -o hello main.go')
71 msg = 'go build failed, output: %s' % output
72 self.assertEqual(status, 0, msg=msg)
73
74 # Run the module
75 status, output = self.target.run('cd /tmp/hello-go && ./hello')
76 msg = 'running go module failed, output: %s' % output
77 self.assertEqual(status, 0, msg=msg)
78
11class GoHelloworldTest(OERuntimeTestCase): 79class GoHelloworldTest(OERuntimeTestCase):
12 @OETestDepends(['ssh.SSHTest.test_ssh']) 80 @OETestDepends(['ssh.SSHTest.test_ssh'])
13 @OEHasPackage(['go-helloworld']) 81 @OEHasPackage(['go-helloworld'])
diff --git a/meta/lib/oeqa/runtime/cases/logrotate.py b/meta/lib/oeqa/runtime/cases/logrotate.py
index 6ad980cb6a..0d4b9ac60b 100644
--- a/meta/lib/oeqa/runtime/cases/logrotate.py
+++ b/meta/lib/oeqa/runtime/cases/logrotate.py
@@ -15,59 +15,61 @@ class LogrotateTest(OERuntimeTestCase):
15 15
16 @classmethod 16 @classmethod
17 def setUpClass(cls): 17 def setUpClass(cls):
18 cls.tc.target.run('cp /etc/logrotate.d/wtmp $HOME/wtmp.oeqabak') 18 cls.tc.target.run('cp /etc/logrotate.d/wtmp $HOME/wtmp.oeqabak',
19 ignore_ssh_fails=True)
19 20
20 @classmethod 21 @classmethod
21 def tearDownClass(cls): 22 def tearDownClass(cls):
22 cls.tc.target.run('mv -f $HOME/wtmp.oeqabak /etc/logrotate.d/wtmp && rm -rf /var/log//logrotate_dir') 23 cls.tc.target.run('mv -f $HOME/wtmp.oeqabak /etc/logrotate.d/wtmp && rm -rf /var/log/logrotate_dir', ignore_ssh_fails=True)
23 cls.tc.target.run('rm -rf /var/log/logrotate_testfile && rm -rf /etc/logrotate.d/logrotate_testfile') 24 cls.tc.target.run('rm -rf /var/log/logrotate_testfile && rm -rf /etc/logrotate.d/logrotate_testfile', ignore_ssh_fails=True)
24 25
25 @OETestDepends(['ssh.SSHTest.test_ssh']) 26 @OETestDepends(['ssh.SSHTest.test_ssh'])
26 @OEHasPackage(['logrotate']) 27 @OEHasPackage(['logrotate'])
27 def test_logrotate_wtmp(self): 28 def test_logrotate_wtmp(self):
28
29 # /var/log/wtmp may not always exist initially, so use touch to ensure it is present 29 # /var/log/wtmp may not always exist initially, so use touch to ensure it is present
30 status, output = self.target.run('touch /var/log/wtmp') 30 status, output = self.target.run('touch /var/log/wtmp')
31 msg = ('Could not create/update /var/log/wtmp with touch') 31 msg = ('Could not create/update /var/log/wtmp with touch')
32 self.assertEqual(status, 0, msg = msg) 32 self.assertEqual(status, 0, msg = msg)
33 33
34 status, output = self.target.run('mkdir /var/log//logrotate_dir') 34 # Create a folder to store rotated file and add the corresponding
35 # configuration option
36 status, output = self.target.run('mkdir /var/log/logrotate_dir')
35 msg = ('Could not create logrotate_dir. Output: %s' % output) 37 msg = ('Could not create logrotate_dir. Output: %s' % output)
36 self.assertEqual(status, 0, msg = msg) 38 self.assertEqual(status, 0, msg = msg)
37 39
38 status, output = self.target.run('echo "create \n olddir /var/log//logrotate_dir \n include /etc/logrotate.d/wtmp" > /tmp/logrotate-test.conf') 40 status, output = self.target.run('echo "create \n olddir /var/log/logrotate_dir \n include /etc/logrotate.d/wtmp" > /tmp/logrotate-test.conf')
39 msg = ('Could not write to /tmp/logrotate-test.conf') 41 msg = ('Could not write to /tmp/logrotate-test.conf')
40 self.assertEqual(status, 0, msg = msg) 42 self.assertEqual(status, 0, msg = msg)
41 43
44 # Call logrotate -f to force the rotation immediately
42 # If logrotate fails to rotate the log, view the verbose output of logrotate to see what prevented it 45 # If logrotate fails to rotate the log, view the verbose output of logrotate to see what prevented it
43 _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test.conf') 46 _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test.conf')
44 status, _ = self.target.run('find /var/log//logrotate_dir -type f | grep wtmp.1') 47 status, _ = self.target.run('find /var/log/logrotate_dir -type f | grep wtmp.1')
45 msg = ("logrotate did not successfully rotate the wtmp log. Output from logrotate -vf: \n%s" % (logrotate_output)) 48 msg = ("logrotate did not successfully rotate the wtmp log. Output from logrotate -vf: \n%s" % (logrotate_output))
46 self.assertEqual(status, 0, msg = msg) 49 self.assertEqual(status, 0, msg = msg)
47 50
48 @OETestDepends(['logrotate.LogrotateTest.test_logrotate_wtmp']) 51 @OETestDepends(['logrotate.LogrotateTest.test_logrotate_wtmp'])
49 def test_logrotate_newlog(self): 52 def test_logrotate_newlog(self):
50
51 status, output = self.target.run('echo "oeqa logrotate test file" > /var/log/logrotate_testfile') 53 status, output = self.target.run('echo "oeqa logrotate test file" > /var/log/logrotate_testfile')
52 msg = ('Could not create logrotate test file in /var/log') 54 msg = ('Could not create logrotate test file in /var/log')
53 self.assertEqual(status, 0, msg = msg) 55 self.assertEqual(status, 0, msg = msg)
54 56
55 status, output = self.target.run('echo "/var/log/logrotate_testfile {\n missingok \n monthly \n rotate 1" > /etc/logrotate.d/logrotate_testfile') 57 # Create a new configuration file dedicated to a /var/log/logrotate_testfile
58 status, output = self.target.run('echo "/var/log/logrotate_testfile {\n missingok \n monthly \n rotate 1}" > /etc/logrotate.d/logrotate_testfile')
56 msg = ('Could not write to /etc/logrotate.d/logrotate_testfile') 59 msg = ('Could not write to /etc/logrotate.d/logrotate_testfile')
57 self.assertEqual(status, 0, msg = msg) 60 self.assertEqual(status, 0, msg = msg)
58 61
59 status, output = self.target.run('echo "create \n olddir /var/log//logrotate_dir \n include /etc/logrotate.d/logrotate_testfile" > /tmp/logrotate-test2.conf') 62 status, output = self.target.run('echo "create \n olddir /var/log/logrotate_dir \n include /etc/logrotate.d/logrotate_testfile" > /tmp/logrotate-test2.conf')
60 msg = ('Could not write to /tmp/logrotate_test2.conf') 63 msg = ('Could not write to /tmp/logrotate_test2.conf')
61 self.assertEqual(status, 0, msg = msg) 64 self.assertEqual(status, 0, msg = msg)
62 65
63 status, output = self.target.run('find /var/log//logrotate_dir -type f | grep logrotate_testfile.1') 66 status, output = self.target.run('find /var/log/logrotate_dir -type f | grep logrotate_testfile.1')
64 msg = ('A rotated log for logrotate_testfile is already present in logrotate_dir') 67 msg = ('A rotated log for logrotate_testfile is already present in logrotate_dir')
65 self.assertEqual(status, 1, msg = msg) 68 self.assertEqual(status, 1, msg = msg)
66 69
70 # Call logrotate -f to force the rotation immediately
67 # If logrotate fails to rotate the log, view the verbose output of logrotate instead of just listing the files in olddir 71 # If logrotate fails to rotate the log, view the verbose output of logrotate instead of just listing the files in olddir
68 _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test2.conf') 72 _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test2.conf')
69 status, _ = self.target.run('find /var/log//logrotate_dir -type f | grep logrotate_testfile.1') 73 status, _ = self.target.run('find /var/log/logrotate_dir -type f | grep logrotate_testfile.1')
70 msg = ('logrotate did not successfully rotate the logrotate_test log. Output from logrotate -vf: \n%s' % (logrotate_output)) 74 msg = ('logrotate did not successfully rotate the logrotate_test log. Output from logrotate -vf: \n%s' % (logrotate_output))
71 self.assertEqual(status, 0, msg = msg) 75 self.assertEqual(status, 0, msg = msg)
72
73
diff --git a/meta/lib/oeqa/runtime/cases/ltp.py b/meta/lib/oeqa/runtime/cases/ltp.py
index f588a93200..0ffdbe23e4 100644
--- a/meta/lib/oeqa/runtime/cases/ltp.py
+++ b/meta/lib/oeqa/runtime/cases/ltp.py
@@ -57,7 +57,7 @@ class LtpTestBase(OERuntimeTestCase):
57 57
58class LtpTest(LtpTestBase): 58class LtpTest(LtpTestBase):
59 59
60 ltp_groups = ["math", "syscalls", "dio", "io", "mm", "ipc", "sched", "nptl", "pty", "containers", "controllers", "filecaps", "cap_bounds", "fcntl-locktests", "commands", "net.ipv6_lib", "input","fs_perms_simple", "cve", "crypto", "ima", "net.nfs", "net_stress.ipsec_icmp", "net.ipv6", "numa", "uevent", "ltp-aiodio.part1", "ltp-aiodio.part2", "ltp-aiodio.part3", "ltp-aiodio.part4"] 60 ltp_groups = ["math", "syscalls", "dio", "mm", "sched", "nptl", "pty", "containers", "controllers", "fcntl-locktests", "commands", "net.ipv6_lib", "input","fs_perms_simple", "cve", "crypto", "ima", "net.nfs", "net_stress.ipsec_icmp", "net.ipv6", "numa", "uevent", "ltp-aiodio.part1", "ltp-aiodio.part2", "ltp-aiodio.part3", "ltp-aiodio.part4"]
61 61
62 ltp_fs = ["fs", "fs_bind"] 62 ltp_fs = ["fs", "fs_bind"]
63 # skip kernel cpuhotplug 63 # skip kernel cpuhotplug
diff --git a/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt b/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt
index 2c0bd9a247..156b0f9c10 100644
--- a/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt
+++ b/meta/lib/oeqa/runtime/cases/parselogs-ignores-mipsarch.txt
@@ -1,2 +1,19 @@
1# These should be reviewed to see if they are still needed 1# These should be reviewed to see if they are still needed
2cacheinfo: Failed to find cpu0 device node 2cacheinfo: Failed to find cpu0 device node
3
4# 6.10 restructures sysctl registration such that mips
5# registers an empty table and generates harmless warnings:
6# failed when register_sysctl_sz sched_fair_sysctls to kernel
7# failed when register_sysctl_sz sched_core_sysctls to kernel
8failed when register_sysctl_sz sched
9
10# With qemu 9.1.0
11# pci 0000:00:00.0: BAR 2: can't handle BAR above 4GB (bus address 0x1f00000010)
12# pci 0000:00:00.0: BAR 5: error updating (0x1105d034 != 0x0100d034)
13BAR 0: error updating
14BAR 1: error updating
15BAR 2: error updating
16BAR 3: error updating
17BAR 4: error updating
18BAR 5: error updating
19: can't handle BAR above 4GB
diff --git a/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt b/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt
index b0c0fc9ddf..143db40d63 100644
--- a/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt
+++ b/meta/lib/oeqa/runtime/cases/parselogs-ignores-qemuall.txt
@@ -13,6 +13,14 @@ FBIOPUT_VSCREENINFO failed, double buffering disabled
13# pci 0000:00:00.0: [Firmware Bug]: reg 0x20: invalid BAR (can't size) 13# pci 0000:00:00.0: [Firmware Bug]: reg 0x20: invalid BAR (can't size)
14# pci 0000:00:00.0: [Firmware Bug]: reg 0x24: invalid BAR (can't size) 14# pci 0000:00:00.0: [Firmware Bug]: reg 0x24: invalid BAR (can't size)
15invalid BAR (can't size) 15invalid BAR (can't size)
16# 6.10+ the invalid BAR warnings are of this format:
17# pci 0000:00:00.0: [Firmware Bug]: BAR 0: invalid; can't size
18# pci 0000:00:00.0: [Firmware Bug]: BAR 1: invalid; can't size
19# pci 0000:00:00.0: [Firmware Bug]: BAR 2: invalid; can't size
20# pci 0000:00:00.0: [Firmware Bug]: BAR 3: invalid; can't size
21# pci 0000:00:00.0: [Firmware Bug]: BAR 4: invalid; can't size
22# pci 0000:00:00.0: [Firmware Bug]: BAR 5: invalid; can't size
23invalid; can't size
16 24
17# These should be reviewed to see if they are still needed 25# These should be reviewed to see if they are still needed
18wrong ELF class 26wrong ELF class
diff --git a/meta/lib/oeqa/runtime/cases/parselogs.py b/meta/lib/oeqa/runtime/cases/parselogs.py
index 6966923c94..47c77fccd5 100644
--- a/meta/lib/oeqa/runtime/cases/parselogs.py
+++ b/meta/lib/oeqa/runtime/cases/parselogs.py
@@ -34,7 +34,7 @@ class ParseLogsTest(OERuntimeTestCase):
34 log_locations = ["/var/log/", "/var/log/dmesg", "/tmp/dmesg_output.log"] 34 log_locations = ["/var/log/", "/var/log/dmesg", "/tmp/dmesg_output.log"]
35 35
36 # The keywords that identify error messages in the log files 36 # The keywords that identify error messages in the log files
37 errors = ["error", "cannot", "can't", "failed"] 37 errors = ["error", "cannot", "can't", "failed", "---[ cut here ]---", "No irq handler for vector"]
38 38
39 # A list of error messages that should be ignored 39 # A list of error messages that should be ignored
40 ignore_errors = [] 40 ignore_errors = []
diff --git a/meta/lib/oeqa/runtime/cases/ping.py b/meta/lib/oeqa/runtime/cases/ping.py
index f72460e7f3..efb91d4cc9 100644
--- a/meta/lib/oeqa/runtime/cases/ping.py
+++ b/meta/lib/oeqa/runtime/cases/ping.py
@@ -7,7 +7,7 @@
7from subprocess import Popen, PIPE 7from subprocess import Popen, PIPE
8from time import sleep 8from time import sleep
9 9
10from oeqa.runtime.case import OERuntimeTestCase 10from oeqa.runtime.case import OERuntimeTestCase, run_network_serialdebug
11from oeqa.core.decorator.oetimeout import OETimeout 11from oeqa.core.decorator.oetimeout import OETimeout
12from oeqa.core.exception import OEQATimeoutError 12from oeqa.core.exception import OEQATimeoutError
13 13
@@ -18,6 +18,13 @@ class PingTest(OERuntimeTestCase):
18 output = '' 18 output = ''
19 count = 0 19 count = 0
20 self.assertNotEqual(len(self.target.ip), 0, msg="No target IP address set") 20 self.assertNotEqual(len(self.target.ip), 0, msg="No target IP address set")
21
22 # If the target IP is localhost (because user-space networking is being used),
23 # then there's no point in pinging it.
24 if self.target.ip.startswith("127.0.0.") or self.target.ip in ("localhost", "::1"):
25 print("runtime/ping: localhost detected, not pinging")
26 return
27
21 try: 28 try:
22 while count < 5: 29 while count < 5:
23 cmd = 'ping -c 1 %s' % self.target.ip 30 cmd = 'ping -c 1 %s' % self.target.ip
@@ -29,6 +36,7 @@ class PingTest(OERuntimeTestCase):
29 count = 0 36 count = 0
30 sleep(1) 37 sleep(1)
31 except OEQATimeoutError: 38 except OEQATimeoutError:
39 run_network_serialdebug(self.target.runner)
32 self.fail("Ping timeout error for address %s, count %s, output: %s" % (self.target.ip, count, output)) 40 self.fail("Ping timeout error for address %s, count %s, output: %s" % (self.target.ip, count, output))
33 msg = ('Expected 5 consecutive, got %d.\n' 41 msg = ('Expected 5 consecutive, got %d.\n'
34 'ping output is:\n%s' % (count,output)) 42 'ping output is:\n%s' % (count,output))
diff --git a/meta/lib/oeqa/runtime/cases/scp.py b/meta/lib/oeqa/runtime/cases/scp.py
index ee97b8ef66..364264369a 100644
--- a/meta/lib/oeqa/runtime/cases/scp.py
+++ b/meta/lib/oeqa/runtime/cases/scp.py
@@ -25,7 +25,7 @@ class ScpTest(OERuntimeTestCase):
25 os.remove(cls.tmp_path) 25 os.remove(cls.tmp_path)
26 26
27 @OETestDepends(['ssh.SSHTest.test_ssh']) 27 @OETestDepends(['ssh.SSHTest.test_ssh'])
28 @OEHasPackage(['openssh-scp']) 28 @OEHasPackage({'openssh-scp', 'openssh-sftp-server'})
29 def test_scp_file(self): 29 def test_scp_file(self):
30 dst = '/tmp/test_scp_file' 30 dst = '/tmp/test_scp_file'
31 31
diff --git a/meta/lib/oeqa/runtime/cases/skeletoninit.py b/meta/lib/oeqa/runtime/cases/skeletoninit.py
index d0fdcbded9..be7b39a9a3 100644
--- a/meta/lib/oeqa/runtime/cases/skeletoninit.py
+++ b/meta/lib/oeqa/runtime/cases/skeletoninit.py
@@ -4,8 +4,7 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7# This test should cover https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=284 7# Image under test must have meta-skeleton layer in bblayers and
8# testcase. Image under test must have meta-skeleton layer in bblayers and
9# IMAGE_INSTALL:append = " service" in local.conf 8# IMAGE_INSTALL:append = " service" in local.conf
10from oeqa.runtime.case import OERuntimeTestCase 9from oeqa.runtime.case import OERuntimeTestCase
11from oeqa.core.decorator.depends import OETestDepends 10from oeqa.core.decorator.depends import OETestDepends
diff --git a/meta/lib/oeqa/runtime/cases/ssh.py b/meta/lib/oeqa/runtime/cases/ssh.py
index cdbef59500..3e9503277e 100644
--- a/meta/lib/oeqa/runtime/cases/ssh.py
+++ b/meta/lib/oeqa/runtime/cases/ssh.py
@@ -4,7 +4,10 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7from oeqa.runtime.case import OERuntimeTestCase 7import time
8import signal
9
10from oeqa.runtime.case import OERuntimeTestCase, run_network_serialdebug
8from oeqa.core.decorator.depends import OETestDepends 11from oeqa.core.decorator.depends import OETestDepends
9from oeqa.runtime.decorator.package import OEHasPackage 12from oeqa.runtime.decorator.package import OEHasPackage
10 13
@@ -13,12 +16,23 @@ class SSHTest(OERuntimeTestCase):
13 @OETestDepends(['ping.PingTest.test_ping']) 16 @OETestDepends(['ping.PingTest.test_ping'])
14 @OEHasPackage(['dropbear', 'openssh-sshd']) 17 @OEHasPackage(['dropbear', 'openssh-sshd'])
15 def test_ssh(self): 18 def test_ssh(self):
16 (status, output) = self.target.run('sleep 20', timeout=2) 19 for i in range(5):
17 msg='run() timed out but return code was zero.' 20 status, output = self.target.run("uname -a", timeout=30, ignore_ssh_fails=True)
18 self.assertNotEqual(status, 0, msg=msg) 21 if status == 0:
19 (status, output) = self.target.run('uname -a') 22 break
20 self.assertEqual(status, 0, msg='SSH Test failed: %s' % output) 23 elif status == 255 or status == -signal.SIGTERM:
21 (status, output) = self.target.run('cat /etc/controllerimage') 24 # ssh returns 255 only if a ssh error occurs. This could
22 msg = "This isn't the right image - /etc/controllerimage " \ 25 # be an issue with "Connection refused" because the port
23 "shouldn't be here %s" % output 26 # isn't open yet, and this could check explicitly for that
24 self.assertEqual(status, 1, msg=msg) 27 # here. However, let's keep it simple and just retry for
28 # all errors a limited amount of times with a sleep to
29 # give it time for the port to open.
30 # We sometimes see -15 (SIGTERM) on slow emulation machines too, likely
31 # from boot/init not being 100% complete, retry for these too.
32 time.sleep(5)
33 continue
34 else:
35 run_network_serialdebug(self.target.runner)
36 self.fail("uname failed with \"%s\" (exit code %s)" % (output, status))
37 if status != 0:
38 self.fail("ssh failed with \"%s\" (exit code %s)" % (output, status))
diff --git a/meta/lib/oeqa/runtime/cases/stap.py b/meta/lib/oeqa/runtime/cases/stap.py
index 3be4162108..d83cb0b2b8 100644
--- a/meta/lib/oeqa/runtime/cases/stap.py
+++ b/meta/lib/oeqa/runtime/cases/stap.py
@@ -16,16 +16,21 @@ class StapTest(OERuntimeTestCase):
16 @OEHasPackage(['gcc-symlinks']) 16 @OEHasPackage(['gcc-symlinks'])
17 @OEHasPackage(['kernel-devsrc']) 17 @OEHasPackage(['kernel-devsrc'])
18 def test_stap(self): 18 def test_stap(self):
19 from oe.utils import parallel_make_value
20 pmv = parallel_make_value((self.td.get('PARALLEL_MAKE') or '').split())
21 parallel_make = "-j %d" % (pmv) if pmv else ""
22
19 try: 23 try:
20 cmd = 'make -j -C /usr/src/kernel scripts prepare' 24 cmd = 'make %s -C /usr/src/kernel scripts prepare' % (parallel_make)
21 status, output = self.target.run(cmd, 900) 25 status, output = self.target.run(cmd, 900)
22 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 26 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
23 27
24 cmd = 'stap -v -p4 -m stap-hello --disable-cache -DSTP_NO_VERREL_CHECK -e \'probe oneshot { print("Hello, "); println("SystemTap!") }\'' 28 cmd = 'stap -v -p4 -m stap_hello --disable-cache -DSTP_NO_VERREL_CHECK -e \'probe oneshot { print("Hello, "); println("SystemTap!") }\''
25 status, output = self.target.run(cmd, 900) 29 status, output = self.target.run(cmd, 900)
26 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 30 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
27 31
28 cmd = 'staprun -v -R -b1 stap-hello.ko' 32 cmd = 'staprun -v -R -b1 stap_hello.ko'
33 status, output = self.target.run(cmd, 60)
29 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 34 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
30 self.assertIn('Hello, SystemTap!', output, msg='\n'.join([cmd, output])) 35 self.assertIn('Hello, SystemTap!', output, msg='\n'.join([cmd, output]))
31 except: 36 except:
diff --git a/meta/lib/oeqa/runtime/cases/systemd.py b/meta/lib/oeqa/runtime/cases/systemd.py
index 5481e1d840..640f28abe9 100644
--- a/meta/lib/oeqa/runtime/cases/systemd.py
+++ b/meta/lib/oeqa/runtime/cases/systemd.py
@@ -145,18 +145,29 @@ class SystemdServiceTests(SystemdTest):
145 Verify that call-stacks generated by systemd-coredump contain symbolicated call-stacks, 145 Verify that call-stacks generated by systemd-coredump contain symbolicated call-stacks,
146 extracted from the minidebuginfo metadata (.gnu_debugdata elf section). 146 extracted from the minidebuginfo metadata (.gnu_debugdata elf section).
147 """ 147 """
148 t_thread = threading.Thread(target=self.target.run, args=("ulimit -c unlimited && sleep 1000",)) 148 # use "env sleep" instead of "sleep" to avoid calling the shell builtin function
149 t_thread = threading.Thread(target=self.target.run, args=("ulimit -c unlimited && env sleep 1000",))
149 t_thread.start() 150 t_thread.start()
150 time.sleep(1) 151 time.sleep(1)
151 152
152 status, output = self.target.run('pidof sleep') 153 status, sleep_pid = self.target.run('pidof sleep')
153 # cause segfault on purpose 154 # cause segfault on purpose
154 self.target.run('kill -SEGV %s' % output) 155 self.target.run('kill -SEGV %s' % sleep_pid)
155 self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % output) 156 self.assertEqual(status, 0, msg = 'Not able to find process that runs sleep, output : %s' % sleep_pid)
156 157
157 (status, output) = self.target.run('coredumpctl info') 158 # Give some time to systemd-coredump@.service to process the coredump
159 for x in range(20):
160 status, output = self.target.run('coredumpctl list %s' % sleep_pid)
161 if status == 0:
162 break
163 time.sleep(1)
164 else:
165 self.fail("Timed out waiting for coredump creation")
166
167 (status, output) = self.target.run('coredumpctl info %s' % sleep_pid)
158 self.assertEqual(status, 0, msg='MiniDebugInfo Test failed: %s' % output) 168 self.assertEqual(status, 0, msg='MiniDebugInfo Test failed: %s' % output)
159 self.assertEqual('sleep_for_duration (busybox.nosuid' in output, True, msg='Call stack is missing minidebuginfo symbols (functions shown as "n/a"): %s' % output) 169 self.assertEqual('sleep_for_duration (busybox.nosuid' in output or 'xnanosleep (sleep.coreutils' in output,
170 True, msg='Call stack is missing minidebuginfo symbols (functions shown as "n/a"): %s' % output)
160 171
161class SystemdJournalTests(SystemdTest): 172class SystemdJournalTests(SystemdTest):
162 173
diff --git a/meta/lib/oeqa/runtime/cases/uki.py b/meta/lib/oeqa/runtime/cases/uki.py
new file mode 100644
index 0000000000..77bc5b9791
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/uki.py
@@ -0,0 +1,16 @@
1# SPDX-License-Identifier: MIT
2#
3
4from oeqa.runtime.case import OERuntimeTestCase
5from oeqa.core.decorator.data import skipIfNotInDataVar
6
7class UkiTest(OERuntimeTestCase):
8
9 @skipIfNotInDataVar('IMAGE_CLASSES', 'uki', 'Test case uki is for images which use uki.bbclass')
10 def test_uki(self):
11 uki_filename = self.td.get('UKI_FILENAME')
12 status, output = self.target.run('ls /boot/EFI/Linux/%s' % uki_filename)
13 self.assertEqual(status, 0, output)
14
15 status, output = self.target.run('echo $( cat /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep %s' % uki_filename)
16 self.assertEqual(status, 0, output)
diff --git a/meta/lib/oeqa/runtime/cases/weston.py b/meta/lib/oeqa/runtime/cases/weston.py
index ee4d336482..e2cecffe83 100644
--- a/meta/lib/oeqa/runtime/cases/weston.py
+++ b/meta/lib/oeqa/runtime/cases/weston.py
@@ -16,7 +16,7 @@ class WestonTest(OERuntimeTestCase):
16 16
17 @classmethod 17 @classmethod
18 def tearDownClass(cls): 18 def tearDownClass(cls):
19 cls.tc.target.run('rm %s' % cls.weston_log_file) 19 cls.tc.target.run('rm %s' % cls.weston_log_file, ignore_ssh_fails=True)
20 20
21 @OETestDepends(['ssh.SSHTest.test_ssh']) 21 @OETestDepends(['ssh.SSHTest.test_ssh'])
22 @OEHasPackage(['weston']) 22 @OEHasPackage(['weston'])
diff --git a/meta/lib/oeqa/runtime/context.py b/meta/lib/oeqa/runtime/context.py
index cb7227a8df..daabc44910 100644
--- a/meta/lib/oeqa/runtime/context.py
+++ b/meta/lib/oeqa/runtime/context.py
@@ -8,6 +8,7 @@ import os
8import sys 8import sys
9 9
10from oeqa.core.context import OETestContext, OETestContextExecutor 10from oeqa.core.context import OETestContext, OETestContextExecutor
11from oeqa.core.target.serial import OESerialTarget
11from oeqa.core.target.ssh import OESSHTarget 12from oeqa.core.target.ssh import OESSHTarget
12from oeqa.core.target.qemu import OEQemuTarget 13from oeqa.core.target.qemu import OEQemuTarget
13 14
@@ -60,7 +61,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
60 runtime_group = self.parser.add_argument_group('runtime options') 61 runtime_group = self.parser.add_argument_group('runtime options')
61 62
62 runtime_group.add_argument('--target-type', action='store', 63 runtime_group.add_argument('--target-type', action='store',
63 default=self.default_target_type, choices=['simpleremote', 'qemu'], 64 default=self.default_target_type, choices=['simpleremote', 'qemu', 'serial'],
64 help="Target type of device under test, default: %s" \ 65 help="Target type of device under test, default: %s" \
65 % self.default_target_type) 66 % self.default_target_type)
66 runtime_group.add_argument('--target-ip', action='store', 67 runtime_group.add_argument('--target-ip', action='store',
@@ -108,6 +109,8 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
108 target = OESSHTarget(logger, target_ip, server_ip, **kwargs) 109 target = OESSHTarget(logger, target_ip, server_ip, **kwargs)
109 elif target_type == 'qemu': 110 elif target_type == 'qemu':
110 target = OEQemuTarget(logger, server_ip, **kwargs) 111 target = OEQemuTarget(logger, server_ip, **kwargs)
112 elif target_type == 'serial':
113 target = OESerialTarget(logger, target_ip, server_ip, **kwargs)
111 else: 114 else:
112 # XXX: This code uses the old naming convention for controllers and 115 # XXX: This code uses the old naming convention for controllers and
113 # targets, the idea it is to leave just targets as the controller 116 # targets, the idea it is to leave just targets as the controller
@@ -203,8 +206,15 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
203 206
204 super(OERuntimeTestContextExecutor, self)._process_args(logger, args) 207 super(OERuntimeTestContextExecutor, self)._process_args(logger, args)
205 208
209 td = self.tc_kwargs['init']['td']
210
206 target_kwargs = {} 211 target_kwargs = {}
212 target_kwargs['machine'] = td.get("MACHINE") or None
207 target_kwargs['qemuboot'] = args.qemu_boot 213 target_kwargs['qemuboot'] = args.qemu_boot
214 target_kwargs['serialcontrol_cmd'] = td.get("TEST_SERIALCONTROL_CMD") or None
215 target_kwargs['serialcontrol_extra_args'] = td.get("TEST_SERIALCONTROL_EXTRA_ARGS") or ""
216 target_kwargs['serialcontrol_ps1'] = td.get("TEST_SERIALCONTROL_PS1") or None
217 target_kwargs['serialcontrol_connect_timeout'] = td.get("TEST_SERIALCONTROL_CONNECT_TIMEOUT") or None
208 218
209 self.tc_kwargs['init']['target'] = \ 219 self.tc_kwargs['init']['target'] = \
210 OERuntimeTestContextExecutor.getTarget(args.target_type, 220 OERuntimeTestContextExecutor.getTarget(args.target_type,
diff --git a/meta/lib/oeqa/sdk/case.py b/meta/lib/oeqa/sdk/case.py
index c45882689c..03cfde88ff 100644
--- a/meta/lib/oeqa/sdk/case.py
+++ b/meta/lib/oeqa/sdk/case.py
@@ -6,8 +6,11 @@
6 6
7import os 7import os
8import subprocess 8import subprocess
9import shutil
10import unittest
9 11
10from oeqa.core.case import OETestCase 12from oeqa.core.case import OETestCase
13from oeqa.sdkext.context import OESDKExtTestContext
11 14
12class OESDKTestCase(OETestCase): 15class OESDKTestCase(OETestCase):
13 def _run(self, cmd): 16 def _run(self, cmd):
@@ -15,18 +18,79 @@ class OESDKTestCase(OETestCase):
15 (self.tc.sdk_env, cmd), shell=True, executable="/bin/bash", 18 (self.tc.sdk_env, cmd), shell=True, executable="/bin/bash",
16 stderr=subprocess.STDOUT, universal_newlines=True) 19 stderr=subprocess.STDOUT, universal_newlines=True)
17 20
21 def ensure_host_package(self, *packages, recipe=None):
22 """
23 Check that the host variation of one of the packages listed is available
24 in the SDK (nativesdk-foo for SDK, foo-native for eSDK). The package is
25 a list for the case where debian-renaming may have occured, and the
26 manifest could contain 'foo' or 'libfoo'.
27
28 If testing an eSDK and the package is not found, then try to install the
29 specified recipe to install it from sstate.
30 """
31
32 # In a SDK the manifest is correct. In an eSDK the manifest may be
33 # correct (type=full) or not include packages that exist in sstate but
34 # not installed yet (minimal) so we should try to install the recipe.
35 for package in packages:
36 if isinstance(self.tc, OESDKExtTestContext):
37 package = package + "-native"
38 else:
39 package = "nativesdk-" + package
40
41 if self.tc.hasHostPackage(package):
42 break
43 else:
44 if isinstance(self.tc, OESDKExtTestContext):
45 recipe = (recipe or packages[0]) + "-native"
46 print("Trying to install %s..." % recipe)
47 try:
48 self._run('devtool sdk-install %s' % recipe)
49 except subprocess.CalledProcessError:
50 raise unittest.SkipTest("Test %s needs one of %s" % (self.id(), ", ".join(packages)))
51 else:
52 raise unittest.SkipTest("Test %s needs one of %s" % (self.id(), ", ".join(packages)))
53
54 def ensure_target_package(self, *packages, multilib=False, recipe=None):
55 """
56 Check that at least one of the packages listed is available in the SDK,
57 adding the multilib prefix if required. The target package is a list for
58 the case where debian-renaming may have occured, and the manifest could
59 contain 'foo' or 'libfoo'.
60
61 If testing an eSDK and the package is not found, then try to install the
62 specified recipe to install it from sstate.
63 """
64
65 # In a SDK the manifest is correct. In an eSDK the manifest may be
66 # correct (type=full) or not include packages that exist in sstate but
67 # not installed yet (minimal) so we should try to install the recipe.
68 for package in packages:
69 if self.tc.hasTargetPackage(package, multilib=multilib):
70 break
71 else:
72 if isinstance(self.tc, OESDKExtTestContext):
73 recipe = recipe or packages[0]
74 print("Trying to install %s..." % recipe)
75 self._run('devtool sdk-install %s' % recipe)
76 else:
77 raise unittest.SkipTest("Test %s needs one of %s" % (self.id(), ", ".join(packages)))
78
79
18 def fetch(self, workdir, dl_dir, url, archive=None): 80 def fetch(self, workdir, dl_dir, url, archive=None):
19 if not archive: 81 if not archive:
20 from urllib.parse import urlparse 82 from urllib.parse import urlparse
21 archive = os.path.basename(urlparse(url).path) 83 archive = os.path.basename(urlparse(url).path)
22 84
23 if dl_dir: 85 if dl_dir:
24 tarball = os.path.join(dl_dir, archive) 86 archive_tarball = os.path.join(dl_dir, archive)
25 if os.path.exists(tarball): 87 if os.path.exists(archive_tarball):
26 return tarball 88 return archive_tarball
27 89
28 tarball = os.path.join(workdir, archive) 90 tarball = os.path.join(workdir, archive)
29 subprocess.check_output(["wget", "-O", tarball, url], stderr=subprocess.STDOUT) 91 subprocess.check_output(["wget", "-O", tarball, url], stderr=subprocess.STDOUT)
92 if dl_dir and not os.path.exists(archive_tarball):
93 shutil.copyfile(tarball, archive_tarball)
30 return tarball 94 return tarball
31 95
32 def check_elf(self, path, target_os=None, target_arch=None): 96 def check_elf(self, path, target_os=None, target_arch=None):
diff --git a/meta/lib/oeqa/sdk/cases/assimp.py b/meta/lib/oeqa/sdk/cases/assimp.py
deleted file mode 100644
index e986838aea..0000000000
--- a/meta/lib/oeqa/sdk/cases/assimp.py
+++ /dev/null
@@ -1,45 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10import unittest
11from oeqa.sdk.case import OESDKTestCase
12
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class BuildAssimp(OESDKTestCase):
17 """
18 Test case to build a project using cmake.
19 """
20
21 def setUp(self):
22 if not (self.tc.hasHostPackage("nativesdk-cmake") or
23 self.tc.hasHostPackage("cmake-native")):
24 raise unittest.SkipTest("Needs cmake")
25
26 def test_assimp(self):
27 with tempfile.TemporaryDirectory(prefix="assimp", dir=self.tc.sdk_dir) as testdir:
28 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://github.com/assimp/assimp/archive/v5.3.1.tar.gz")
29
30 dirs = {}
31 dirs["source"] = os.path.join(testdir, "assimp-5.3.1")
32 dirs["build"] = os.path.join(testdir, "build")
33 dirs["install"] = os.path.join(testdir, "install")
34
35 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
36 self.assertTrue(os.path.isdir(dirs["source"]))
37 # Apply the zlib patch https://github.com/madler/zlib/commit/a566e156b3fa07b566ddbf6801b517a9dba04fa3
38 # this sed wont be needed once assimp moves its zlib copy to v1.3.1+
39 self._run("sed -i '/# ifdef _FILE_OFFSET_BITS/I,+2 d' {source}/contrib/zlib/gzguts.h".format(**dirs))
40 os.makedirs(dirs["build"])
41
42 self._run("cd {build} && cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DASSIMP_BUILD_ZLIB=ON {source}".format(**dirs))
43 self._run("cmake --build {build} -- -j".format(**dirs))
44 self._run("cmake --build {build} --target install -- DESTDIR={install}".format(**dirs))
45 self.check_elf(os.path.join(dirs["install"], "usr", "local", "lib", "libassimp.so.5.3.0"))
diff --git a/meta/lib/oeqa/sdk/cases/autotools.py b/meta/lib/oeqa/sdk/cases/autotools.py
new file mode 100644
index 0000000000..ecafafa7d6
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/autotools.py
@@ -0,0 +1,52 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import tempfile
9import subprocess
10import unittest
11
12from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class AutotoolsTest(OESDKTestCase):
17 """
18 Check that autotools will cross-compile correctly.
19 """
20 def setUp(self):
21 libc = self.td.get("TCLIBC")
22 if libc in [ 'newlib' ]:
23 raise unittest.SkipTest("AutotoolsTest class: SDK doesn't contain a supported C library")
24
25 def test_cpio(self):
26 from oe.utils import parallel_make_value
27 pmv = parallel_make_value((self.td.get('PARALLEL_MAKE') or '').split())
28
29 with tempfile.TemporaryDirectory(prefix="cpio-", dir=self.tc.sdk_dir) as testdir:
30 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftpmirror.gnu.org/gnu/cpio/cpio-2.15.tar.gz")
31
32 opts = {}
33 opts["source"] = os.path.join(testdir, "cpio-2.15")
34 opts["build"] = os.path.join(testdir, "build")
35 opts["install"] = os.path.join(testdir, "install")
36 opts["parallel_make"] = "-j %d" % (pmv) if pmv else ""
37
38 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
39 self.assertTrue(os.path.isdir(opts["source"]))
40 os.makedirs(opts["build"])
41
42 self._run("cd {build} && {source}/configure CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration' $CONFIGURE_FLAGS".format(**opts))
43
44 # Check that configure detected the target correctly
45 with open(os.path.join(opts["build"], "config.log")) as f:
46 host_sys = self.td["HOST_SYS"]
47 self.assertIn(f"host_alias='{host_sys}'\n", f.readlines())
48
49 self._run("cd {build} && make CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration' {parallel_make}".format(**opts))
50 self._run("cd {build} && make install DESTDIR={install}".format(**opts))
51
52 self.check_elf(os.path.join(opts["install"], "usr", "local", "bin", "cpio"))
diff --git a/meta/lib/oeqa/sdk/cases/buildcpio.py b/meta/lib/oeqa/sdk/cases/buildcpio.py
deleted file mode 100644
index 51003b19cd..0000000000
--- a/meta/lib/oeqa/sdk/cases/buildcpio.py
+++ /dev/null
@@ -1,37 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import tempfile
9import subprocess
10import unittest
11
12from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class BuildCpioTest(OESDKTestCase):
17 """
18 Check that autotools will cross-compile correctly.
19 """
20 def test_cpio(self):
21 with tempfile.TemporaryDirectory(prefix="cpio-", dir=self.tc.sdk_dir) as testdir:
22 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftp.gnu.org/gnu/cpio/cpio-2.15.tar.gz")
23
24 dirs = {}
25 dirs["source"] = os.path.join(testdir, "cpio-2.15")
26 dirs["build"] = os.path.join(testdir, "build")
27 dirs["install"] = os.path.join(testdir, "install")
28
29 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
30 self.assertTrue(os.path.isdir(dirs["source"]))
31 os.makedirs(dirs["build"])
32
33 self._run("cd {build} && {source}/configure $CONFIGURE_FLAGS".format(**dirs))
34 self._run("cd {build} && make -j".format(**dirs))
35 self._run("cd {build} && make install DESTDIR={install}".format(**dirs))
36
37 self.check_elf(os.path.join(dirs["install"], "usr", "local", "bin", "cpio"))
diff --git a/meta/lib/oeqa/sdk/cases/buildepoxy.py b/meta/lib/oeqa/sdk/cases/buildepoxy.py
deleted file mode 100644
index 147ee3e0ee..0000000000
--- a/meta/lib/oeqa/sdk/cases/buildepoxy.py
+++ /dev/null
@@ -1,44 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10import unittest
11
12from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class EpoxyTest(OESDKTestCase):
17 """
18 Test that Meson builds correctly.
19 """
20 def setUp(self):
21 if not (self.tc.hasHostPackage("nativesdk-meson") or
22 self.tc.hasHostPackage("meson-native")):
23 raise unittest.SkipTest("EpoxyTest class: SDK doesn't contain Meson")
24
25 def test_epoxy(self):
26 with tempfile.TemporaryDirectory(prefix="epoxy", dir=self.tc.sdk_dir) as testdir:
27 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://github.com/anholt/libepoxy/releases/download/1.5.3/libepoxy-1.5.3.tar.xz")
28
29 dirs = {}
30 dirs["source"] = os.path.join(testdir, "libepoxy-1.5.3")
31 dirs["build"] = os.path.join(testdir, "build")
32 dirs["install"] = os.path.join(testdir, "install")
33
34 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
35 self.assertTrue(os.path.isdir(dirs["source"]))
36 os.makedirs(dirs["build"])
37
38 log = self._run("meson --warnlevel 1 -Degl=no -Dglx=no -Dx11=false {build} {source}".format(**dirs))
39 # Check that Meson thinks we're doing a cross build and not a native
40 self.assertIn("Build type: cross build", log)
41 self._run("ninja -C {build} -v".format(**dirs))
42 self._run("DESTDIR={install} ninja -C {build} -v install".format(**dirs))
43
44 self.check_elf(os.path.join(dirs["install"], "usr", "local", "lib", "libepoxy.so"))
diff --git a/meta/lib/oeqa/sdk/cases/buildgalculator.py b/meta/lib/oeqa/sdk/cases/buildgalculator.py
deleted file mode 100644
index 178f07472d..0000000000
--- a/meta/lib/oeqa/sdk/cases/buildgalculator.py
+++ /dev/null
@@ -1,46 +0,0 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10import unittest
11
12from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class GalculatorTest(OESDKTestCase):
17 """
18 Test that autotools and GTK+ 3 compiles correctly.
19 """
20 def setUp(self):
21 if not (self.tc.hasTargetPackage("gtk+3", multilib=True) or \
22 self.tc.hasTargetPackage("libgtk-3.0", multilib=True)):
23 raise unittest.SkipTest("GalculatorTest class: SDK don't support gtk+3")
24 if not (self.tc.hasHostPackage("nativesdk-gettext-dev") or
25 self.tc.hasHostPackage("gettext-native")):
26 raise unittest.SkipTest("GalculatorTest class: SDK doesn't contain gettext")
27
28 def test_galculator(self):
29 with tempfile.TemporaryDirectory(prefix="galculator", dir=self.tc.sdk_dir) as testdir:
30 tarball = self.fetch(testdir, self.td["DL_DIR"], "http://galculator.mnim.org/downloads/galculator-2.1.4.tar.bz2")
31
32 dirs = {}
33 dirs["source"] = os.path.join(testdir, "galculator-2.1.4")
34 dirs["build"] = os.path.join(testdir, "build")
35 dirs["install"] = os.path.join(testdir, "install")
36
37 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
38 self.assertTrue(os.path.isdir(dirs["source"]))
39 os.makedirs(dirs["build"])
40
41 self._run("cd {source} && sed -i -e '/s_preferences.*prefs;/d' src/main.c && autoreconf -i -f -I $OECORE_TARGET_SYSROOT/usr/share/aclocal -I m4".format(**dirs))
42 self._run("cd {build} && {source}/configure $CONFIGURE_FLAGS".format(**dirs))
43 self._run("cd {build} && make -j".format(**dirs))
44 self._run("cd {build} && make install DESTDIR={install}".format(**dirs))
45
46 self.check_elf(os.path.join(dirs["install"], "usr", "local", "bin", "galculator"))
diff --git a/meta/lib/oeqa/sdk/cases/cmake.py b/meta/lib/oeqa/sdk/cases/cmake.py
new file mode 100644
index 0000000000..81fd327ca3
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/cmake.py
@@ -0,0 +1,51 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10import unittest
11from oeqa.sdk.case import OESDKTestCase
12
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class CMakeTest(OESDKTestCase):
17 """
18 Test case to build a project using cmake.
19 """
20
21 def setUp(self):
22 libc = self.td.get("TCLIBC")
23 if libc in [ 'newlib' ]:
24 raise unittest.SkipTest("CMakeTest class: SDK doesn't contain a supported C library")
25
26 self.ensure_host_package("cmake")
27
28 def test_assimp(self):
29 from oe.utils import parallel_make_value
30 pmv = parallel_make_value((self.td.get('PARALLEL_MAKE') or '').split())
31
32 with tempfile.TemporaryDirectory(prefix="assimp", dir=self.tc.sdk_dir) as testdir:
33 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://github.com/assimp/assimp/archive/v5.4.1.tar.gz")
34
35 opts = {}
36 opts["source"] = os.path.join(testdir, "assimp-5.4.1")
37 opts["build"] = os.path.join(testdir, "build")
38 opts["install"] = os.path.join(testdir, "install")
39 opts["parallel_make"] = "-j %d" % (pmv) if pmv else ""
40
41 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
42 self.assertTrue(os.path.isdir(opts["source"]))
43 # Apply the zlib patch https://github.com/madler/zlib/commit/a566e156b3fa07b566ddbf6801b517a9dba04fa3
44 # this sed wont be needed once assimp moves its zlib copy to v1.3.1+
45 self._run("sed -i '/# ifdef _FILE_OFFSET_BITS/I,+2 d' {source}/contrib/zlib/gzguts.h".format(**opts))
46 os.makedirs(opts["build"])
47
48 self._run("cd {build} && cmake -DASSIMP_WARNINGS_AS_ERRORS=OFF -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DASSIMP_BUILD_ZLIB=ON {source}".format(**opts))
49 self._run("cmake --build {build} -- {parallel_make}".format(**opts))
50 self._run("cmake --build {build} --target install -- DESTDIR={install}".format(**opts))
51 self.check_elf(os.path.join(opts["install"], "usr", "local", "lib", "libassimp.so.5.4.1"))
diff --git a/meta/lib/oeqa/sdk/cases/gcc.py b/meta/lib/oeqa/sdk/cases/gcc.py
index fc28b9c3d4..e810d2c42b 100644
--- a/meta/lib/oeqa/sdk/cases/gcc.py
+++ b/meta/lib/oeqa/sdk/cases/gcc.py
@@ -26,6 +26,10 @@ class GccCompileTest(OESDKTestCase):
26 os.path.join(self.tc.sdk_dir, f)) 26 os.path.join(self.tc.sdk_dir, f))
27 27
28 def setUp(self): 28 def setUp(self):
29 libc = self.td.get("TCLIBC")
30 if libc in [ 'newlib' ]:
31 raise unittest.SkipTest("GccCompileTest class: SDK doesn't contain a supported C library")
32
29 machine = self.td.get("MACHINE") 33 machine = self.td.get("MACHINE")
30 if not (self.tc.hasHostPackage("packagegroup-cross-canadian-%s" % machine) or 34 if not (self.tc.hasHostPackage("packagegroup-cross-canadian-%s" % machine) or
31 self.tc.hasHostPackage("^gcc-", regex=True)): 35 self.tc.hasHostPackage("^gcc-", regex=True)):
diff --git a/meta/lib/oeqa/sdk/cases/go.py b/meta/lib/oeqa/sdk/cases/go.py
new file mode 100644
index 0000000000..a050df7a9f
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/go.py
@@ -0,0 +1,107 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import shutil
9import unittest
10
11from oeqa.core.utils.path import remove_safe
12from oeqa.sdk.case import OESDKTestCase
13
14from oeqa.utils.subprocesstweak import errors_have_output
15from oe.go import map_arch
16errors_have_output()
17
18class GoCompileTest(OESDKTestCase):
19 td_vars = ['MACHINE', 'TARGET_ARCH']
20
21 @classmethod
22 def setUpClass(self):
23 # Copy test.go file to SDK directory (same as GCC test uses files_dir)
24 shutil.copyfile(os.path.join(self.tc.files_dir, 'test.go'),
25 os.path.join(self.tc.sdk_dir, 'test.go'))
26
27 def setUp(self):
28 target_arch = self.td.get("TARGET_ARCH")
29 # Check for go-cross-canadian package (uses target architecture)
30 if not self.tc.hasHostPackage("go-cross-canadian-%s" % target_arch):
31 raise unittest.SkipTest("GoCompileTest class: SDK doesn't contain a Go cross-canadian toolchain")
32
33 # Additional runtime check for go command availability
34 try:
35 self._run('which go')
36 except Exception as e:
37 raise unittest.SkipTest("GoCompileTest class: go command not available: %s" % str(e))
38
39 def test_go_build(self):
40 """Test Go build command (native compilation)"""
41 self._run('cd %s; go build -o test test.go' % self.tc.sdk_dir)
42
43 def test_go_module(self):
44 """Test Go module creation and building"""
45 # Create a simple Go module
46 self._run('cd %s; go mod init hello-go' % self.tc.sdk_dir)
47 self._run('cd %s; go build -o hello-go' % self.tc.sdk_dir)
48
49 @classmethod
50 def tearDownClass(self):
51 files = [os.path.join(self.tc.sdk_dir, f) \
52 for f in ['test.go', 'test', 'hello-go', 'go.mod', 'go.sum']]
53 for f in files:
54 remove_safe(f)
55
56class GoHostCompileTest(OESDKTestCase):
57 td_vars = ['MACHINE', 'SDK_SYS', 'TARGET_ARCH']
58
59 @classmethod
60 def setUpClass(self):
61 # Copy test.go file to SDK directory (same as GCC test uses files_dir)
62 shutil.copyfile(os.path.join(self.tc.files_dir, 'test.go'),
63 os.path.join(self.tc.sdk_dir, 'test.go'))
64
65 def setUp(self):
66 target_arch = self.td.get("TARGET_ARCH")
67 # Check for go-cross-canadian package (uses target architecture)
68 if not self.tc.hasHostPackage("go-cross-canadian-%s" % target_arch):
69 raise unittest.SkipTest("GoHostCompileTest class: SDK doesn't contain a Go cross-canadian toolchain")
70
71 # Additional runtime check for go command availability
72 try:
73 self._run('which go')
74 except Exception as e:
75 raise unittest.SkipTest("GoHostCompileTest class: go command not available: %s" % str(e))
76
77 def _get_go_arch(self):
78 """Get Go architecture from SDK_SYS"""
79 sdksys = self.td.get("SDK_SYS")
80 arch = sdksys.split('-')[0]
81
82 # Use mapping for other architectures
83 return map_arch(arch)
84
85 def test_go_cross_compile(self):
86 """Test Go cross-compilation for target"""
87 goarch = self._get_go_arch()
88 self._run('cd %s; GOOS=linux GOARCH=%s go build -o test-%s test.go' % (self.tc.sdk_dir, goarch, goarch))
89
90 def test_go_module_cross_compile(self):
91 """Test Go module cross-compilation"""
92 goarch = self._get_go_arch()
93 self._run('cd %s; go mod init hello-go' % self.tc.sdk_dir)
94 self._run('cd %s; GOOS=linux GOARCH=%s go build -o hello-go-%s' % (self.tc.sdk_dir, goarch, goarch))
95
96 @classmethod
97 def tearDownClass(self):
98 # Clean up files with dynamic architecture names
99 files = [os.path.join(self.tc.sdk_dir, f) \
100 for f in ['test.go', 'go.mod', 'go.sum']]
101 # Add common architecture-specific files that might be created
102 common_archs = ['arm64', 'arm', 'amd64', '386', 'mips', 'mipsle', 'ppc64', 'ppc64le', 'riscv64']
103 for arch in common_archs:
104 files.extend([os.path.join(self.tc.sdk_dir, f) \
105 for f in ['test-%s' % arch, 'hello-go-%s' % arch]])
106 for f in files:
107 remove_safe(f)
diff --git a/meta/lib/oeqa/sdk/cases/gtk3.py b/meta/lib/oeqa/sdk/cases/gtk3.py
new file mode 100644
index 0000000000..cdaf50ed38
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/gtk3.py
@@ -0,0 +1,40 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10
11from oeqa.sdk.cases.meson import MesonTestBase
12
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class GTK3Test(MesonTestBase):
17
18 def setUp(self):
19 super().setUp()
20 self.ensure_target_package("gtk+3", "libgtk-3.0", recipe="gtk+3")
21 self.ensure_host_package("glib-2.0-utils", "libglib-2.0-utils", recipe="glib-2.0")
22
23 """
24 Test that autotools and GTK+ 3 compiles correctly.
25 """
26 def test_libhandy(self):
27 with tempfile.TemporaryDirectory(prefix="libhandy", dir=self.tc.sdk_dir) as testdir:
28 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://download.gnome.org/sources/libhandy/1.8/libhandy-1.8.3.tar.xz")
29
30 sourcedir = os.path.join(testdir, "libhandy-1.8.3")
31 builddir = os.path.join(testdir, "build")
32 installdir = os.path.join(testdir, "install")
33
34 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
35 self.assertTrue(os.path.isdir(sourcedir))
36 os.makedirs(builddir)
37
38 self.build_meson(sourcedir, builddir, installdir, "-Dglade_catalog=disabled -Dintrospection=disabled -Dvapi=false")
39 self.assertTrue(os.path.isdir(installdir))
40 self.check_elf(os.path.join(installdir, "usr", "local", "lib", "libhandy-1.so"))
diff --git a/meta/lib/oeqa/sdk/cases/kmod.py b/meta/lib/oeqa/sdk/cases/kmod.py
new file mode 100644
index 0000000000..0c4d8ddb54
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/kmod.py
@@ -0,0 +1,43 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import subprocess
9import tempfile
10
11from oeqa.sdk.case import OESDKTestCase
12from oeqa.sdkext.context import OESDKExtTestContext
13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output()
15
16class KernelModuleTest(OESDKTestCase):
17 """
18 Test that out-of-tree kernel modules build.
19 """
20 def test_cryptodev(self):
21 if isinstance(self.tc, OESDKExtTestContext):
22 self.skipTest(f"{self.id()} does not support eSDK (https://bugzilla.yoctoproject.org/show_bug.cgi?id=15850)")
23
24 from oe.utils import parallel_make_value
25 pmv = parallel_make_value((self.td.get('PARALLEL_MAKE') or '').split())
26 parallel_make = "-j %d" % (pmv) if pmv else ""
27
28 self.ensure_target_package("kernel-devsrc")
29 # These targets need to be built before kernel modules can be built.
30 self._run("make %s -C $OECORE_TARGET_SYSROOT/usr/src/kernel prepare scripts" % (parallel_make))
31
32 with tempfile.TemporaryDirectory(prefix="cryptodev", dir=self.tc.sdk_dir) as testdir:
33 git_url = "https://github.com/cryptodev-linux/cryptodev-linux"
34 # This is a knnown-good commit post-1.13 that builds with kernel 6.7+
35 git_sha = "bb8bc7cf60d2c0b097c8b3b0e807f805b577a53f"
36
37 sourcedir = os.path.join(testdir, "cryptodev-linux")
38 subprocess.check_output(["git", "clone", git_url, sourcedir], stderr=subprocess.STDOUT)
39 self.assertTrue(os.path.isdir(sourcedir))
40 subprocess.check_output(["git", "-C", sourcedir, "checkout", git_sha], stderr=subprocess.STDOUT)
41
42 self._run("make -C %s V=1 KERNEL_DIR=$OECORE_TARGET_SYSROOT/usr/src/kernel" % sourcedir)
43 self.check_elf(os.path.join(sourcedir, "cryptodev.ko"))
diff --git a/meta/lib/oeqa/sdk/cases/buildlzip.py b/meta/lib/oeqa/sdk/cases/makefile.py
index b4b7d85b88..fc041ca8d8 100644
--- a/meta/lib/oeqa/sdk/cases/buildlzip.py
+++ b/meta/lib/oeqa/sdk/cases/makefile.py
@@ -4,27 +4,37 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7import os, tempfile, subprocess, unittest 7import os, tempfile, subprocess
8import unittest
8from oeqa.sdk.case import OESDKTestCase 9from oeqa.sdk.case import OESDKTestCase
9from oeqa.utils.subprocesstweak import errors_have_output 10from oeqa.utils.subprocesstweak import errors_have_output
10errors_have_output() 11errors_have_output()
11 12
12class BuildLzipTest(OESDKTestCase): 13class MakefileTest(OESDKTestCase):
13 """ 14 """
14 Test that "plain" compilation works, using just $CC $CFLAGS etc. 15 Test that "plain" compilation works, using just $CC $CFLAGS etc.
15 """ 16 """
17 def setUp(self):
18 libc = self.td.get("TCLIBC")
19 if libc in [ 'newlib' ]:
20 raise unittest.SkipTest("MakefileTest class: SDK doesn't contain a supported C library")
21
16 def test_lzip(self): 22 def test_lzip(self):
23 from oe.utils import parallel_make_value
24 pmv = parallel_make_value((self.td.get('PARALLEL_MAKE') or '').split())
25
17 with tempfile.TemporaryDirectory(prefix="lzip", dir=self.tc.sdk_dir) as testdir: 26 with tempfile.TemporaryDirectory(prefix="lzip", dir=self.tc.sdk_dir) as testdir:
18 tarball = self.fetch(testdir, self.td["DL_DIR"], "http://downloads.yoctoproject.org/mirror/sources/lzip-1.19.tar.gz") 27 tarball = self.fetch(testdir, self.td["DL_DIR"], "http://downloads.yoctoproject.org/mirror/sources/lzip-1.19.tar.gz")
19 28
20 dirs = {} 29 opts = {}
21 dirs["source"] = os.path.join(testdir, "lzip-1.19") 30 opts["source"] = os.path.join(testdir, "lzip-1.19")
22 dirs["build"] = os.path.join(testdir, "build") 31 opts["build"] = os.path.join(testdir, "build")
23 dirs["install"] = os.path.join(testdir, "install") 32 opts["install"] = os.path.join(testdir, "install")
33 opts["parallel_make"] = "-j %d" % (pmv) if pmv else ""
24 34
25 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT) 35 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
26 self.assertTrue(os.path.isdir(dirs["source"])) 36 self.assertTrue(os.path.isdir(opts["source"]))
27 os.makedirs(dirs["build"]) 37 os.makedirs(opts["build"])
28 38
29 cmd = """cd {build} && \ 39 cmd = """cd {build} && \
30 {source}/configure --srcdir {source} \ 40 {source}/configure --srcdir {source} \
@@ -33,7 +43,7 @@ class BuildLzipTest(OESDKTestCase):
33 CXXFLAGS="$CXXFLAGS" \ 43 CXXFLAGS="$CXXFLAGS" \
34 LDFLAGS="$LDFLAGS" \ 44 LDFLAGS="$LDFLAGS" \
35 """ 45 """
36 self._run(cmd.format(**dirs)) 46 self._run(cmd.format(**opts))
37 self._run("cd {build} && make -j".format(**dirs)) 47 self._run("cd {build} && make {parallel_make}".format(**opts))
38 self._run("cd {build} && make install DESTDIR={install}".format(**dirs)) 48 self._run("cd {build} && make install DESTDIR={install}".format(**opts))
39 self.check_elf(os.path.join(dirs["install"], "usr", "local", "bin", "lzip")) 49 self.check_elf(os.path.join(opts["install"], "usr", "local", "bin", "lzip"))
diff --git a/meta/lib/oeqa/sdk/cases/manifest.py b/meta/lib/oeqa/sdk/cases/manifest.py
new file mode 100644
index 0000000000..ee59a5f338
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/manifest.py
@@ -0,0 +1,26 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.sdk.case import OESDKTestCase
8from oeqa.sdkext.context import OESDKExtTestContext
9
10
11class ManifestTest(OESDKTestCase):
12 def test_manifests(self):
13 """
14 Verify that the host and target manifests are not empty, unless this is
15 a minimal eSDK without toolchain in which case they should be empty.
16 """
17 if (
18 isinstance(self.tc, OESDKExtTestContext)
19 and self.td.get("SDK_EXT_TYPE") == "minimal"
20 and self.td.get("SDK_INCLUDE_TOOLCHAIN") == "0"
21 ):
22 self.assertEqual(self.tc.target_pkg_manifest, {})
23 self.assertEqual(self.tc.host_pkg_manifest, {})
24 else:
25 self.assertNotEqual(self.tc.target_pkg_manifest, {})
26 self.assertNotEqual(self.tc.host_pkg_manifest, {})
diff --git a/meta/lib/oeqa/sdk/cases/maturin.py b/meta/lib/oeqa/sdk/cases/maturin.py
index ea10f568b2..e3e8edc781 100644
--- a/meta/lib/oeqa/sdk/cases/maturin.py
+++ b/meta/lib/oeqa/sdk/cases/maturin.py
@@ -8,7 +8,6 @@ import os
8import shutil 8import shutil
9import unittest 9import unittest
10 10
11from oeqa.core.utils.path import remove_safe
12from oeqa.sdk.case import OESDKTestCase 11from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output 12from oeqa.utils.subprocesstweak import errors_have_output
14 13
@@ -17,44 +16,24 @@ errors_have_output()
17 16
18class MaturinTest(OESDKTestCase): 17class MaturinTest(OESDKTestCase):
19 def setUp(self): 18 def setUp(self):
20 if not ( 19 self.ensure_host_package("python3-maturin")
21 self.tc.hasHostPackage("nativesdk-python3-maturin")
22 or self.tc.hasHostPackage("python3-maturin-native")
23 ):
24 raise unittest.SkipTest("No python3-maturin package in the SDK")
25 20
26 def test_maturin_list_python(self): 21 def test_maturin_list_python(self):
27 py_major = self._run("python3 -c 'import sys; print(sys.version_info.major)'") 22 out = self._run(r"""python3 -c 'import sys; print(f"{sys.executable}\n{sys.version_info.major}.{sys.version_info.minor}")'""")
28 py_minor = self._run("python3 -c 'import sys; print(sys.version_info.minor)'") 23 executable, version = out.splitlines()
29 python_version = "%s.%s" % (py_major.strip(), py_minor.strip())
30 cmd = "maturin list-python"
31 output = self._run(cmd)
32 self.assertRegex(output, r"^🐍 1 python interpreter found:\n")
33 self.assertRegex(
34 output,
35 r" - CPython %s (.+)/usr/bin/python%s$" % (python_version, python_version),
36 )
37 24
25 output = self._run("maturin list-python")
26 # The output looks like this:
27 # - CPython 3.13 at /usr/bin/python3
28 # We don't want to assume CPython so just check for the version and path.
29 expected = f"{version} at {executable}"
30 self.assertIn(expected, output)
38 31
39class MaturinDevelopTest(OESDKTestCase): 32class MaturinDevelopTest(OESDKTestCase):
40 @classmethod
41 def setUpClass(self):
42 targetdir = os.path.join(self.tc.sdk_dir, "guessing-game")
43 try:
44 shutil.rmtree(targetdir)
45 except FileNotFoundError:
46 pass
47 shutil.copytree(
48 os.path.join(self.tc.files_dir, "maturin/guessing-game"), targetdir
49 )
50
51 def setUp(self): 33 def setUp(self):
52 machine = self.td.get("MACHINE") 34 machine = self.td.get("MACHINE")
53 if not ( 35 self.ensure_host_package("python3-maturin")
54 self.tc.hasHostPackage("nativesdk-python3-maturin") 36
55 or self.tc.hasHostPackage("python3-maturin-native")
56 ):
57 raise unittest.SkipTest("No python3-maturin package in the SDK")
58 if not ( 37 if not (
59 self.tc.hasHostPackage("packagegroup-rust-cross-canadian-%s" % machine) 38 self.tc.hasHostPackage("packagegroup-rust-cross-canadian-%s" % machine)
60 ): 39 ):
@@ -68,9 +47,17 @@ class MaturinDevelopTest(OESDKTestCase):
68 (1) that a .venv can been created. 47 (1) that a .venv can been created.
69 (2) a functional 'rustc' and 'cargo' 48 (2) a functional 'rustc' and 'cargo'
70 """ 49 """
71 self._run("cd %s/guessing-game; python3 -m venv .venv" % self.tc.sdk_dir) 50 targetdir = os.path.join(self.tc.sdk_dir, "guessing-game")
72 cmd = "cd %s/guessing-game; maturin develop" % self.tc.sdk_dir 51 try:
73 output = self._run(cmd) 52 shutil.rmtree(targetdir)
53 except FileNotFoundError:
54 pass
55 shutil.copytree(
56 os.path.join(self.tc.files_dir, "maturin/guessing-game"), targetdir
57 )
58
59 self._run("cd %s; python3 -m venv .venv" % targetdir)
60 output = self._run("cd %s; maturin develop" % targetdir)
74 self.assertRegex(output, r"🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.8") 61 self.assertRegex(output, r"🔗 Found pyo3 bindings with abi3 support for Python ≥ 3.8")
75 self.assertRegex(output, r"🐍 Not using a specific python interpreter") 62 self.assertRegex(output, r"🐍 Not using a specific python interpreter")
76 self.assertRegex(output, r"📡 Using build options features from pyproject.toml") 63 self.assertRegex(output, r"📡 Using build options features from pyproject.toml")
diff --git a/meta/lib/oeqa/sdk/cases/meson.py b/meta/lib/oeqa/sdk/cases/meson.py
new file mode 100644
index 0000000000..a809ca3a53
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/meson.py
@@ -0,0 +1,72 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import json
8import os
9import subprocess
10import tempfile
11import unittest
12
13from oeqa.sdk.case import OESDKTestCase
14from oeqa.sdkext.context import OESDKExtTestContext
15from oeqa.utils.subprocesstweak import errors_have_output
16errors_have_output()
17
18class MesonTestBase(OESDKTestCase):
19 def setUp(self):
20 libc = self.td.get("TCLIBC")
21 if libc in [ 'newlib' ]:
22 raise unittest.SkipTest("MesonTest class: SDK doesn't contain a supported C library")
23
24 if isinstance(self.tc, OESDKExtTestContext):
25 self.skipTest(f"{self.id()} does not support eSDK (https://bugzilla.yoctoproject.org/show_bug.cgi?id=15854)")
26
27 self.ensure_host_package("meson")
28 self.ensure_host_package("pkgconfig")
29
30 def build_meson(self, sourcedir, builddir, installdir=None, options=""):
31 """
32 Given a source tree in sourcedir, configure it to build in builddir with
33 the specified options, and if installdir is set also install.
34 """
35 log = self._run(f"meson setup --warnlevel 1 {builddir} {sourcedir} {options}")
36
37 # Check that Meson thinks we're doing a cross build and not a native
38 self.assertIn("Build type: cross build", log)
39
40 # Check that the cross-compiler used is the one we set.
41 data = json.loads(self._run(f"meson introspect --compilers {builddir}"))
42 self.assertIn(self.td.get("CC").split()[0], data["host"]["c"]["exelist"])
43
44 # Check that the target architectures was set correctly.
45 data = json.loads(self._run(f"meson introspect --machines {builddir}"))
46 self.assertEqual(data["host"]["cpu"], self.td["HOST_ARCH"])
47
48 self._run(f"meson compile -C {builddir} -v")
49
50 if installdir:
51 self._run(f"meson install -C {builddir} --destdir {installdir}")
52
53class MesonTest(MesonTestBase):
54 """
55 Test that Meson builds correctly.
56 """
57
58 def test_epoxy(self):
59 with tempfile.TemporaryDirectory(prefix="epoxy", dir=self.tc.sdk_dir) as testdir:
60 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://github.com/anholt/libepoxy/releases/download/1.5.3/libepoxy-1.5.3.tar.xz")
61
62 sourcedir = os.path.join(testdir, "libepoxy-1.5.3")
63 builddir = os.path.join(testdir, "build")
64 installdir = os.path.join(testdir, "install")
65
66 subprocess.check_output(["tar", "xf", tarball, "-C", testdir], stderr=subprocess.STDOUT)
67 self.assertTrue(os.path.isdir(sourcedir))
68
69 os.makedirs(builddir)
70 self.build_meson(sourcedir, builddir, installdir, "-Degl=no -Dglx=no -Dx11=false")
71 self.assertTrue(os.path.isdir(installdir))
72 self.check_elf(os.path.join(installdir, "usr", "local", "lib", "libepoxy.so"))
diff --git a/meta/lib/oeqa/sdk/cases/perl.py b/meta/lib/oeqa/sdk/cases/perl.py
index 8eab4442e8..a72bd2461a 100644
--- a/meta/lib/oeqa/sdk/cases/perl.py
+++ b/meta/lib/oeqa/sdk/cases/perl.py
@@ -4,7 +4,6 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7import unittest
8from oeqa.sdk.case import OESDKTestCase 7from oeqa.sdk.case import OESDKTestCase
9 8
10from oeqa.utils.subprocesstweak import errors_have_output 9from oeqa.utils.subprocesstweak import errors_have_output
@@ -12,9 +11,7 @@ errors_have_output()
12 11
13class PerlTest(OESDKTestCase): 12class PerlTest(OESDKTestCase):
14 def setUp(self): 13 def setUp(self):
15 if not (self.tc.hasHostPackage("nativesdk-perl") or 14 self.ensure_host_package("perl")
16 self.tc.hasHostPackage("perl-native")):
17 raise unittest.SkipTest("No perl package in the SDK")
18 15
19 def test_perl(self): 16 def test_perl(self):
20 cmd = "perl -e '$_=\"Uryyb, jbeyq\"; tr/a-zA-Z/n-za-mN-ZA-M/;print'" 17 cmd = "perl -e '$_=\"Uryyb, jbeyq\"; tr/a-zA-Z/n-za-mN-ZA-M/;print'"
diff --git a/meta/lib/oeqa/sdk/cases/python.py b/meta/lib/oeqa/sdk/cases/python.py
index 5ea992b9f3..b990cd889a 100644
--- a/meta/lib/oeqa/sdk/cases/python.py
+++ b/meta/lib/oeqa/sdk/cases/python.py
@@ -4,7 +4,6 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7import subprocess, unittest
8from oeqa.sdk.case import OESDKTestCase 7from oeqa.sdk.case import OESDKTestCase
9 8
10from oeqa.utils.subprocesstweak import errors_have_output 9from oeqa.utils.subprocesstweak import errors_have_output
@@ -12,9 +11,7 @@ errors_have_output()
12 11
13class Python3Test(OESDKTestCase): 12class Python3Test(OESDKTestCase):
14 def setUp(self): 13 def setUp(self):
15 if not (self.tc.hasHostPackage("nativesdk-python3-core") or 14 self.ensure_host_package("python3-core", recipe="python3")
16 self.tc.hasHostPackage("python3-core-native")):
17 raise unittest.SkipTest("No python3 package in the SDK")
18 15
19 def test_python3(self): 16 def test_python3(self):
20 cmd = "python3 -c \"import codecs; print(codecs.encode('Uryyb, jbeyq', 'rot13'))\"" 17 cmd = "python3 -c \"import codecs; print(codecs.encode('Uryyb, jbeyq', 'rot13'))\""
diff --git a/meta/lib/oeqa/sdk/cases/rust.py b/meta/lib/oeqa/sdk/cases/rust.py
index f5d437bb19..4b115bebf5 100644
--- a/meta/lib/oeqa/sdk/cases/rust.py
+++ b/meta/lib/oeqa/sdk/cases/rust.py
@@ -8,7 +8,6 @@ import os
8import shutil 8import shutil
9import unittest 9import unittest
10 10
11from oeqa.core.utils.path import remove_safe
12from oeqa.sdk.case import OESDKTestCase 11from oeqa.sdk.case import OESDKTestCase
13 12
14from oeqa.utils.subprocesstweak import errors_have_output 13from oeqa.utils.subprocesstweak import errors_have_output
@@ -32,6 +31,7 @@ class RustCompileTest(OESDKTestCase):
32 raise unittest.SkipTest("RustCompileTest class: SDK doesn't contain a Rust cross-canadian toolchain") 31 raise unittest.SkipTest("RustCompileTest class: SDK doesn't contain a Rust cross-canadian toolchain")
33 32
34 def test_cargo_build(self): 33 def test_cargo_build(self):
34 self._run('cd %s/hello; cargo add zstd' % (self.tc.sdk_dir))
35 self._run('cd %s/hello; cargo build' % self.tc.sdk_dir) 35 self._run('cd %s/hello; cargo build' % self.tc.sdk_dir)
36 36
37class RustHostCompileTest(OESDKTestCase): 37class RustHostCompileTest(OESDKTestCase):
@@ -53,5 +53,6 @@ class RustHostCompileTest(OESDKTestCase):
53 53
54 def test_cargo_build(self): 54 def test_cargo_build(self):
55 sdksys = self.td.get("SDK_SYS") 55 sdksys = self.td.get("SDK_SYS")
56 self._run('cd %s/hello; cargo add zstd' % (self.tc.sdk_dir))
56 self._run('cd %s/hello; cargo build --target %s-gnu' % (self.tc.sdk_dir, sdksys)) 57 self._run('cd %s/hello; cargo build --target %s-gnu' % (self.tc.sdk_dir, sdksys))
57 self._run('cd %s/hello; cargo run --target %s-gnu' % (self.tc.sdk_dir, sdksys)) 58 self._run('cd %s/hello; cargo run --target %s-gnu' % (self.tc.sdk_dir, sdksys))
diff --git a/meta/lib/oeqa/sdk/context.py b/meta/lib/oeqa/sdk/context.py
index 01c38c24e6..d4fdd83207 100644
--- a/meta/lib/oeqa/sdk/context.py
+++ b/meta/lib/oeqa/sdk/context.py
@@ -23,6 +23,13 @@ class OESDKTestContext(OETestContext):
23 self.target_pkg_manifest = target_pkg_manifest 23 self.target_pkg_manifest = target_pkg_manifest
24 self.host_pkg_manifest = host_pkg_manifest 24 self.host_pkg_manifest = host_pkg_manifest
25 25
26 # match multilib according to sdk_env
27 self.multilib = ""
28 multilibs = self.td.get('MULTILIB_VARIANTS', '').split()
29 for ml in multilibs:
30 if ml in os.path.basename(self.sdk_env):
31 self.multilib = ml
32
26 def _hasPackage(self, manifest, pkg, regex=False): 33 def _hasPackage(self, manifest, pkg, regex=False):
27 if regex: 34 if regex:
28 # do regex match 35 # do regex match
@@ -40,12 +47,8 @@ class OESDKTestContext(OETestContext):
40 return self._hasPackage(self.host_pkg_manifest, pkg, regex=regex) 47 return self._hasPackage(self.host_pkg_manifest, pkg, regex=regex)
41 48
42 def hasTargetPackage(self, pkg, multilib=False, regex=False): 49 def hasTargetPackage(self, pkg, multilib=False, regex=False):
43 if multilib: 50 if multilib and self.multilib:
44 # match multilib according to sdk_env 51 pkg = self.multilib + '-' + pkg
45 mls = self.td.get('MULTILIB_VARIANTS', '').split()
46 for ml in mls:
47 if ('ml'+ml) in self.sdk_env:
48 pkg = ml + '-' + pkg
49 return self._hasPackage(self.target_pkg_manifest, pkg, regex=regex) 52 return self._hasPackage(self.target_pkg_manifest, pkg, regex=regex)
50 53
51class OESDKTestContextExecutor(OETestContextExecutor): 54class OESDKTestContextExecutor(OETestContextExecutor):
diff --git a/meta/lib/oeqa/sdk/testsdk.py b/meta/lib/oeqa/sdk/testsdk.py
index 518b09febb..cffcf9f49a 100644
--- a/meta/lib/oeqa/sdk/testsdk.py
+++ b/meta/lib/oeqa/sdk/testsdk.py
@@ -31,6 +31,28 @@ class TestSDK(TestSDKBase):
31 context_class = OESDKTestContext 31 context_class = OESDKTestContext
32 test_type = 'sdk' 32 test_type = 'sdk'
33 33
34 def sdk_dir_names(self, d):
35 """Return list from TESTSDK_CASE_DIRS."""
36 testdirs = d.getVar("TESTSDK_CASE_DIRS")
37 if testdirs:
38 return testdirs.split()
39
40 bb.fatal("TESTSDK_CASE_DIRS unset, can't find SDK test directories.")
41
42 def get_sdk_paths(self, d):
43 """
44 Return a list of paths where SDK test cases reside.
45
46 SDK tests are expected in <LAYER_DIR>/lib/oeqa/<dirname>/cases
47 """
48 paths = []
49 for layer in d.getVar("BBLAYERS").split():
50 for dirname in self.sdk_dir_names(d):
51 case_path = os.path.join(layer, "lib", "oeqa", dirname, "cases")
52 if os.path.isdir(case_path):
53 paths.append(case_path)
54 return paths
55
34 def get_tcname(self, d): 56 def get_tcname(self, d):
35 """ 57 """
36 Get the name of the SDK file 58 Get the name of the SDK file
@@ -114,7 +136,8 @@ class TestSDK(TestSDKBase):
114 host_pkg_manifest=host_pkg_manifest, **context_args) 136 host_pkg_manifest=host_pkg_manifest, **context_args)
115 137
116 try: 138 try:
117 tc.loadTests(self.context_executor_class.default_cases) 139 modules = (d.getVar("TESTSDK_SUITES") or "").split()
140 tc.loadTests(self.get_sdk_paths(d), modules)
118 except Exception as e: 141 except Exception as e:
119 import traceback 142 import traceback
120 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc()) 143 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
diff --git a/meta/lib/oeqa/sdkext/cases/devtool.py b/meta/lib/oeqa/sdkext/cases/devtool.py
index 5ffb732556..d0746e68eb 100644
--- a/meta/lib/oeqa/sdkext/cases/devtool.py
+++ b/meta/lib/oeqa/sdkext/cases/devtool.py
@@ -69,10 +69,9 @@ class DevtoolTest(OESDKExtTestCase):
69 self._test_devtool_build(self.myapp_cmake_dst) 69 self._test_devtool_build(self.myapp_cmake_dst)
70 70
71 def test_extend_autotools_recipe_creation(self): 71 def test_extend_autotools_recipe_creation(self):
72 req = 'https://github.com/rdfa/librdfa' 72 recipe = "test-dbus-wait"
73 recipe = "librdfa" 73 self._run('devtool sdk-install dbus')
74 self._run('devtool sdk-install libxml2') 74 self._run('devtool add %s https://git.yoctoproject.org/git/dbus-wait' % (recipe) )
75 self._run('devtool add %s %s' % (recipe, req) )
76 try: 75 try:
77 self._run('devtool build %s' % recipe) 76 self._run('devtool build %s' % recipe)
78 finally: 77 finally:
diff --git a/meta/lib/oeqa/sdkext/context.py b/meta/lib/oeqa/sdkext/context.py
index 2ac2bf6ff7..2da57e2ccf 100644
--- a/meta/lib/oeqa/sdkext/context.py
+++ b/meta/lib/oeqa/sdkext/context.py
@@ -12,11 +12,11 @@ class OESDKExtTestContext(OESDKTestContext):
12 12
13 # FIXME - We really need to do better mapping of names here, this at 13 # FIXME - We really need to do better mapping of names here, this at
14 # least allows some tests to run 14 # least allows some tests to run
15 def hasHostPackage(self, pkg): 15 def hasHostPackage(self, pkg, regex=False):
16 # We force a toolchain to be installed into the eSDK even if its minimal 16 # We force a toolchain to be installed into the eSDK even if its minimal
17 if pkg.startswith("packagegroup-cross-canadian-"): 17 if pkg.startswith("packagegroup-cross-canadian-"):
18 return True 18 return True
19 return self._hasPackage(self.host_pkg_manifest, pkg) 19 return self._hasPackage(self.host_pkg_manifest, pkg, regex)
20 20
21class OESDKExtTestContextExecutor(OESDKTestContextExecutor): 21class OESDKExtTestContextExecutor(OESDKTestContextExecutor):
22 _context_class = OESDKExtTestContext 22 _context_class = OESDKExtTestContext
diff --git a/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt b/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt
index 19d773dd63..b31f1622e2 100644
--- a/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt
+++ b/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt
@@ -1,4 +1,4 @@
1cmake_minimum_required (VERSION 2.6) 1cmake_minimum_required (VERSION 3.10)
2project (myapp) 2project (myapp)
3# The version number. 3# The version number.
4set (myapp_VERSION_MAJOR 1) 4set (myapp_VERSION_MAJOR 1)
diff --git a/meta/lib/oeqa/sdkext/testsdk.py b/meta/lib/oeqa/sdkext/testsdk.py
index 9d5a99d900..6dc23065a4 100644
--- a/meta/lib/oeqa/sdkext/testsdk.py
+++ b/meta/lib/oeqa/sdkext/testsdk.py
@@ -82,7 +82,8 @@ class TestSDKExt(TestSDKBase):
82 host_pkg_manifest=host_pkg_manifest) 82 host_pkg_manifest=host_pkg_manifest)
83 83
84 try: 84 try:
85 tc.loadTests(OESDKExtTestContextExecutor.default_cases) 85 modules = (d.getVar("TESTSDK_SUITES") or "").split()
86 tc.loadTests(OESDKExtTestContextExecutor.default_cases, modules)
86 except Exception as e: 87 except Exception as e:
87 import traceback 88 import traceback
88 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc()) 89 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc())
diff --git a/meta/lib/oeqa/selftest/cases/archiver.py b/meta/lib/oeqa/selftest/cases/archiver.py
index 3cb888c506..612ec675a7 100644
--- a/meta/lib/oeqa/selftest/cases/archiver.py
+++ b/meta/lib/oeqa/selftest/cases/archiver.py
@@ -190,28 +190,28 @@ class Archiver(OESelftestTestCase):
190 Test that the archiver works with `ARCHIVER_MODE[src] = "original"`. 190 Test that the archiver works with `ARCHIVER_MODE[src] = "original"`.
191 """ 191 """
192 192
193 self._test_archiver_mode('original', 'ed-1.14.1.tar.lz') 193 self._test_archiver_mode('original', 'ed-1.21.1.tar.lz')
194 194
195 def test_archiver_mode_patched(self): 195 def test_archiver_mode_patched(self):
196 """ 196 """
197 Test that the archiver works with `ARCHIVER_MODE[src] = "patched"`. 197 Test that the archiver works with `ARCHIVER_MODE[src] = "patched"`.
198 """ 198 """
199 199
200 self._test_archiver_mode('patched', 'selftest-ed-native-1.14.1-r0-patched.tar.xz') 200 self._test_archiver_mode('patched', 'selftest-ed-native-1.21.1-r0-patched.tar.xz')
201 201
202 def test_archiver_mode_configured(self): 202 def test_archiver_mode_configured(self):
203 """ 203 """
204 Test that the archiver works with `ARCHIVER_MODE[src] = "configured"`. 204 Test that the archiver works with `ARCHIVER_MODE[src] = "configured"`.
205 """ 205 """
206 206
207 self._test_archiver_mode('configured', 'selftest-ed-native-1.14.1-r0-configured.tar.xz') 207 self._test_archiver_mode('configured', 'selftest-ed-native-1.21.1-r0-configured.tar.xz')
208 208
209 def test_archiver_mode_recipe(self): 209 def test_archiver_mode_recipe(self):
210 """ 210 """
211 Test that the archiver works with `ARCHIVER_MODE[recipe] = "1"`. 211 Test that the archiver works with `ARCHIVER_MODE[recipe] = "1"`.
212 """ 212 """
213 213
214 self._test_archiver_mode('patched', 'selftest-ed-native-1.14.1-r0-recipe.tar.xz', 214 self._test_archiver_mode('patched', 'selftest-ed-native-1.21.1-r0-recipe.tar.xz',
215 'ARCHIVER_MODE[recipe] = "1"\n') 215 'ARCHIVER_MODE[recipe] = "1"\n')
216 216
217 def test_archiver_mode_diff(self): 217 def test_archiver_mode_diff(self):
@@ -220,7 +220,7 @@ class Archiver(OESelftestTestCase):
220 Exclusions controlled by `ARCHIVER_MODE[diff-exclude]` are not yet tested. 220 Exclusions controlled by `ARCHIVER_MODE[diff-exclude]` are not yet tested.
221 """ 221 """
222 222
223 self._test_archiver_mode('patched', 'selftest-ed-native-1.14.1-r0-diff.gz', 223 self._test_archiver_mode('patched', 'selftest-ed-native-1.21.1-r0-diff.gz',
224 'ARCHIVER_MODE[diff] = "1"\n') 224 'ARCHIVER_MODE[diff] = "1"\n')
225 225
226 def test_archiver_mode_dumpdata(self): 226 def test_archiver_mode_dumpdata(self):
@@ -228,7 +228,7 @@ class Archiver(OESelftestTestCase):
228 Test that the archiver works with `ARCHIVER_MODE[dumpdata] = "1"`. 228 Test that the archiver works with `ARCHIVER_MODE[dumpdata] = "1"`.
229 """ 229 """
230 230
231 self._test_archiver_mode('patched', 'selftest-ed-native-1.14.1-r0-showdata.dump', 231 self._test_archiver_mode('patched', 'selftest-ed-native-1.21.1-r0-showdata.dump',
232 'ARCHIVER_MODE[dumpdata] = "1"\n') 232 'ARCHIVER_MODE[dumpdata] = "1"\n')
233 233
234 def test_archiver_mode_mirror(self): 234 def test_archiver_mode_mirror(self):
@@ -236,7 +236,7 @@ class Archiver(OESelftestTestCase):
236 Test that the archiver works with `ARCHIVER_MODE[src] = "mirror"`. 236 Test that the archiver works with `ARCHIVER_MODE[src] = "mirror"`.
237 """ 237 """
238 238
239 self._test_archiver_mode('mirror', 'ed-1.14.1.tar.lz', 239 self._test_archiver_mode('mirror', 'ed-1.21.1.tar.lz',
240 'BB_GENERATE_MIRROR_TARBALLS = "1"\n') 240 'BB_GENERATE_MIRROR_TARBALLS = "1"\n')
241 241
242 def test_archiver_mode_mirror_excludes(self): 242 def test_archiver_mode_mirror_excludes(self):
@@ -247,7 +247,7 @@ class Archiver(OESelftestTestCase):
247 """ 247 """
248 248
249 target='selftest-ed' 249 target='selftest-ed'
250 target_file_name = 'ed-1.14.1.tar.lz' 250 target_file_name = 'ed-1.21.1.tar.lz'
251 251
252 features = 'INHERIT += "archiver"\n' 252 features = 'INHERIT += "archiver"\n'
253 features += 'ARCHIVER_MODE[src] = "mirror"\n' 253 features += 'ARCHIVER_MODE[src] = "mirror"\n'
@@ -285,7 +285,7 @@ class Archiver(OESelftestTestCase):
285 bitbake('-c deploy_archives %s' % (target)) 285 bitbake('-c deploy_archives %s' % (target))
286 286
287 bb_vars = get_bb_vars(['DEPLOY_DIR_SRC']) 287 bb_vars = get_bb_vars(['DEPLOY_DIR_SRC'])
288 for target_file_name in ['ed-1.14.1.tar.lz', 'hello.c']: 288 for target_file_name in ['ed-1.21.1.tar.lz', 'hello.c']:
289 glob_str = os.path.join(bb_vars['DEPLOY_DIR_SRC'], 'mirror', target_file_name) 289 glob_str = os.path.join(bb_vars['DEPLOY_DIR_SRC'], 'mirror', target_file_name)
290 glob_result = glob.glob(glob_str) 290 glob_result = glob.glob(glob_str)
291 self.assertTrue(glob_result, 'Missing archive file %s' % (target_file_name)) 291 self.assertTrue(glob_result, 'Missing archive file %s' % (target_file_name))
diff --git a/meta/lib/oeqa/selftest/cases/barebox.py b/meta/lib/oeqa/selftest/cases/barebox.py
new file mode 100644
index 0000000000..3f8f232432
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/barebox.py
@@ -0,0 +1,44 @@
1# Qemu-based barebox bootloader integration testing
2#
3# Copyright OpenEmbedded Contributors
4#
5# SPDX-License-Identifier: MIT
6#
7
8from oeqa.selftest.case import OESelftestTestCase
9from oeqa.utils.commands import bitbake, runqemu
10from oeqa.core.decorator.data import skipIfNotArch
11from oeqa.core.decorator import OETestTag
12
13barebox_boot_patterns = {
14 'search_reached_prompt': r"stop autoboot",
15 'search_login_succeeded': r"barebox@[^:]+:[^ ]+ ",
16 'search_cmd_finished': r"barebox@[a-zA-Z0-9\-\s]+:/"
17 }
18
19
20class BareboxTest(OESelftestTestCase):
21
22 @skipIfNotArch(['arm', 'aarch64'])
23 @OETestTag("runqemu")
24 def test_boot_barebox(self):
25 """
26 Tests building barebox and booting it with QEMU
27 """
28
29 self.write_config("""
30QB_DEFAULT_KERNEL = "barebox-dt-2nd.img"
31PREFERRED_PROVIDER_virtual/bootloader = "barebox"
32QEMU_USE_KVM = "False"
33""")
34
35 bitbake("virtual/bootloader core-image-minimal")
36
37 with runqemu('core-image-minimal', ssh=False, runqemuparams='nographic',
38 boot_patterns=barebox_boot_patterns) as qemu:
39
40 # test if barebox console works
41 cmd = "version"
42 status, output = qemu.run_serial(cmd)
43 self.assertEqual(status, 1, msg=output)
44 self.assertTrue("barebox" in output, msg=output)
diff --git a/meta/lib/oeqa/selftest/cases/bbclasses.py b/meta/lib/oeqa/selftest/cases/bbclasses.py
new file mode 100644
index 0000000000..10545ebe65
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/bbclasses.py
@@ -0,0 +1,106 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import get_bb_vars, bitbake
9
10class Systemd(OESelftestTestCase):
11 """
12 Tests related to the systemd bbclass.
13 """
14
15 def getVars(self, recipe):
16 self.bb_vars = get_bb_vars(
17 [
18 'BPN',
19 'D',
20 'INIT_D_DIR',
21 'prefix',
22 'systemd_system_unitdir',
23 'sysconfdir',
24 ],
25 recipe,
26 )
27
28 def fileExists(self, filename):
29 self.assertExists(filename.format(**self.bb_vars))
30
31 def fileNotExists(self, filename):
32 self.assertNotExists(filename.format(**self.bb_vars))
33
34 def test_systemd_in_distro(self):
35 """
36 Summary: Verify that no sysvinit files are installed when the
37 systemd distro feature is enabled, but sysvinit is not.
38 Expected: Systemd service file exists, but /etc does not.
39 Product: OE-Core
40 Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
41 """
42
43 self.write_config("""
44DISTRO_FEATURES:append = " systemd usrmerge"
45DISTRO_FEATURES:remove = "sysvinit"
46VIRTUAL-RUNTIME_init_manager = "systemd"
47""")
48 bitbake("systemd-only systemd-and-sysvinit -c install")
49
50 self.getVars("systemd-only")
51 self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service")
52
53 self.getVars("systemd-and-sysvinit")
54 self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service")
55 self.fileNotExists("{D}{sysconfdir}")
56
57 def test_systemd_and_sysvinit_in_distro(self):
58 """
59 Summary: Verify that both systemd and sysvinit files are installed
60 when both the systemd and sysvinit distro features are
61 enabled.
62 Expected: Systemd service file and sysvinit initscript exist.
63 Product: OE-Core
64 Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
65 """
66
67 self.write_config("""
68DISTRO_FEATURES:append = " systemd sysvinit usrmerge"
69VIRTUAL-RUNTIME_init_manager = "systemd"
70""")
71 bitbake("systemd-only systemd-and-sysvinit -c install")
72
73 self.getVars("systemd-only")
74 self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service")
75
76 self.getVars("systemd-and-sysvinit")
77 self.fileExists("{D}{systemd_system_unitdir}/{BPN}.service")
78 self.fileExists("{D}{INIT_D_DIR}/{BPN}")
79
80 def test_sysvinit_in_distro(self):
81 """
82 Summary: Verify that no systemd service files are installed when the
83 sysvinit distro feature is enabled, but systemd is not.
84 Expected: The systemd service file does not exist, nor does /usr.
85 The sysvinit initscript exists.
86 Product: OE-Core
87 Author: Peter Kjellerstedt <peter.kjellerstedt@axis.com>
88 """
89
90 self.write_config("""
91DISTRO_FEATURES:remove = "systemd"
92DISTRO_FEATURES:append = " sysvinit usrmerge"
93VIRTUAL-RUNTIME_init_manager = "sysvinit"
94""")
95 bitbake("systemd-only systemd-and-sysvinit -c install")
96
97 self.getVars("systemd-only")
98 self.fileNotExists("{D}{systemd_system_unitdir}/{BPN}.service")
99 self.fileNotExists("{D}{prefix}")
100 self.fileNotExists("{D}{sysconfdir}")
101 self.fileExists("{D}")
102
103 self.getVars("systemd-and-sysvinit")
104 self.fileNotExists("{D}{systemd_system_unitdir}/{BPN}.service")
105 self.fileNotExists("{D}{prefix}")
106 self.fileExists("{D}{INIT_D_DIR}/{BPN}")
diff --git a/meta/lib/oeqa/selftest/cases/bblayers.py b/meta/lib/oeqa/selftest/cases/bblayers.py
index 695d17377d..982287c9a5 100644
--- a/meta/lib/oeqa/selftest/cases/bblayers.py
+++ b/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -157,7 +157,12 @@ class BitbakeLayers(OESelftestTestCase):
157 with open(jsonfile) as f: 157 with open(jsonfile) as f:
158 data = json.load(f) 158 data = json.load(f)
159 for s in data['sources']: 159 for s in data['sources']:
160 data['sources'][s]['git-remote']['rev'] = '5200799866b92259e855051112520006e1aaaac0' 160 if s == 'poky' or s == 'build':
161 data['sources'][s]['git-remote']['rev'] = '5200799866b92259e855051112520006e1aaaac0'
162 elif s == 'meta-yocto':
163 data['sources'][s]['git-remote']['rev'] = '913bd8ba4dd1d5d2a38261bde985b64a36e36281'
164 elif s == 'openembedded-core':
165 data['sources'][s]['git-remote']['rev'] = '744a2277844ec9a384a9ca7dae2a634d5a0d3590'
161 with open(jsonfile, 'w') as f: 166 with open(jsonfile, 'w') as f:
162 json.dump(data, f) 167 json.dump(data, f)
163 168
@@ -240,3 +245,92 @@ class BitbakeLayers(OESelftestTestCase):
240 self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2)) 245 self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2))
241 self.assertEqual(second_rev_2, second_rev_1, "Revision should not be updated: '{}'".format(second_rev_2)) 246 self.assertEqual(second_rev_2, second_rev_1, "Revision should not be updated: '{}'".format(second_rev_2))
242 self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2)) 247 self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2))
248
249class BitbakeConfigBuild(OESelftestTestCase):
250 def test_enable_disable_fragments(self):
251 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
252 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
253
254 runCmd('bitbake-config-build enable-fragment selftest/test-fragment')
255 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue')
256 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
257
258 runCmd('bitbake-config-build enable-fragment selftest/more-fragments-here/test-another-fragment')
259 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue')
260 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue')
261
262 fragment_metadata_command = "bitbake-getvar -f {} --value {}"
263 result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_SUMMARY"))
264 self.assertIn("This is a configuration fragment intended for testing in oe-selftest context", result.output)
265 result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_DESCRIPTION"))
266 self.assertIn("It defines a variable that can be checked inside the test.", result.output)
267 result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_SUMMARY"))
268 self.assertIn("This is a second configuration fragment intended for testing in oe-selftest context", result.output)
269 result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_DESCRIPTION"))
270 self.assertIn("It defines another variable that can be checked inside the test.", result.output)
271
272 runCmd('bitbake-config-build disable-fragment selftest/test-fragment')
273 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
274 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue')
275
276 runCmd('bitbake-config-build disable-fragment selftest/more-fragments-here/test-another-fragment')
277 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
278 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
279
280 def test_enable_disable_builtin_fragments(self):
281 """
282 Tests that the meta-selftest properly adds a new built-in fragment from
283 its layer.conf configuration file.
284 The test sequence goes as follows:
285 1. Verify that SELFTEST_BUILTIN_FRAGMENT_VARIABLE is not set yet.
286 2. Verify that SELFTEST_BUILTIN_FRAGMENT_VARIABLE is set after setting
287 the fragment.
288 3. Verify that SELFTEST_BUILTIN_FRAGMENT_VARIABLE is set after setting
289 the fragment with another value that replaces the first one.
290 4. Repeat steps 2 and 3 to verify that going back and forth between values
291 works.
292 5. Verify that SELFTEST_BUILTIN_FRAGMENT_VARIABLE is not set after
293 removing the final assignment.
294 """
295 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), None)
296
297 runCmd('bitbake-config-build enable-fragment selftest-fragment/somevalue')
298 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), 'somevalue')
299
300 runCmd('bitbake-config-build enable-fragment selftest-fragment/someothervalue')
301 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), 'someothervalue')
302
303 runCmd('bitbake-config-build enable-fragment selftest-fragment/somevalue')
304 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), 'somevalue')
305
306 runCmd('bitbake-config-build enable-fragment selftest-fragment/someothervalue')
307 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), 'someothervalue')
308
309 runCmd('bitbake-config-build disable-fragment selftest-fragment/someothervalue')
310 self.assertEqual(get_bb_var('SELFTEST_BUILTIN_FRAGMENT_VARIABLE'), None)
311
312 def test_show_fragment(self):
313 """
314 Test that bitbake-config-build show-fragment returns the expected
315 output. Use bitbake-config-build list-fragments --verbose to get the
316 path to the fragment.
317 """
318 result = runCmd('bitbake-config-build --quiet list-fragments --verbose')
319 test_fragment_re = re.compile(r'^Path: .*conf/fragments/test-fragment.conf$')
320 fragment_path, fragment_content = '', ''
321
322 for line in result.output.splitlines():
323 m = re.match(test_fragment_re, line)
324 if m:
325 fragment_path = ' '.join(line.split()[1:])
326 break
327
328 if not fragment_path:
329 raise Exception("Couldn't find the fragment")
330
331 with open(fragment_path, 'r') as f:
332 fragment_content = f'{fragment_path}:\n\n{f.read()}'.strip()
333
334 result = runCmd('bitbake-config-build --quiet show-fragment selftest/test-fragment')
335
336 self.assertEqual(result.output.strip(), fragment_content)
diff --git a/meta/lib/oeqa/selftest/cases/bblock.py b/meta/lib/oeqa/selftest/cases/bblock.py
index 2b62d2a0aa..cb99d32bb5 100644
--- a/meta/lib/oeqa/selftest/cases/bblock.py
+++ b/meta/lib/oeqa/selftest/cases/bblock.py
@@ -122,11 +122,11 @@ class BBLock(OESelftestTestCase):
122 else: 122 else:
123 machine = "qemux86-64" 123 machine = "qemux86-64"
124 124
125 self.write_config('MACHINE = "%s"\n' % machine) 125 self.write_config('MACHINE:forcevariable = "%s"\n' % machine)
126 126
127 self.lock_recipes(recipes, tasks) 127 self.lock_recipes(recipes, tasks)
128 128
129 self.write_config('MACHINE = "%s"\n' % self.td["MACHINE"]) 129 self.write_config('MACHINE:forcevariable = "%s"\n' % self.td["MACHINE"])
130 # modify quilt's do_compile task 130 # modify quilt's do_compile task
131 self.modify_tasks(recipes, tasks) 131 self.modify_tasks(recipes, tasks)
132 132
diff --git a/meta/lib/oeqa/selftest/cases/bbtests.py b/meta/lib/oeqa/selftest/cases/bbtests.py
index 98e9f81661..51934ef70d 100644
--- a/meta/lib/oeqa/selftest/cases/bbtests.py
+++ b/meta/lib/oeqa/selftest/cases/bbtests.py
@@ -233,6 +233,7 @@ INHERIT:remove = \"report-error\"
233 233
234 def test_non_gplv3(self): 234 def test_non_gplv3(self):
235 self.write_config('''INCOMPATIBLE_LICENSE = "GPL-3.0-or-later" 235 self.write_config('''INCOMPATIBLE_LICENSE = "GPL-3.0-or-later"
236OVERRIDES .= ":gplv3test"
236require conf/distro/include/no-gplv3.inc 237require conf/distro/include/no-gplv3.inc
237''') 238''')
238 result = bitbake('selftest-ed', ignore_status=True) 239 result = bitbake('selftest-ed', ignore_status=True)
@@ -241,7 +242,7 @@ require conf/distro/include/no-gplv3.inc
241 arch = get_bb_var('SSTATE_PKGARCH') 242 arch = get_bb_var('SSTATE_PKGARCH')
242 filename = os.path.join(lic_dir, arch, 'selftest-ed', 'generic_GPL-3.0-or-later') 243 filename = os.path.join(lic_dir, arch, 'selftest-ed', 'generic_GPL-3.0-or-later')
243 self.assertFalse(os.path.isfile(filename), msg="License file %s exists and shouldn't" % filename) 244 self.assertFalse(os.path.isfile(filename), msg="License file %s exists and shouldn't" % filename)
244 filename = os.path.join(lic_dir, arch, 'selftest-ed', 'generic_GPL-2.0-or-later') 245 filename = os.path.join(lic_dir, arch, 'selftest-ed', 'generic_GPL-2.0-only')
245 self.assertTrue(os.path.isfile(filename), msg="License file %s doesn't exist" % filename) 246 self.assertTrue(os.path.isfile(filename), msg="License file %s doesn't exist" % filename)
246 247
247 def test_setscene_only(self): 248 def test_setscene_only(self):
@@ -375,3 +376,21 @@ require conf/distro/include/no-gplv3.inc
375 self.assertGreater(result.status, 0, "Build should have failed if ${ is in the path") 376 self.assertGreater(result.status, 0, "Build should have failed if ${ is in the path")
376 self.assertTrue(re.search("ERROR: Directory name /.* contains unexpanded bitbake variable. This may cause build failures and WORKDIR polution", 377 self.assertTrue(re.search("ERROR: Directory name /.* contains unexpanded bitbake variable. This may cause build failures and WORKDIR polution",
377 result.output), msg = "mkdirhier with unexpanded variable should have failed: %s" % result.output) 378 result.output), msg = "mkdirhier with unexpanded variable should have failed: %s" % result.output)
379
380 def test_bb_env_bb_getvar_equality(self):
381 """ Test if "bitbake -e" output is identical to "bitbake-getvar" output for a variable set from an anonymous function
382 """
383 self.write_config('''INHERIT += "test_anon_func"
384TEST_SET_FROM_ANON_FUNC ?= ""''')
385
386 result_bb_e = runCmd('bitbake -e')
387 bb_e_var_match = re.search('^TEST_SET_FROM_ANON_FUNC="(?P<value>.*)"$', result_bb_e.output, re.MULTILINE)
388 self.assertTrue(bb_e_var_match, msg = "Can't find TEST_SET_FROM_ANON_FUNC value in \"bitbake -e\" output")
389 bb_e_var_value = bb_e_var_match.group("value")
390
391 result_bb_getvar = runCmd('bitbake-getvar TEST_SET_FROM_ANON_FUNC --value')
392 bb_getvar_var_value = result_bb_getvar.output.strip()
393 self.assertEqual(bb_e_var_value, bb_getvar_var_value,
394 msg='''"bitbake -e" output differs from bitbake-getvar output for TEST_SET_FROM_ANON_FUNC (set from anonymous function)
395bitbake -e: "%s"
396bitbake-getvar: "%s"''' % (bb_e_var_value, bb_getvar_var_value))
diff --git a/meta/lib/oeqa/selftest/cases/binutils.py b/meta/lib/oeqa/selftest/cases/binutils.py
index 1688eabe4e..5ff263d342 100644
--- a/meta/lib/oeqa/selftest/cases/binutils.py
+++ b/meta/lib/oeqa/selftest/cases/binutils.py
@@ -33,7 +33,7 @@ class BinutilsCrossSelfTest(OESelftestTestCase, OEPTestResultTestCase):
33 features.append('CHECK_TARGETS = "{0}"'.format(suite)) 33 features.append('CHECK_TARGETS = "{0}"'.format(suite))
34 self.write_config("\n".join(features)) 34 self.write_config("\n".join(features))
35 35
36 recipe = "binutils-cross-testsuite" 36 recipe = "binutils-testsuite"
37 bb_vars = get_bb_vars(["B", "TARGET_SYS", "T"], recipe) 37 bb_vars = get_bb_vars(["B", "TARGET_SYS", "T"], recipe)
38 builddir, target_sys, tdir = bb_vars["B"], bb_vars["TARGET_SYS"], bb_vars["T"] 38 builddir, target_sys, tdir = bb_vars["B"], bb_vars["TARGET_SYS"], bb_vars["T"]
39 39
diff --git a/meta/lib/oeqa/selftest/cases/buildhistory.py b/meta/lib/oeqa/selftest/cases/buildhistory.py
index 2d55994916..1edc60c72d 100644
--- a/meta/lib/oeqa/selftest/cases/buildhistory.py
+++ b/meta/lib/oeqa/selftest/cases/buildhistory.py
@@ -9,14 +9,14 @@ import re
9import datetime 9import datetime
10 10
11from oeqa.selftest.case import OESelftestTestCase 11from oeqa.selftest.case import OESelftestTestCase
12from oeqa.utils.commands import bitbake, get_bb_vars 12from oeqa.utils.commands import bitbake, get_bb_vars, get_bb_var, runCmd
13 13
14 14
15class BuildhistoryBase(OESelftestTestCase): 15class BuildhistoryTests(OESelftestTestCase):
16 16
17 def config_buildhistory(self, tmp_bh_location=False): 17 def config_buildhistory(self, tmp_bh_location=False):
18 bb_vars = get_bb_vars(['USER_CLASSES', 'INHERIT']) 18 bb_vars = get_bb_vars(['USER_CLASSES', 'INHERIT'])
19 if (not 'buildhistory' in bb_vars['USER_CLASSES']) and (not 'buildhistory' in bb_vars['INHERIT']): 19 if (not bb_vars['USER_CLASSES'] or not 'buildhistory' in bb_vars['USER_CLASSES']) and (not 'buildhistory' in bb_vars['INHERIT']):
20 add_buildhistory_config = 'INHERIT += "buildhistory"\nBUILDHISTORY_COMMIT = "1"' 20 add_buildhistory_config = 'INHERIT += "buildhistory"\nBUILDHISTORY_COMMIT = "1"'
21 self.append_config(add_buildhistory_config) 21 self.append_config(add_buildhistory_config)
22 22
@@ -48,5 +48,58 @@ class BuildhistoryBase(OESelftestTestCase):
48 else: 48 else:
49 self.assertEqual(result.status, 0, msg="Command 'bitbake %s' has failed unexpectedly: %s" % (target, result.output)) 49 self.assertEqual(result.status, 0, msg="Command 'bitbake %s' has failed unexpectedly: %s" % (target, result.output))
50 50
51 # No tests should be added to the base class. 51
52 # Please create a new class that inherit this one, or use one of those already available for adding tests. 52 def test_buildhistory_basic(self):
53 self.run_buildhistory_operation('xcursor-transparent-theme')
54 self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR')), "buildhistory dir was not created.")
55
56 def test_buildhistory_buildtime_pr_backwards(self):
57 target = 'xcursor-transparent-theme'
58 error = "ERROR:.*QA Issue: Package version for package %s went backwards which would break package feeds \(from .*-r1.* to .*-r0.*\)" % target
59 self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
60 self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True, error_regex=error)
61
62 def test_fileinfo(self):
63 self.config_buildhistory()
64 bitbake('hicolor-icon-theme')
65 history_dir = get_bb_var('BUILDHISTORY_DIR_PACKAGE', 'hicolor-icon-theme')
66 self.assertTrue(os.path.isdir(history_dir), 'buildhistory dir was not created.')
67
68 def load_bh(f):
69 d = {}
70 for line in open(f):
71 split = [s.strip() for s in line.split('=', 1)]
72 if len(split) > 1:
73 d[split[0]] = split[1]
74 return d
75
76 data = load_bh(os.path.join(history_dir, 'hicolor-icon-theme', 'latest'))
77 self.assertIn('FILELIST', data)
78 self.assertEqual(data['FILELIST'], '/usr/share/icons/hicolor/index.theme')
79 self.assertGreater(int(data['PKGSIZE']), 0)
80
81 data = load_bh(os.path.join(history_dir, 'hicolor-icon-theme-dev', 'latest'))
82 if 'FILELIST' in data:
83 self.assertEqual(data['FILELIST'], '/usr/share/pkgconfig/default-icon-theme.pc')
84 self.assertGreater(int(data['PKGSIZE']), 0)
85
86 def test_buildhistory_diff(self):
87 target = 'xcursor-transparent-theme'
88 self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
89 self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True)
90 result = runCmd("oe-pkgdata-util read-value PKGV %s" % target)
91 pkgv = result.output.rstrip()
92 result = runCmd("buildhistory-diff -p %s" % get_bb_var('BUILDHISTORY_DIR'))
93 expected_endlines = [
94 "xcursor-transparent-theme-dev: RRECOMMENDS: removed \"xcursor-transparent-theme (['= %s-r1'])\", added \"xcursor-transparent-theme (['= %s-r0'])\"" % (pkgv, pkgv),
95 "xcursor-transparent-theme-staticdev: RDEPENDS: removed \"xcursor-transparent-theme-dev (['= %s-r1'])\", added \"xcursor-transparent-theme-dev (['= %s-r0'])\"" % (pkgv, pkgv)
96 ]
97 for line in result.output.splitlines():
98 for el in expected_endlines:
99 if line.endswith(el):
100 expected_endlines.remove(el)
101 break
102 else:
103 self.fail('Unexpected line:\n%s\nExpected line endings:\n %s' % (line, '\n '.join(expected_endlines)))
104 if expected_endlines:
105 self.fail('Missing expected line endings:\n %s' % '\n '.join(expected_endlines)) \ No newline at end of file
diff --git a/meta/lib/oeqa/selftest/cases/buildoptions.py b/meta/lib/oeqa/selftest/cases/buildoptions.py
index 31dafaa9c5..b46f55e256 100644
--- a/meta/lib/oeqa/selftest/cases/buildoptions.py
+++ b/meta/lib/oeqa/selftest/cases/buildoptions.py
@@ -10,7 +10,6 @@ import glob as g
10import shutil 10import shutil
11import tempfile 11import tempfile
12from oeqa.selftest.case import OESelftestTestCase 12from oeqa.selftest.case import OESelftestTestCase
13from oeqa.selftest.cases.buildhistory import BuildhistoryBase
14from oeqa.core.decorator.data import skipIfMachine 13from oeqa.core.decorator.data import skipIfMachine
15from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars 14from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars
16import oeqa.utils.ftools as ftools 15import oeqa.utils.ftools as ftools
@@ -84,7 +83,7 @@ class SanityOptionsTest(OESelftestTestCase):
84 83
85 self.write_config("INHERIT:remove = \"report-error\"") 84 self.write_config("INHERIT:remove = \"report-error\"")
86 if "packages-list" not in get_bb_var("ERROR_QA"): 85 if "packages-list" not in get_bb_var("ERROR_QA"):
87 self.append_config("ERROR_QA:append = \" packages-list\"") 86 self.append_config("ERROR_QA:append:pn-xcursor-transparent-theme = \" packages-list\"")
88 87
89 self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') 88 self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"')
90 self.add_command_to_tearDown('bitbake -c clean xcursor-transparent-theme') 89 self.add_command_to_tearDown('bitbake -c clean xcursor-transparent-theme')
@@ -94,8 +93,8 @@ class SanityOptionsTest(OESelftestTestCase):
94 self.assertTrue(line and line.startswith("ERROR:"), msg=res.output) 93 self.assertTrue(line and line.startswith("ERROR:"), msg=res.output)
95 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output)) 94 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output))
96 self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') 95 self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"')
97 self.append_config('ERROR_QA:remove = "packages-list"') 96 self.append_config('ERROR_QA:remove:pn-xcursor-transparent-theme = "packages-list"')
98 self.append_config('WARN_QA:append = " packages-list"') 97 self.append_config('WARN_QA:append:pn-xcursor-transparent-theme = " packages-list"')
99 res = bitbake("xcursor-transparent-theme -f -c package") 98 res = bitbake("xcursor-transparent-theme -f -c package")
100 self.delete_recipeinc('xcursor-transparent-theme') 99 self.delete_recipeinc('xcursor-transparent-theme')
101 line = self.getline(res, "QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors.") 100 line = self.getline(res, "QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors.")
@@ -139,43 +138,6 @@ class SanityOptionsTest(OESelftestTestCase):
139 138
140 self.assertNotIn(err, ret.output) 139 self.assertNotIn(err, ret.output)
141 140
142
143class BuildhistoryTests(BuildhistoryBase):
144
145 def test_buildhistory_basic(self):
146 self.run_buildhistory_operation('xcursor-transparent-theme')
147 self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR')), "buildhistory dir was not created.")
148
149 def test_buildhistory_buildtime_pr_backwards(self):
150 target = 'xcursor-transparent-theme'
151 error = "ERROR:.*QA Issue: Package version for package %s went backwards which would break package feeds \(from .*-r1.* to .*-r0.*\)" % target
152 self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
153 self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True, error_regex=error)
154
155 def test_fileinfo(self):
156 self.config_buildhistory()
157 bitbake('hicolor-icon-theme')
158 history_dir = get_bb_var('BUILDHISTORY_DIR_PACKAGE', 'hicolor-icon-theme')
159 self.assertTrue(os.path.isdir(history_dir), 'buildhistory dir was not created.')
160
161 def load_bh(f):
162 d = {}
163 for line in open(f):
164 split = [s.strip() for s in line.split('=', 1)]
165 if len(split) > 1:
166 d[split[0]] = split[1]
167 return d
168
169 data = load_bh(os.path.join(history_dir, 'hicolor-icon-theme', 'latest'))
170 self.assertIn('FILELIST', data)
171 self.assertEqual(data['FILELIST'], '/usr/share/icons/hicolor/index.theme')
172 self.assertGreater(int(data['PKGSIZE']), 0)
173
174 data = load_bh(os.path.join(history_dir, 'hicolor-icon-theme-dev', 'latest'))
175 if 'FILELIST' in data:
176 self.assertEqual(data['FILELIST'], '')
177 self.assertEqual(int(data['PKGSIZE']), 0)
178
179class ArchiverTest(OESelftestTestCase): 141class ArchiverTest(OESelftestTestCase):
180 def test_arch_work_dir_and_export_source(self): 142 def test_arch_work_dir_and_export_source(self):
181 """ 143 """
@@ -211,11 +173,11 @@ class SourceMirroring(OESelftestTestCase):
211 def test_yocto_source_mirror(self): 173 def test_yocto_source_mirror(self):
212 self.write_config(""" 174 self.write_config("""
213BB_ALLOWED_NETWORKS = "downloads.yoctoproject.org" 175BB_ALLOWED_NETWORKS = "downloads.yoctoproject.org"
214MIRRORS = "" 176MIRRORS:forcevariable = ""
215DL_DIR = "${TMPDIR}/test_downloads" 177DL_DIR = "${TMPDIR}/test_downloads"
216STAMPS_DIR = "${TMPDIR}/test_stamps" 178STAMPS_DIR = "${TMPDIR}/test_stamps"
217SSTATE_DIR = "${TMPDIR}/test_sstate-cache" 179SSTATE_DIR = "${TMPDIR}/test_sstate-cache"
218PREMIRRORS = "\\ 180PREMIRRORS:forcevariable = "\\
219 bzr://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\ 181 bzr://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\
220 cvs://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\ 182 cvs://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\
221 git://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\ 183 git://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n \\
@@ -229,11 +191,10 @@ PREMIRRORS = "\\
229 https://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n" 191 https://.*/.* http://downloads.yoctoproject.org/mirror/sources/ \\n"
230""") 192""")
231 193
232 bitbake("world --runall fetch") 194 bitbake("world --runall fetch --continue")
233 195
234 196
235class Poisoning(OESelftestTestCase): 197class Poisoning(OESelftestTestCase):
236 def test_poisoning(self): 198 def test_poisoning(self):
237 res = bitbake("poison", ignore_status=True) 199 # The poison recipe fails if the poisoning didn't work
238 self.assertNotEqual(res.status, 0) 200 bitbake("poison")
239 self.assertTrue("is unsafe for cross-compilation" in res.output)
diff --git a/meta/lib/oeqa/selftest/cases/containerimage.py b/meta/lib/oeqa/selftest/cases/containerimage.py
index 23c0a1408a..d1ac305a84 100644
--- a/meta/lib/oeqa/selftest/cases/containerimage.py
+++ b/meta/lib/oeqa/selftest/cases/containerimage.py
@@ -42,7 +42,6 @@ class ContainerImageTests(OESelftestTestCase):
42 self.write_config("""PREFERRED_PROVIDER_virtual/kernel = "linux-dummy" 42 self.write_config("""PREFERRED_PROVIDER_virtual/kernel = "linux-dummy"
43IMAGE_FSTYPES = "container" 43IMAGE_FSTYPES = "container"
44PACKAGE_CLASSES = "package_ipk" 44PACKAGE_CLASSES = "package_ipk"
45IMAGE_FEATURES = ""
46IMAGE_BUILDINFO_FILE = "" 45IMAGE_BUILDINFO_FILE = ""
47INIT_MANAGER = "sysvinit" 46INIT_MANAGER = "sysvinit"
48IMAGE_INSTALL:remove = "ssh-pregen-hostkeys" 47IMAGE_INSTALL:remove = "ssh-pregen-hostkeys"
@@ -55,8 +54,6 @@ IMAGE_INSTALL:remove = "ssh-pregen-hostkeys"
55 expected_files = [ 54 expected_files = [
56 './', 55 './',
57 '.{bindir}/theapp', 56 '.{bindir}/theapp',
58 '.{sysconfdir}/default/',
59 '.{sysconfdir}/default/postinst',
60 '.{sysconfdir}/ld.so.cache', 57 '.{sysconfdir}/ld.so.cache',
61 '.{sysconfdir}/timestamp', 58 '.{sysconfdir}/timestamp',
62 '.{sysconfdir}/version', 59 '.{sysconfdir}/version',
diff --git a/meta/lib/oeqa/selftest/cases/cve_check.py b/meta/lib/oeqa/selftest/cases/cve_check.py
index 60cecd1328..511e4b81b4 100644
--- a/meta/lib/oeqa/selftest/cases/cve_check.py
+++ b/meta/lib/oeqa/selftest/cases/cve_check.py
@@ -72,6 +72,259 @@ class CVECheck(OESelftestTestCase):
72 self.assertEqual(convert_cve_version("6.2_rc8"), "6.2-rc8") 72 self.assertEqual(convert_cve_version("6.2_rc8"), "6.2-rc8")
73 self.assertEqual(convert_cve_version("6.2_rc31"), "6.2-rc31") 73 self.assertEqual(convert_cve_version("6.2_rc31"), "6.2-rc31")
74 74
75 def test_product_match(self):
76 from oe.cve_check import has_cve_product_match
77
78 status = {}
79 status["detail"] = "ignored"
80 status["vendor"] = "*"
81 status["product"] = "*"
82 status["description"] = ""
83 status["mapping"] = ""
84
85 self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), True)
86 self.assertEqual(has_cve_product_match(status, "*:*"), True)
87 self.assertEqual(has_cve_product_match(status, "some_product"), True)
88 self.assertEqual(has_cve_product_match(status, "glibc"), True)
89 self.assertEqual(has_cve_product_match(status, "glibca"), True)
90 self.assertEqual(has_cve_product_match(status, "aglibc"), True)
91 self.assertEqual(has_cve_product_match(status, "*"), True)
92 self.assertEqual(has_cve_product_match(status, "aglibc glibc test:test"), True)
93
94 status["product"] = "glibc"
95 self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), False)
96 # The CPE in the recipe must be defined, no * accepted
97 self.assertEqual(has_cve_product_match(status, "*:*"), False)
98 self.assertEqual(has_cve_product_match(status, "*"), False)
99 self.assertEqual(has_cve_product_match(status, "some_product"), False)
100 self.assertEqual(has_cve_product_match(status, "glibc"), True)
101 self.assertEqual(has_cve_product_match(status, "glibca"), False)
102 self.assertEqual(has_cve_product_match(status, "aglibc"), False)
103 self.assertEqual(has_cve_product_match(status, "some_vendor:glibc"), True)
104 self.assertEqual(has_cve_product_match(status, "some_vendor:glibc test"), True)
105 self.assertEqual(has_cve_product_match(status, "test some_vendor:glibc"), True)
106
107 status["vendor"] = "glibca"
108 status["product"] = "glibc"
109 self.assertEqual(has_cve_product_match(status, "some_vendor:some_product"), False)
110 # The CPE in the recipe must be defined, no * accepted
111 self.assertEqual(has_cve_product_match(status, "*:*"), False)
112 self.assertEqual(has_cve_product_match(status, "*"), False)
113 self.assertEqual(has_cve_product_match(status, "some_product"), False)
114 self.assertEqual(has_cve_product_match(status, "glibc"), False)
115 self.assertEqual(has_cve_product_match(status, "glibca"), False)
116 self.assertEqual(has_cve_product_match(status, "aglibc"), False)
117 self.assertEqual(has_cve_product_match(status, "some_vendor:glibc"), False)
118 self.assertEqual(has_cve_product_match(status, "glibca:glibc"), True)
119 self.assertEqual(has_cve_product_match(status, "test:test glibca:glibc"), True)
120 self.assertEqual(has_cve_product_match(status, "test glibca:glibc"), True)
121 self.assertEqual(has_cve_product_match(status, "glibca:glibc test"), True)
122
123 def test_parse_cve_from_patch_filename(self):
124 from oe.cve_check import parse_cve_from_filename
125
126 # Patch filename without CVE ID
127 self.assertEqual(parse_cve_from_filename("0001-test.patch"), "")
128
129 # Patch with single CVE ID
130 self.assertEqual(
131 parse_cve_from_filename("CVE-2022-12345.patch"), "CVE-2022-12345"
132 )
133
134 # Patch with multiple CVE IDs
135 self.assertEqual(
136 parse_cve_from_filename("CVE-2022-41741-CVE-2022-41742.patch"),
137 "CVE-2022-41742",
138 )
139
140 # Patches with CVE ID and appended text
141 self.assertEqual(
142 parse_cve_from_filename("CVE-2023-3019-0001.patch"), "CVE-2023-3019"
143 )
144 self.assertEqual(
145 parse_cve_from_filename("CVE-2024-21886-1.patch"), "CVE-2024-21886"
146 )
147
148 # Patch with CVE ID and prepended text
149 self.assertEqual(
150 parse_cve_from_filename("grep-CVE-2012-5667.patch"), "CVE-2012-5667"
151 )
152 self.assertEqual(
153 parse_cve_from_filename("0001-CVE-2012-5667.patch"), "CVE-2012-5667"
154 )
155
156 # Patch with CVE ID and both prepended and appended text
157 self.assertEqual(
158 parse_cve_from_filename(
159 "0001-tpm2_import-fix-fixed-AES-key-CVE-2021-3565-0001.patch"
160 ),
161 "CVE-2021-3565",
162 )
163
164 # Only grab the last CVE ID in the filename
165 self.assertEqual(
166 parse_cve_from_filename("CVE-2012-5667-CVE-2012-5668.patch"),
167 "CVE-2012-5668",
168 )
169
170 # Test invalid CVE ID with incorrect length (must be at least 4 digits)
171 self.assertEqual(
172 parse_cve_from_filename("CVE-2024-001.patch"),
173 "",
174 )
175
176 # Test valid CVE ID with very long length
177 self.assertEqual(
178 parse_cve_from_filename("CVE-2024-0000000000000000000000001.patch"),
179 "CVE-2024-0000000000000000000000001",
180 )
181
182 def test_parse_cve_from_patch_contents(self):
183 import textwrap
184 from oe.cve_check import parse_cves_from_patch_contents
185
186 # Standard patch file excerpt without any patches
187 self.assertEqual(
188 parse_cves_from_patch_contents(
189 textwrap.dedent("""\
190 remove "*" for root since we don't have a /etc/shadow so far.
191
192 Upstream-Status: Inappropriate [configuration]
193
194 Signed-off-by: Scott Garman <scott.a.garman@intel.com>
195
196 --- base-passwd/passwd.master~nobash
197 +++ base-passwd/passwd.master
198 @@ -1,4 +1,4 @@
199 -root:*:0:0:root:/root:/bin/sh
200 +root::0:0:root:/root:/bin/sh
201 daemon:*:1:1:daemon:/usr/sbin:/bin/sh
202 bin:*:2:2:bin:/bin:/bin/sh
203 sys:*:3:3:sys:/dev:/bin/sh
204 """)
205 ),
206 set(),
207 )
208
209 # Patch file with multiple CVE IDs (space-separated)
210 self.assertEqual(
211 parse_cves_from_patch_contents(
212 textwrap.dedent("""\
213 There is an assertion in function _cairo_arc_in_direction().
214
215 CVE: CVE-2019-6461 CVE-2019-6462
216 Upstream-Status: Pending
217 Signed-off-by: Ross Burton <ross.burton@intel.com>
218
219 diff --git a/src/cairo-arc.c b/src/cairo-arc.c
220 index 390397bae..1bde774a4 100644
221 --- a/src/cairo-arc.c
222 +++ b/src/cairo-arc.c
223 @@ -186,7 +186,8 @@ _cairo_arc_in_direction (cairo_t *cr,
224 if (cairo_status (cr))
225 return;
226
227 - assert (angle_max >= angle_min);
228 + if (angle_max < angle_min)
229 + return;
230
231 if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
232 angle_max = fmod (angle_max - angle_min, 2 * M_PI);
233 """),
234 ),
235 {"CVE-2019-6461", "CVE-2019-6462"},
236 )
237
238 # Patch file with multiple CVE IDs (comma-separated w/ both space and no space)
239 self.assertEqual(
240 parse_cves_from_patch_contents(
241 textwrap.dedent("""\
242 There is an assertion in function _cairo_arc_in_direction().
243
244 CVE: CVE-2019-6461,CVE-2019-6462, CVE-2019-6463
245 Upstream-Status: Pending
246 Signed-off-by: Ross Burton <ross.burton@intel.com>
247
248 diff --git a/src/cairo-arc.c b/src/cairo-arc.c
249 index 390397bae..1bde774a4 100644
250 --- a/src/cairo-arc.c
251 +++ b/src/cairo-arc.c
252 @@ -186,7 +186,8 @@ _cairo_arc_in_direction (cairo_t *cr,
253 if (cairo_status (cr))
254 return;
255
256 - assert (angle_max >= angle_min);
257 + if (angle_max < angle_min)
258 + return;
259
260 if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
261 angle_max = fmod (angle_max - angle_min, 2 * M_PI);
262
263 """),
264 ),
265 {"CVE-2019-6461", "CVE-2019-6462", "CVE-2019-6463"},
266 )
267
268 # Patch file with multiple CVE IDs (&-separated)
269 self.assertEqual(
270 parse_cves_from_patch_contents(
271 textwrap.dedent("""\
272 There is an assertion in function _cairo_arc_in_direction().
273
274 CVE: CVE-2019-6461 & CVE-2019-6462
275 Upstream-Status: Pending
276 Signed-off-by: Ross Burton <ross.burton@intel.com>
277
278 diff --git a/src/cairo-arc.c b/src/cairo-arc.c
279 index 390397bae..1bde774a4 100644
280 --- a/src/cairo-arc.c
281 +++ b/src/cairo-arc.c
282 @@ -186,7 +186,8 @@ _cairo_arc_in_direction (cairo_t *cr,
283 if (cairo_status (cr))
284 return;
285
286 - assert (angle_max >= angle_min);
287 + if (angle_max < angle_min)
288 + return;
289
290 if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
291 angle_max = fmod (angle_max - angle_min, 2 * M_PI);
292 """),
293 ),
294 {"CVE-2019-6461", "CVE-2019-6462"},
295 )
296
297 # Patch file with multiple lines with CVE IDs
298 self.assertEqual(
299 parse_cves_from_patch_contents(
300 textwrap.dedent("""\
301 There is an assertion in function _cairo_arc_in_direction().
302
303 CVE: CVE-2019-6461 & CVE-2019-6462
304
305 CVE: CVE-2019-6463 & CVE-2019-6464
306 Upstream-Status: Pending
307 Signed-off-by: Ross Burton <ross.burton@intel.com>
308
309 diff --git a/src/cairo-arc.c b/src/cairo-arc.c
310 index 390397bae..1bde774a4 100644
311 --- a/src/cairo-arc.c
312 +++ b/src/cairo-arc.c
313 @@ -186,7 +186,8 @@ _cairo_arc_in_direction (cairo_t *cr,
314 if (cairo_status (cr))
315 return;
316
317 - assert (angle_max >= angle_min);
318 + if (angle_max < angle_min)
319 + return;
320
321 if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
322 angle_max = fmod (angle_max - angle_min, 2 * M_PI);
323
324 """),
325 ),
326 {"CVE-2019-6461", "CVE-2019-6462", "CVE-2019-6463", "CVE-2019-6464"},
327 )
75 328
76 def test_recipe_report_json(self): 329 def test_recipe_report_json(self):
77 config = """ 330 config = """
@@ -217,9 +470,10 @@ CVE_CHECK_REPORT_PATCHED = "1"
217 # m4 CVE should not be in logrotate 470 # m4 CVE should not be in logrotate
218 self.assertNotIn("CVE-2008-1687", found_cves) 471 self.assertNotIn("CVE-2008-1687", found_cves)
219 # logrotate has both Patched and Ignored CVEs 472 # logrotate has both Patched and Ignored CVEs
473 detail = "version-not-in-range"
220 self.assertIn("CVE-2011-1098", found_cves) 474 self.assertIn("CVE-2011-1098", found_cves)
221 self.assertEqual(found_cves["CVE-2011-1098"]["status"], "Patched") 475 self.assertEqual(found_cves["CVE-2011-1098"]["status"], "Patched")
222 self.assertEqual(len(found_cves["CVE-2011-1098"]["detail"]), 0) 476 self.assertEqual(found_cves["CVE-2011-1098"]["detail"], detail)
223 self.assertEqual(len(found_cves["CVE-2011-1098"]["description"]), 0) 477 self.assertEqual(len(found_cves["CVE-2011-1098"]["description"]), 0)
224 detail = "not-applicable-platform" 478 detail = "not-applicable-platform"
225 description = "CVE is debian, gentoo or SUSE specific on the way logrotate was installed/used" 479 description = "CVE is debian, gentoo or SUSE specific on the way logrotate was installed/used"
diff --git a/meta/lib/oeqa/selftest/cases/debuginfod.py b/meta/lib/oeqa/selftest/cases/debuginfod.py
index 505b4be837..46c0cd87bb 100644
--- a/meta/lib/oeqa/selftest/cases/debuginfod.py
+++ b/meta/lib/oeqa/selftest/cases/debuginfod.py
@@ -62,7 +62,7 @@ class Debuginfod(OESelftestTestCase):
62 62
63 raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest) 63 raise TimeoutError("Cannot connect debuginfod, still %d scan jobs running" % latest)
64 64
65 def start_debuginfod(self): 65 def start_debuginfod(self, feed_dir):
66 # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot 66 # We assume that the caller has already bitbake'd elfutils-native:do_addto_recipe_sysroot
67 67
68 # Save some useful paths for later 68 # Save some useful paths for later
@@ -82,7 +82,7 @@ class Debuginfod(OESelftestTestCase):
82 # Disable rescanning, this is a one-shot test 82 # Disable rescanning, this is a one-shot test
83 "--rescan-time=0", 83 "--rescan-time=0",
84 "--groom-time=0", 84 "--groom-time=0",
85 get_bb_var("DEPLOY_DIR"), 85 feed_dir,
86 ] 86 ]
87 87
88 format = get_bb_var("PACKAGE_CLASSES").split()[0] 88 format = get_bb_var("PACKAGE_CLASSES").split()[0]
@@ -114,11 +114,12 @@ class Debuginfod(OESelftestTestCase):
114 self.write_config(""" 114 self.write_config("""
115TMPDIR = "${TOPDIR}/tmp-debuginfod" 115TMPDIR = "${TOPDIR}/tmp-debuginfod"
116DISTRO_FEATURES:append = " debuginfod" 116DISTRO_FEATURES:append = " debuginfod"
117INHERIT += "localpkgfeed"
117""") 118""")
118 bitbake("elfutils-native:do_addto_recipe_sysroot xz xz:do_package") 119 bitbake("elfutils-native:do_addto_recipe_sysroot xz xz:do_package xz:do_localpkgfeed")
119 120
120 try: 121 try:
121 self.start_debuginfod() 122 self.start_debuginfod(get_bb_var("LOCALPKGFEED_DIR", "xz"))
122 123
123 env = os.environ.copy() 124 env = os.environ.copy()
124 env["DEBUGINFOD_URLS"] = "http://localhost:%d/" % self.port 125 env["DEBUGINFOD_URLS"] = "http://localhost:%d/" % self.port
@@ -141,12 +142,13 @@ DISTRO_FEATURES:append = " debuginfod"
141 self.write_config(""" 142 self.write_config("""
142TMPDIR = "${TOPDIR}/tmp-debuginfod" 143TMPDIR = "${TOPDIR}/tmp-debuginfod"
143DISTRO_FEATURES:append = " debuginfod" 144DISTRO_FEATURES:append = " debuginfod"
145INHERIT += "localpkgfeed"
144CORE_IMAGE_EXTRA_INSTALL += "elfutils xz" 146CORE_IMAGE_EXTRA_INSTALL += "elfutils xz"
145 """) 147 """)
146 bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot") 148 bitbake("core-image-minimal elfutils-native:do_addto_recipe_sysroot xz:do_localpkgfeed")
147 149
148 try: 150 try:
149 self.start_debuginfod() 151 self.start_debuginfod(get_bb_var("LOCALPKGFEED_DIR", "xz"))
150 152
151 with runqemu("core-image-minimal", runqemuparams="nographic") as qemu: 153 with runqemu("core-image-minimal", runqemuparams="nographic") as qemu:
152 cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port) 154 cmd = "DEBUGINFOD_URLS=http://%s:%d/ debuginfod-find debuginfo /usr/bin/xz" % (qemu.server_ip, self.port)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 882225dde3..b92f017b81 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -16,14 +16,13 @@ import json
16 16
17from oeqa.selftest.case import OESelftestTestCase 17from oeqa.selftest.case import OESelftestTestCase
18from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer 18from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer
19from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer 19from oeqa.utils.commands import get_bb_vars, runqemu, runqemu_check_taps, get_test_layer
20from oeqa.core.decorator import OETestTag 20from oeqa.core.decorator import OETestTag
21from bb.utils import mkdirhier, edit_bblayers_conf
21 22
22oldmetapath = None 23oldmetapath = None
23 24
24def setUpModule(): 25def setUpModule():
25 import bb.utils
26
27 global templayerdir 26 global templayerdir
28 templayerdir = tempfile.mkdtemp(prefix='devtoolqa') 27 templayerdir = tempfile.mkdtemp(prefix='devtoolqa')
29 corecopydir = os.path.join(templayerdir, 'core-copy') 28 corecopydir = os.path.join(templayerdir, 'core-copy')
@@ -64,23 +63,27 @@ def setUpModule():
64 # under COREBASE and we don't want to copy that, so we have 63 # under COREBASE and we don't want to copy that, so we have
65 # to be selective. 64 # to be selective.
66 result = runCmd('git status --porcelain', cwd=oldreporoot) 65 result = runCmd('git status --porcelain', cwd=oldreporoot)
66
67 # Also copy modifications to the 'scripts/' directory
68 canonical_layerpath_scripts = os.path.normpath(canonical_layerpath + "../scripts")
69
67 for line in result.output.splitlines(): 70 for line in result.output.splitlines():
68 if line.startswith(' M ') or line.startswith('?? '): 71 if line.startswith(' M ') or line.startswith('?? '):
69 relpth = line.split()[1] 72 relpth = line.split()[1]
70 pth = os.path.join(oldreporoot, relpth) 73 pth = os.path.join(oldreporoot, relpth)
71 if pth.startswith(canonical_layerpath): 74 if pth.startswith(canonical_layerpath) or pth.startswith(canonical_layerpath_scripts):
72 if relpth.endswith('/'): 75 if relpth.endswith('/'):
73 destdir = os.path.join(corecopydir, relpth) 76 destdir = os.path.join(corecopydir, relpth)
74 # avoid race condition by not copying .pyc files YPBZ#13421,13803 77 # avoid race condition by not copying .pyc files YPBZ#13421,13803
75 shutil.copytree(pth, destdir, ignore=shutil.ignore_patterns('*.pyc', '__pycache__')) 78 shutil.copytree(pth, destdir, ignore=shutil.ignore_patterns('*.pyc', '__pycache__'))
76 else: 79 else:
77 destdir = os.path.join(corecopydir, os.path.dirname(relpth)) 80 destdir = os.path.join(corecopydir, os.path.dirname(relpth))
78 bb.utils.mkdirhier(destdir) 81 mkdirhier(destdir)
79 shutil.copy2(pth, destdir) 82 shutil.copy2(pth, destdir)
80 return newmetapath 83 return newmetapath
81 else: 84 else:
82 return layerpath 85 return layerpath
83 bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) 86 edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb)
84 87
85def tearDownModule(): 88def tearDownModule():
86 if oldmetapath: 89 if oldmetapath:
@@ -92,7 +95,7 @@ def tearDownModule():
92 else: 95 else:
93 return layerpath 96 return layerpath
94 bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf') 97 bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf')
95 bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) 98 edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb)
96 shutil.rmtree(templayerdir) 99 shutil.rmtree(templayerdir)
97 100
98class DevtoolTestCase(OESelftestTestCase): 101class DevtoolTestCase(OESelftestTestCase):
@@ -150,7 +153,7 @@ class DevtoolTestCase(OESelftestTestCase):
150 value = invalue 153 value = invalue
151 invar = None 154 invar = None
152 elif '=' in line: 155 elif '=' in line:
153 splitline = line.split('=', 1) 156 splitline = re.split(r"[?+:]*=[+]?", line, 1)
154 var = splitline[0].rstrip() 157 var = splitline[0].rstrip()
155 value = splitline[1].strip().strip('"') 158 value = splitline[1].strip().strip('"')
156 if value.endswith('\\'): 159 if value.endswith('\\'):
@@ -273,18 +276,8 @@ class DevtoolTestCase(OESelftestTestCase):
273 machine = get_bb_var('MACHINE') 276 machine = get_bb_var('MACHINE')
274 if not machine.startswith('qemu'): 277 if not machine.startswith('qemu'):
275 self.skipTest('This test only works with qemu machines') 278 self.skipTest('This test only works with qemu machines')
276 if not os.path.exists('/etc/runqemu-nosudo'): 279 if not runqemu_check_taps():
277 self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') 280 self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test')
278 result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ip tuntap show', ignore_status=True)
279 if result.status != 0:
280 result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ifconfig -a', ignore_status=True)
281 if result.status != 0:
282 self.skipTest('Failed to determine if tap devices exist with ifconfig or ip: %s' % result.output)
283 for line in result.output.splitlines():
284 if line.startswith('tap'):
285 break
286 else:
287 self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test')
288 281
289 def _test_devtool_add_git_url(self, git_url, version, pn, resulting_src_uri, srcrev=None): 282 def _test_devtool_add_git_url(self, git_url, version, pn, resulting_src_uri, srcrev=None):
290 self.track_for_cleanup(self.workspacedir) 283 self.track_for_cleanup(self.workspacedir)
@@ -317,7 +310,7 @@ class DevtoolBase(DevtoolTestCase):
317 cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate 310 cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate
318 cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n' 311 cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n'
319 % cls.original_sstate) 312 % cls.original_sstate)
320 cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687"\n') 313 cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yoctoproject.org:8686"\n')
321 314
322 @classmethod 315 @classmethod
323 def tearDownClass(cls): 316 def tearDownClass(cls):
@@ -410,9 +403,9 @@ class DevtoolAddTests(DevtoolBase):
410 test_file_content = "TEST CONTENT" 403 test_file_content = "TEST CONTENT"
411 test_file_package_root = os.path.join(tempdir, pn) 404 test_file_package_root = os.path.join(tempdir, pn)
412 test_file_dir_full = os.path.join(test_file_package_root, test_file_dir) 405 test_file_dir_full = os.path.join(test_file_package_root, test_file_dir)
413 bb.utils.mkdirhier(test_file_dir_full) 406 mkdirhier(test_file_dir_full)
414 with open(os.path.join(test_file_dir_full, test_file_name), "w") as f: 407 with open(os.path.join(test_file_dir_full, test_file_name), "w") as f:
415 f.write(test_file_content) 408 f.write(test_file_content)
416 bin_package_path = os.path.join(tempdir, "%s.tar.gz" % pn) 409 bin_package_path = os.path.join(tempdir, "%s.tar.gz" % pn)
417 runCmd("tar czf %s -C %s ." % (bin_package_path, test_file_package_root)) 410 runCmd("tar czf %s -C %s ." % (bin_package_path, test_file_package_root))
418 411
@@ -465,7 +458,7 @@ class DevtoolAddTests(DevtoolBase):
465 checkvars = {} 458 checkvars = {}
466 checkvars['LICENSE'] = 'GPL-2.0-only' 459 checkvars['LICENSE'] = 'GPL-2.0-only'
467 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263' 460 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263'
468 checkvars['S'] = '${WORKDIR}/git' 461 checkvars['S'] = None
469 checkvars['PV'] = '0.1+git' 462 checkvars['PV'] = '0.1+git'
470 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master' 463 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master'
471 checkvars['SRCREV'] = srcrev 464 checkvars['SRCREV'] = srcrev
@@ -515,7 +508,13 @@ class DevtoolAddTests(DevtoolBase):
515 # normally cover, which triggers the installed-vs-shipped QA test we have 508 # normally cover, which triggers the installed-vs-shipped QA test we have
516 # within do_package 509 # within do_package
517 recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version) 510 recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version)
518 result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- \'-DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules\'' % recipefile) 511 # There is no upstream release that supports building with CMake 4+ yet, so we explicitly
512 # set the policy minimum version via EXTRA_OECMAKE. That's easier than applying backported
513 # patches.
514 result = runCmd(
515 "recipetool setvar %s EXTRA_OECMAKE -- '-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules'"
516 % recipefile
517 )
519 with open(recipefile, 'a') as f: 518 with open(recipefile, 'a') as f:
520 f.write('\nFILES:${PN}-dev += "${datadir}/cmake/Modules"\n') 519 f.write('\nFILES:${PN}-dev += "${datadir}/cmake/Modules"\n')
521 # We don't have the ability to pick up this dependency automatically yet... 520 # We don't have the ability to pick up this dependency automatically yet...
@@ -561,7 +560,7 @@ class DevtoolAddTests(DevtoolBase):
561 recipefile = get_bb_var('FILE', testrecipe) 560 recipefile = get_bb_var('FILE', testrecipe)
562 self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named') 561 self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named')
563 checkvars = {} 562 checkvars = {}
564 checkvars['S'] = '${WORKDIR}/MarkupSafe-${PV}' 563 checkvars['S'] = '${UNPACKDIR}/MarkupSafe-${PV}'
565 checkvars['SRC_URI'] = url.replace(testver, '${PV}') 564 checkvars['SRC_URI'] = url.replace(testver, '${PV}')
566 self._test_recipe_contents(recipefile, checkvars, []) 565 self._test_recipe_contents(recipefile, checkvars, [])
567 # Try with version specified 566 # Try with version specified
@@ -578,7 +577,7 @@ class DevtoolAddTests(DevtoolBase):
578 recipefile = get_bb_var('FILE', testrecipe) 577 recipefile = get_bb_var('FILE', testrecipe)
579 self.assertIn('%s_%s.bb' % (testrecipe, fakever), recipefile, 'Recipe file incorrectly named') 578 self.assertIn('%s_%s.bb' % (testrecipe, fakever), recipefile, 'Recipe file incorrectly named')
580 checkvars = {} 579 checkvars = {}
581 checkvars['S'] = '${WORKDIR}/MarkupSafe-%s' % testver 580 checkvars['S'] = '${UNPACKDIR}/MarkupSafe-%s' % testver
582 checkvars['SRC_URI'] = url 581 checkvars['SRC_URI'] = url
583 self._test_recipe_contents(recipefile, checkvars, []) 582 self._test_recipe_contents(recipefile, checkvars, [])
584 583
@@ -605,7 +604,7 @@ class DevtoolAddTests(DevtoolBase):
605 recipefile = get_bb_var('FILE', testrecipe) 604 recipefile = get_bb_var('FILE', testrecipe)
606 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') 605 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
607 checkvars = {} 606 checkvars = {}
608 checkvars['S'] = '${WORKDIR}/git' 607 checkvars['S'] = None
609 checkvars['PV'] = '1.0+git' 608 checkvars['PV'] = '1.0+git'
610 checkvars['SRC_URI'] = url_branch 609 checkvars['SRC_URI'] = url_branch
611 checkvars['SRCREV'] = '${AUTOREV}' 610 checkvars['SRCREV'] = '${AUTOREV}'
@@ -624,7 +623,7 @@ class DevtoolAddTests(DevtoolBase):
624 recipefile = get_bb_var('FILE', testrecipe) 623 recipefile = get_bb_var('FILE', testrecipe)
625 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') 624 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
626 checkvars = {} 625 checkvars = {}
627 checkvars['S'] = '${WORKDIR}/git' 626 checkvars['S'] = None
628 checkvars['PV'] = '1.5+git' 627 checkvars['PV'] = '1.5+git'
629 checkvars['SRC_URI'] = url_branch 628 checkvars['SRC_URI'] = url_branch
630 checkvars['SRCREV'] = checkrev 629 checkvars['SRCREV'] = checkrev
@@ -753,6 +752,25 @@ class DevtoolModifyTests(DevtoolBase):
753 result = runCmd('devtool status') 752 result = runCmd('devtool status')
754 self.assertNotIn('mdadm', result.output) 753 self.assertNotIn('mdadm', result.output)
755 754
755 def test_devtool_modify_go(self):
756 import oe.path
757 from tempfile import TemporaryDirectory
758 with TemporaryDirectory(prefix='devtoolqa') as tempdir:
759 self.track_for_cleanup(self.workspacedir)
760 self.add_command_to_tearDown('bitbake -c clean go-helloworld')
761 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
762 result = runCmd('devtool modify go-helloworld -x %s' % tempdir)
763 self.assertExists(
764 oe.path.join(tempdir, 'src', 'golang.org', 'x', 'example', 'go.mod'),
765 'Extracted source could not be found'
766 )
767 self.assertExists(
768 oe.path.join(self.workspacedir, 'conf', 'layer.conf'),
769 'Workspace directory not created'
770 )
771 matches = glob.glob(oe.path.join(self.workspacedir, 'appends', 'go-helloworld_*.bbappend'))
772 self.assertTrue(matches, 'bbappend not created %s' % result.output)
773
756 def test_devtool_buildclean(self): 774 def test_devtool_buildclean(self):
757 def assertFile(path, *paths): 775 def assertFile(path, *paths):
758 f = os.path.join(path, *paths) 776 f = os.path.join(path, *paths)
@@ -879,13 +897,8 @@ class DevtoolModifyTests(DevtoolBase):
879 self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) 897 self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
880 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') 898 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
881 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) 899 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
882 srcfile = os.path.join(tempdir, 'oe-local-files/share/dot.bashrc') 900 srcfile = os.path.join(tempdir, 'share/dot.bashrc')
883 srclink = os.path.join(tempdir, 'share/dot.bashrc')
884 self.assertExists(srcfile, 'Extracted source could not be found') 901 self.assertExists(srcfile, 'Extracted source could not be found')
885 if os.path.islink(srclink) and os.path.exists(srclink) and os.path.samefile(srcfile, srclink):
886 correct_symlink = True
887 self.assertTrue(correct_symlink, 'Source symlink to oe-local-files is broken')
888
889 matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe)) 902 matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe))
890 self.assertTrue(matches, 'bbappend not created') 903 self.assertTrue(matches, 'bbappend not created')
891 # Test devtool status 904 # Test devtool status
@@ -956,9 +969,9 @@ class DevtoolModifyTests(DevtoolBase):
956 # others git:// in SRC_URI 969 # others git:// in SRC_URI
957 # cointains a patch 970 # cointains a patch
958 testrecipe = 'hello-rs' 971 testrecipe = 'hello-rs'
959 bb_vars = get_bb_vars(['SRC_URI', 'FILE', 'WORKDIR', 'CARGO_HOME'], testrecipe) 972 bb_vars = get_bb_vars(['SRC_URI', 'FILE', 'UNPACKDIR', 'CARGO_HOME'], testrecipe)
960 recipefile = bb_vars['FILE'] 973 recipefile = bb_vars['FILE']
961 workdir = bb_vars['WORKDIR'] 974 unpackdir = bb_vars['UNPACKDIR']
962 cargo_home = bb_vars['CARGO_HOME'] 975 cargo_home = bb_vars['CARGO_HOME']
963 src_uri = bb_vars['SRC_URI'].split() 976 src_uri = bb_vars['SRC_URI'].split()
964 self.assertTrue(src_uri[0].startswith('git://'), 977 self.assertTrue(src_uri[0].startswith('git://'),
@@ -1009,7 +1022,7 @@ class DevtoolModifyTests(DevtoolBase):
1009 # Configure the recipe to check that the git dependencies are correctly patched in cargo config 1022 # Configure the recipe to check that the git dependencies are correctly patched in cargo config
1010 bitbake('-c configure %s' % testrecipe) 1023 bitbake('-c configure %s' % testrecipe)
1011 1024
1012 cargo_config_path = os.path.join(cargo_home, 'config') 1025 cargo_config_path = os.path.join(cargo_home, 'config.toml')
1013 with open(cargo_config_path, "r") as f: 1026 with open(cargo_config_path, "r") as f:
1014 cargo_config_contents = [line.strip('\n') for line in f.readlines()] 1027 cargo_config_contents = [line.strip('\n') for line in f.readlines()]
1015 1028
@@ -1029,7 +1042,7 @@ class DevtoolModifyTests(DevtoolBase):
1029 self.assertEqual(parms['type'], 'git-dependency', 'git dependencies uri should have "type=git-dependency"') 1042 self.assertEqual(parms['type'], 'git-dependency', 'git dependencies uri should have "type=git-dependency"')
1030 raw_url = raw_url.replace("git://", '%s://' % parms['protocol']) 1043 raw_url = raw_url.replace("git://", '%s://' % parms['protocol'])
1031 patch_line = '[patch."%s"]' % raw_url 1044 patch_line = '[patch."%s"]' % raw_url
1032 path_patched = os.path.join(workdir, parms['destsuffix']) 1045 path_patched = os.path.join(unpackdir, parms['destsuffix'])
1033 path_override_line = '%s = { path = "%s" }' % (parms['name'], path_patched) 1046 path_override_line = '%s = { path = "%s" }' % (parms['name'], path_patched)
1034 # Would have been better to use tomllib to read this file :/ 1047 # Would have been better to use tomllib to read this file :/
1035 self.assertIn(patch_line, cargo_config_contents) 1048 self.assertIn(patch_line, cargo_config_contents)
@@ -1167,13 +1180,16 @@ class DevtoolUpdateTests(DevtoolBase):
1167 result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir) 1180 result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir)
1168 result = runCmd('git add devtool-new-file', cwd=tempdir) 1181 result = runCmd('git add devtool-new-file', cwd=tempdir)
1169 result = runCmd('git commit -m "Add a new file"', cwd=tempdir) 1182 result = runCmd('git commit -m "Add a new file"', cwd=tempdir)
1170 self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) 1183 cleanup_cmd = 'cd %s; rm %s/*.patch; git add %s; git checkout %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))
1184 self.add_command_to_tearDown(cleanup_cmd)
1171 result = runCmd('devtool update-recipe %s' % testrecipe) 1185 result = runCmd('devtool update-recipe %s' % testrecipe)
1172 result = runCmd('git add minicom', cwd=os.path.dirname(recipefile)) 1186 result = runCmd('git add minicom', cwd=os.path.dirname(recipefile))
1173 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1187 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1174 ('A ', '.*/0001-Change-the-README.patch$'), 1188 ('A ', '.*/0001-Change-the-README.patch$'),
1175 ('A ', '.*/0002-Add-a-new-file.patch$')] 1189 ('A ', '.*/0002-Add-a-new-file.patch$')]
1176 self._check_repo_status(os.path.dirname(recipefile), expected_status) 1190 self._check_repo_status(os.path.dirname(recipefile), expected_status)
1191 result = runCmd(cleanup_cmd)
1192 self._check_repo_status(os.path.dirname(recipefile), [])
1177 1193
1178 def test_devtool_update_recipe_git(self): 1194 def test_devtool_update_recipe_git(self):
1179 # Check preconditions 1195 # Check preconditions
@@ -1230,7 +1246,7 @@ class DevtoolUpdateTests(DevtoolBase):
1230 1246
1231 def test_devtool_update_recipe_append(self): 1247 def test_devtool_update_recipe_append(self):
1232 # Check preconditions 1248 # Check preconditions
1233 testrecipe = 'mdadm' 1249 testrecipe = 'minicom'
1234 bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe) 1250 bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1235 recipefile = bb_vars['FILE'] 1251 recipefile = bb_vars['FILE']
1236 src_uri = bb_vars['SRC_URI'] 1252 src_uri = bb_vars['SRC_URI']
@@ -1248,7 +1264,7 @@ class DevtoolUpdateTests(DevtoolBase):
1248 # Check git repo 1264 # Check git repo
1249 self._check_src_repo(tempsrcdir) 1265 self._check_src_repo(tempsrcdir)
1250 # Add a commit 1266 # Add a commit
1251 result = runCmd("sed 's!\\(#define VERSION\\W*\"[^\"]*\\)\"!\\1-custom\"!' -i ReadMe.c", cwd=tempsrcdir) 1267 result = runCmd('echo "Additional line" >> README', cwd=tempsrcdir)
1252 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir) 1268 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
1253 self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe)) 1269 self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe))
1254 # Create a temporary layer and add it to bblayers.conf 1270 # Create a temporary layer and add it to bblayers.conf
@@ -1278,7 +1294,7 @@ class DevtoolUpdateTests(DevtoolBase):
1278 with open(bbappendfile, 'r') as f: 1294 with open(bbappendfile, 'r') as f:
1279 self.assertEqual(expectedlines, f.readlines()) 1295 self.assertEqual(expectedlines, f.readlines())
1280 # Drop new commit and check patch gets deleted 1296 # Drop new commit and check patch gets deleted
1281 result = runCmd('git reset HEAD^', cwd=tempsrcdir) 1297 result = runCmd('git reset HEAD^ --hard', cwd=tempsrcdir)
1282 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) 1298 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1283 self.assertNotExists(patchfile, 'Patch file not deleted') 1299 self.assertNotExists(patchfile, 'Patch file not deleted')
1284 expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n', 1300 expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n',
@@ -1287,6 +1303,7 @@ class DevtoolUpdateTests(DevtoolBase):
1287 self.assertEqual(expectedlines2, f.readlines()) 1303 self.assertEqual(expectedlines2, f.readlines())
1288 # Put commit back and check we can run it if layer isn't in bblayers.conf 1304 # Put commit back and check we can run it if layer isn't in bblayers.conf
1289 os.remove(bbappendfile) 1305 os.remove(bbappendfile)
1306 result = runCmd('echo "Additional line" >> README', cwd=tempsrcdir)
1290 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir) 1307 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
1291 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) 1308 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1292 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) 1309 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
@@ -1361,7 +1378,7 @@ class DevtoolUpdateTests(DevtoolBase):
1361 with open(bbappendfile, 'r') as f: 1378 with open(bbappendfile, 'r') as f:
1362 self.assertEqual(expectedlines, set(f.readlines())) 1379 self.assertEqual(expectedlines, set(f.readlines()))
1363 # Drop new commit and check SRCREV changes 1380 # Drop new commit and check SRCREV changes
1364 result = runCmd('git reset HEAD^', cwd=tempsrcdir) 1381 result = runCmd('git reset HEAD^ --hard', cwd=tempsrcdir)
1365 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) 1382 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1366 self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created') 1383 self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
1367 result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) 1384 result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
@@ -1373,6 +1390,7 @@ class DevtoolUpdateTests(DevtoolBase):
1373 self.assertEqual(expectedlines, set(f.readlines())) 1390 self.assertEqual(expectedlines, set(f.readlines()))
1374 # Put commit back and check we can run it if layer isn't in bblayers.conf 1391 # Put commit back and check we can run it if layer isn't in bblayers.conf
1375 os.remove(bbappendfile) 1392 os.remove(bbappendfile)
1393 result = runCmd('echo "# Additional line" >> Makefile.am', cwd=tempsrcdir)
1376 result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir) 1394 result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir)
1377 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) 1395 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1378 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) 1396 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
@@ -1404,11 +1422,12 @@ class DevtoolUpdateTests(DevtoolBase):
1404 # Try building just to ensure we haven't broken that 1422 # Try building just to ensure we haven't broken that
1405 bitbake("%s" % testrecipe) 1423 bitbake("%s" % testrecipe)
1406 # Edit / commit local source 1424 # Edit / commit local source
1407 runCmd('echo "/* Foobar */" >> oe-local-files/makedevs.c', cwd=tempdir) 1425 runCmd('echo "/* Foobar */" >> makedevs.c', cwd=tempdir)
1408 runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir) 1426 runCmd('echo "Foo" > new-local', cwd=tempdir)
1409 runCmd('echo "Bar" > new-file', cwd=tempdir) 1427 runCmd('echo "Bar" > new-file', cwd=tempdir)
1410 runCmd('git add new-file', cwd=tempdir) 1428 runCmd('git add new-file', cwd=tempdir)
1411 runCmd('git commit -m "Add new file"', cwd=tempdir) 1429 runCmd('git commit -m "Add new file"', cwd=tempdir)
1430 runCmd('git add new-local', cwd=tempdir)
1412 runCmd('devtool update-recipe %s' % testrecipe) 1431 runCmd('devtool update-recipe %s' % testrecipe)
1413 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1432 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1414 (' M', '.*/makedevs/makedevs.c$'), 1433 (' M', '.*/makedevs/makedevs.c$'),
@@ -1434,8 +1453,8 @@ class DevtoolUpdateTests(DevtoolBase):
1434 self.assertExists(local_file, 'File makedevs.c not created') 1453 self.assertExists(local_file, 'File makedevs.c not created')
1435 self.assertExists(patchfile, 'File new_local not created') 1454 self.assertExists(patchfile, 'File new_local not created')
1436 1455
1437 def test_devtool_update_recipe_local_files_2(self): 1456 def _test_devtool_update_recipe_local_files_2(self):
1438 """Check local source files support when oe-local-files is in Git""" 1457 """Check local source files support when editing local files in Git"""
1439 testrecipe = 'devtool-test-local' 1458 testrecipe = 'devtool-test-local'
1440 recipefile = get_bb_var('FILE', testrecipe) 1459 recipefile = get_bb_var('FILE', testrecipe)
1441 recipedir = os.path.dirname(recipefile) 1460 recipedir = os.path.dirname(recipefile)
@@ -1450,17 +1469,13 @@ class DevtoolUpdateTests(DevtoolBase):
1450 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) 1469 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1451 # Check git repo 1470 # Check git repo
1452 self._check_src_repo(tempdir) 1471 self._check_src_repo(tempdir)
1453 # Add oe-local-files to Git
1454 runCmd('rm oe-local-files/.gitignore', cwd=tempdir)
1455 runCmd('git add oe-local-files', cwd=tempdir)
1456 runCmd('git commit -m "Add local sources"', cwd=tempdir)
1457 # Edit / commit local sources 1472 # Edit / commit local sources
1458 runCmd('echo "# Foobar" >> oe-local-files/file1', cwd=tempdir) 1473 runCmd('echo "# Foobar" >> file1', cwd=tempdir)
1459 runCmd('git commit -am "Edit existing file"', cwd=tempdir) 1474 runCmd('git commit -am "Edit existing file"', cwd=tempdir)
1460 runCmd('git rm oe-local-files/file2', cwd=tempdir) 1475 runCmd('git rm file2', cwd=tempdir)
1461 runCmd('git commit -m"Remove file"', cwd=tempdir) 1476 runCmd('git commit -m"Remove file"', cwd=tempdir)
1462 runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir) 1477 runCmd('echo "Foo" > new-local', cwd=tempdir)
1463 runCmd('git add oe-local-files/new-local', cwd=tempdir) 1478 runCmd('git add new-local', cwd=tempdir)
1464 runCmd('git commit -m "Add new local file"', cwd=tempdir) 1479 runCmd('git commit -m "Add new local file"', cwd=tempdir)
1465 runCmd('echo "Gar" > new-file', cwd=tempdir) 1480 runCmd('echo "Gar" > new-file', cwd=tempdir)
1466 runCmd('git add new-file', cwd=tempdir) 1481 runCmd('git add new-file', cwd=tempdir)
@@ -1469,7 +1484,7 @@ class DevtoolUpdateTests(DevtoolBase):
1469 os.path.dirname(recipefile)) 1484 os.path.dirname(recipefile))
1470 # Checkout unmodified file to working copy -> devtool should still pick 1485 # Checkout unmodified file to working copy -> devtool should still pick
1471 # the modified version from HEAD 1486 # the modified version from HEAD
1472 runCmd('git checkout HEAD^ -- oe-local-files/file1', cwd=tempdir) 1487 runCmd('git checkout HEAD^ -- file1', cwd=tempdir)
1473 runCmd('devtool update-recipe %s' % testrecipe) 1488 runCmd('devtool update-recipe %s' % testrecipe)
1474 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1489 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1475 (' M', '.*/file1$'), 1490 (' M', '.*/file1$'),
@@ -1544,7 +1559,7 @@ class DevtoolUpdateTests(DevtoolBase):
1544 # (don't bother with cleaning the recipe on teardown, we won't be building it) 1559 # (don't bother with cleaning the recipe on teardown, we won't be building it)
1545 result = runCmd('devtool modify %s' % testrecipe) 1560 result = runCmd('devtool modify %s' % testrecipe)
1546 # Modify one file 1561 # Modify one file
1547 runCmd('echo "Another line" >> file2', cwd=os.path.join(self.workspacedir, 'sources', testrecipe, 'oe-local-files')) 1562 runCmd('echo "Another line" >> file2', cwd=os.path.join(self.workspacedir, 'sources', testrecipe))
1548 self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) 1563 self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1549 result = runCmd('devtool update-recipe %s' % testrecipe) 1564 result = runCmd('devtool update-recipe %s' % testrecipe)
1550 expected_status = [(' M', '.*/%s/file2$' % testrecipe)] 1565 expected_status = [(' M', '.*/%s/file2$' % testrecipe)]
@@ -1607,12 +1622,12 @@ class DevtoolUpdateTests(DevtoolBase):
1607 # Check preconditions 1622 # Check preconditions
1608 testrecipe = 'dos2unix' 1623 testrecipe = 'dos2unix'
1609 self.append_config('ERROR_QA:remove:pn-dos2unix = "patch-status"\n') 1624 self.append_config('ERROR_QA:remove:pn-dos2unix = "patch-status"\n')
1610 bb_vars = get_bb_vars(['SRC_URI', 'S', 'WORKDIR', 'FILE'], testrecipe) 1625 bb_vars = get_bb_vars(['SRC_URI', 'S', 'UNPACKDIR', 'FILE', 'BB_GIT_DEFAULT_DESTSUFFIX'], testrecipe)
1611 self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe) 1626 self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe)
1612 workdir_git = '%s/git/' % bb_vars['WORKDIR'] 1627 unpackdir_git = '%s/%s/' % (bb_vars['UNPACKDIR'], bb_vars['BB_GIT_DEFAULT_DESTSUFFIX'])
1613 if not bb_vars['S'].startswith(workdir_git): 1628 if not bb_vars['S'].startswith(unpackdir_git):
1614 self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe) 1629 self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe)
1615 subdir = bb_vars['S'].split(workdir_git, 1)[1] 1630 subdir = bb_vars['S'].split(unpackdir_git, 1)[1]
1616 # Clean up anything in the workdir/sysroot/sstate cache 1631 # Clean up anything in the workdir/sysroot/sstate cache
1617 bitbake('%s -c cleansstate' % testrecipe) 1632 bitbake('%s -c cleansstate' % testrecipe)
1618 # Try modifying a recipe 1633 # Try modifying a recipe
@@ -1740,6 +1755,8 @@ class DevtoolExtractTests(DevtoolBase):
1740 self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found') 1755 self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
1741 self._check_src_repo(tempdir) 1756 self._check_src_repo(tempdir)
1742 1757
1758class DevtoolResetTests(DevtoolBase):
1759
1743 def test_devtool_reset_all(self): 1760 def test_devtool_reset_all(self):
1744 tempdir = tempfile.mkdtemp(prefix='devtoolqa') 1761 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1745 self.track_for_cleanup(tempdir) 1762 self.track_for_cleanup(tempdir)
@@ -1766,6 +1783,21 @@ class DevtoolExtractTests(DevtoolBase):
1766 matches2 = glob.glob(stampprefix2 + '*') 1783 matches2 = glob.glob(stampprefix2 + '*')
1767 self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) 1784 self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2)
1768 1785
1786 def test_devtool_reset_re_plus_plus(self):
1787 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1788 self.track_for_cleanup(tempdir)
1789 self.track_for_cleanup(self.workspacedir)
1790 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1791 testrecipe = 'devtool-test-reset-re++'
1792 result = runCmd('devtool modify %s' % testrecipe)
1793 result = runCmd('devtool reset -n %s' % testrecipe)
1794 self.assertIn(testrecipe, result.output)
1795 result = runCmd('devtool status')
1796 self.assertNotIn(testrecipe, result.output)
1797 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', testrecipe), 'Recipe directory should not exist after resetting')
1798
1799class DevtoolDeployTargetTests(DevtoolBase):
1800
1769 @OETestTag("runqemu") 1801 @OETestTag("runqemu")
1770 def test_devtool_deploy_target(self): 1802 def test_devtool_deploy_target(self):
1771 self._check_runqemu_prerequisites() 1803 self._check_runqemu_prerequisites()
@@ -1802,36 +1834,41 @@ class DevtoolExtractTests(DevtoolBase):
1802 # Boot the image 1834 # Boot the image
1803 with runqemu(testimage) as qemu: 1835 with runqemu(testimage) as qemu:
1804 # Now really test deploy-target 1836 # Now really test deploy-target
1805 result = runCmd('devtool deploy-target -c %s root@%s' % (testrecipe, qemu.ip)) 1837 for extra_opt in ['', '--strip']:
1806 # Run a test command to see if it was installed properly 1838 deploy_cmd= 'devtool deploy-target -c %s root@%s %s' % (testrecipe, qemu.ip, extra_opt)
1807 sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' 1839 self.logger.debug(deploy_cmd)
1808 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand)) 1840 result = runCmd(deploy_cmd)
1809 # Check if it deployed all of the files with the right ownership/perms 1841 # Run a test command to see if it was installed properly
1810 # First look on the host - need to do this under pseudo to get the correct ownership/perms 1842 sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
1811 bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD'], testrecipe) 1843 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
1812 installdir = bb_vars['D'] 1844 # Check if it deployed all of the files with the right ownership/perms
1813 fakerootenv = bb_vars['FAKEROOTENV'] 1845 # First look on the host - need to do this under pseudo to get the correct ownership/perms
1814 fakerootcmd = bb_vars['FAKEROOTCMD'] 1846 bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD'], testrecipe)
1815 result = runCmd('%s %s find . -type f -exec ls -l {} \\;' % (fakerootenv, fakerootcmd), cwd=installdir) 1847 installdir = bb_vars['D']
1816 filelist1 = self._process_ls_output(result.output) 1848 fakerootenv = bb_vars['FAKEROOTENV']
1817 1849 fakerootcmd = bb_vars['FAKEROOTCMD']
1818 # Now look on the target 1850 result = runCmd('%s %s find . -type f -exec ls -l {} \\;' % (fakerootenv, fakerootcmd), cwd=installdir)
1819 tempdir2 = tempfile.mkdtemp(prefix='devtoolqa') 1851 filelist1 = self._process_ls_output(result.output)
1820 self.track_for_cleanup(tempdir2) 1852
1821 tmpfilelist = os.path.join(tempdir2, 'files.txt') 1853 # Now look on the target
1822 with open(tmpfilelist, 'w') as f: 1854 tempdir2 = tempfile.mkdtemp(prefix='devtoolqa')
1823 for line in filelist1: 1855 self.track_for_cleanup(tempdir2)
1824 splitline = line.split() 1856 tmpfilelist = os.path.join(tempdir2, 'files.txt')
1825 f.write(splitline[-1] + '\n') 1857 with open(tmpfilelist, 'w') as f:
1826 result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip)) 1858 for line in filelist1:
1827 filelist2 = self._process_ls_output(result.output) 1859 splitline = line.split()
1828 filelist1.sort(key=lambda item: item.split()[-1]) 1860 f.write(splitline[-1] + '\n')
1829 filelist2.sort(key=lambda item: item.split()[-1]) 1861 result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip))
1830 self.assertEqual(filelist1, filelist2) 1862 filelist2 = self._process_ls_output(result.output)
1831 # Test undeploy-target 1863 filelist1.sort(key=lambda item: item.split()[-1])
1832 result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip)) 1864 filelist2.sort(key=lambda item: item.split()[-1])
1833 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True) 1865 self.assertEqual(filelist1, filelist2)
1834 self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have') 1866 # Test undeploy-target
1867 result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip))
1868 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
1869 self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
1870
1871class DevtoolBuildImageTests(DevtoolBase):
1835 1872
1836 def test_devtool_build_image(self): 1873 def test_devtool_build_image(self):
1837 """Test devtool build-image plugin""" 1874 """Test devtool build-image plugin"""
@@ -1920,13 +1957,11 @@ class DevtoolUpgradeTests(DevtoolBase):
1920 self.assertNotIn(recipe, result.output) 1957 self.assertNotIn(recipe, result.output)
1921 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') 1958 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
1922 1959
1923 def test_devtool_upgrade_git(self): 1960 def _test_devtool_upgrade_git_by_recipe(self, recipe, commit):
1924 # Check preconditions 1961 # Check preconditions
1925 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') 1962 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1926 self.track_for_cleanup(self.workspacedir) 1963 self.track_for_cleanup(self.workspacedir)
1927 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') 1964 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1928 recipe = 'devtool-upgrade-test2'
1929 commit = '6cc6077a36fe2648a5f993fe7c16c9632f946517'
1930 oldrecipefile = get_bb_var('FILE', recipe) 1965 oldrecipefile = get_bb_var('FILE', recipe)
1931 tempdir = tempfile.mkdtemp(prefix='devtoolqa') 1966 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1932 self.track_for_cleanup(tempdir) 1967 self.track_for_cleanup(tempdir)
@@ -1956,6 +1991,12 @@ class DevtoolUpgradeTests(DevtoolBase):
1956 self.assertNotIn(recipe, result.output) 1991 self.assertNotIn(recipe, result.output)
1957 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') 1992 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
1958 1993
1994 def test_devtool_upgrade_git(self):
1995 self._test_devtool_upgrade_git_by_recipe('devtool-upgrade-test2', '6cc6077a36fe2648a5f993fe7c16c9632f946517')
1996
1997 def test_devtool_upgrade_gitsm(self):
1998 self._test_devtool_upgrade_git_by_recipe('devtool-upgrade-test5', 'a2885dd7d25380d23627e7544b7bbb55014b16ee')
1999
1959 def test_devtool_upgrade_drop_md5sum(self): 2000 def test_devtool_upgrade_drop_md5sum(self):
1960 # Check preconditions 2001 # Check preconditions
1961 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') 2002 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
@@ -2004,6 +2045,52 @@ class DevtoolUpgradeTests(DevtoolBase):
2004 newlines = f.readlines() 2045 newlines = f.readlines()
2005 self.assertEqual(desiredlines, newlines) 2046 self.assertEqual(desiredlines, newlines)
2006 2047
2048 def test_devtool_upgrade_recipe_upgrade_extra_tasks(self):
2049 # Check preconditions
2050 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
2051 self.track_for_cleanup(self.workspacedir)
2052 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
2053 recipe = 'python3-guessing-game'
2054 version = '0.2.0'
2055 commit = '40cf004c2772ffa20ea803fa3be1528a75be3e98'
2056 oldrecipefile = get_bb_var('FILE', recipe)
2057 oldcratesincfile = os.path.join(os.path.dirname(oldrecipefile), os.path.basename(oldrecipefile).strip('_git.bb') + '-crates.inc')
2058 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
2059 self.track_for_cleanup(tempdir)
2060 # Check that recipe is not already under devtool control
2061 result = runCmd('devtool status')
2062 self.assertNotIn(recipe, result.output)
2063 # Check upgrade
2064 result = runCmd('devtool upgrade %s %s --version %s --srcrev %s' % (recipe, tempdir, version, commit))
2065 # Check if srctree at least is populated
2066 self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, commit))
2067 # Check new recipe file and new -crates.inc files are present
2068 newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldrecipefile))
2069 newcratesincfile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldcratesincfile))
2070 self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
2071 self.assertExists(newcratesincfile, 'Recipe crates.inc file should exist after upgrade')
2072 # Check devtool status and make sure recipe is present
2073 result = runCmd('devtool status')
2074 self.assertIn(recipe, result.output)
2075 self.assertIn(tempdir, result.output)
2076 # Check recipe got changed as expected
2077 with open(oldrecipefile + '.upgraded', 'r') as f:
2078 desiredlines = f.readlines()
2079 with open(newrecipefile, 'r') as f:
2080 newlines = f.readlines()
2081 self.assertEqual(desiredlines, newlines)
2082 # Check crates.inc got changed as expected
2083 with open(oldcratesincfile + '.upgraded', 'r') as f:
2084 desiredlines = f.readlines()
2085 with open(newcratesincfile, 'r') as f:
2086 newlines = f.readlines()
2087 self.assertEqual(desiredlines, newlines)
2088 # Check devtool reset recipe
2089 result = runCmd('devtool reset %s -n' % recipe)
2090 result = runCmd('devtool status')
2091 self.assertNotIn(recipe, result.output)
2092 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
2093
2007 def test_devtool_layer_plugins(self): 2094 def test_devtool_layer_plugins(self):
2008 """Test that devtool can use plugins from other layers. 2095 """Test that devtool can use plugins from other layers.
2009 2096
@@ -2329,7 +2416,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2329 newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename) 2416 newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename)
2330 self.assertExists(newsrctree, 'Source directory not renamed') 2417 self.assertExists(newsrctree, 'Source directory not renamed')
2331 checkvars = {} 2418 checkvars = {}
2332 checkvars['S'] = '${WORKDIR}/%s-%s' % (recipename, recipever) 2419 checkvars['S'] = '${UNPACKDIR}/%s-%s' % (recipename, recipever)
2333 checkvars['SRC_URI'] = url 2420 checkvars['SRC_URI'] = url
2334 self._test_recipe_contents(newrecipefile, checkvars, []) 2421 self._test_recipe_contents(newrecipefile, checkvars, [])
2335 # Try again - change just name this time 2422 # Try again - change just name this time
@@ -2341,7 +2428,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2341 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists') 2428 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
2342 self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed') 2429 self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed')
2343 checkvars = {} 2430 checkvars = {}
2344 checkvars['S'] = '${WORKDIR}/%s-${PV}' % recipename 2431 checkvars['S'] = '${UNPACKDIR}/%s-${PV}' % recipename
2345 checkvars['SRC_URI'] = url.replace(recipever, '${PV}') 2432 checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
2346 self._test_recipe_contents(newrecipefile, checkvars, []) 2433 self._test_recipe_contents(newrecipefile, checkvars, [])
2347 # Try again - change just version this time 2434 # Try again - change just version this time
@@ -2352,7 +2439,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2352 self.assertExists(newrecipefile, 'Recipe file not renamed') 2439 self.assertExists(newrecipefile, 'Recipe file not renamed')
2353 self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists') 2440 self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists')
2354 checkvars = {} 2441 checkvars = {}
2355 checkvars['S'] = '${WORKDIR}/${BPN}-%s' % recipever 2442 checkvars['S'] = '${UNPACKDIR}/${BPN}-%s' % recipever
2356 checkvars['SRC_URI'] = url 2443 checkvars['SRC_URI'] = url
2357 self._test_recipe_contents(newrecipefile, checkvars, []) 2444 self._test_recipe_contents(newrecipefile, checkvars, [])
2358 2445
@@ -2442,7 +2529,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
2442 'IMAGE_CLASSES += "image-combined-dbg"', 2529 'IMAGE_CLASSES += "image-combined-dbg"',
2443 'IMAGE_GEN_DEBUGFS = "1"', 2530 'IMAGE_GEN_DEBUGFS = "1"',
2444 'IMAGE_INSTALL:append = " gdbserver %s"' % ' '.join( 2531 'IMAGE_INSTALL:append = " gdbserver %s"' % ' '.join(
2445 [r + '-ptest' for r in recipe_names]) 2532 [r + '-ptest' for r in recipe_names]),
2533 'DISTRO_FEATURES:append = " ptest"'
2446 ] 2534 ]
2447 self.write_config("\n".join(conf_lines)) 2535 self.write_config("\n".join(conf_lines))
2448 2536
@@ -2474,7 +2562,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
2474 self.track_for_cleanup(tempdir) 2562 self.track_for_cleanup(tempdir)
2475 self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name) 2563 self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name)
2476 2564
2477 result = runCmd('devtool modify %s -x %s' % (recipe_name, tempdir)) 2565 result = runCmd('devtool modify %s -x %s --debug-build' % (recipe_name, tempdir))
2478 self.assertExists(os.path.join(tempdir, build_file), 2566 self.assertExists(os.path.join(tempdir, build_file),
2479 'Extracted source could not be found') 2567 'Extracted source could not be found')
2480 self.assertExists(os.path.join(self.workspacedir, 'conf', 2568 self.assertExists(os.path.join(self.workspacedir, 'conf',
@@ -2526,11 +2614,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
2526 i_and_d_script_path = os.path.join( 2614 i_and_d_script_path = os.path.join(
2527 self._workspace_scripts_dir(recipe_name), i_and_d_script) 2615 self._workspace_scripts_dir(recipe_name), i_and_d_script)
2528 self.assertExists(i_and_d_script_path) 2616 self.assertExists(i_and_d_script_path)
2529 del_script = "delete_package_dirs_" + recipe_id
2530 del_script_path = os.path.join(
2531 self._workspace_scripts_dir(recipe_name), del_script)
2532 self.assertExists(del_script_path)
2533 runCmd(del_script_path, cwd=tempdir)
2534 2617
2535 def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): 2618 def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe):
2536 """Verify deployment and execution in Qemu system work for one recipe. 2619 """Verify deployment and execution in Qemu system work for one recipe.
diff --git a/meta/lib/oeqa/selftest/cases/distrodata.py b/meta/lib/oeqa/selftest/cases/distrodata.py
index ad952c004b..f2c6124d70 100644
--- a/meta/lib/oeqa/selftest/cases/distrodata.py
+++ b/meta/lib/oeqa/selftest/cases/distrodata.py
@@ -20,10 +20,10 @@ class Distrodata(OESelftestTestCase):
20 feature = 'LICENSE_FLAGS_ACCEPTED += " commercial"\n' 20 feature = 'LICENSE_FLAGS_ACCEPTED += " commercial"\n'
21 self.write_config(feature) 21 self.write_config(feature)
22 22
23 pkgs = oe.recipeutils.get_recipe_upgrade_status() 23 pkggroups = oe.recipeutils.get_recipe_upgrade_status()
24 24
25 regressed_failures = [pkg[0] for pkg in pkgs if pkg[1] == 'UNKNOWN_BROKEN'] 25 regressed_failures = [pkg['pn'] for pkgs in pkggroups for pkg in pkgs if pkg['status'] == 'UNKNOWN_BROKEN']
26 regressed_successes = [pkg[0] for pkg in pkgs if pkg[1] == 'KNOWN_BROKEN'] 26 regressed_successes = [pkg['pn'] for pkgs in pkggroups for pkg in pkgs if pkg['status'] == 'KNOWN_BROKEN']
27 msg = "" 27 msg = ""
28 if len(regressed_failures) > 0: 28 if len(regressed_failures) > 0:
29 msg = msg + """ 29 msg = msg + """
@@ -55,8 +55,8 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re
55 return False 55 return False
56 56
57 def is_maintainer_exception(entry): 57 def is_maintainer_exception(entry):
58 exceptions = ["musl", "newlib", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data", 58 exceptions = ["musl", "newlib", "picolibc", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", "libx11-compose-data",
59 "cve-update-nvd2-native",] 59 "cve-update-nvd2-native", "barebox", "libglvnd"]
60 for i in exceptions: 60 for i in exceptions:
61 if i in entry: 61 if i in entry:
62 return True 62 return True
@@ -115,3 +115,15 @@ The list of oe-core recipes with maintainers is empty. This may indicate that th
115 self.fail(""" 115 self.fail("""
116Unable to find recipes for the following entries in maintainers.inc: 116Unable to find recipes for the following entries in maintainers.inc:
117""" + "\n".join(['%s' % i for i in missing_recipes])) 117""" + "\n".join(['%s' % i for i in missing_recipes]))
118
119 def test_common_include_recipes(self):
120 """
121 Summary: Test that obtaining recipes that share includes between them returns a sane result
122 Expected: At least cmake and qemu entries are present in the output
123 Product: oe-core
124 Author: Alexander Kanavin <alex.kanavin@gmail.com>
125 """
126 recipes = oe.recipeutils.get_common_include_recipes()
127
128 self.assertIn({'qemu-system-native', 'qemu', 'qemu-native'}, recipes)
129 self.assertIn({'cmake-native', 'cmake'}, recipes)
diff --git a/meta/lib/oeqa/selftest/cases/efibootpartition.py b/meta/lib/oeqa/selftest/cases/efibootpartition.py
index fa74103dec..fcfcdaf7e4 100644
--- a/meta/lib/oeqa/selftest/cases/efibootpartition.py
+++ b/meta/lib/oeqa/selftest/cases/efibootpartition.py
@@ -6,7 +6,7 @@
6# 6#
7 7
8from oeqa.selftest.case import OESelftestTestCase 8from oeqa.selftest.case import OESelftestTestCase
9from oeqa.utils.commands import bitbake, runqemu 9from oeqa.utils.commands import bitbake, runqemu, get_bb_var
10from oeqa.core.decorator.data import skipIfNotMachine 10from oeqa.core.decorator.data import skipIfNotMachine
11import oe.types 11import oe.types
12 12
@@ -14,17 +14,18 @@ class GenericEFITest(OESelftestTestCase):
14 """EFI booting test class""" 14 """EFI booting test class"""
15 @skipIfNotMachine("qemux86-64", "test is qemux86-64 specific currently") 15 @skipIfNotMachine("qemux86-64", "test is qemux86-64 specific currently")
16 def test_boot_efi(self): 16 def test_boot_efi(self):
17 cmd = "runqemu nographic serial wic ovmf" 17 image = "core-image-minimal"
18 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', image) or ""
19 cmd = "runqemu %s nographic serial wic ovmf" % (runqemu_params)
18 if oe.types.qemu_use_kvm(self.td.get('QEMU_USE_KVM', 0), self.td["TARGET_ARCH"]): 20 if oe.types.qemu_use_kvm(self.td.get('QEMU_USE_KVM', 0), self.td["TARGET_ARCH"]):
19 cmd += " kvm" 21 cmd += " kvm"
20 image = "core-image-minimal"
21 22
22 self.write_config(""" 23 self.write_config("""
23EFI_PROVIDER = "systemd-boot" 24EFI_PROVIDER = "grub-efi"
24IMAGE_FSTYPES:pn-%s:append = " wic" 25IMAGE_FSTYPES:pn-%s:append = " wic"
25MACHINE_FEATURES:append = " efi" 26MACHINE_FEATURES:append = " efi"
26WKS_FILE = "efi-bootdisk.wks.in" 27WKS_FILE = "efi-bootdisk.wks.in"
27IMAGE_INSTALL:append = " grub-efi systemd-boot kernel-image-bzimage" 28IMAGE_INSTALL:append = " grub-efi kernel-image-bzimage"
28""" 29"""
29% (image)) 30% (image))
30 31
diff --git a/meta/lib/oeqa/selftest/cases/esdk.py b/meta/lib/oeqa/selftest/cases/esdk.py
index 9f5de2cde7..7a5fe00a08 100644
--- a/meta/lib/oeqa/selftest/cases/esdk.py
+++ b/meta/lib/oeqa/selftest/cases/esdk.py
@@ -27,11 +27,7 @@ class oeSDKExtSelfTest(OESelftestTestCase):
27 return glob.glob(pattern)[0] 27 return glob.glob(pattern)[0]
28 28
29 @staticmethod 29 @staticmethod
30 def run_esdk_cmd(env_eSDK, tmpdir_eSDKQA, cmd, postconfig=None, **options): 30 def run_esdk_cmd(env_eSDK, tmpdir_eSDKQA, cmd, **options):
31 if postconfig:
32 esdk_conf_file = os.path.join(tmpdir_eSDKQA, 'conf', 'local.conf')
33 with open(esdk_conf_file, 'a+') as f:
34 f.write(postconfig)
35 if not options: 31 if not options:
36 options = {} 32 options = {}
37 if not 'shell' in options: 33 if not 'shell' in options:
diff --git a/meta/lib/oeqa/selftest/cases/fetch.py b/meta/lib/oeqa/selftest/cases/fetch.py
index 44099176fc..1beef5cfed 100644
--- a/meta/lib/oeqa/selftest/cases/fetch.py
+++ b/meta/lib/oeqa/selftest/cases/fetch.py
@@ -74,8 +74,8 @@ class Dependencies(OESelftestTestCase):
74 tinfoil.prepare(config_only=False, quiet=2) 74 tinfoil.prepare(config_only=False, quiet=2)
75 75
76 r = """ 76 r = """
77 LICENSE="CLOSED" 77 LICENSE = "CLOSED"
78 SRC_URI="http://example.com/tarball.zip" 78 SRC_URI = "http://example.com/tarball.zip"
79 """ 79 """
80 f = self.write_recipe(textwrap.dedent(r), tempdir) 80 f = self.write_recipe(textwrap.dedent(r), tempdir)
81 d = tinfoil.parse_recipe_file(f) 81 d = tinfoil.parse_recipe_file(f)
@@ -84,8 +84,8 @@ class Dependencies(OESelftestTestCase):
84 84
85 # Verify that the downloadfilename overrides the URI 85 # Verify that the downloadfilename overrides the URI
86 r = """ 86 r = """
87 LICENSE="CLOSED" 87 LICENSE = "CLOSED"
88 SRC_URI="https://example.com/tarball;downloadfilename=something.zip" 88 SRC_URI = "https://example.com/tarball;downloadfilename=something.zip"
89 """ 89 """
90 f = self.write_recipe(textwrap.dedent(r), tempdir) 90 f = self.write_recipe(textwrap.dedent(r), tempdir)
91 d = tinfoil.parse_recipe_file(f) 91 d = tinfoil.parse_recipe_file(f)
@@ -93,8 +93,8 @@ class Dependencies(OESelftestTestCase):
93 self.assertIn("unzip-native", d.getVarFlag("do_unpack", "depends") or "") 93 self.assertIn("unzip-native", d.getVarFlag("do_unpack", "depends") or "")
94 94
95 r = """ 95 r = """
96 LICENSE="CLOSED" 96 LICENSE = "CLOSED"
97 SRC_URI="ftp://example.com/tarball.lz" 97 SRC_URI = "ftp://example.com/tarball.lz"
98 """ 98 """
99 f = self.write_recipe(textwrap.dedent(r), tempdir) 99 f = self.write_recipe(textwrap.dedent(r), tempdir)
100 d = tinfoil.parse_recipe_file(f) 100 d = tinfoil.parse_recipe_file(f)
@@ -102,8 +102,8 @@ class Dependencies(OESelftestTestCase):
102 self.assertIn("lzip-native", d.getVarFlag("do_unpack", "depends")) 102 self.assertIn("lzip-native", d.getVarFlag("do_unpack", "depends"))
103 103
104 r = """ 104 r = """
105 LICENSE="CLOSED" 105 LICENSE = "CLOSED"
106 SRC_URI="git://example.com/repo;branch=master;rev=ffffffffffffffffffffffffffffffffffffffff" 106 SRC_URI = "git://example.com/repo;branch=master;rev=ffffffffffffffffffffffffffffffffffffffff"
107 """ 107 """
108 f = self.write_recipe(textwrap.dedent(r), tempdir) 108 f = self.write_recipe(textwrap.dedent(r), tempdir)
109 d = tinfoil.parse_recipe_file(f) 109 d = tinfoil.parse_recipe_file(f)
diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py
index 347c065377..195b9ee8b5 100644
--- a/meta/lib/oeqa/selftest/cases/fitimage.py
+++ b/meta/lib/oeqa/selftest/cases/fitimage.py
@@ -4,12 +4,740 @@
4# SPDX-License-Identifier: MIT 4# SPDX-License-Identifier: MIT
5# 5#
6 6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
9import os 7import os
10import re 8import re
9import shlex
10import logging
11import pprint
12import tempfile
13
14import oe.fitimage
15
16from oeqa.selftest.case import OESelftestTestCase
17from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_bb_var
18
19
20class BbVarsMockGenKeys:
21 def __init__(self, keydir, gen_keys="0", sign_enabled="0", keyname="", sign_ind="0", img_keyname=""):
22 self.bb_vars = {
23 'FIT_GENERATE_KEYS': gen_keys,
24 'FIT_KEY_GENRSA_ARGS': "-F4",
25 'FIT_KEY_REQ_ARGS': "-batch -new",
26 'FIT_KEY_SIGN_PKCS': "-x509",
27 'FIT_SIGN_INDIVIDUAL': sign_ind,
28 'FIT_SIGN_NUMBITS': "2048",
29 'UBOOT_SIGN_ENABLE': sign_enabled,
30 'UBOOT_SIGN_IMG_KEYNAME': img_keyname,
31 'UBOOT_SIGN_KEYDIR': keydir,
32 'UBOOT_SIGN_KEYNAME': keyname,
33 }
34
35 def getVar(self, var):
36 return self.bb_vars[var]
37
38class FitImageTestCase(OESelftestTestCase):
39 """Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass
40
41 A brief summary showing the structure of a test case:
42
43 self._test_fitimage()
44 # Generate a local.conf file and bitbake the bootloader or the kernel
45 self._bitbake_fit_image()
46
47 # Check if the its file contains the expected paths and attributes.
48 # The _get_req_* functions are implemented by more specific chield classes.
49 self._check_its_file()
50 req_its_paths = self._get_req_its_paths()
51 req_sigvalues_config = self._get_req_sigvalues_config()
52 req_sigvalues_image = self._get_req_sigvalues_image()
53 # Compare the its file against req_its_paths, req_sigvalues_config, req_sigvalues_image
54
55 # Call the dumpimage utiliy and check that it prints all the expected paths and attributes
56 # The _get_req_* functions are implemented by more specific chield classes.
57 self._check_fitimage()
58 self._get_req_sections()
59 # Compare the output of the dumpimage utility against
60 """
61
62 MKIMAGE_HASH_LENGTHS = { 'sha256': 64, 'sha384': 96, 'sha512': 128 }
63 MKIMAGE_SIGNATURE_LENGTHS = { 'rsa2048': 512 }
64
65 def _gen_signing_key(self, bb_vars):
66 """Generate a key pair and a singing certificate
67
68 Generate a UBOOT_SIGN_KEYNAME in the UBOOT_SIGN_KEYDIR similar to what
69 the FIT_GENERATE_KEYS feature does. However, having a static key is
70 probably a more realistic use case than generating a random key with
71 each clean build. So this needs to be tested as well.
72 The FIT_GENERATE_KEYS generates 2 keys: The UBOOT_SIGN_KEYNAME and the
73 UBOOT_SIGN_IMG_KEYNAME. The UBOOT_SIGN_IMG_KEYNAME is used by the
74 FIT_SIGN_INDIVIDUAL feature only. Testing if everything is working if
75 there is only one key available is important as well. Therefore this
76 function generates only the keys which are really needed, not just two.
77 """
11 78
12class FitImageTests(OESelftestTestCase): 79 # Define some variables which are usually defined by the kernel-fitimage.bbclass.
80 # But for testing purpose check if the uboot-sign.bbclass is independent from
81 # the kernel-fitimage.bbclass
82 fit_sign_numbits = bb_vars.get('FIT_SIGN_NUMBITS', "2048")
83 fit_key_genrsa_args = bb_vars.get('FIT_KEY_GENRSA_ARGS', "-F4")
84 fit_key_req_args = bb_vars.get('FIT_KEY_REQ_ARGS', "-batch -new")
85 fit_key_sign_pkcs = bb_vars.get('FIT_KEY_SIGN_PKCS', "-x509")
86
87 uboot_sign_keydir = bb_vars['UBOOT_SIGN_KEYDIR']
88 sign_keys = [bb_vars['UBOOT_SIGN_KEYNAME']]
89 if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1":
90 sign_keys.append(bb_vars['UBOOT_SIGN_IMG_KEYNAME'])
91 for sign_key in sign_keys:
92 sing_key_path = os.path.join(uboot_sign_keydir, sign_key)
93 if not os.path.isdir(uboot_sign_keydir):
94 os.makedirs(uboot_sign_keydir)
95 openssl_bindir = FitImageTestCase._setup_native('openssl-native')
96 openssl_path = os.path.join(openssl_bindir, 'openssl')
97 runCmd("%s genrsa %s -out %s.key %s" % (
98 openssl_path,
99 fit_key_genrsa_args,
100 sing_key_path,
101 fit_sign_numbits
102 ))
103 runCmd("%s req %s %s -key %s.key -out %s.crt" % (
104 openssl_path,
105 fit_key_req_args,
106 fit_key_sign_pkcs,
107 sing_key_path,
108 sing_key_path
109 ))
110
111 @staticmethod
112 def _gen_random_file(file_path, num_bytes=65536):
113 with open(file_path, 'wb') as file_out:
114 file_out.write(os.urandom(num_bytes))
115
116 @staticmethod
117 def _setup_native(native_recipe):
118 """Build a native recipe and return the path to its bindir in RECIPE_SYSROOT_NATIVE"""
119 bitbake(native_recipe + " -c addto_recipe_sysroot")
120 vars = get_bb_vars(['RECIPE_SYSROOT_NATIVE', 'bindir'], native_recipe)
121 return os.path.join(vars['RECIPE_SYSROOT_NATIVE'], vars['bindir'])
122
123 def _verify_fit_image_signature(self, uboot_tools_bindir, fitimage_path, dtb_path, conf_name=None):
124 """Verify the signature of a fit configuration
125
126 The fit_check_sign utility from u-boot-tools-native is called.
127 uboot-fit_check_sign -f fitImage -k $dtb_path -c conf-$dtb_name
128 dtb_path refers to a binary device tree containing the public key.
129 """
130 fit_check_sign_path = os.path.join(uboot_tools_bindir, 'uboot-fit_check_sign')
131 cmd = '%s -f %s -k %s' % (fit_check_sign_path, fitimage_path, dtb_path)
132 if conf_name:
133 cmd += ' -c %s' % conf_name
134 result = runCmd(cmd)
135 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
136 self.assertIn("Signature check OK", result.output)
137
138 def _verify_dtb_property(self, dtc_bindir, dtb_path, node_path, property_name, req_property, absent=False):
139 """Verify device tree properties
140
141 The fdtget utility from dtc-native is called and the property is compared.
142 """
143 fdtget_path = os.path.join(dtc_bindir, 'fdtget')
144 cmd = '%s %s %s %s' % (fdtget_path, dtb_path, node_path, property_name)
145 if absent:
146 result = runCmd(cmd, ignore_status=True)
147 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
148 self.assertIn("FDT_ERR_NOTFOUND", result.output)
149 else:
150 result = runCmd(cmd)
151 self.logger.debug("%s\nreturned: %s\n%s", cmd, str(result.status), result.output)
152 self.assertEqual(req_property, result.output.strip())
153
154 @staticmethod
155 def _find_string_in_bin_file(file_path, search_string):
156 """find strings in a binary file
157
158 Shell equivalent: strings "$1" | grep "$2" | wc -l
159 return number of matches
160 """
161 found_positions = 0
162 with open(file_path, 'rb') as file:
163 content = file.read().decode('ascii', errors='ignore')
164 found_positions = content.count(search_string)
165 return found_positions
166
167 @staticmethod
168 def _get_uboot_mkimage_sign_args(uboot_mkimage_sign_args):
169 """Retrive the string passed via -c to the mkimage command
170
171 Example: If a build configutation defines
172 UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
173 this function returns "a smart comment"
174 """
175 a_comment = None
176 if uboot_mkimage_sign_args:
177 mkimage_args = shlex.split(uboot_mkimage_sign_args)
178 try:
179 c_index = mkimage_args.index('-c')
180 a_comment = mkimage_args[c_index+1]
181 except ValueError:
182 pass
183 return a_comment
184
185 @staticmethod
186 def _get_dtb_files(bb_vars):
187 """Return a list of devicetree names
188
189 The list should be used to check the dtb and conf nodes in the FIT image or its file.
190 In addition to the entries from KERNEL_DEVICETREE, the external devicetree and the
191 external devicetree overlay added by the test recipe bbb-dtbs-as-ext are handled as well.
192 """
193 kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE')
194 all_dtbs = []
195 dtb_symlinks = []
196 if kernel_devicetree:
197 all_dtbs += [os.path.basename(dtb) for dtb in kernel_devicetree.split()]
198 # Support only the test recipe which provides 1 devicetree and 1 devicetree overlay
199 pref_prov_dtb = bb_vars.get('PREFERRED_PROVIDER_virtual/dtb')
200 if pref_prov_dtb == "bbb-dtbs-as-ext":
201 all_dtbs += ["am335x-bonegreen-ext.dtb", "BBORG_RELAY-00A2.dtbo"]
202 dtb_symlinks.append("am335x-bonegreen-ext-alias.dtb")
203 return (all_dtbs, dtb_symlinks)
204
205 def _is_req_dict_in_dict(self, found_dict, req_dict):
206 """
207 Check if all key-value pairs in the required dictionary are present in the found dictionary.
208
209 This function recursively checks if the required dictionary (`req_dict`) is a subset of the found dictionary (`found_dict`).
210 It supports nested dictionaries, strings, lists, and sets as values.
211
212 Args:
213 found_dict (dict): The dictionary to search within.
214 req_dict (dict): The dictionary containing the required key-value pairs.
215 """
216 for key, value in req_dict.items():
217 self.assertIn(key, found_dict)
218 if isinstance(value, dict):
219 self._is_req_dict_in_dict(found_dict[key], value)
220 elif isinstance(value, str):
221 self.assertIn(value, found_dict[key])
222 elif isinstance(value, list):
223 self.assertLessEqual(set(value), set(found_dict[key]))
224 elif isinstance(value, set):
225 self.assertLessEqual(value, found_dict[key])
226 else:
227 self.assertEqual(value, found_dict[key])
228
229 def _check_its_file(self, bb_vars, its_file_path):
230 """Check if the its file contains the expected sections and fields"""
231 # print the its file for debugging
232 if logging.DEBUG >= self.logger.level:
233 with open(its_file_path) as its_file:
234 self.logger.debug("its file: %s" % its_file.read())
235
236 # Generate a list of expected paths in the its file
237 req_its_paths = self._get_req_its_paths(bb_vars)
238 self.logger.debug("req_its_paths:\n%s\n" % pprint.pformat(req_its_paths, indent=4))
239
240 # Generate a dict of expected configuration signature nodes
241 req_sigvalues_config = self._get_req_sigvalues_config(bb_vars)
242 self.logger.debug("req_sigvalues_config:\n%s\n" % pprint.pformat(req_sigvalues_config, indent=4))
243
244 # Generate a dict of expected image signature nodes
245 req_sigvalues_image = self._get_req_sigvalues_image(bb_vars)
246 self.logger.debug("req_sigvalues_image:\n%s\n" % pprint.pformat(req_sigvalues_image, indent=4))
247
248 # Parse the its file for paths and signatures
249 its_path = []
250 its_paths = []
251 linect = 0
252 sigs = {}
253 with open(its_file_path) as its_file:
254 for line in its_file:
255 linect += 1
256 line = line.strip()
257 if line.endswith('};'):
258 its_path.pop()
259 elif line.endswith('{'):
260 its_path.append(line[:-1].strip())
261 its_paths.append(its_path[:])
262 # kernel-fitimage uses signature-1, uboot-sign uses signature
263 elif its_path and (its_path[-1] == 'signature-1' or its_path[-1] == 'signature'):
264 itsdotpath = '.'.join(its_path)
265 if not itsdotpath in sigs:
266 sigs[itsdotpath] = {}
267 if not '=' in line or not line.endswith(';'):
268 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (its_file_path, linect, line))
269 key, value = line.split('=', 1)
270 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
271
272 # Check if all expected paths are found in the its file
273 self.logger.debug("itspaths:\n%s\n" % pprint.pformat(its_paths, indent=4))
274 for req_path in req_its_paths:
275 if not req_path in its_paths:
276 self.fail('Missing path in its file: %s (%s)' % (req_path, its_file_path))
277
278 # Check if all the expected singnature nodes (images and configurations) are found
279 self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4))
280 if req_sigvalues_config or req_sigvalues_image:
281 for its_path, values in sigs.items():
282 if bb_vars.get('FIT_CONF_PREFIX', "conf-") in its_path:
283 reqsigvalues = req_sigvalues_config
284 else:
285 reqsigvalues = req_sigvalues_image
286 for reqkey, reqvalue in reqsigvalues.items():
287 value = values.get(reqkey, None)
288 if value is None:
289 self.fail('Missing key "%s" in its file signature section %s (%s)' % (reqkey, its_path, its_file_path))
290 self.assertEqual(value, reqvalue)
291
292 # Generate a list of expected fields in the its file
293 req_its_fields = self._get_req_its_fields(bb_vars)
294 self.logger.debug("req_its_fields:\n%s\n" % pprint.pformat(req_its_fields, indent=4))
295
296 # Check if all expected fields are in the its file
297 if req_its_fields:
298 field_index = 0
299 field_index_last = len(req_its_fields) - 1
300 with open(its_file_path) as its_file:
301 for line in its_file:
302 if req_its_fields[field_index] in line:
303 if field_index < field_index_last:
304 field_index +=1
305 else:
306 break
307 self.assertEqual(field_index, field_index_last,
308 "Fields in Image Tree Source File %s did not match, error in finding %s"
309 % (its_file_path, req_its_fields[field_index]))
310
311 def _check_fitimage(self, bb_vars, fitimage_path, uboot_tools_bindir):
312 """Run dumpimage on the final FIT image and parse the output into a dict"""
313 dumpimage_path = os.path.join(uboot_tools_bindir, 'dumpimage')
314 cmd = '%s -l %s' % (dumpimage_path, fitimage_path)
315 self.logger.debug("Analyzing output from dumpimage: %s" % cmd)
316 dumpimage_result = runCmd(cmd)
317 in_section = None
318 sections = {}
319 self.logger.debug("dumpimage output: %s" % dumpimage_result.output)
320 for line in dumpimage_result.output.splitlines():
321 # Find potentially hashed and signed sections
322 if line.startswith((' Configuration', ' Image')):
323 in_section = re.search(r'\((.*)\)', line).groups()[0]
324 # Key value lines start with two spaces otherwise the section ended
325 elif not line.startswith(" "):
326 in_section = None
327 # Handle key value lines of this section
328 elif in_section:
329 if not in_section in sections:
330 sections[in_section] = {}
331 try:
332 key, value = line.split(':', 1)
333 key = key.strip()
334 value = value.strip()
335 except ValueError as val_err:
336 # Handle multiple entries as e.g. for Loadables as a list
337 if key and line.startswith(" "):
338 value = sections[in_section][key] + "," + line.strip()
339 else:
340 raise ValueError(f"Error processing line: '{line}'. Original error: {val_err}")
341 sections[in_section][key] = value
342
343 # Check if the requested dictionary is a subset of the parsed dictionary
344 req_sections, num_signatures = self._get_req_sections(bb_vars)
345 self.logger.debug("req_sections: \n%s\n" % pprint.pformat(req_sections, indent=4))
346 self.logger.debug("dumpimage sections: \n%s\n" % pprint.pformat(sections, indent=4))
347 self._is_req_dict_in_dict(sections, req_sections)
348
349 # Call the signing related checks if the function is provided by a inherited class
350 self._check_signing(bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path)
351
352 def _get_req_its_paths(self, bb_vars):
353 self.logger.error("This function needs to be implemented")
354 return []
355
356 def _get_req_its_fields(self, bb_vars):
357 self.logger.error("This function needs to be implemented")
358 return []
359
360 def _get_req_sigvalues_config(self, bb_vars):
361 self.logger.error("This function needs to be implemented")
362 return {}
363
364 def _get_req_sigvalues_image(self, bb_vars):
365 self.logger.error("This function needs to be implemented")
366 return {}
367
368 def _get_req_sections(self, bb_vars):
369 self.logger.error("This function needs to be implemented")
370 return ({}, 0)
371
372 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
373 """Verify the signatures in the FIT image."""
374 self.fail("Function needs to be implemented by inheriting classes")
375
376 def _bitbake_fit_image(self, bb_vars):
377 """Bitbake the FIT image and return the paths to the its file and the FIT image"""
378 self.fail("Function needs to be implemented by inheriting classes")
379
380 def _test_fitimage(self, bb_vars):
381 """Check if the its file and the FIT image are created and signed correctly"""
382 fitimage_its_path, fitimage_path = self._bitbake_fit_image(bb_vars)
383 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
384 self.assertExists(fitimage_path, "%s FIT image doesn't exist" % (fitimage_path))
385
386 self.logger.debug("Checking its: %s" % fitimage_its_path)
387 self._check_its_file(bb_vars, fitimage_its_path)
388
389 # Setup u-boot-tools-native
390 uboot_tools_bindir = FitImageTestCase._setup_native('u-boot-tools-native')
391
392 # Verify the FIT image
393 self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir)
394
395class KernelFitImageBase(FitImageTestCase):
396 """Test cases for the linux-yocto-fitimage recipe"""
397
398 def _fit_get_bb_vars(self, additional_vars=[]):
399 """Retrieve BitBake variables specific to the test case.
400
401 Call the get_bb_vars function once and get all variables needed by the test case.
402 """
403 internal_used = {
404 'DEPLOY_DIR_IMAGE',
405 'FIT_CONF_DEFAULT_DTB',
406 'FIT_CONF_PREFIX',
407 'FIT_DESC',
408 'FIT_HASH_ALG',
409 'FIT_KERNEL_COMP_ALG',
410 'FIT_SIGN_ALG',
411 'FIT_SIGN_INDIVIDUAL',
412 'FIT_UBOOT_ENV',
413 'INITRAMFS_IMAGE_BUNDLE',
414 'INITRAMFS_IMAGE_NAME',
415 'INITRAMFS_IMAGE',
416 'KERNEL_DEPLOYSUBDIR',
417 'KERNEL_DEVICETREE',
418 'KERNEL_FIT_LINK_NAME',
419 'MACHINE',
420 'PREFERRED_PROVIDER_virtual/dtb',
421 'UBOOT_ARCH',
422 'UBOOT_ENTRYPOINT',
423 'UBOOT_LOADADDRESS',
424 'UBOOT_MKIMAGE_KERNEL_TYPE',
425 'UBOOT_MKIMAGE_SIGN_ARGS',
426 'UBOOT_RD_ENTRYPOINT',
427 'UBOOT_RD_LOADADDRESS',
428 'UBOOT_SIGN_ENABLE',
429 'UBOOT_SIGN_IMG_KEYNAME',
430 'UBOOT_SIGN_KEYDIR',
431 'UBOOT_SIGN_KEYNAME',
432 }
433 bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), self.kernel_recipe)
434 self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4))
435 return bb_vars
436
437 def _config_add_kernel_classes(self, config):
438 config += '# Use kernel-fit-extra-artifacts.bbclass for the creation of the vmlinux artifact' + os.linesep
439 config += 'KERNEL_CLASSES = "kernel-fit-extra-artifacts"' + os.linesep
440 return config
441
442 @property
443 def kernel_recipe(self):
444 return "linux-yocto-fitimage"
445
446 def _config_add_uboot_env(self, config):
447 """Generate an u-boot environment
448
449 Create a boot.cmd file that is packed into the FIT image as a source-able text file.
450 Updates the configuration to include the boot.cmd file.
451 """
452 fit_uenv_file = "boot.cmd"
453 test_files_dir = "test-files"
454 fit_uenv_path = os.path.join(self.builddir, test_files_dir, fit_uenv_file)
455
456 config += '# Add an u-boot script to the fitImage' + os.linesep
457 config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep
458 config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep
459 config += 'SRC_URI:append:pn-%s = " file://${FIT_UBOOT_ENV}"' % self.kernel_recipe + os.linesep
460
461 if not os.path.isdir(test_files_dir):
462 os.makedirs(test_files_dir)
463 self.logger.debug("Writing to: %s" % fit_uenv_path)
464 with open(fit_uenv_path, "w") as f:
465 f.write('echo "hello world"')
466
467 return config
468
469 def _bitbake_fit_image(self, bb_vars):
470 """Bitbake the kernel and return the paths to the its file and the FIT image"""
471 bitbake(self.kernel_recipe)
472
473 # Find the right its file and the final fitImage and check if both files are available
474 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
475 initramfs_image = bb_vars['INITRAMFS_IMAGE']
476 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
477 initramfs_image_name = bb_vars['INITRAMFS_IMAGE_NAME']
478 kernel_fit_link_name = bb_vars['KERNEL_FIT_LINK_NAME']
479 if not initramfs_image and initramfs_image_bundle != "1":
480 fitimage_its_name = "fitImage-its-%s" % kernel_fit_link_name
481 fitimage_name = "fitImage"
482 elif initramfs_image and initramfs_image_bundle != "1":
483 fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
484 fitimage_name = "fitImage-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
485 elif initramfs_image and initramfs_image_bundle == "1":
486 fitimage_its_name = "fitImage-its-%s-%s" % (initramfs_image_name, kernel_fit_link_name)
487 fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT}
488 else:
489 self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE')
490 kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR']
491 if kernel_deploysubdir:
492 fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_its_name))
493 fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_name))
494 else:
495 fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name))
496 fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name))
497 return (fitimage_its_path, fitimage_path)
498
499 def _get_req_its_paths(self, bb_vars):
500 """Generate a list of expected paths in the its file
501
502 Example:
503 [
504 ['/', 'images', 'kernel-1', 'hash-1'],
505 ['/', 'images', 'kernel-1', 'signature-1'],
506 ]
507 """
508 dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars)
509 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
510 fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
511 initramfs_image = bb_vars['INITRAMFS_IMAGE']
512 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
513 uboot_sign_enable = bb_vars.get('UBOOT_SIGN_ENABLE')
514
515 # image nodes
516 images = [ 'kernel-1' ]
517 if dtb_files:
518 images += [ 'fdt-' + dtb for dtb in dtb_files ]
519 if fit_uboot_env:
520 images.append('bootscr-' + fit_uboot_env)
521 if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if
522 images.append('setup-1')
523 if initramfs_image and initramfs_image_bundle != "1":
524 images.append('ramdisk-1')
525
526 # configuration nodes (one per DTB and also one per symlink)
527 if dtb_files:
528 configurations = [bb_vars['FIT_CONF_PREFIX'] + conf for conf in dtb_files + dtb_symlinks]
529 else:
530 configurations = [bb_vars['FIT_CONF_PREFIX'] + '1']
531
532 # Create a list of paths for all image and configuration nodes
533 req_its_paths = []
534 for image in images:
535 req_its_paths.append(['/', 'images', image, 'hash-1'])
536 if uboot_sign_enable == "1" and fit_sign_individual == "1":
537 req_its_paths.append(['/', 'images', image, 'signature-1'])
538 for configuration in configurations:
539 req_its_paths.append(['/', 'configurations', configuration, 'hash-1'])
540 if uboot_sign_enable == "1":
541 req_its_paths.append(['/', 'configurations', configuration, 'signature-1'])
542 return req_its_paths
543
544 def _get_req_its_fields(self, bb_vars):
545 initramfs_image = bb_vars['INITRAMFS_IMAGE']
546 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
547 uboot_rd_loadaddress = bb_vars.get('UBOOT_RD_LOADADDRESS')
548 uboot_rd_entrypoint = bb_vars.get('UBOOT_RD_ENTRYPOINT')
549
550 its_field_check = [
551 'description = "%s";' % bb_vars['FIT_DESC'],
552 'description = "Linux kernel";',
553 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";',
554 # 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal...
555 'data = /incbin/("linux.bin");',
556 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";',
557 'os = "linux";',
558 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;',
559 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;',
560 ]
561 if initramfs_image and initramfs_image_bundle != "1":
562 its_field_check.append('type = "ramdisk";')
563 if uboot_rd_loadaddress:
564 its_field_check.append("load = <%s>;" % uboot_rd_loadaddress)
565 if uboot_rd_entrypoint:
566 its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint)
567
568 fit_conf_default_dtb = bb_vars.get('FIT_CONF_DEFAULT_DTB')
569 if fit_conf_default_dtb:
570 fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-")
571 its_field_check.append('default = "' + fit_conf_prefix + fit_conf_default_dtb + '";')
572
573 its_field_check.append('kernel = "kernel-1";')
574
575 if initramfs_image and initramfs_image_bundle != "1":
576 its_field_check.append('ramdisk = "ramdisk-1";')
577
578 return its_field_check
579
580 def _get_req_sigvalues_config(self, bb_vars):
581 """Generate a dictionary of expected configuration signature nodes"""
582 if bb_vars.get('UBOOT_SIGN_ENABLE') != "1":
583 return {}
584 sign_images = '"kernel", "fdt"'
585 if bb_vars['INITRAMFS_IMAGE'] and bb_vars['INITRAMFS_IMAGE_BUNDLE'] != "1":
586 sign_images += ', "ramdisk"'
587 if bb_vars['FIT_UBOOT_ENV']:
588 sign_images += ', "bootscr"'
589 req_sigvalues_config = {
590 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
591 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_KEYNAME'],
592 'sign-images': sign_images,
593 }
594 return req_sigvalues_config
595
596 def _get_req_sigvalues_image(self, bb_vars):
597 """Generate a dictionary of expected image signature nodes"""
598 if bb_vars['FIT_SIGN_INDIVIDUAL'] != "1":
599 return {}
600 req_sigvalues_image = {
601 'algo': '"%s,%s"' % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG']),
602 'key-name-hint': '"%s"' % bb_vars['UBOOT_SIGN_IMG_KEYNAME'],
603 }
604 return req_sigvalues_image
605
606 def _get_req_sections(self, bb_vars):
607 """Generate a dictionary of expected sections in the output of dumpimage"""
608 dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars)
609 fit_hash_alg = bb_vars['FIT_HASH_ALG']
610 fit_sign_alg = bb_vars['FIT_SIGN_ALG']
611 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
612 fit_uboot_env = bb_vars['FIT_UBOOT_ENV']
613 initramfs_image = bb_vars['INITRAMFS_IMAGE']
614 initramfs_image_bundle = bb_vars['INITRAMFS_IMAGE_BUNDLE']
615 uboot_sign_enable = bb_vars['UBOOT_SIGN_ENABLE']
616 uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
617 uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
618 num_signatures = 0
619 req_sections = {
620 "kernel-1": {
621 "Type": "Kernel Image",
622 "OS": "Linux",
623 "Load Address": bb_vars['UBOOT_LOADADDRESS'],
624 "Entry Point": bb_vars['UBOOT_ENTRYPOINT'],
625 }
626 }
627 # Create one section per DTB
628 for dtb in dtb_files:
629 req_sections['fdt-' + dtb] = {
630 "Type": "Flat Device Tree",
631 }
632 # Add a script section if there is a script
633 if fit_uboot_env:
634 req_sections['bootscr-' + fit_uboot_env] = { "Type": "Script" }
635 # Add the initramfs
636 if initramfs_image and initramfs_image_bundle != "1":
637 req_sections['ramdisk-1'] = {
638 "Type": "RAMDisk Image",
639 "Load Address": bb_vars['UBOOT_RD_LOADADDRESS'],
640 "Entry Point": bb_vars['UBOOT_RD_ENTRYPOINT']
641 }
642 # Create a configuration section for each DTB
643 if dtb_files:
644 for dtb in dtb_files + dtb_symlinks:
645 conf_name = bb_vars['FIT_CONF_PREFIX'] + dtb
646 # Assume that DTBs with an "-alias" in its name are symlink DTBs created e.g. by the
647 # bbb-dtbs-as-ext test recipe. Make the configuration node pointing to the real DTB.
648 real_dtb = dtb.replace("-alias", "")
649 # dtb overlays do not refer to a kernel (yet?)
650 if dtb.endswith('.dtbo'):
651 req_sections[conf_name] = {
652 "FDT": 'fdt-' + real_dtb,
653 }
654 else:
655 req_sections[conf_name] = {
656 "Kernel": "kernel-1",
657 "FDT": 'fdt-' + real_dtb,
658 }
659 if initramfs_image and initramfs_image_bundle != "1":
660 req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1"
661 else:
662 conf_name = bb_vars['FIT_CONF_PREFIX'] + '1'
663 req_sections[conf_name] = {
664 "Kernel": "kernel-1"
665 }
666 if initramfs_image and initramfs_image_bundle != "1":
667 req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1"
668
669 # Add signing related properties if needed
670 if uboot_sign_enable == "1":
671 for section in req_sections:
672 req_sections[section]['Hash algo'] = fit_hash_alg
673 if section.startswith(bb_vars['FIT_CONF_PREFIX']):
674 req_sections[section]['Hash value'] = "unavailable"
675 req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
676 num_signatures += 1
677 elif fit_sign_individual == "1":
678 req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
679 num_signatures += 1
680 return (req_sections, num_signatures)
681
682 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
683 """Verify the signature nodes in the FIT image"""
684 if bb_vars['UBOOT_SIGN_ENABLE'] == "1":
685 self.logger.debug("Verifying signatures in the FIT image")
686 else:
687 self.logger.debug("FIT image is not signed. Signature verification is not needed.")
688 return
689
690 fit_hash_alg = bb_vars['FIT_HASH_ALG']
691 fit_sign_alg = bb_vars['FIT_SIGN_ALG']
692 uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
693 uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
694 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
695 kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR']
696 fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL']
697 fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg]
698 fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg]
699 for section, values in sections.items():
700 # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
701 if section.startswith(bb_vars['FIT_CONF_PREFIX']):
702 sign_algo = values.get('Sign algo', None)
703 req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname)
704 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
705 sign_value = values.get('Sign value', None)
706 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
707 dtb_file_name = section.replace(bb_vars['FIT_CONF_PREFIX'], '')
708 dtb_path = os.path.join(deploy_dir_image, dtb_file_name)
709 if kernel_deploysubdir:
710 dtb_path = os.path.join(deploy_dir_image, kernel_deploysubdir, dtb_file_name)
711 # External devicetrees created by devicetree.bbclass are in a subfolder and have priority
712 dtb_path_ext = os.path.join(deploy_dir_image, "devicetree", dtb_file_name)
713 if os.path.exists(dtb_path_ext):
714 dtb_path = dtb_path_ext
715 self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section)
716 else:
717 # Image nodes always need a hash which gets indirectly signed by the config signature
718 hash_algo = values.get('Hash algo', None)
719 self.assertEqual(hash_algo, fit_hash_alg)
720 hash_value = values.get('Hash value', None)
721 self.assertEqual(len(hash_value), fit_hash_alg_len, 'Hash value for section %s not expected length' % section)
722 # Optionally, if FIT_SIGN_INDIVIDUAL = 1 also the image nodes have a signature (which is redundant but possible)
723 if fit_sign_individual == "1":
724 sign_algo = values.get('Sign algo', None)
725 req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_img_keyname)
726 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
727 sign_value = values.get('Sign value', None)
728 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
729
730 # Search for the string passed to mkimage in each signed section of the FIT image.
731 # Looks like mkimage supports to add a comment but does not support to read it back.
732 a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['UBOOT_MKIMAGE_SIGN_ARGS'])
733 self.logger.debug("a_comment: %s" % a_comment)
734 if a_comment:
735 found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
736 self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
737 (num_signatures, a_comment))
738
739class KernelFitImageRecipeTests(KernelFitImageBase):
740 """Test cases for the kernel-fitimage bbclass"""
13 741
14 def test_fit_image(self): 742 def test_fit_image(self):
15 """ 743 """
@@ -25,10 +753,7 @@ class FitImageTests(OESelftestTestCase):
25 Author: Usama Arif <usama.arif@arm.com> 753 Author: Usama Arif <usama.arif@arm.com>
26 """ 754 """
27 config = """ 755 config = """
28# Enable creation of fitImage
29KERNEL_IMAGETYPE = "Image" 756KERNEL_IMAGETYPE = "Image"
30KERNEL_IMAGETYPES += " fitImage "
31KERNEL_CLASSES = " kernel-fitimage "
32 757
33# RAM disk variables including load address and entrypoint for kernel and RAM disk 758# RAM disk variables including load address and entrypoint for kernel and RAM disk
34IMAGE_FSTYPES += "cpio.gz" 759IMAGE_FSTYPES += "cpio.gz"
@@ -40,79 +765,145 @@ UBOOT_RD_ENTRYPOINT = "0x88000000"
40UBOOT_LOADADDRESS = "0x80080000" 765UBOOT_LOADADDRESS = "0x80080000"
41UBOOT_ENTRYPOINT = "0x80080000" 766UBOOT_ENTRYPOINT = "0x80080000"
42FIT_DESC = "A model description" 767FIT_DESC = "A model description"
768FIT_CONF_PREFIX = "foo-"
43""" 769"""
770 config = self._config_add_kernel_classes(config)
44 self.write_config(config) 771 self.write_config(config)
772 bb_vars = self._fit_get_bb_vars()
773 self._test_fitimage(bb_vars)
45 774
46 # fitImage is created as part of linux recipe 775 def test_get_compatible_from_dtb(self):
47 image = "virtual/kernel" 776 """Test the oe.fitimage.get_compatible_from_dtb function
48 bitbake(image)
49 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'INITRAMFS_IMAGE_NAME', 'KERNEL_FIT_LINK_NAME'], image)
50
51 fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
52 "fitImage-its-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
53 fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
54 "fitImage-%s-%s" % (bb_vars['INITRAMFS_IMAGE_NAME'], bb_vars['KERNEL_FIT_LINK_NAME']))
55
56 self.assertTrue(os.path.exists(fitimage_its_path),
57 "%s image tree source doesn't exist" % (fitimage_its_path))
58 self.assertTrue(os.path.exists(fitimage_path),
59 "%s FIT image doesn't exist" % (fitimage_path))
60
61 # Check that the type, load address, entrypoint address and default
62 # values for kernel and ramdisk in Image Tree Source are as expected.
63 # The order of fields in the below array is important. Not all the
64 # fields are tested, only the key fields that wont vary between
65 # different architectures.
66 its_field_check = [
67 'description = "A model description";',
68 'type = "kernel";',
69 'load = <0x80080000>;',
70 'entry = <0x80080000>;',
71 'type = "ramdisk";',
72 'load = <0x88000000>;',
73 'entry = <0x88000000>;',
74 'default = "conf-1";',
75 'kernel = "kernel-1";',
76 'ramdisk = "ramdisk-1";'
77 ]
78 777
79 with open(fitimage_its_path) as its_file: 778 1. bitbake bbb-dtbs-as-ext
80 field_index = 0 779 2. Check if symlink_points_below returns the path to the DTB
81 for line in its_file: 780 3. Check if the expected compatible string is found by get_compatible_from_dtb()
82 if field_index == len(its_field_check): 781 """
83 break 782 DTB_RECIPE = "bbb-dtbs-as-ext"
84 if its_field_check[field_index] in line: 783 DTB_FILE = "am335x-bonegreen-ext.dtb"
85 field_index +=1 784 DTB_SYMLINK = "am335x-bonegreen-ext-alias.dtb"
785 DTBO_FILE = "BBORG_RELAY-00A2.dtbo"
786 EXPECTED_COMP = ["ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"]
86 787
87 if field_index != len(its_field_check): # if its equal, the test passed 788 config = """
88 self.assertTrue(field_index == len(its_field_check), 789DISTRO = "poky"
89 "Fields in Image Tree Source File %s did not match, error in finding %s" 790MACHINE:forcevariable = "beaglebone-yocto"
90 % (fitimage_its_path, its_field_check[field_index])) 791"""
792 self.write_config(config)
793
794 # Provide the fdtget command called by get_compatible_from_dtb
795 dtc_bindir = FitImageTestCase._setup_native('dtc-native')
796 fdtget_path = os.path.join(dtc_bindir, "fdtget")
797 self.assertExists(fdtget_path)
798
799 # bitbake an external DTB with a symlink to it and a DTB overlay
800 bitbake(DTB_RECIPE)
801 deploy_dir_image = get_bb_var("DEPLOY_DIR_IMAGE", DTB_RECIPE)
802 devicetree_dir = os.path.join(deploy_dir_image, "devicetree")
803 dtb_path = os.path.join(devicetree_dir, DTB_FILE)
804 dtb_alias_path = os.path.join(devicetree_dir, DTB_SYMLINK)
805 dtbo_file = os.path.join(devicetree_dir, DTBO_FILE)
806 self.assertExists(dtb_path)
807 self.assertExists(dtb_alias_path)
808 self.assertExists(dtbo_file)
809
810 # Test symlink_points_below
811 linked_dtb = oe.fitimage.symlink_points_below(dtb_alias_path, devicetree_dir)
812 self.assertEqual(linked_dtb, DTB_FILE)
813
814 # Check if get_compatible_from_dtb finds the expected compatible string in the DTBs
815 comp = oe.fitimage.get_compatible_from_dtb(dtb_path, fdtget_path)
816 self.assertEqual(comp, EXPECTED_COMP)
817 comp_alias = oe.fitimage.get_compatible_from_dtb(dtb_alias_path, fdtget_path)
818 self.assertEqual(comp_alias, EXPECTED_COMP)
819 # The alias is a symlink, therefore the compatible string is equal
820 self.assertEqual(comp_alias, comp)
821
822 def test_fit_image_ext_dtb_dtbo(self):
823 """
824 Summary: Check if FIT image and Image Tree Source (its) are created correctly.
825 Expected: 1) its and FIT image are built successfully
826 2) The its file contains also the external devicetree overlay
827 3) Dumping the FIT image indicates the devicetree overlay
828 """
829 config = """
830# Enable creation of fitImage
831MACHINE:forcevariable = "beaglebone-yocto"
832# Add a devicetree overlay which does not need kernel sources
833PREFERRED_PROVIDER_virtual/dtb = "bbb-dtbs-as-ext"
834"""
835 config = self._config_add_kernel_classes(config)
836 config = self._config_add_uboot_env(config)
837 self.write_config(config)
838 bb_vars = self._fit_get_bb_vars()
839 self._test_fitimage(bb_vars)
840
841
842 def test_sign_fit_image_configurations(self):
843 """
844 Summary: Check if FIT image and Image Tree Source (its) are created
845 and the configuration nodes are signed correctly.
846 Expected: 1) its and FIT image are built successfully
847 2) Scanning the its file indicates signing is enabled
848 as requested by UBOOT_SIGN_ENABLE
849 3) Dumping the FIT image indicates signature values
850 are present (only for the configuration nodes as
851 FIT_SIGN_INDIVIDUAL is disabled)
852 4) Verify the FIT image contains the comments passed via
853 UBOOT_MKIMAGE_SIGN_ARGS once per configuration node.
854 """
855 # Generate a configuration section which gets included into the local.conf file
856 config = """
857# Enable creation of fitImage
858MACHINE:forcevariable = "beaglebone-yocto"
859UBOOT_SIGN_ENABLE = "1"
860UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
861UBOOT_SIGN_KEYNAME = "dev"
862UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
863FIT_CONF_DEFAULT_DTB = "am335x-bonegreen.dtb"
864"""
865 config = self._config_add_kernel_classes(config)
866 config = self._config_add_uboot_env(config)
867 self.write_config(config)
868
869 # Retrieve some variables from bitbake
870 bb_vars = self._fit_get_bb_vars([
871 'FIT_KEY_GENRSA_ARGS',
872 'FIT_KEY_REQ_ARGS',
873 'FIT_KEY_SIGN_PKCS',
874 'FIT_SIGN_NUMBITS',
875 'UBOOT_SIGN_KEYDIR',
876 ])
91 877
878 self._gen_signing_key(bb_vars)
879 self._test_fitimage(bb_vars)
92 880
93 def test_sign_fit_image(self): 881 def test_sign_fit_image_individual(self):
94 """ 882 """
95 Summary: Check if FIT image and Image Tree Source (its) are created 883 Summary: Check if FIT image and Image Tree Source (its) are created
96 and signed correctly. 884 and all nodes are signed correctly.
97 Expected: 1) its and FIT image are built successfully 885 Expected: 1) its and FIT image are built successfully
98 2) Scanning the its file indicates signing is enabled 886 2) Scanning the its file indicates signing is enabled
99 as requested by UBOOT_SIGN_ENABLE (using keys generated 887 as requested by UBOOT_SIGN_ENABLE
100 via FIT_GENERATE_KEYS)
101 3) Dumping the FIT image indicates signature values 888 3) Dumping the FIT image indicates signature values
102 are present (including for images as enabled via 889 are present (including for images as enabled via
103 FIT_SIGN_INDIVIDUAL) 890 FIT_SIGN_INDIVIDUAL)
104 4) Examination of the do_assemble_fitimage runfile/logfile 891 This also implies that FIT_GENERATE_KEYS = "1" works.
105 indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and 892 4) Verify the FIT image contains the comments passed via
106 UBOOT_MKIMAGE_SIGN_ARGS are working as expected. 893 UBOOT_MKIMAGE_SIGN_ARGS once per image and per
894 configuration node.
895 Note: This test is mostly for backward compatibility.
896 The recommended approach is to sign the configuration nodes
897 which include also the hashes of all the images. Signing
898 all the images individually is therefore redundant.
107 Product: oe-core 899 Product: oe-core
108 Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon 900 Author: Paul Eggleton <paul.eggleton@microsoft.com> based upon
109 work by Usama Arif <usama.arif@arm.com> 901 work by Usama Arif <usama.arif@arm.com>
110 """ 902 """
903 # Generate a configuration section which gets included into the local.conf file
111 config = """ 904 config = """
112# Enable creation of fitImage 905# Enable creation of fitImage
113MACHINE = "beaglebone-yocto" 906MACHINE:forcevariable = "beaglebone-yocto"
114KERNEL_IMAGETYPES += " fitImage "
115KERNEL_CLASSES = " kernel-fitimage test-mkimage-wrapper "
116UBOOT_SIGN_ENABLE = "1" 907UBOOT_SIGN_ENABLE = "1"
117FIT_GENERATE_KEYS = "1" 908FIT_GENERATE_KEYS = "1"
118UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" 909UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
@@ -121,211 +912,494 @@ UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
121FIT_SIGN_INDIVIDUAL = "1" 912FIT_SIGN_INDIVIDUAL = "1"
122UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" 913UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
123""" 914"""
915 config = self._config_add_kernel_classes(config)
916 config = self._config_add_uboot_env(config)
124 self.write_config(config) 917 self.write_config(config)
918 bb_vars = self._fit_get_bb_vars()
125 919
126 # fitImage is created as part of linux recipe 920 # Ensure new keys are generated and FIT_GENERATE_KEYS = "1" is tested
127 image = "virtual/kernel" 921 bitbake("kernel-signing-keys-native -c compile -f")
128 bitbake(image)
129 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'KERNEL_FIT_LINK_NAME'], image)
130
131 fitimage_its_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
132 "fitImage-its-%s" % (bb_vars['KERNEL_FIT_LINK_NAME']))
133 fitimage_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'],
134 "fitImage-%s.bin" % (bb_vars['KERNEL_FIT_LINK_NAME']))
135
136 self.assertTrue(os.path.exists(fitimage_its_path),
137 "%s image tree source doesn't exist" % (fitimage_its_path))
138 self.assertTrue(os.path.exists(fitimage_path),
139 "%s FIT image doesn't exist" % (fitimage_path))
140
141 req_itspaths = [
142 ['/', 'images', 'kernel-1'],
143 ['/', 'images', 'kernel-1', 'signature-1'],
144 ['/', 'images', 'fdt-am335x-boneblack.dtb'],
145 ['/', 'images', 'fdt-am335x-boneblack.dtb', 'signature-1'],
146 ['/', 'configurations', 'conf-am335x-boneblack.dtb'],
147 ['/', 'configurations', 'conf-am335x-boneblack.dtb', 'signature-1'],
148 ]
149
150 itspath = []
151 itspaths = []
152 linect = 0
153 sigs = {}
154 with open(fitimage_its_path) as its_file:
155 linect += 1
156 for line in its_file:
157 line = line.strip()
158 if line.endswith('};'):
159 itspath.pop()
160 elif line.endswith('{'):
161 itspath.append(line[:-1].strip())
162 itspaths.append(itspath[:])
163 elif itspath and itspath[-1] == 'signature-1':
164 itsdotpath = '.'.join(itspath)
165 if not itsdotpath in sigs:
166 sigs[itsdotpath] = {}
167 if not '=' in line or not line.endswith(';'):
168 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
169 key, value = line.split('=', 1)
170 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
171
172 for reqpath in req_itspaths:
173 if not reqpath in itspaths:
174 self.fail('Missing section in its file: %s' % reqpath)
175 922
176 reqsigvalues_image = { 923 self._test_fitimage(bb_vars)
177 'algo': '"sha256,rsa2048"',
178 'key-name-hint': '"img-oe-selftest"',
179 }
180 reqsigvalues_config = {
181 'algo': '"sha256,rsa2048"',
182 'key-name-hint': '"cfg-oe-selftest"',
183 'sign-images': '"kernel", "fdt"',
184 }
185 924
186 for itspath, values in sigs.items(): 925 def test_fit_image_sign_initramfs(self):
187 if 'conf-' in itspath:
188 reqsigvalues = reqsigvalues_config
189 else:
190 reqsigvalues = reqsigvalues_image
191 for reqkey, reqvalue in reqsigvalues.items():
192 value = values.get(reqkey, None)
193 if value is None:
194 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
195 self.assertEqual(value, reqvalue)
196
197 # Dump the image to see if it really got signed
198 bitbake("u-boot-tools-native -c addto_recipe_sysroot")
199 result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=')
200 recipe_sysroot_native = result.output.split('=')[1].strip('"')
201 dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage')
202 result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path))
203 in_signed = None
204 signed_sections = {}
205 for line in result.output.splitlines():
206 if line.startswith((' Configuration', ' Image')):
207 in_signed = re.search(r'\((.*)\)', line).groups()[0]
208 elif re.match('^ *', line) in (' ', ''):
209 in_signed = None
210 elif in_signed:
211 if not in_signed in signed_sections:
212 signed_sections[in_signed] = {}
213 key, value = line.split(':', 1)
214 signed_sections[in_signed][key.strip()] = value.strip()
215 self.assertIn('kernel-1', signed_sections)
216 self.assertIn('fdt-am335x-boneblack.dtb', signed_sections)
217 self.assertIn('conf-am335x-boneblack.dtb', signed_sections)
218 for signed_section, values in signed_sections.items():
219 value = values.get('Sign algo', None)
220 if signed_section.startswith("conf"):
221 self.assertEqual(value, 'sha256,rsa2048:cfg-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
222 else:
223 self.assertEqual(value, 'sha256,rsa2048:img-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
224 value = values.get('Sign value', None)
225 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
226
227 # Check for UBOOT_MKIMAGE_SIGN_ARGS
228 result = runCmd('bitbake -e virtual/kernel | grep ^T=')
229 tempdir = result.output.split('=', 1)[1].strip().strip('')
230 result = runCmd('grep "a smart comment" %s/run.do_assemble_fitimage' % tempdir, ignore_status=True)
231 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN_ARGS value did not get used')
232
233 # Check for evidence of test-mkimage-wrapper class
234 result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True)
235 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work')
236 result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_assemble_fitimage' % tempdir, ignore_status=True)
237 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work')
238
239 def test_uboot_fit_image(self):
240 """ 926 """
241 Summary: Check if Uboot FIT image and Image Tree Source 927 Summary: Verifies the content of the initramfs node in the FIT Image Tree Source (its)
242 (its) are built and the Image Tree Source has the 928 The FIT settings are set by the test case.
243 correct fields. 929 The machine used is beaglebone-yocto.
244 Expected: 1. u-boot-fitImage and u-boot-its can be built 930 Expected: 1. The ITS is generated with initramfs support
245 2. The type, load address, entrypoint address and 931 2. All the fields in the kernel node are as expected (matching the
246 default values of U-boot image are correct in the 932 conf settings)
247 Image Tree Source. Not all the fields are tested, 933 3. The kernel is included in all the available configurations and
248 only the key fields that wont vary between 934 its hash is included in the configuration signature
249 different architectures. 935
250 Product: oe-core 936 Product: oe-core
251 Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> 937 Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
252 based on work by Usama Arif <usama.arif@arm.com>
253 """ 938 """
939
254 config = """ 940 config = """
255# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 941DISTRO = "poky"
256MACHINE = "qemuarm" 942MACHINE:forcevariable = "beaglebone-yocto"
257UBOOT_MACHINE = "am57xx_evm_defconfig" 943INITRAMFS_IMAGE = "core-image-minimal-initramfs"
258SPL_BINARY = "MLO" 944INITRAMFS_SCRIPTS = ""
945UBOOT_MACHINE = "am335x_evm_defconfig"
946UBOOT_SIGN_ENABLE = "1"
947UBOOT_SIGN_KEYNAME = "beaglebonekey"
948UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
949UBOOT_DTB_BINARY = "u-boot.dtb"
950UBOOT_ENTRYPOINT = "0x80000000"
951UBOOT_LOADADDRESS = "0x80000000"
952UBOOT_RD_LOADADDRESS = "0x88000000"
953UBOOT_RD_ENTRYPOINT = "0x88000000"
954UBOOT_DTB_LOADADDRESS = "0x82000000"
955UBOOT_ARCH = "arm"
956UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
957UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
958UBOOT_EXTLINUX = "0"
959KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
960FIT_KERNEL_COMP_ALG = "none"
961FIT_HASH_ALG = "sha256"
962"""
963 config = self._config_add_kernel_classes(config)
964 config = self._config_add_uboot_env(config)
965 self.write_config(config)
259 966
260# Enable creation of the U-Boot fitImage 967 # Retrieve some variables from bitbake
261UBOOT_FITIMAGE_ENABLE = "1" 968 bb_vars = self._fit_get_bb_vars([
969 'FIT_KEY_GENRSA_ARGS',
970 'FIT_KEY_REQ_ARGS',
971 'FIT_KEY_SIGN_PKCS',
972 'FIT_SIGN_NUMBITS',
973 'UBOOT_SIGN_KEYDIR',
974 ])
262 975
263# (U-boot) fitImage properties 976 self._gen_signing_key(bb_vars)
264UBOOT_LOADADDRESS = "0x80080000" 977 self._test_fitimage(bb_vars)
265UBOOT_ENTRYPOINT = "0x80080000" 978
266UBOOT_FIT_DESC = "A model description" 979 def test_fit_image_sign_initramfs_bundle(self):
980 """
981 Summary: Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its)
982 The FIT settings are set by the test case.
983 The machine used is beaglebone-yocto.
984 Expected: 1. The ITS is generated with initramfs bundle support
985 2. All the fields in the kernel node are as expected (matching the
986 conf settings)
987 3. The kernel is included in all the available configurations and
988 its hash is included in the configuration signature
989
990 Product: oe-core
991 Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com>
992 """
267 993
268# Enable creation of Kernel fitImage 994 config = """
269KERNEL_IMAGETYPES += " fitImage " 995DISTRO = "poky"
270KERNEL_CLASSES = " kernel-fitimage" 996MACHINE:forcevariable = "beaglebone-yocto"
997INITRAMFS_IMAGE_BUNDLE = "1"
998INITRAMFS_IMAGE = "core-image-minimal-initramfs"
999INITRAMFS_SCRIPTS = ""
1000UBOOT_MACHINE = "am335x_evm_defconfig"
271UBOOT_SIGN_ENABLE = "1" 1001UBOOT_SIGN_ENABLE = "1"
272FIT_GENERATE_KEYS = "1" 1002UBOOT_SIGN_KEYNAME = "beaglebonekey"
273UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" 1003UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
274UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest" 1004UBOOT_DTB_BINARY = "u-boot.dtb"
275UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" 1005UBOOT_ENTRYPOINT = "0x80000000"
276FIT_SIGN_INDIVIDUAL = "1" 1006UBOOT_LOADADDRESS = "0x80000000"
1007UBOOT_DTB_LOADADDRESS = "0x82000000"
1008UBOOT_ARCH = "arm"
1009UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
1010UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
1011UBOOT_EXTLINUX = "0"
1012KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
1013FIT_KERNEL_COMP_ALG = "none"
1014FIT_HASH_ALG = "sha256"
277""" 1015"""
1016 config = self._config_add_kernel_classes(config)
1017 config = self._config_add_uboot_env(config)
278 self.write_config(config) 1018 self.write_config(config)
1019 bb_vars = self._fit_get_bb_vars()
1020 self._gen_signing_key(bb_vars)
1021 self._test_fitimage(bb_vars)
1022
1023class FitImagePyTests(KernelFitImageBase):
1024 """Test cases for the fitimage.py module without calling bitbake"""
1025
1026 def _test_fitimage_py(self, bb_vars_overrides=None):
1027 topdir = os.path.join(os.environ['BUILDDIR'])
1028 fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its')
1029
1030 # Provide variables without calling bitbake
1031 bb_vars = {
1032 # image-fitimage.conf
1033 'FIT_DESC': "Kernel fitImage for a dummy distro",
1034 'FIT_HASH_ALG': "sha256",
1035 'FIT_SIGN_ALG': "rsa2048",
1036 'FIT_PAD_ALG': "pkcs-1.5",
1037 'FIT_GENERATE_KEYS': "0",
1038 'FIT_SIGN_NUMBITS': "2048",
1039 'FIT_KEY_GENRSA_ARGS': "-F4",
1040 'FIT_KEY_REQ_ARGS': "-batch -new",
1041 'FIT_KEY_SIGN_PKCS': "-x509",
1042 'FIT_SIGN_INDIVIDUAL': "0",
1043 'FIT_CONF_PREFIX': "conf-",
1044 'FIT_SUPPORTED_INITRAMFS_FSTYPES': "cpio.lz4 cpio.lzo cpio.lzma cpio.xz cpio.zst cpio.gz ext2.gz cpio",
1045 'FIT_CONF_DEFAULT_DTB': "",
1046 'FIT_ADDRESS_CELLS': "1",
1047 'FIT_UBOOT_ENV': "",
1048 # kernel.bbclass
1049 'UBOOT_ENTRYPOINT': "0x20008000",
1050 'UBOOT_LOADADDRESS': "0x20008000",
1051 'INITRAMFS_IMAGE': "",
1052 'INITRAMFS_IMAGE_BUNDLE': "",
1053 # kernel-uboot.bbclass
1054 'FIT_KERNEL_COMP_ALG': "gzip",
1055 'FIT_KERNEL_COMP_ALG_EXTENSION': ".gz",
1056 'UBOOT_MKIMAGE_KERNEL_TYPE': "kernel",
1057 # uboot-config.bbclass
1058 'UBOOT_MKIMAGE_DTCOPTS': "",
1059 'UBOOT_MKIMAGE': "uboot-mkimage",
1060 'UBOOT_MKIMAGE_SIGN': "uboot-mkimage",
1061 'UBOOT_MKIMAGE_SIGN_ARGS': "",
1062 'UBOOT_SIGN_ENABLE': "0",
1063 'UBOOT_SIGN_KEYDIR': None,
1064 'UBOOT_SIGN_KEYNAME': None,
1065 'UBOOT_SIGN_IMG_KEYNAME': None,
1066 # others
1067 'MACHINE': "qemux86-64",
1068 'UBOOT_ARCH': "x86",
1069 'HOST_PREFIX': "x86_64-poky-linux-"
1070 }
1071 if bb_vars_overrides:
1072 bb_vars.update(bb_vars_overrides)
1073
1074 root_node = oe.fitimage.ItsNodeRootKernel(
1075 bb_vars["FIT_DESC"], bb_vars["FIT_ADDRESS_CELLS"],
1076 bb_vars['HOST_PREFIX'], bb_vars['UBOOT_ARCH'], bb_vars["FIT_CONF_PREFIX"],
1077 oe.types.boolean(bb_vars['UBOOT_SIGN_ENABLE']), bb_vars["UBOOT_SIGN_KEYDIR"],
1078 bb_vars["UBOOT_MKIMAGE"], bb_vars["UBOOT_MKIMAGE_DTCOPTS"],
1079 bb_vars["UBOOT_MKIMAGE_SIGN"], bb_vars["UBOOT_MKIMAGE_SIGN_ARGS"],
1080 bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'], bb_vars['FIT_PAD_ALG'],
1081 bb_vars['UBOOT_SIGN_KEYNAME'],
1082 oe.types.boolean(bb_vars['FIT_SIGN_INDIVIDUAL']), bb_vars['UBOOT_SIGN_IMG_KEYNAME']
1083 )
1084
1085 root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", "none",
1086 bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'),
1087 bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE'), bb_vars.get("UBOOT_ENTRYSYMBOL")
1088 )
1089
1090 dtb_files, _ = FitImageTestCase._get_dtb_files(bb_vars)
1091 for dtb in dtb_files:
1092 root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb),
1093 bb_vars.get("UBOOT_DTB_LOADADDRESS"), bb_vars.get("UBOOT_DTBO_LOADADDRESS"))
1094
1095 if bb_vars.get('FIT_UBOOT_ENV'):
1096 root_node.fitimage_emit_section_boot_script(
1097 "bootscr-" + bb_vars['FIT_UBOOT_ENV'], bb_vars['FIT_UBOOT_ENV'])
1098
1099 if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if
1100 root_node.fitimage_emit_section_setup("setup-1", "setup1.bin")
1101
1102 if bb_vars.get('INITRAMFS_IMAGE') and bb_vars.get("INITRAMFS_IMAGE_BUNDLE") != "1":
1103 root_node.fitimage_emit_section_ramdisk("ramdisk-1", "a-dir/a-initramfs-1",
1104 "core-image-minimal-initramfs",
1105 bb_vars.get("UBOOT_RD_LOADADDRESS"), bb_vars.get("UBOOT_RD_ENTRYPOINT"))
1106
1107 root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB'])
1108 root_node.write_its_file(fitimage_its_path)
1109
1110 self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path))
1111 self.logger.debug("Checking its: %s" % fitimage_its_path)
1112 self._check_its_file(bb_vars, fitimage_its_path)
1113
1114 def test_fitimage_py_default(self):
1115 self._test_fitimage_py()
1116
1117 def test_fitimage_py_default_dtb(self):
1118 bb_vars_overrides = {
1119 'KERNEL_DEVICETREE': "one.dtb two.dtb three.dtb",
1120 'FIT_CONF_DEFAULT_DTB': "two.dtb"
1121 }
1122 self._test_fitimage_py(bb_vars_overrides)
1123
1124
1125class UBootFitImageTests(FitImageTestCase):
1126 """Test cases for the uboot-sign bbclass"""
1127
1128 BOOTLOADER_RECIPE = "virtual/bootloader"
279 1129
280 # The U-Boot fitImage is created as part of the U-Boot recipe 1130 def _fit_get_bb_vars(self, additional_vars=[]):
281 bitbake("virtual/bootloader") 1131 """Get bb_vars as needed by _test_sign_fit_image
282 1132
283 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') 1133 Call the get_bb_vars function once and get all variables needed by the test case.
284 machine = get_bb_var('MACHINE') 1134 """
285 fitimage_its_path = os.path.join(deploy_dir_image, 1135 internal_used = {
286 "u-boot-its-%s" % (machine,)) 1136 'DEPLOY_DIR_IMAGE',
287 fitimage_path = os.path.join(deploy_dir_image, 1137 'FIT_HASH_ALG',
288 "u-boot-fitImage-%s" % (machine,)) 1138 'FIT_KEY_GENRSA_ARGS',
289 1139 'FIT_KEY_REQ_ARGS',
290 self.assertTrue(os.path.exists(fitimage_its_path), 1140 'FIT_KEY_SIGN_PKCS',
291 "%s image tree source doesn't exist" % (fitimage_its_path)) 1141 'FIT_SIGN_ALG',
292 self.assertTrue(os.path.exists(fitimage_path), 1142 'FIT_SIGN_INDIVIDUAL',
293 "%s FIT image doesn't exist" % (fitimage_path)) 1143 'FIT_SIGN_NUMBITS',
294 1144 'MACHINE',
295 # Check that the type, load address, entrypoint address and default 1145 'SPL_MKIMAGE_SIGN_ARGS',
296 # values for kernel and ramdisk in Image Tree Source are as expected. 1146 'SPL_SIGN_ENABLE',
297 # The order of fields in the below array is important. Not all the 1147 'SPL_SIGN_KEYNAME',
298 # fields are tested, only the key fields that wont vary between 1148 'UBOOT_ARCH',
299 # different architectures. 1149 'UBOOT_DTB_BINARY',
1150 'UBOOT_DTB_IMAGE',
1151 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT',
1152 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS',
1153 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE',
1154 'UBOOT_FIT_CONF_USER_LOADABLES',
1155 'UBOOT_FIT_DESC',
1156 'UBOOT_FIT_HASH_ALG',
1157 'UBOOT_FIT_SIGN_ALG',
1158 'UBOOT_FIT_TEE_ENTRYPOINT',
1159 'UBOOT_FIT_TEE_LOADADDRESS',
1160 'UBOOT_FIT_TEE',
1161 'UBOOT_FIT_UBOOT_ENTRYPOINT',
1162 'UBOOT_FIT_UBOOT_LOADADDRESS',
1163 'UBOOT_FIT_USER_SETTINGS',
1164 'UBOOT_FITIMAGE_ENABLE',
1165 'UBOOT_NODTB_BINARY',
1166 'UBOOT_SIGN_ENABLE',
1167 'UBOOT_SIGN_IMG_KEYNAME',
1168 'UBOOT_SIGN_KEYDIR',
1169 'UBOOT_SIGN_KEYNAME',
1170 }
1171 bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), UBootFitImageTests.BOOTLOADER_RECIPE)
1172 self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4))
1173 return bb_vars
1174
1175 def _bitbake_fit_image(self, bb_vars):
1176 """Bitbake the bootloader and return the paths to the its file and the FIT image"""
1177 bitbake(UBootFitImageTests.BOOTLOADER_RECIPE)
1178
1179 deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE']
1180 machine = bb_vars['MACHINE']
1181 fitimage_its_path = os.path.join(deploy_dir_image, "u-boot-its-%s" % machine)
1182 fitimage_path = os.path.join(deploy_dir_image, "u-boot-fitImage-%s" % machine)
1183 return (fitimage_its_path, fitimage_path)
1184
1185 def _get_req_its_paths(self, bb_vars):
1186 # image nodes
1187 images = [ 'uboot', 'fdt', ]
1188 if bb_vars['UBOOT_FIT_TEE'] == "1":
1189 images.append('tee')
1190 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
1191 images.append('atf')
1192 # if bb_vars['UBOOT_FIT_USER_SETTINGS']:
1193
1194 # configuration nodes
1195 configurations = [ 'conf']
1196
1197 # Create a list of paths for all image and configuration nodes
1198 req_its_paths = []
1199 for image in images:
1200 req_its_paths.append(['/', 'images', image])
1201 if bb_vars['SPL_SIGN_ENABLE'] == "1":
1202 req_its_paths.append(['/', 'images', image, 'signature'])
1203 for configuration in configurations:
1204 req_its_paths.append(['/', 'configurations', configuration])
1205 return req_its_paths
1206
1207 def _get_req_its_fields(self, bb_vars):
1208 loadables = ["uboot"]
300 its_field_check = [ 1209 its_field_check = [
301 'description = "A model description";', 1210 'description = "%s";' % bb_vars['UBOOT_FIT_DESC'],
1211 'description = "U-Boot image";',
1212 'data = /incbin/("%s");' % bb_vars['UBOOT_NODTB_BINARY'],
302 'type = "standalone";', 1213 'type = "standalone";',
303 'load = <0x80080000>;', 1214 'os = "u-boot";',
304 'entry = <0x80080000>;', 1215 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
305 'default = "conf";', 1216 'compression = "none";',
306 'loadables = "uboot";', 1217 'load = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
307 'fdt = "fdt";' 1218 'entry = <%s>;' % bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
1219 'description = "U-Boot FDT";',
1220 'data = /incbin/("%s");' % bb_vars['UBOOT_DTB_BINARY'],
1221 'type = "flat_dt";',
1222 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
1223 'compression = "none";',
1224 ]
1225 if bb_vars['UBOOT_FIT_TEE'] == "1":
1226 its_field_check += [
1227 'description = "Trusted Execution Environment";',
1228 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_TEE_IMAGE'],
1229 'type = "tee";',
1230 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
1231 'os = "tee";',
1232 'load = <%s>;' % bb_vars['UBOOT_FIT_TEE_LOADADDRESS'],
1233 'entry = <%s>;' % bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'],
1234 'compression = "none";',
1235 ]
1236 loadables.insert(0, "tee")
1237 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
1238 its_field_check += [
1239 'description = "ARM Trusted Firmware";',
1240 'data = /incbin/("%s");' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'],
1241 'type = "firmware";',
1242 'arch = "%s";' % bb_vars['UBOOT_ARCH'],
1243 'os = "arm-trusted-firmware";',
1244 'load = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
1245 'entry = <%s>;' % bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'],
1246 'compression = "none";',
308 ] 1247 ]
1248 loadables.insert(0, "atf")
1249 its_field_check += [
1250 'default = "conf";',
1251 'description = "Boot with signed U-Boot FIT";',
1252 'loadables = "%s";' % '", "'.join(loadables),
1253 'fdt = "fdt";',
1254 ]
1255 return its_field_check
1256
1257 def _get_req_sigvalues_config(self, bb_vars):
1258 # COnfigurations are not signed by uboot-sign
1259 return {}
1260
1261 def _get_req_sigvalues_image(self, bb_vars):
1262 if bb_vars['SPL_SIGN_ENABLE'] != "1":
1263 return {}
1264 req_sigvalues_image = {
1265 'algo': '"%s,%s"' % (bb_vars['UBOOT_FIT_HASH_ALG'], bb_vars['UBOOT_FIT_SIGN_ALG']),
1266 'key-name-hint': '"%s"' % bb_vars['SPL_SIGN_KEYNAME'],
1267 }
1268 return req_sigvalues_image
309 1269
310 with open(fitimage_its_path) as its_file: 1270 def _get_req_sections(self, bb_vars):
311 field_index = 0 1271 """Generate the expected output of dumpimage for beaglebone targets
312 for line in its_file:
313 if field_index == len(its_field_check):
314 break
315 if its_field_check[field_index] in line:
316 field_index +=1
317 1272
318 if field_index != len(its_field_check): # if its equal, the test passed 1273 The dict generated by this function is supposed to be compared against
319 self.assertTrue(field_index == len(its_field_check), 1274 the dict which is generated by the _dump_fitimage function.
320 "Fields in Image Tree Source File %s did not match, error in finding %s" 1275 """
321 % (fitimage_its_path, its_field_check[field_index])) 1276 loadables = ['uboot']
1277 req_sections = {
1278 "uboot": {
1279 "Type": "Standalone Program",
1280 "Load Address": bb_vars['UBOOT_FIT_UBOOT_LOADADDRESS'],
1281 "Entry Point": bb_vars['UBOOT_FIT_UBOOT_ENTRYPOINT'],
1282 },
1283 "fdt": {
1284 "Type": "Flat Device Tree",
1285 }
1286 }
1287 if bb_vars['UBOOT_FIT_TEE'] == "1":
1288 loadables.insert(0, "tee")
1289 req_sections['tee'] = {
1290 "Type": "Trusted Execution Environment Image",
1291 # "Load Address": bb_vars['UBOOT_FIT_TEE_LOADADDRESS'], not printed by mkimage?
1292 # "Entry Point": bb_vars['UBOOT_FIT_TEE_ENTRYPOINT'], not printed by mkimage?
1293 }
1294 if bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE'] == "1":
1295 loadables.insert(0, "atf")
1296 req_sections['atf'] = {
1297 "Type": "Firmware",
1298 "Load Address": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS'],
1299 # "Entry Point": bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT'], not printed by mkimage?
1300 }
1301 req_sections["conf"] = {
1302 "Kernel": "unavailable",
1303 "FDT": "fdt",
1304 "Loadables": ','.join(loadables),
1305 }
322 1306
323 def test_uboot_sign_fit_image(self): 1307 # Add signing related properties if needed
1308 uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
1309 uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
1310 spl_sign_enable = bb_vars['SPL_SIGN_ENABLE']
1311 spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
1312 num_signatures = 0
1313 if spl_sign_enable == "1":
1314 for section in req_sections:
1315 if not section.startswith('conf'):
1316 req_sections[section]['Sign algo'] = "%s,%s:%s" % \
1317 (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
1318 num_signatures += 1
1319 return (req_sections, num_signatures)
1320
1321 def _check_signing(self, bb_vars, sections, num_signatures, uboot_tools_bindir, fitimage_path):
1322 if bb_vars['UBOOT_FITIMAGE_ENABLE'] == '1' and bb_vars['SPL_SIGN_ENABLE'] == "1":
1323 self.logger.debug("Verifying signatures in the FIT image")
1324 else:
1325 self.logger.debug("FIT image is not signed. Signature verification is not needed.")
1326 return
1327
1328 uboot_fit_hash_alg = bb_vars['UBOOT_FIT_HASH_ALG']
1329 uboot_fit_sign_alg = bb_vars['UBOOT_FIT_SIGN_ALG']
1330 spl_sign_keyname = bb_vars['SPL_SIGN_KEYNAME']
1331 fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[uboot_fit_sign_alg]
1332 for section, values in sections.items():
1333 # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1")
1334 if section.startswith("conf"):
1335 # uboot-sign does not sign configuration nodes
1336 pass
1337 else:
1338 # uboot-sign does not add hash nodes, only image signatures
1339 sign_algo = values.get('Sign algo', None)
1340 req_sign_algo = "%s,%s:%s" % (uboot_fit_hash_alg, uboot_fit_sign_alg, spl_sign_keyname)
1341 self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section)
1342 sign_value = values.get('Sign value', None)
1343 self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section)
1344
1345 # Search for the string passed to mkimage in each signed section of the FIT image.
1346 # Looks like mkimage supports to add a comment but does not support to read it back.
1347 a_comment = FitImageTestCase._get_uboot_mkimage_sign_args(bb_vars['SPL_MKIMAGE_SIGN_ARGS'])
1348 self.logger.debug("a_comment: %s" % a_comment)
1349 if a_comment:
1350 found_comments = FitImageTestCase._find_string_in_bin_file(fitimage_path, a_comment)
1351 self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." %
1352 (num_signatures, a_comment))
1353
1354 def _check_kernel_dtb(self, bb_vars):
1355 """
1356 Check if the device-tree from U-Boot has the kernel public key(s).
1357
1358 The concat_dtb function of the uboot-sign.bbclass injects the public keys
1359 which are required for verifying the kernel at run-time into the DTB from
1360 U-Boot. The following example is from a build with FIT_SIGN_INDIVIDUAL
1361 set to "1". If it is set to "0" the key-the-kernel-image-key node is not
1362 present.
1363 / {
1364 ...
1365 signature {
1366 key-the-kernel-image-key {
1367 required = "image";
1368 algo = "sha256,rsa2048";
1369 ...
1370 };
1371 key-the-kernel-config-key {
1372 required = "conf";
1373 algo = "sha256,rsa2048";
1374 ...
1375 };
1376 };
1377 """
1378 # Setup u-boot-tools-native
1379 dtc_bindir = FitImageTestCase._setup_native('dtc-native')
1380
1381 # Check if 1 or 2 signature sections are in the DTB.
1382 uboot_dtb_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], bb_vars['UBOOT_DTB_IMAGE'])
1383 algo = "%s,%s" % (bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'])
1384 if bb_vars['FIT_SIGN_INDIVIDUAL'] == "1":
1385 uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME']
1386 key_dtb_path = "/signature/key-" + uboot_sign_img_keyname
1387 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "image")
1388 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo)
1389 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_img_keyname)
1390
1391 uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME']
1392 key_dtb_path = "/signature/key-" + uboot_sign_keyname
1393 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "required", "conf")
1394 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "algo", algo)
1395 self._verify_dtb_property(dtc_bindir, uboot_dtb_path, key_dtb_path, "key-name-hint", uboot_sign_keyname)
1396
1397
1398 def test_uboot_fit_image(self):
324 """ 1399 """
325 Summary: Check if Uboot FIT image and Image Tree Source 1400 Summary: Check if Uboot FIT image and Image Tree Source
326 (its) are built and the Image Tree Source has the 1401 (its) are built and the Image Tree Source has the
327 correct fields, in the scenario where the Kernel 1402 correct fields.
328 is also creating/signing it's fitImage.
329 Expected: 1. u-boot-fitImage and u-boot-its can be built 1403 Expected: 1. u-boot-fitImage and u-boot-its can be built
330 2. The type, load address, entrypoint address and 1404 2. The type, load address, entrypoint address and
331 default values of U-boot image are correct in the 1405 default values of U-boot image are correct in the
@@ -338,7 +1412,7 @@ FIT_SIGN_INDIVIDUAL = "1"
338 """ 1412 """
339 config = """ 1413 config = """
340# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 1414# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
341MACHINE = "qemuarm" 1415MACHINE:forcevariable = "qemuarm"
342UBOOT_MACHINE = "am57xx_evm_defconfig" 1416UBOOT_MACHINE = "am57xx_evm_defconfig"
343SPL_BINARY = "MLO" 1417SPL_BINARY = "MLO"
344 1418
@@ -349,61 +1423,10 @@ UBOOT_FITIMAGE_ENABLE = "1"
349UBOOT_LOADADDRESS = "0x80080000" 1423UBOOT_LOADADDRESS = "0x80080000"
350UBOOT_ENTRYPOINT = "0x80080000" 1424UBOOT_ENTRYPOINT = "0x80080000"
351UBOOT_FIT_DESC = "A model description" 1425UBOOT_FIT_DESC = "A model description"
352KERNEL_IMAGETYPES += " fitImage "
353KERNEL_CLASSES = " kernel-fitimage "
354INHERIT += "test-mkimage-wrapper"
355UBOOT_SIGN_ENABLE = "1"
356FIT_GENERATE_KEYS = "1"
357UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
358UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
359UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
360FIT_SIGN_INDIVIDUAL = "1"
361UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
362""" 1426"""
363 self.write_config(config) 1427 self.write_config(config)
364 1428 bb_vars = self._fit_get_bb_vars()
365 # The U-Boot fitImage is created as part of the U-Boot recipe 1429 self._test_fitimage(bb_vars)
366 bitbake("virtual/bootloader")
367
368 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
369 machine = get_bb_var('MACHINE')
370 fitimage_its_path = os.path.join(deploy_dir_image,
371 "u-boot-its-%s" % (machine,))
372 fitimage_path = os.path.join(deploy_dir_image,
373 "u-boot-fitImage-%s" % (machine,))
374
375 self.assertTrue(os.path.exists(fitimage_its_path),
376 "%s image tree source doesn't exist" % (fitimage_its_path))
377 self.assertTrue(os.path.exists(fitimage_path),
378 "%s FIT image doesn't exist" % (fitimage_path))
379
380 # Check that the type, load address, entrypoint address and default
381 # values for kernel and ramdisk in Image Tree Source are as expected.
382 # The order of fields in the below array is important. Not all the
383 # fields are tested, only the key fields that wont vary between
384 # different architectures.
385 its_field_check = [
386 'description = "A model description";',
387 'type = "standalone";',
388 'load = <0x80080000>;',
389 'entry = <0x80080000>;',
390 'default = "conf";',
391 'loadables = "uboot";',
392 'fdt = "fdt";'
393 ]
394
395 with open(fitimage_its_path) as its_file:
396 field_index = 0
397 for line in its_file:
398 if field_index == len(its_field_check):
399 break
400 if its_field_check[field_index] in line:
401 field_index +=1
402
403 if field_index != len(its_field_check): # if its equal, the test passed
404 self.assertTrue(field_index == len(its_field_check),
405 "Fields in Image Tree Source File %s did not match, error in finding %s"
406 % (fitimage_its_path, its_field_check[field_index]))
407 1430
408 1431
409 def test_sign_standalone_uboot_fit_image(self): 1432 def test_sign_standalone_uboot_fit_image(self):
@@ -426,15 +1449,11 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot comment'"
426 Usama Arif <usama.arif@arm.com> 1449 Usama Arif <usama.arif@arm.com>
427 """ 1450 """
428 config = """ 1451 config = """
429# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at 1452# There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at
430# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 1453# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
431MACHINE = "qemuarm" 1454MACHINE:forcevariable = "qemuarm"
432UBOOT_MACHINE = "am57xx_evm_defconfig" 1455UBOOT_MACHINE = "am57xx_evm_defconfig"
433SPL_BINARY = "MLO" 1456SPL_BINARY = "MLO"
434# The kernel-fitimage class is a dependency even if we're only
435# creating/signing the U-Boot fitImage
436KERNEL_CLASSES = " kernel-fitimage"
437INHERIT += "test-mkimage-wrapper"
438# Enable creation and signing of the U-Boot fitImage 1457# Enable creation and signing of the U-Boot fitImage
439UBOOT_FITIMAGE_ENABLE = "1" 1458UBOOT_FITIMAGE_ENABLE = "1"
440SPL_SIGN_ENABLE = "1" 1459SPL_SIGN_ENABLE = "1"
@@ -452,106 +1471,9 @@ UBOOT_FIT_GENERATE_KEYS = "1"
452UBOOT_FIT_HASH_ALG = "sha256" 1471UBOOT_FIT_HASH_ALG = "sha256"
453""" 1472"""
454 self.write_config(config) 1473 self.write_config(config)
1474 bb_vars = self._fit_get_bb_vars()
1475 self._test_fitimage(bb_vars)
455 1476
456 # The U-Boot fitImage is created as part of the U-Boot recipe
457 bitbake("virtual/bootloader")
458
459 image_type = "core-image-minimal"
460 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
461 machine = get_bb_var('MACHINE')
462 fitimage_its_path = os.path.join(deploy_dir_image,
463 "u-boot-its-%s" % (machine,))
464 fitimage_path = os.path.join(deploy_dir_image,
465 "u-boot-fitImage-%s" % (machine,))
466
467 self.assertTrue(os.path.exists(fitimage_its_path),
468 "%s image tree source doesn't exist" % (fitimage_its_path))
469 self.assertTrue(os.path.exists(fitimage_path),
470 "%s FIT image doesn't exist" % (fitimage_path))
471
472 req_itspaths = [
473 ['/', 'images', 'uboot'],
474 ['/', 'images', 'uboot', 'signature'],
475 ['/', 'images', 'fdt'],
476 ['/', 'images', 'fdt', 'signature'],
477 ]
478
479 itspath = []
480 itspaths = []
481 linect = 0
482 sigs = {}
483 with open(fitimage_its_path) as its_file:
484 linect += 1
485 for line in its_file:
486 line = line.strip()
487 if line.endswith('};'):
488 itspath.pop()
489 elif line.endswith('{'):
490 itspath.append(line[:-1].strip())
491 itspaths.append(itspath[:])
492 elif itspath and itspath[-1] == 'signature':
493 itsdotpath = '.'.join(itspath)
494 if not itsdotpath in sigs:
495 sigs[itsdotpath] = {}
496 if not '=' in line or not line.endswith(';'):
497 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line))
498 key, value = line.split('=', 1)
499 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
500
501 for reqpath in req_itspaths:
502 if not reqpath in itspaths:
503 self.fail('Missing section in its file: %s' % reqpath)
504
505 reqsigvalues_image = {
506 'algo': '"sha256,rsa2048"',
507 'key-name-hint': '"spl-oe-selftest"',
508 }
509
510 for itspath, values in sigs.items():
511 reqsigvalues = reqsigvalues_image
512 for reqkey, reqvalue in reqsigvalues.items():
513 value = values.get(reqkey, None)
514 if value is None:
515 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
516 self.assertEqual(value, reqvalue)
517
518 # Dump the image to see if it really got signed
519 bitbake("u-boot-tools-native -c addto_recipe_sysroot")
520 result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=')
521 recipe_sysroot_native = result.output.split('=')[1].strip('"')
522 dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage')
523 result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path))
524 in_signed = None
525 signed_sections = {}
526 for line in result.output.splitlines():
527 if line.startswith((' Image')):
528 in_signed = re.search(r'\((.*)\)', line).groups()[0]
529 elif re.match(' \w', line):
530 in_signed = None
531 elif in_signed:
532 if not in_signed in signed_sections:
533 signed_sections[in_signed] = {}
534 key, value = line.split(':', 1)
535 signed_sections[in_signed][key.strip()] = value.strip()
536 self.assertIn('uboot', signed_sections)
537 self.assertIn('fdt', signed_sections)
538 for signed_section, values in signed_sections.items():
539 value = values.get('Sign algo', None)
540 self.assertEqual(value, 'sha256,rsa2048:spl-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
541 value = values.get('Sign value', None)
542 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
543
544 # Check for SPL_MKIMAGE_SIGN_ARGS
545 result = runCmd('bitbake -e virtual/bootloader | grep ^T=')
546 tempdir = result.output.split('=', 1)[1].strip().strip('')
547 result = runCmd('grep "a smart U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
548 self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used')
549
550 # Check for evidence of test-mkimage-wrapper class
551 result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
552 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work')
553 result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
554 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work')
555 1477
556 def test_sign_cascaded_uboot_fit_image(self): 1478 def test_sign_cascaded_uboot_fit_image(self):
557 """ 1479 """
@@ -565,9 +1487,9 @@ UBOOT_FIT_HASH_ALG = "sha256"
565 via UBOOT_FIT_GENERATE_KEYS) 1487 via UBOOT_FIT_GENERATE_KEYS)
566 3) Dumping the FIT image indicates signature values 1488 3) Dumping the FIT image indicates signature values
567 are present 1489 are present
568 4) Examination of the do_uboot_assemble_fitimage 1490 4) Examination of the do_uboot_assemble_fitimage that
569 runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN 1491 UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN and SPL_MKIMAGE_SIGN_ARGS
570 and SPL_MKIMAGE_SIGN_ARGS are working as expected. 1492 are working as expected.
571 Product: oe-core 1493 Product: oe-core
572 Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon 1494 Author: Klaus Heinrich Kiwi <klaus@linux.vnet.ibm.com> based upon
573 work by Paul Eggleton <paul.eggleton@microsoft.com> and 1495 work by Paul Eggleton <paul.eggleton@microsoft.com> and
@@ -576,7 +1498,7 @@ UBOOT_FIT_HASH_ALG = "sha256"
576 config = """ 1498 config = """
577# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at 1499# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
578# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set 1500# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
579MACHINE = "qemuarm" 1501MACHINE:forcevariable = "qemuarm"
580UBOOT_MACHINE = "am57xx_evm_defconfig" 1502UBOOT_MACHINE = "am57xx_evm_defconfig"
581SPL_BINARY = "MLO" 1503SPL_BINARY = "MLO"
582# Enable creation and signing of the U-Boot fitImage 1504# Enable creation and signing of the U-Boot fitImage
@@ -588,7 +1510,7 @@ UBOOT_DTB_BINARY = "u-boot.dtb"
588UBOOT_ENTRYPOINT = "0x80000000" 1510UBOOT_ENTRYPOINT = "0x80000000"
589UBOOT_LOADADDRESS = "0x80000000" 1511UBOOT_LOADADDRESS = "0x80000000"
590UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1512UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
591UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded Kernel comment'" 1513UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
592UBOOT_DTB_LOADADDRESS = "0x82000000" 1514UBOOT_DTB_LOADADDRESS = "0x82000000"
593UBOOT_ARCH = "arm" 1515UBOOT_ARCH = "arm"
594SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1516SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
@@ -596,251 +1518,214 @@ SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart cascaded U-Boot comment'"
596UBOOT_EXTLINUX = "0" 1518UBOOT_EXTLINUX = "0"
597UBOOT_FIT_GENERATE_KEYS = "1" 1519UBOOT_FIT_GENERATE_KEYS = "1"
598UBOOT_FIT_HASH_ALG = "sha256" 1520UBOOT_FIT_HASH_ALG = "sha256"
599KERNEL_IMAGETYPES += " fitImage "
600KERNEL_CLASSES = " kernel-fitimage "
601INHERIT += "test-mkimage-wrapper"
602UBOOT_SIGN_ENABLE = "1" 1521UBOOT_SIGN_ENABLE = "1"
603FIT_GENERATE_KEYS = "1"
604UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" 1522UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
605UBOOT_SIGN_IMG_KEYNAME = "img-oe-selftest"
606UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" 1523UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
607FIT_SIGN_INDIVIDUAL = "1"
608""" 1524"""
609 self.write_config(config) 1525 self.write_config(config)
1526 bb_vars = self._fit_get_bb_vars()
610 1527
611 # The U-Boot fitImage is created as part of the U-Boot recipe 1528 self._gen_signing_key(bb_vars)
612 bitbake("virtual/bootloader") 1529 self._test_fitimage(bb_vars)
613 1530 self._check_kernel_dtb(bb_vars)
614 image_type = "core-image-minimal"
615 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
616 machine = get_bb_var('MACHINE')
617 fitimage_its_path = os.path.join(deploy_dir_image,
618 "u-boot-its-%s" % (machine,))
619 fitimage_path = os.path.join(deploy_dir_image,
620 "u-boot-fitImage-%s" % (machine,))
621
622 self.assertTrue(os.path.exists(fitimage_its_path),
623 "%s image tree source doesn't exist" % (fitimage_its_path))
624 self.assertTrue(os.path.exists(fitimage_path),
625 "%s FIT image doesn't exist" % (fitimage_path))
626
627 req_itspaths = [
628 ['/', 'images', 'uboot'],
629 ['/', 'images', 'uboot', 'signature'],
630 ['/', 'images', 'fdt'],
631 ['/', 'images', 'fdt', 'signature'],
632 ]
633 1531
634 itspath = [] 1532 def test_uboot_atf_tee_fit_image(self):
635 itspaths = [] 1533 """
636 linect = 0 1534 Summary: Check if U-boot FIT image and Image Tree Source
637 sigs = {} 1535 (its) are built and the Image Tree Source has the
638 with open(fitimage_its_path) as its_file: 1536 correct fields.
639 linect += 1 1537 Expected: 1. Create atf and tee dummy images
640 for line in its_file: 1538 2. Both u-boot-fitImage and u-boot-its can be built
641 line = line.strip() 1539 3. The os, load address, entrypoint address and
642 if line.endswith('};'): 1540 default values of U-boot, ATF and TEE images are
643 itspath.pop() 1541 correct in the Image Tree Source. Not all the
644 elif line.endswith('{'): 1542 fields are tested, only the key fields that wont
645 itspath.append(line[:-1].strip()) 1543 vary between different architectures.
646 itspaths.append(itspath[:]) 1544 Product: oe-core
647 elif itspath and itspath[-1] == 'signature': 1545 Author: Jamin Lin <jamin_lin@aspeedtech.com>
648 itsdotpath = '.'.join(itspath) 1546 """
649 if not itsdotpath in sigs: 1547 config = """
650 sigs[itsdotpath] = {} 1548# We need at least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
651 if not '=' in line or not line.endswith(';'): 1549MACHINE:forcevariable = "qemuarm"
652 self.fail('Unexpected formatting in %s sigs section line %d:%s' % (fitimage_its_path, linect, line)) 1550UBOOT_MACHINE = "am57xx_evm_defconfig"
653 key, value = line.split('=', 1) 1551SPL_BINARY = "MLO"
654 sigs[itsdotpath][key.rstrip()] = value.lstrip().rstrip(';')
655 1552
656 for reqpath in req_itspaths: 1553# Enable creation of the U-Boot fitImage
657 if not reqpath in itspaths: 1554UBOOT_FITIMAGE_ENABLE = "1"
658 self.fail('Missing section in its file: %s' % reqpath)
659 1555
660 reqsigvalues_image = { 1556# (U-boot) fitImage properties
661 'algo': '"sha256,rsa2048"', 1557UBOOT_LOADADDRESS = "0x80080000"
662 'key-name-hint': '"spl-cascaded-oe-selftest"', 1558UBOOT_ENTRYPOINT = "0x80080000"
663 } 1559UBOOT_FIT_DESC = "A model description"
664 1560
665 for itspath, values in sigs.items(): 1561# Enable creation of the TEE fitImage
666 reqsigvalues = reqsigvalues_image 1562UBOOT_FIT_TEE = "1"
667 for reqkey, reqvalue in reqsigvalues.items():
668 value = values.get(reqkey, None)
669 if value is None:
670 self.fail('Missing key "%s" in its file signature section %s' % (reqkey, itspath))
671 self.assertEqual(value, reqvalue)
672
673 # Dump the image to see if it really got signed
674 bitbake("u-boot-tools-native -c addto_recipe_sysroot")
675 result = runCmd('bitbake -e u-boot-tools-native | grep ^RECIPE_SYSROOT_NATIVE=')
676 recipe_sysroot_native = result.output.split('=')[1].strip('"')
677 dumpimage_path = os.path.join(recipe_sysroot_native, 'usr', 'bin', 'dumpimage')
678 result = runCmd('%s -l %s' % (dumpimage_path, fitimage_path))
679 in_signed = None
680 signed_sections = {}
681 for line in result.output.splitlines():
682 if line.startswith((' Image')):
683 in_signed = re.search(r'\((.*)\)', line).groups()[0]
684 elif re.match(' \w', line):
685 in_signed = None
686 elif in_signed:
687 if not in_signed in signed_sections:
688 signed_sections[in_signed] = {}
689 key, value = line.split(':', 1)
690 signed_sections[in_signed][key.strip()] = value.strip()
691 self.assertIn('uboot', signed_sections)
692 self.assertIn('fdt', signed_sections)
693 for signed_section, values in signed_sections.items():
694 value = values.get('Sign algo', None)
695 self.assertEqual(value, 'sha256,rsa2048:spl-cascaded-oe-selftest', 'Signature algorithm for %s not expected value' % signed_section)
696 value = values.get('Sign value', None)
697 self.assertEqual(len(value), 512, 'Signature value for section %s not expected length' % signed_section)
698
699 # Check for SPL_MKIMAGE_SIGN_ARGS
700 result = runCmd('bitbake -e virtual/bootloader | grep ^T=')
701 tempdir = result.output.split('=', 1)[1].strip().strip('')
702 result = runCmd('grep "a smart cascaded U-Boot comment" %s/run.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
703 self.assertEqual(result.status, 0, 'SPL_MKIMAGE_SIGN_ARGS value did not get used')
704
705 # Check for evidence of test-mkimage-wrapper class
706 result = runCmd('grep "### uboot-mkimage wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
707 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE did not work')
708 result = runCmd('grep "### uboot-mkimage signing wrapper message" %s/log.do_uboot_assemble_fitimage' % tempdir, ignore_status=True)
709 self.assertEqual(result.status, 0, 'UBOOT_MKIMAGE_SIGN did not work')
710
711
712
713 def test_initramfs_bundle(self):
714 """
715 Summary: Verifies the content of the initramfs bundle node in the FIT Image Tree Source (its)
716 The FIT settings are set by the test case.
717 The machine used is beaglebone-yocto.
718 Expected: 1. The ITS is generated with initramfs bundle support
719 2. All the fields in the kernel node are as expected (matching the
720 conf settings)
721 3. The kernel is included in all the available configurations and
722 its hash is included in the configuration signature
723 1563
724 Product: oe-core 1564# TEE fitImage properties
725 Author: Abdellatif El Khlifi <abdellatif.elkhlifi@arm.com> 1565UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin"
726 """ 1566UBOOT_FIT_TEE_LOADADDRESS = "0x80180000"
1567UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000"
1568
1569# Enable creation of the ATF fitImage
1570UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1"
1571
1572# ATF fitImage properties
1573UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin"
1574UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000"
1575UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
1576"""
1577 self.write_config(config)
727 1578
1579 bb_vars = self._fit_get_bb_vars([
1580 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
1581 'UBOOT_FIT_TEE_IMAGE',
1582 ])
1583
1584 # Create an ATF dummy image
1585 dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
1586 FitImageTestCase._gen_random_file(dummy_atf)
1587
1588 # Create a TEE dummy image
1589 dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
1590 FitImageTestCase._gen_random_file(dummy_tee)
1591
1592 self._test_fitimage(bb_vars)
1593
1594 def test_sign_standalone_uboot_atf_tee_fit_image(self):
1595 """
1596 Summary: Check if U-Boot FIT image and Image Tree Source (its) are
1597 created and signed correctly for the scenario where only
1598 the U-Boot proper fitImage is being created and signed.
1599 Expected: 1. Create atf and tee dummy images
1600 2. U-Boot its and FIT image are built successfully
1601 3. Scanning the its file indicates signing is enabled
1602 as requested by SPL_SIGN_ENABLE (using keys generated
1603 via UBOOT_FIT_GENERATE_KEYS)
1604 4. Dumping the FIT image indicates signature values
1605 are present
1606 5. Examination of the do_uboot_assemble_fitimage
1607 runfile/logfile indicate that UBOOT_MKIMAGE, UBOOT_MKIMAGE_SIGN
1608 and SPL_MKIMAGE_SIGN_ARGS are working as expected.
1609 Product: oe-core
1610 Author: Jamin Lin <jamin_lin@aspeedtech.com>
1611 """
728 config = """ 1612 config = """
729DISTRO="poky" 1613# There's no U-boot deconfig with CONFIG_FIT_SIGNATURE yet, so we need at
730MACHINE = "beaglebone-yocto" 1614# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
731INITRAMFS_IMAGE_BUNDLE = "1" 1615MACHINE:forcevariable = "qemuarm"
732INITRAMFS_IMAGE = "core-image-minimal-initramfs" 1616UBOOT_MACHINE = "am57xx_evm_defconfig"
733INITRAMFS_SCRIPTS = "" 1617SPL_BINARY = "MLO"
734UBOOT_MACHINE = "am335x_evm_defconfig" 1618# Enable creation and signing of the U-Boot fitImage
735KERNEL_CLASSES = " kernel-fitimage " 1619UBOOT_FITIMAGE_ENABLE = "1"
736KERNEL_IMAGETYPES = "fitImage" 1620SPL_SIGN_ENABLE = "1"
737UBOOT_SIGN_ENABLE = "1" 1621SPL_SIGN_KEYNAME = "spl-oe-selftest"
738UBOOT_SIGN_KEYNAME = "beaglebonekey" 1622SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
739UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
740UBOOT_DTB_BINARY = "u-boot.dtb" 1623UBOOT_DTB_BINARY = "u-boot.dtb"
741UBOOT_ENTRYPOINT = "0x80000000" 1624UBOOT_ENTRYPOINT = "0x80000000"
742UBOOT_LOADADDRESS = "0x80000000" 1625UBOOT_LOADADDRESS = "0x80000000"
743UBOOT_DTB_LOADADDRESS = "0x82000000"
744UBOOT_ARCH = "arm" 1626UBOOT_ARCH = "arm"
745UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1627SPL_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
746UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" 1628SPL_MKIMAGE_SIGN_ARGS = "-c 'a smart U-Boot ATF TEE comment'"
747UBOOT_EXTLINUX = "0" 1629UBOOT_EXTLINUX = "0"
748FIT_GENERATE_KEYS = "1" 1630UBOOT_FIT_GENERATE_KEYS = "1"
749KERNEL_IMAGETYPE_REPLACEMENT = "zImage" 1631UBOOT_FIT_HASH_ALG = "sha256"
750FIT_KERNEL_COMP_ALG = "none"
751FIT_HASH_ALG = "sha256"
752"""
753 self.write_config(config)
754 1632
755 # fitImage is created as part of linux recipe 1633# Enable creation of the TEE fitImage
756 bitbake("virtual/kernel") 1634UBOOT_FIT_TEE = "1"
757 1635
758 image_type = get_bb_var('INITRAMFS_IMAGE') 1636# TEE fitImage properties
759 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') 1637UBOOT_FIT_TEE_IMAGE = "${TOPDIR}/tee-dummy.bin"
760 machine = get_bb_var('MACHINE') 1638UBOOT_FIT_TEE_LOADADDRESS = "0x80180000"
761 fitimage_its_path = os.path.join(deploy_dir_image, 1639UBOOT_FIT_TEE_ENTRYPOINT = "0x80180000"
762 "fitImage-its-%s-%s-%s" % (image_type, machine, machine))
763 fitimage_path = os.path.join(deploy_dir_image,"fitImage")
764 1640
765 self.assertTrue(os.path.exists(fitimage_its_path), 1641# Enable creation of the ATF fitImage
766 "%s image tree source doesn't exist" % (fitimage_its_path)) 1642UBOOT_FIT_ARM_TRUSTED_FIRMWARE = "1"
767 self.assertTrue(os.path.exists(fitimage_path),
768 "%s FIT image doesn't exist" % (fitimage_path))
769 1643
770 kernel_load = str(get_bb_var('UBOOT_LOADADDRESS')) 1644# ATF fitImage properties
771 kernel_entry = str(get_bb_var('UBOOT_ENTRYPOINT')) 1645UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE = "${TOPDIR}/atf-dummy.bin"
772 kernel_type = str(get_bb_var('UBOOT_MKIMAGE_KERNEL_TYPE')) 1646UBOOT_FIT_ARM_TRUSTED_FIRMWARE_LOADADDRESS = "0x80280000"
773 kernel_compression = str(get_bb_var('FIT_KERNEL_COMP_ALG')) 1647UBOOT_FIT_ARM_TRUSTED_FIRMWARE_ENTRYPOINT = "0x80280000"
774 uboot_arch = str(get_bb_var('UBOOT_ARCH')) 1648"""
775 fit_hash_alg = str(get_bb_var('FIT_HASH_ALG')) 1649 self.write_config(config)
776 1650
777 its_file = open(fitimage_its_path) 1651 bb_vars = self._fit_get_bb_vars([
1652 'UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE',
1653 'UBOOT_FIT_TEE_IMAGE',
1654 ])
778 1655
779 its_lines = [line.strip() for line in its_file.readlines()] 1656 # Create an ATF dummy image
1657 dummy_atf = os.path.join(self.builddir, bb_vars['UBOOT_FIT_ARM_TRUSTED_FIRMWARE_IMAGE'])
1658 FitImageTestCase._gen_random_file(dummy_atf)
780 1659
781 exp_node_lines = [ 1660 # Create a TEE dummy image
782 'kernel-1 {', 1661 dummy_tee = os.path.join(self.builddir, bb_vars['UBOOT_FIT_TEE_IMAGE'])
783 'description = "Linux kernel";', 1662 FitImageTestCase._gen_random_file(dummy_tee)
784 'data = /incbin/("linux.bin");',
785 'type = "' + kernel_type + '";',
786 'arch = "' + uboot_arch + '";',
787 'os = "linux";',
788 'compression = "' + kernel_compression + '";',
789 'load = <' + kernel_load + '>;',
790 'entry = <' + kernel_entry + '>;',
791 'hash-1 {',
792 'algo = "' + fit_hash_alg +'";',
793 '};',
794 '};'
795 ]
796 1663
797 node_str = exp_node_lines[0] 1664 self._test_fitimage(bb_vars)
798 1665
799 test_passed = False
800 1666
801 print ("checking kernel node\n") 1667 def test_sign_uboot_kernel_individual(self):
1668 """
1669 Summary: Check if the device-tree from U-Boot has two public keys
1670 for verifying the kernel FIT image created by the
1671 kernel-fitimage.bbclass included.
1672 This test sets: FIT_SIGN_INDIVIDUAL = "1"
1673 Expected: There must be two signature nodes. One is required for
1674 the individual image nodes, the other is required for the
1675 verification of the configuration section.
1676 """
1677 config = """
1678# Enable creation of fitImage
1679MACHINE:forcevariable = "beaglebone-yocto"
1680UBOOT_SIGN_ENABLE = "1"
1681UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
1682UBOOT_SIGN_KEYNAME = "the-kernel-config-key"
1683UBOOT_SIGN_IMG_KEYNAME = "the-kernel-image-key"
1684UBOOT_MKIMAGE_DTCOPTS="-I dts -O dtb -p 2000"
1685FIT_SIGN_INDIVIDUAL = "1"
1686"""
1687 self.write_config(config)
1688 bb_vars = self._fit_get_bb_vars()
1689 self._gen_signing_key(bb_vars)
802 1690
803 if node_str in its_lines: 1691 bitbake(UBootFitImageTests.BOOTLOADER_RECIPE)
804 node_start_idx = its_lines.index(node_str)
805 node = its_lines[node_start_idx:(node_start_idx + len(exp_node_lines))]
806 if node == exp_node_lines:
807 print("kernel node verified")
808 else:
809 self.assertTrue(test_passed == True,"kernel node does not match expectation")
810
811 rx_configs = re.compile("^conf-.*")
812 its_configs = list(filter(rx_configs.match, its_lines))
813
814 for cfg_str in its_configs:
815 cfg_start_idx = its_lines.index(cfg_str)
816 line_idx = cfg_start_idx + 2
817 node_end = False
818 while node_end == False:
819 if its_lines[line_idx] == "};" and its_lines[line_idx-1] == "};" :
820 node_end = True
821 line_idx = line_idx + 1
822
823 node = its_lines[cfg_start_idx:line_idx]
824 print("checking configuration " + cfg_str.rstrip(" {"))
825 rx_desc_line = re.compile("^description.*1 Linux kernel.*")
826 if len(list(filter(rx_desc_line.match, node))) != 1:
827 self.assertTrue(test_passed == True,"kernel keyword not found in the description line")
828 break
829 else:
830 print("kernel keyword found in the description line")
831 1692
832 if 'kernel = "kernel-1";' not in node: 1693 # Just check the DTB of u-boot since there is no u-boot FIT image
833 self.assertTrue(test_passed == True,"kernel line not found") 1694 self._check_kernel_dtb(bb_vars)
834 break
835 else:
836 print("kernel line found")
837 1695
838 rx_sign_line = re.compile("^sign-images.*kernel.*")
839 if len(list(filter(rx_sign_line.match, node))) != 1:
840 self.assertTrue(test_passed == True,"kernel hash not signed")
841 break
842 else:
843 print("kernel hash signed")
844 1696
845 test_passed = True 1697 def test_sign_uboot_fit_image_without_spl(self):
846 self.assertTrue(test_passed == True,"Initramfs bundle test success") 1698 """
1699 Summary: Check if U-Boot FIT image and Image Tree Source (its) are
1700 created and signed correctly for the scenario where only
1701 the U-Boot proper fitImage is being created and signed
1702 (no SPL included).
1703 Expected: 1) U-Boot its and FIT image are built successfully
1704 2) Scanning the its file indicates signing is enabled
1705 as requested by SPL_SIGN_ENABLE (using keys generated
1706 via UBOOT_FIT_GENERATE_KEYS)
1707 3) Dumping the FIT image indicates signature values
1708 are present
1709 4) Examination of the do_uboot_assemble_fitimage
1710 runfile/logfile indicate that UBOOT_MKIMAGE and
1711 UBOOT_MKIMAGE_SIGN are working as expected.
1712 Product: oe-core
1713 Author: Jamin Lin <jamin_lin@aspeedtech.com>
1714 """
1715 config = """
1716# There's no U-boot defconfig with CONFIG_FIT_SIGNATURE yet, so we need at
1717# least CONFIG_SPL_LOAD_FIT and CONFIG_SPL_OF_CONTROL set
1718MACHINE:forcevariable = "qemuarm"
1719UBOOT_MACHINE = "am57xx_evm_defconfig"
1720# Enable creation and signing of the U-Boot fitImage (no SPL)
1721UBOOT_FITIMAGE_ENABLE = "1"
1722SPL_DTB_BINARY = ""
1723SPL_SIGN_ENABLE = "1"
1724SPL_SIGN_KEYNAME = "spl-oe-selftest"
1725SPL_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
1726UBOOT_FIT_GENERATE_KEYS = "1"
1727"""
1728 self.write_config(config)
1729 bb_vars = self._fit_get_bb_vars()
1730 self._test_fitimage(bb_vars)
1731
diff --git a/meta/lib/oeqa/selftest/cases/gcc.py b/meta/lib/oeqa/selftest/cases/gcc.py
index 89360178fe..d01a791cb5 100644
--- a/meta/lib/oeqa/selftest/cases/gcc.py
+++ b/meta/lib/oeqa/selftest/cases/gcc.py
@@ -37,7 +37,7 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
37 features = [] 37 features = []
38 features.append('MAKE_CHECK_TARGETS = "{0}"'.format(" ".join(targets))) 38 features.append('MAKE_CHECK_TARGETS = "{0}"'.format(" ".join(targets)))
39 if ssh is not None: 39 if ssh is not None:
40 features.append('TOOLCHAIN_TEST_TARGET = "ssh"') 40 features.append('TOOLCHAIN_TEST_TARGET = "linux-ssh"')
41 features.append('TOOLCHAIN_TEST_HOST = "{0}"'.format(ssh)) 41 features.append('TOOLCHAIN_TEST_HOST = "{0}"'.format(ssh))
42 features.append('TOOLCHAIN_TEST_HOST_USER = "root"') 42 features.append('TOOLCHAIN_TEST_HOST_USER = "root"')
43 features.append('TOOLCHAIN_TEST_HOST_PORT = "22"') 43 features.append('TOOLCHAIN_TEST_HOST_PORT = "22"')
@@ -71,7 +71,7 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
71 71
72 def run_check_emulated(self, *args, **kwargs): 72 def run_check_emulated(self, *args, **kwargs):
73 # build core-image-minimal with required packages 73 # build core-image-minimal with required packages
74 default_installed_packages = ["libgcc", "libstdc++", "libatomic", "libgomp"] 74 default_installed_packages = ["libgcc", "libstdc++", "libatomic", "libgomp", "libitm"]
75 features = [] 75 features = []
76 features.append('IMAGE_FEATURES += "ssh-server-openssh"') 76 features.append('IMAGE_FEATURES += "ssh-server-openssh"')
77 features.append('CORE_IMAGE_EXTRA_INSTALL += "{0}"'.format(" ".join(default_installed_packages))) 77 features.append('CORE_IMAGE_EXTRA_INSTALL += "{0}"'.format(" ".join(default_installed_packages)))
@@ -79,10 +79,13 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
79 bitbake("core-image-minimal") 79 bitbake("core-image-minimal")
80 80
81 # wrap the execution with a qemu instance 81 # wrap the execution with a qemu instance
82 with runqemu("core-image-minimal", runqemuparams = "nographic") as qemu: 82 # Increase RAM to 4GB to accommodate some GCC tests that require more than 3GB
83 with runqemu("core-image-minimal", runqemuparams = "nographic", qemuparams=" -m 4096") as qemu:
83 # validate that SSH is working 84 # validate that SSH is working
84 status, _ = qemu.run("uname") 85 status, _ = qemu.run("uname")
85 self.assertEqual(status, 0) 86 self.assertEqual(status, 0)
87 qemu.run('echo "MaxStartups 75:30:100" >> /etc/ssh/sshd_config')
88 qemu.run('service sshd restart')
86 89
87 return self.run_check(*args, ssh=qemu.ip, **kwargs) 90 return self.run_check(*args, ssh=qemu.ip, **kwargs)
88 91
diff --git a/meta/lib/oeqa/selftest/cases/gdbserver.py b/meta/lib/oeqa/selftest/cases/gdbserver.py
index 9da97ae780..1c71333190 100644
--- a/meta/lib/oeqa/selftest/cases/gdbserver.py
+++ b/meta/lib/oeqa/selftest/cases/gdbserver.py
@@ -19,6 +19,7 @@ class GdbServerTest(OESelftestTestCase):
19 19
20 features = """ 20 features = """
21IMAGE_GEN_DEBUGFS = "1" 21IMAGE_GEN_DEBUGFS = "1"
22IMAGE_FSTYPES += "tar.bz2"
22IMAGE_FSTYPES_DEBUGFS = "tar.bz2" 23IMAGE_FSTYPES_DEBUGFS = "tar.bz2"
23CORE_IMAGE_EXTRA_INSTALL = "gdbserver" 24CORE_IMAGE_EXTRA_INSTALL = "gdbserver"
24 """ 25 """
@@ -54,7 +55,7 @@ CORE_IMAGE_EXTRA_INSTALL = "gdbserver"
54 self.logger.warning("starting gdb %s" % cmd) 55 self.logger.warning("starting gdb %s" % cmd)
55 r = runCmd(cmd, native_sysroot=native_sysroot, target_sys=target_sys) 56 r = runCmd(cmd, native_sysroot=native_sysroot, target_sys=target_sys)
56 self.assertEqual(0, r.status) 57 self.assertEqual(0, r.status)
57 line_re = r"Line \d+ of \"/usr/src/debug/kmod/.*/tools/kmod.c\" starts at address 0x[0-9A-Fa-f]+ <kmod_help>" 58 line_re = r"Line \d+ of \".*\" starts at address 0x[0-9A-Fa-f]+ <kmod_help>"
58 self.assertRegex(r.output, line_re) 59 self.assertRegex(r.output, line_re)
59 break 60 break
60 else: 61 else:
diff --git a/meta/lib/oeqa/selftest/cases/imagefeatures.py b/meta/lib/oeqa/selftest/cases/imagefeatures.py
index dc88c222bd..87c3da228a 100644
--- a/meta/lib/oeqa/selftest/cases/imagefeatures.py
+++ b/meta/lib/oeqa/selftest/cases/imagefeatures.py
@@ -250,12 +250,7 @@ USERADD_GID_TABLES += "files/static-group"
250DISTRO_FEATURES:append = " pam opengl wayland" 250DISTRO_FEATURES:append = " pam opengl wayland"
251 251
252# Switch to systemd 252# Switch to systemd
253DISTRO_FEATURES:append = " systemd usrmerge" 253INIT_MANAGER = "systemd"
254VIRTUAL-RUNTIME_init_manager = "systemd"
255VIRTUAL-RUNTIME_initscripts = ""
256VIRTUAL-RUNTIME_syslog = ""
257VIRTUAL-RUNTIME_login_manager = "shadow-base"
258DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
259 254
260# Replace busybox 255# Replace busybox
261PREFERRED_PROVIDER_virtual/base-utils = "packagegroup-core-base-utils" 256PREFERRED_PROVIDER_virtual/base-utils = "packagegroup-core-base-utils"
@@ -282,16 +277,19 @@ SKIP_RECIPE[busybox] = "Don't build this"
282 """ 277 """
283 278
284 image = 'core-image-minimal' 279 image = 'core-image-minimal'
285 image_fstypes_debugfs = 'tar.bz2' 280 config = """
286 features = 'IMAGE_GEN_DEBUGFS = "1"\n' 281IMAGE_GEN_DEBUGFS = "1"
287 features += 'IMAGE_FSTYPES_DEBUGFS = "%s"\n' % image_fstypes_debugfs 282IMAGE_FSTYPES_DEBUGFS = "tar.bz2 tar.bz2.sha256sum"
288 self.write_config(features) 283"""
284 self.write_config(config)
289 285
290 bitbake(image) 286 bitbake(image)
291 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image) 287 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
292 288
293 dbg_tar_file = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "%s-dbg.%s" % (bb_vars['IMAGE_LINK_NAME'], image_fstypes_debugfs)) 289 dbg_tar_file = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "%s-dbg.tar.bz2" % bb_vars['IMAGE_LINK_NAME'])
294 self.assertTrue(os.path.exists(dbg_tar_file), 'debug filesystem not generated at %s' % dbg_tar_file) 290 self.assertTrue(os.path.exists(dbg_tar_file), 'debug filesystem not generated at %s' % dbg_tar_file)
291 dbg_tar_sha_file = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "%s-dbg.tar.bz2.sha256sum" % bb_vars['IMAGE_LINK_NAME'])
292 self.assertTrue(os.path.exists(dbg_tar_sha_file), 'debug filesystem sha256sum not generated at %s' % dbg_tar_sha_file)
295 result = runCmd('cd %s; tar xvf %s' % (bb_vars['DEPLOY_DIR_IMAGE'], dbg_tar_file)) 293 result = runCmd('cd %s; tar xvf %s' % (bb_vars['DEPLOY_DIR_IMAGE'], dbg_tar_file))
296 self.assertEqual(result.status, 0, msg='Failed to extract %s: %s' % (dbg_tar_file, result.output)) 294 self.assertEqual(result.status, 0, msg='Failed to extract %s: %s' % (dbg_tar_file, result.output))
297 result = runCmd('find %s -name %s' % (bb_vars['DEPLOY_DIR_IMAGE'], "udevadm")) 295 result = runCmd('find %s -name %s' % (bb_vars['DEPLOY_DIR_IMAGE'], "udevadm"))
@@ -319,7 +317,7 @@ SKIP_RECIPE[busybox] = "Don't build this"
319 """ 317 """
320 config = """ 318 config = """
321DISTRO_FEATURES:append = " api-documentation" 319DISTRO_FEATURES:append = " api-documentation"
322CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc" 320CORE_IMAGE_EXTRA_INSTALL = "man-pages"
323""" 321"""
324 self.write_config(config) 322 self.write_config(config)
325 bitbake("core-image-minimal") 323 bitbake("core-image-minimal")
@@ -330,7 +328,7 @@ CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc"
330 self.assertEqual(status, 1, 'Failed to run apropos: %s' % (output)) 328 self.assertEqual(status, 1, 'Failed to run apropos: %s' % (output))
331 self.assertIn("iso_8859_15", output) 329 self.assertIn("iso_8859_15", output)
332 330
333 # This manpage is provided by kmod 331 # This manpage is provided by man-pages
334 status, output = qemu.run_serial("man --pager=cat modprobe") 332 status, output = qemu.run_serial("man --pager=cat intro")
335 self.assertEqual(status, 1, 'Failed to run man: %s' % (output)) 333 self.assertEqual(status, 1, 'Failed to run man: %s' % (output))
336 self.assertIn("force-modversion", output) 334 self.assertIn("introduction to user commands", output)
diff --git a/meta/lib/oeqa/selftest/cases/incompatible_lic.py b/meta/lib/oeqa/selftest/cases/incompatible_lic.py
index f4af67a239..93884f5731 100644
--- a/meta/lib/oeqa/selftest/cases/incompatible_lic.py
+++ b/meta/lib/oeqa/selftest/cases/incompatible_lic.py
@@ -102,6 +102,7 @@ class IncompatibleLicensePerImageTests(OESelftestTestCase):
102 return """ 102 return """
103IMAGE_INSTALL:append = " bash" 103IMAGE_INSTALL:append = " bash"
104INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*" 104INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*"
105MACHINE_ESSENTIAL_EXTRA_RDEPENDS:remove = "tar"
105""" 106"""
106 107
107 def test_bash_default(self): 108 def test_bash_default(self):
@@ -114,7 +115,7 @@ INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*"
114 115
115 def test_bash_and_license(self): 116 def test_bash_and_license(self):
116 self.disable_class("create-spdx") 117 self.disable_class("create-spdx")
117 self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " & SomeLicense"') 118 self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " & SomeLicense"\nERROR_QA:remove:pn-bash = "license-exists"')
118 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" 119 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"
119 120
120 result = bitbake('core-image-minimal', ignore_status=True) 121 result = bitbake('core-image-minimal', ignore_status=True)
@@ -123,12 +124,12 @@ INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*"
123 124
124 def test_bash_or_license(self): 125 def test_bash_or_license(self):
125 self.disable_class("create-spdx") 126 self.disable_class("create-spdx")
126 self.write_config(self.default_config() + '\nLICENSE:append:pn-bash = " | SomeLicense"') 127 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"')
127 128
128 bitbake('core-image-minimal') 129 bitbake('core-image-minimal')
129 130
130 def test_bash_license_exceptions(self): 131 def test_bash_license_exceptions(self):
131 self.write_config(self.default_config() + '\nINCOMPATIBLE_LICENSE_EXCEPTIONS:pn-core-image-minimal = "bash:GPL-3.0-or-later"') 132 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"')
132 133
133 bitbake('core-image-minimal') 134 bitbake('core-image-minimal')
134 135
@@ -136,6 +137,8 @@ class NoGPL3InImagesTests(OESelftestTestCase):
136 def test_core_image_minimal(self): 137 def test_core_image_minimal(self):
137 self.write_config(""" 138 self.write_config("""
138INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*" 139INCOMPATIBLE_LICENSE:pn-core-image-minimal = "GPL-3.0* LGPL-3.0*"
140
141require conf/distro/include/no-gplv3.inc
139""") 142""")
140 bitbake('core-image-minimal') 143 bitbake('core-image-minimal')
141 144
diff --git a/meta/lib/oeqa/selftest/cases/layerappend.py b/meta/lib/oeqa/selftest/cases/layerappend.py
index 379ed589ad..64b17117cc 100644
--- a/meta/lib/oeqa/selftest/cases/layerappend.py
+++ b/meta/lib/oeqa/selftest/cases/layerappend.py
@@ -37,7 +37,7 @@ FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
37SRC_URI:append = " file://appendtest.txt" 37SRC_URI:append = " file://appendtest.txt"
38 38
39sysroot_stage_all:append() { 39sysroot_stage_all:append() {
40 install -m 644 ${WORKDIR}/appendtest.txt ${SYSROOT_DESTDIR}/ 40 install -m 644 ${UNPACKDIR}/appendtest.txt ${SYSROOT_DESTDIR}/
41} 41}
42 42
43""" 43"""
diff --git a/meta/lib/oeqa/selftest/cases/liboe.py b/meta/lib/oeqa/selftest/cases/liboe.py
index d5ffffdcb4..930354c931 100644
--- a/meta/lib/oeqa/selftest/cases/liboe.py
+++ b/meta/lib/oeqa/selftest/cases/liboe.py
@@ -9,11 +9,11 @@ from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake, runCmd
9import oe.path 9import oe.path
10import os 10import os
11 11
12class LibOE(OESelftestTestCase): 12class CopyTreeTests(OESelftestTestCase):
13 13
14 @classmethod 14 @classmethod
15 def setUpClass(cls): 15 def setUpClass(cls):
16 super(LibOE, cls).setUpClass() 16 super().setUpClass()
17 cls.tmp_dir = get_bb_var('TMPDIR') 17 cls.tmp_dir = get_bb_var('TMPDIR')
18 18
19 def test_copy_tree_special(self): 19 def test_copy_tree_special(self):
@@ -102,3 +102,36 @@ class LibOE(OESelftestTestCase):
102 self.assertEqual(dstcnt, len(testfiles), "Number of files in dst (%s) differs from number of files in src(%s)." % (dstcnt, srccnt)) 102 self.assertEqual(dstcnt, len(testfiles), "Number of files in dst (%s) differs from number of files in src(%s)." % (dstcnt, srccnt))
103 103
104 oe.path.remove(testloc) 104 oe.path.remove(testloc)
105
106class SubprocessTests(OESelftestTestCase):
107
108 def test_subprocess_tweak(self):
109 """
110 Test that the string representation of
111 oeqa.utils.subprocesstweak.OETestCalledProcessError includes stdout and
112 stderr, as expected.
113 """
114 script = """
115#! /bin/sh
116echo Ivn fgqbhg | tr '[a-zA-Z]' '[n-za-mN-ZA-M]'
117echo Ivn fgqree | tr '[a-zA-Z]' '[n-za-mN-ZA-M]' >&2
118exit 42
119 """
120
121 import subprocess
122 import unittest.mock
123 from oeqa.utils.subprocesstweak import OETestCalledProcessError
124
125 with self.assertRaises(OETestCalledProcessError) as cm:
126 with unittest.mock.patch("subprocess.CalledProcessError", OETestCalledProcessError):
127 subprocess.run(["bash", "-"], input=script, text=True, capture_output=True, check=True)
128
129 e = cm.exception
130 self.assertEqual(e.returncode, 42)
131 self.assertEqual("Via stdout\n", e.stdout)
132 self.assertEqual("Via stderr\n", e.stderr)
133
134 string = str(e)
135 self.assertIn("exit status 42", string)
136 self.assertIn("Standard Output: Via stdout", string)
137 self.assertIn("Standard Error: Via stderr", string)
diff --git a/meta/lib/oeqa/selftest/cases/locales.py b/meta/lib/oeqa/selftest/cases/locales.py
index 4ca8ffb7aa..ac4888ef66 100644
--- a/meta/lib/oeqa/selftest/cases/locales.py
+++ b/meta/lib/oeqa/selftest/cases/locales.py
@@ -14,7 +14,7 @@ class LocalesTest(OESelftestTestCase):
14 features = [] 14 features = []
15 features.append('EXTRA_IMAGE_FEATURES = "empty-root-password allow-empty-password allow-root-login"') 15 features.append('EXTRA_IMAGE_FEATURES = "empty-root-password allow-empty-password allow-root-login"')
16 features.append('IMAGE_INSTALL:append = " glibc-utils localedef"') 16 features.append('IMAGE_INSTALL:append = " glibc-utils localedef"')
17 features.append('GLIBC_GENERATE_LOCALES = "en_US.UTF-8 fr_FR.UTF-8"') 17 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"')
18 features.append('IMAGE_LINGUAS:append = " en-us fr-fr"') 18 features.append('IMAGE_LINGUAS:append = " en-us fr-fr"')
19 if binary_enabled: 19 if binary_enabled:
20 features.append('ENABLE_BINARY_LOCALE_GENERATION = "1"') 20 features.append('ENABLE_BINARY_LOCALE_GENERATION = "1"')
diff --git a/meta/lib/oeqa/selftest/cases/meta_ide.py b/meta/lib/oeqa/selftest/cases/meta_ide.py
index ffe0d2604d..f105302d8b 100644
--- a/meta/lib/oeqa/selftest/cases/meta_ide.py
+++ b/meta/lib/oeqa/selftest/cases/meta_ide.py
@@ -20,8 +20,8 @@ class MetaIDE(OESelftestTestCase):
20 bitbake('meta-ide-support') 20 bitbake('meta-ide-support')
21 bitbake('build-sysroots -c build_native_sysroot') 21 bitbake('build-sysroots -c build_native_sysroot')
22 bitbake('build-sysroots -c build_target_sysroot') 22 bitbake('build-sysroots -c build_target_sysroot')
23 bb_vars = get_bb_vars(['MULTIMACH_TARGET_SYS', 'DEPLOY_DIR_IMAGE', 'COREBASE']) 23 bb_vars = get_bb_vars(['MACHINE_ARCH', 'TARGET_VENDOR', 'TARGET_OS', 'DEPLOY_DIR_IMAGE', 'COREBASE'])
24 cls.environment_script = 'environment-setup-%s' % bb_vars['MULTIMACH_TARGET_SYS'] 24 cls.environment_script = 'environment-setup-%s%s-%s' % (bb_vars['MACHINE_ARCH'], bb_vars['TARGET_VENDOR'], bb_vars['TARGET_OS'])
25 cls.deploydir = bb_vars['DEPLOY_DIR_IMAGE'] 25 cls.deploydir = bb_vars['DEPLOY_DIR_IMAGE']
26 cls.environment_script_path = '%s/%s' % (cls.deploydir, cls.environment_script) 26 cls.environment_script_path = '%s/%s' % (cls.deploydir, cls.environment_script)
27 cls.corebasedir = bb_vars['COREBASE'] 27 cls.corebasedir = bb_vars['COREBASE']
@@ -37,19 +37,19 @@ class MetaIDE(OESelftestTestCase):
37 37
38 def test_meta_ide_can_compile_c_program(self): 38 def test_meta_ide_can_compile_c_program(self):
39 runCmd('cp %s/test.c %s' % (self.tc.files_dir, self.tmpdir_metaideQA)) 39 runCmd('cp %s/test.c %s' % (self.tc.files_dir, self.tmpdir_metaideQA))
40 runCmd("cd %s; . %s; $CC test.c -lm" % (self.tmpdir_metaideQA, self.environment_script_path)) 40 runCmd(". %s; cd %s; $CC test.c -lm" % (self.environment_script_path, self.tmpdir_metaideQA))
41 compiled_file = '%s/a.out' % self.tmpdir_metaideQA 41 compiled_file = '%s/a.out' % self.tmpdir_metaideQA
42 self.assertExists(compiled_file) 42 self.assertExists(compiled_file)
43 43
44 def test_meta_ide_can_build_cpio_project(self): 44 def test_meta_ide_can_build_cpio_project(self):
45 dl_dir = self.td.get('DL_DIR', None) 45 dl_dir = self.td.get('DL_DIR', None)
46 self.project = SDKBuildProject(self.tmpdir_metaideQA + "/cpio/", self.environment_script_path, 46 self.project = SDKBuildProject(self.tmpdir_metaideQA + "/cpio/", self.environment_script_path,
47 "https://ftp.gnu.org/gnu/cpio/cpio-2.15.tar.gz", 47 "https://ftpmirror.gnu.org/gnu/cpio/cpio-2.15.tar.gz",
48 self.tmpdir_metaideQA, self.td['DATETIME'], dl_dir=dl_dir) 48 self.tmpdir_metaideQA, self.td['DATETIME'], dl_dir=dl_dir)
49 self.project.download_archive() 49 self.project.download_archive()
50 self.assertEqual(self.project.run_configure('$CONFIGURE_FLAGS'), 0, 50 self.assertEqual(self.project.run_configure('CFLAGS="-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration" $CONFIGURE_FLAGS'), 0,
51 msg="Running configure failed") 51 msg="Running configure failed")
52 self.assertEqual(self.project.run_make(), 0, 52 self.assertEqual(self.project.run_make(make_args="CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration'"), 0,
53 msg="Running make failed") 53 msg="Running make failed")
54 self.assertEqual(self.project.run_install(), 0, 54 self.assertEqual(self.project.run_install(), 0,
55 msg="Running make install failed") 55 msg="Running make install failed")
diff --git a/meta/lib/oeqa/selftest/cases/minidebuginfo.py b/meta/lib/oeqa/selftest/cases/minidebuginfo.py
index 2919f07939..a8923460f9 100644
--- a/meta/lib/oeqa/selftest/cases/minidebuginfo.py
+++ b/meta/lib/oeqa/selftest/cases/minidebuginfo.py
@@ -8,6 +8,7 @@ import subprocess
8import tempfile 8import tempfile
9import shutil 9import shutil
10 10
11from oeqa.core.decorator import OETestTag
11from oeqa.selftest.case import OESelftestTestCase 12from oeqa.selftest.case import OESelftestTestCase
12from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd 13from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd
13 14
@@ -42,3 +43,18 @@ IMAGE_FSTYPES = "tar.bz2"
42 native_sysroot = native_sysroot, target_sys = target_sys) 43 native_sysroot = native_sysroot, target_sys = target_sys)
43 self.assertIn(".gnu_debugdata", r.output) 44 self.assertIn(".gnu_debugdata", r.output)
44 45
46 @OETestTag("runqemu")
47 def test_minidebuginfo_qemu(self):
48 """
49 Test minidebuginfo inside a qemu.
50 This runs test_systemd_coredump_minidebuginfo and other minidebuginfo runtime tests which may be added in the future.
51 """
52
53 self.write_config("""
54DISTRO_FEATURES:append = " minidebuginfo"
55INIT_MANAGER = "systemd"
56IMAGE_CLASSES += "testimage"
57TEST_SUITES = "ping ssh systemd"
58 """)
59 bitbake('core-image-minimal')
60 bitbake('-c testimage core-image-minimal')
diff --git a/meta/lib/oeqa/selftest/cases/oescripts.py b/meta/lib/oeqa/selftest/cases/oescripts.py
index f69efccfee..ebaf01f777 100644
--- a/meta/lib/oeqa/selftest/cases/oescripts.py
+++ b/meta/lib/oeqa/selftest/cases/oescripts.py
@@ -9,39 +9,19 @@ import shutil
9import importlib 9import importlib
10import unittest 10import unittest
11from oeqa.selftest.case import OESelftestTestCase 11from oeqa.selftest.case import OESelftestTestCase
12from oeqa.selftest.cases.buildhistory import BuildhistoryBase
13from oeqa.utils.commands import runCmd, bitbake, get_bb_var 12from oeqa.utils.commands import runCmd, bitbake, get_bb_var
14from oeqa.utils import CommandError 13from oeqa.utils import CommandError
15 14
16class BuildhistoryDiffTests(BuildhistoryBase):
17
18 def test_buildhistory_diff(self):
19 target = 'xcursor-transparent-theme'
20 self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
21 self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True)
22 result = runCmd("oe-pkgdata-util read-value PKGV %s" % target)
23 pkgv = result.output.rstrip()
24 result = runCmd("buildhistory-diff -p %s" % get_bb_var('BUILDHISTORY_DIR'))
25 expected_endlines = [
26 "xcursor-transparent-theme-dev: RRECOMMENDS: removed \"xcursor-transparent-theme (['= %s-r1'])\", added \"xcursor-transparent-theme (['= %s-r0'])\"" % (pkgv, pkgv),
27 "xcursor-transparent-theme-staticdev: RDEPENDS: removed \"xcursor-transparent-theme-dev (['= %s-r1'])\", added \"xcursor-transparent-theme-dev (['= %s-r0'])\"" % (pkgv, pkgv)
28 ]
29 for line in result.output.splitlines():
30 for el in expected_endlines:
31 if line.endswith(el):
32 expected_endlines.remove(el)
33 break
34 else:
35 self.fail('Unexpected line:\n%s\nExpected line endings:\n %s' % (line, '\n '.join(expected_endlines)))
36 if expected_endlines:
37 self.fail('Missing expected line endings:\n %s' % '\n '.join(expected_endlines))
38
39@unittest.skipUnless(importlib.util.find_spec("cairo"), "Python cairo module is not present") 15@unittest.skipUnless(importlib.util.find_spec("cairo"), "Python cairo module is not present")
40class OEPybootchartguyTests(OESelftestTestCase): 16class OEPybootchartguyTests(OESelftestTestCase):
41 17
42 @classmethod 18 @classmethod
43 def setUpClass(cls): 19 def setUpClass(cls):
44 super().setUpClass() 20 super().setUpClass()
21 cls.write_config(cls,
22"""
23INHERIT += "buildstats"
24""")
45 bitbake("core-image-minimal -c rootfs -f") 25 bitbake("core-image-minimal -c rootfs -f")
46 cls.tmpdir = get_bb_var('TMPDIR') 26 cls.tmpdir = get_bb_var('TMPDIR')
47 cls.buildstats = cls.tmpdir + "/buildstats/" + sorted(os.listdir(cls.tmpdir + "/buildstats"))[-1] 27 cls.buildstats = cls.tmpdir + "/buildstats/" + sorted(os.listdir(cls.tmpdir + "/buildstats"))[-1]
@@ -175,7 +155,7 @@ class OEListPackageconfigTests(OESelftestTestCase):
175 def test_packageconfig_flags_option_all(self): 155 def test_packageconfig_flags_option_all(self):
176 results = runCmd('%s/contrib/list-packageconfig-flags.py -a' % self.scripts_dir) 156 results = runCmd('%s/contrib/list-packageconfig-flags.py -a' % self.scripts_dir)
177 expected_endlines = [] 157 expected_endlines = []
178 expected_endlines.append("pinentry-1.2.1") 158 expected_endlines.append("pinentry-1.3.2")
179 expected_endlines.append("PACKAGECONFIG ncurses") 159 expected_endlines.append("PACKAGECONFIG ncurses")
180 expected_endlines.append("PACKAGECONFIG[qt] --enable-pinentry-qt, --disable-pinentry-qt, qtbase-native qtbase") 160 expected_endlines.append("PACKAGECONFIG[qt] --enable-pinentry-qt, --disable-pinentry-qt, qtbase-native qtbase")
181 expected_endlines.append("PACKAGECONFIG[gtk2] --enable-pinentry-gtk2, --disable-pinentry-gtk2, gtk+ glib-2.0") 161 expected_endlines.append("PACKAGECONFIG[gtk2] --enable-pinentry-gtk2, --disable-pinentry-gtk2, gtk+ glib-2.0")
diff --git a/meta/lib/oeqa/selftest/cases/overlayfs.py b/meta/lib/oeqa/selftest/cases/overlayfs.py
index e31063567b..580fbdcb9c 100644
--- a/meta/lib/oeqa/selftest/cases/overlayfs.py
+++ b/meta/lib/oeqa/selftest/cases/overlayfs.py
@@ -5,7 +5,7 @@
5# 5#
6 6
7from oeqa.selftest.case import OESelftestTestCase 7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import bitbake, runqemu 8from oeqa.utils.commands import bitbake, runqemu, get_bb_vars
9from oeqa.core.decorator import OETestTag 9from oeqa.core.decorator import OETestTag
10from oeqa.core.decorator.data import skipIfNotMachine 10from oeqa.core.decorator.data import skipIfNotMachine
11 11
@@ -466,6 +466,45 @@ IMAGE_INSTALL:append = " overlayfs-user"
466 line = getline_qemu(output, "Read-only file system") 466 line = getline_qemu(output, "Read-only file system")
467 self.assertTrue(line, msg=output) 467 self.assertTrue(line, msg=output)
468 468
469 @skipIfNotMachine("qemux86-64", "tests are qemux86-64 specific currently")
470 def test_postinst_on_target_for_read_only_rootfs(self):
471 """
472 Summary: The purpose of this test case is to verify that post-installation
473 on target scripts are executed even if using read-only rootfs when
474 read-only-rootfs-delayed-postinsts is set
475 Expected: The test files are created on first boot
476 """
477
478 import oe.path
479
480 vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal")
481 sysconfdir = vars["sysconfdir"]
482 self.assertIsNotNone(sysconfdir)
483 # Need to use oe.path here as sysconfdir starts with /
484 targettestdir = os.path.join(sysconfdir, "postinst-test")
485
486 config = self.get_working_config()
487
488 args = {
489 'OVERLAYFS_INIT_OPTION': "",
490 'OVERLAYFS_ETC_USE_ORIG_INIT_NAME': 1,
491 'OVERLAYFS_ROOTFS_TYPE': "ext4",
492 'OVERLAYFS_ETC_CREATE_MOUNT_DIRS': 1
493 }
494
495 # read-only-rootfs is already set in get_working_config()
496 config += 'EXTRA_IMAGE_FEATURES += "read-only-rootfs-delayed-postinsts"\n'
497 config += 'CORE_IMAGE_EXTRA_INSTALL = "postinst-delayed-b"\n'
498
499 self.write_config(config.format(**args))
500
501 res = bitbake('core-image-minimal')
502
503 with runqemu('core-image-minimal', image_fstype='wic') as qemu:
504 for filename in ("rootfs", "delayed-a", "delayed-b"):
505 status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename))
506 self.assertIn("found", output, "%s was not present on boot" % filename)
507
469 def get_working_config(self): 508 def get_working_config(self):
470 return """ 509 return """
471# Use systemd as init manager 510# Use systemd as init manager
diff --git a/meta/lib/oeqa/selftest/cases/package.py b/meta/lib/oeqa/selftest/cases/package.py
index 1aa6c03f8a..38ed7173fe 100644
--- a/meta/lib/oeqa/selftest/cases/package.py
+++ b/meta/lib/oeqa/selftest/cases/package.py
@@ -103,11 +103,37 @@ class PackageTests(OESelftestTestCase):
103 103
104 dest = get_bb_var('PKGDEST', 'selftest-hardlink') 104 dest = get_bb_var('PKGDEST', 'selftest-hardlink')
105 bindir = get_bb_var('bindir', 'selftest-hardlink') 105 bindir = get_bb_var('bindir', 'selftest-hardlink')
106 libdir = get_bb_var('libdir', 'selftest-hardlink')
107 libexecdir = get_bb_var('libexecdir', 'selftest-hardlink')
106 108
107 def checkfiles(): 109 def checkfiles():
108 # Recipe creates 4 hardlinked files, there is a copy in package/ and a copy in packages-split/ 110 # Recipe creates 4 hardlinked files, there is a copy in package/ and a copy in packages-split/
109 # so expect 8 in total. 111 # so expect 8 in total.
110 self.assertEqual(os.stat(dest + "/selftest-hardlink" + bindir + "/hello1").st_nlink, 8) 112 self.assertEqual(os.stat(dest + "/selftest-hardlink" + bindir + "/hello1").st_nlink, 8)
113 self.assertEqual(os.stat(dest + "/selftest-hardlink" + libexecdir + "/hello3").st_nlink, 8)
114
115 # Check dbg version
116 # 2 items, a copy in both package/packages-split so 4
117 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + bindir + "/.debug/hello1").st_nlink, 4)
118 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello1").st_nlink, 4)
119
120 # Even though the libexecdir name is 'hello3' or 'hello4', that isn't the debug target name
121 self.assertEqual(os.path.exists(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello3"), False)
122 self.assertEqual(os.path.exists(dest + "/selftest-hardlink-dbg" + libexecdir + "/.debug/hello4"), False)
123
124 # Check the staticdev libraries
125 # 101 items, a copy in both package/packages-split so 202
126 self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello.a").st_nlink, 202)
127 self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-25.a").st_nlink, 202)
128 self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-50.a").st_nlink, 202)
129 self.assertEqual(os.stat(dest + "/selftest-hardlink-staticdev" + libdir + "/libhello-75.a").st_nlink, 202)
130
131 # Check static dbg
132 # 101 items, a copy in both package/packages-split so 202
133 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello.a").st_nlink, 202)
134 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-25.a").st_nlink, 202)
135 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-50.a").st_nlink, 202)
136 self.assertEqual(os.stat(dest + "/selftest-hardlink-dbg" + libdir + "/.debug-static/libhello-75.a").st_nlink, 202)
111 137
112 # Test a sparse file remains sparse 138 # Test a sparse file remains sparse
113 sparsestat = os.stat(dest + "/selftest-hardlink" + bindir + "/sparsetest") 139 sparsestat = os.stat(dest + "/selftest-hardlink" + bindir + "/sparsetest")
diff --git a/meta/lib/oeqa/selftest/cases/picolibc.py b/meta/lib/oeqa/selftest/cases/picolibc.py
new file mode 100644
index 0000000000..e40b4fc3d3
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/picolibc.py
@@ -0,0 +1,18 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import bitbake, get_bb_var
9
10class PicolibcTest(OESelftestTestCase):
11
12 def test_picolibc(self):
13 compatible_machines = ['qemuarm', 'qemuarm64', 'qemuriscv32', 'qemuriscv64']
14 machine = get_bb_var('MACHINE')
15 if machine not in compatible_machines:
16 self.skipTest('This test only works with machines : %s' % ' '.join(compatible_machines))
17 self.write_config('TCLIBC = "picolibc"')
18 bitbake("picolibc-helloworld")
diff --git a/meta/lib/oeqa/selftest/cases/recipetool.py b/meta/lib/oeqa/selftest/cases/recipetool.py
index 126906df50..0bd724c8ee 100644
--- a/meta/lib/oeqa/selftest/cases/recipetool.py
+++ b/meta/lib/oeqa/selftest/cases/recipetool.py
@@ -138,7 +138,7 @@ class RecipetoolAppendTests(RecipetoolBase):
138 '\n', 138 '\n',
139 'do_install:append() {\n', 139 'do_install:append() {\n',
140 ' install -d ${D}${%s}\n' % dirname, 140 ' install -d ${D}${%s}\n' % dirname,
141 ' install -m 0755 ${WORKDIR}/%s ${D}${%s}/ls\n' % (testfile2name, dirname), 141 ' install -m 0755 ${UNPACKDIR}/%s ${D}${%s}/ls\n' % (testfile2name, dirname),
142 '}\n'] 142 '}\n']
143 self._try_recipetool_appendfile('coreutils', lspath, testfile2, '-r coreutils', expectedlines, [testfile2name]) 143 self._try_recipetool_appendfile('coreutils', lspath, testfile2, '-r coreutils', expectedlines, [testfile2name])
144 # Now try bbappending the same file again, contents should not change 144 # Now try bbappending the same file again, contents should not change
@@ -164,7 +164,7 @@ class RecipetoolAppendTests(RecipetoolBase):
164 '\n', 164 '\n',
165 'do_install:append() {\n', 165 'do_install:append() {\n',
166 ' install -d ${D}${datadir}\n', 166 ' install -d ${D}${datadir}\n',
167 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 167 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
168 '}\n'] 168 '}\n']
169 self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase', expectedlines, ['testfile']) 169 self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase', expectedlines, ['testfile'])
170 # Try adding another file, this time where the source file is executable 170 # Try adding another file, this time where the source file is executable
@@ -179,8 +179,8 @@ class RecipetoolAppendTests(RecipetoolBase):
179 '\n', 179 '\n',
180 'do_install:append() {\n', 180 'do_install:append() {\n',
181 ' install -d ${D}${datadir}\n', 181 ' install -d ${D}${datadir}\n',
182 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 182 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
183 ' install -m 0755 ${WORKDIR}/%s ${D}${datadir}/scriptname\n' % testfile2name, 183 ' install -m 0755 ${UNPACKDIR}/%s ${D}${datadir}/scriptname\n' % testfile2name,
184 '}\n'] 184 '}\n']
185 self._try_recipetool_appendfile('netbase', '/usr/share/scriptname', testfile2, '-r netbase', expectedlines, ['testfile', testfile2name]) 185 self._try_recipetool_appendfile('netbase', '/usr/share/scriptname', testfile2, '-r netbase', expectedlines, ['testfile', testfile2name])
186 186
@@ -192,7 +192,7 @@ class RecipetoolAppendTests(RecipetoolBase):
192 '\n', 192 '\n',
193 'do_install:append() {\n', 193 'do_install:append() {\n',
194 ' install -d ${D}${bindir}\n', 194 ' install -d ${D}${bindir}\n',
195 ' install -m 0755 ${WORKDIR}/testfile ${D}${bindir}/selftest-recipetool-testbin\n', 195 ' install -m 0755 ${UNPACKDIR}/testfile ${D}${bindir}/selftest-recipetool-testbin\n',
196 '}\n'] 196 '}\n']
197 _, output = self._try_recipetool_appendfile('netbase', '/usr/bin/selftest-recipetool-testbin', self.testfile, '-r netbase', expectedlines, ['testfile']) 197 _, output = self._try_recipetool_appendfile('netbase', '/usr/bin/selftest-recipetool-testbin', self.testfile, '-r netbase', expectedlines, ['testfile'])
198 self.assertNotIn('WARNING: ', output) 198 self.assertNotIn('WARNING: ', output)
@@ -207,7 +207,7 @@ class RecipetoolAppendTests(RecipetoolBase):
207 '\n', 207 '\n',
208 'do_install:append:mymachine() {\n', 208 'do_install:append:mymachine() {\n',
209 ' install -d ${D}${datadir}\n', 209 ' install -d ${D}${datadir}\n',
210 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 210 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
211 '}\n'] 211 '}\n']
212 _, output = self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase -m mymachine', expectedlines, ['mymachine/testfile']) 212 _, output = self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase -m mymachine', expectedlines, ['mymachine/testfile'])
213 self.assertNotIn('WARNING: ', output) 213 self.assertNotIn('WARNING: ', output)
@@ -241,7 +241,7 @@ class RecipetoolAppendTests(RecipetoolBase):
241 '\n', 241 '\n',
242 'do_install:append() {\n', 242 'do_install:append() {\n',
243 ' install -d ${D}${datadir}\n', 243 ' install -d ${D}${datadir}\n',
244 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-subdir\n', 244 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-subdir\n',
245 '}\n'] 245 '}\n']
246 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-subdir', self.testfile, '', expectedlines, ['testfile']) 246 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-subdir', self.testfile, '', expectedlines, ['testfile'])
247 self.assertNotIn('WARNING: ', output) 247 self.assertNotIn('WARNING: ', output)
@@ -268,7 +268,7 @@ class RecipetoolAppendTests(RecipetoolBase):
268 '\n', 268 '\n',
269 'do_install:append() {\n', 269 'do_install:append() {\n',
270 ' install -d ${D}${sysconfdir}\n', 270 ' install -d ${D}${sysconfdir}\n',
271 ' install -m 0644 ${WORKDIR}/testfile ${D}${sysconfdir}/selftest-replaceme-patched\n', 271 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${sysconfdir}/selftest-replaceme-patched\n',
272 '}\n'] 272 '}\n']
273 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/etc/selftest-replaceme-patched', self.testfile, '', expectedlines, ['testfile']) 273 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/etc/selftest-replaceme-patched', self.testfile, '', expectedlines, ['testfile'])
274 for line in output.splitlines(): 274 for line in output.splitlines():
@@ -286,7 +286,7 @@ class RecipetoolAppendTests(RecipetoolBase):
286 '\n', 286 '\n',
287 'do_install:append() {\n', 287 'do_install:append() {\n',
288 ' install -d ${D}${datadir}\n', 288 ' install -d ${D}${datadir}\n',
289 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-scripted\n', 289 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-scripted\n',
290 '}\n'] 290 '}\n']
291 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-scripted', self.testfile, '', expectedlines, ['testfile']) 291 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-scripted', self.testfile, '', expectedlines, ['testfile'])
292 self.assertNotIn('WARNING: ', output) 292 self.assertNotIn('WARNING: ', output)
@@ -309,7 +309,7 @@ class RecipetoolAppendTests(RecipetoolBase):
309 '\n', 309 '\n',
310 'do_install:append() {\n', 310 'do_install:append() {\n',
311 ' install -d ${D}${datadir}\n', 311 ' install -d ${D}${datadir}\n',
312 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-postinst\n', 312 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-postinst\n',
313 '}\n'] 313 '}\n']
314 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-postinst', self.testfile, '-r selftest-recipetool-appendfile', expectedlines, ['testfile']) 314 _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-postinst', self.testfile, '-r selftest-recipetool-appendfile', expectedlines, ['testfile'])
315 315
@@ -385,7 +385,7 @@ class RecipetoolCreateTests(RecipetoolBase):
385 checkvars = {} 385 checkvars = {}
386 checkvars['LICENSE'] = 'LGPL-2.1-only' 386 checkvars['LICENSE'] = 'LGPL-2.1-only'
387 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=7fbc338309ac38fefcd64b04bb903e34' 387 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=7fbc338309ac38fefcd64b04bb903e34'
388 checkvars['S'] = '${WORKDIR}/git' 388 checkvars['S'] = None
389 checkvars['PV'] = '1.11+git' 389 checkvars['PV'] = '1.11+git'
390 checkvars['SRC_URI'] = srcuri + ';branch=master' 390 checkvars['SRC_URI'] = srcuri + ';branch=master'
391 checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxext', 'pango']) 391 checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxext', 'pango'])
@@ -757,235 +757,42 @@ class RecipetoolCreateTests(RecipetoolBase):
757 757
758 def test_recipetool_create_go(self): 758 def test_recipetool_create_go(self):
759 # Basic test to check go recipe generation 759 # Basic test to check go recipe generation
760 self.maxDiff = None
761
760 temprecipe = os.path.join(self.tempdir, 'recipe') 762 temprecipe = os.path.join(self.tempdir, 'recipe')
761 os.makedirs(temprecipe) 763 os.makedirs(temprecipe)
762 764
763 recipefile = os.path.join(temprecipe, 'edgex-go_git.bb') 765 recipefile = os.path.join(temprecipe, 'recipetool-go-test_git.bb')
764 deps_require_file = os.path.join(temprecipe, 'edgex-go', 'edgex-go-modules.inc')
765 lics_require_file = os.path.join(temprecipe, 'edgex-go', 'edgex-go-licenses.inc')
766 modules_txt_file = os.path.join(temprecipe, 'edgex-go', 'modules.txt')
767 766
768 srcuri = 'https://github.com/edgexfoundry/edgex-go.git' 767 srcuri = 'https://git.yoctoproject.org/recipetool-go-test.git'
769 srcrev = "v3.0.0" 768 srcrev = "c3e213c01b6c1406b430df03ef0d1ae77de5d2f7"
770 srcbranch = "main" 769 srcbranch = "main"
771 770
772 result = runCmd('recipetool create -o %s %s -S %s -B %s' % (temprecipe, srcuri, srcrev, srcbranch)) 771 result = runCmd('recipetool create -o %s %s -S %s -B %s' % (temprecipe, srcuri, srcrev, srcbranch))
773 772
774 self.maxDiff = None 773 inherits = ['go-mod', 'go-mod-update-modules']
775 inherits = ['go-vendor']
776 774
777 checkvars = {} 775 checkvars = {}
778 checkvars['GO_IMPORT'] = "github.com/edgexfoundry/edgex-go" 776 checkvars['GO_IMPORT'] = "git.yoctoproject.org/recipetool-go-test"
779 checkvars['SRC_URI'] = {'git://${GO_IMPORT};destsuffix=git/src/${GO_IMPORT};nobranch=1;name=${BPN};protocol=https', 777 checkvars['SRC_URI'] = {'git://${GO_IMPORT};protocol=https;nobranch=1;destsuffix=${GO_SRCURI_DESTSUFFIX}'}
780 'file://modules.txt'} 778 checkvars['LIC_FILES_CHKSUM'] = {
781 checkvars['LIC_FILES_CHKSUM'] = {'file://src/${GO_IMPORT}/LICENSE;md5=8f8bc924cf73f6a32381e5fd4c58d603'} 779 'file://src/${GO_IMPORT}/LICENSE;md5=4e3933dd47afbf115e484d11385fb3bd',
780 'file://src/${GO_IMPORT}/is/LICENSE;md5=62beaee5a116dd1e80161667b1df39ab'
781 }
782 782
783 self.assertTrue(os.path.isfile(recipefile))
784 self._test_recipe_contents(recipefile, checkvars, inherits) 783 self._test_recipe_contents(recipefile, checkvars, inherits)
784 self.assertNotIn('Traceback', result.output)
785 785
786 lics_require_file = os.path.join(temprecipe, 'recipetool-go-test-licenses.inc')
787 self.assertFileExists(lics_require_file)
786 checkvars = {} 788 checkvars = {}
787 checkvars['VENDORED_LIC_FILES_CHKSUM'] = set( 789 checkvars['LIC_FILES_CHKSUM'] = {'file://pkg/mod/github.com/godbus/dbus/v5@v5.1.0/LICENSE;md5=09042bd5c6c96a2b9e45ddf1bc517eed;spdx=BSD-2-Clause'}
788 ['file://src/${GO_IMPORT}/vendor/github.com/Microsoft/go-winio/LICENSE;md5=69205ff73858f2c22b2ca135b557e8ef',
789 'file://src/${GO_IMPORT}/vendor/github.com/armon/go-metrics/LICENSE;md5=d2d77030c0183e3d1e66d26dc1f243be',
790 'file://src/${GO_IMPORT}/vendor/github.com/cenkalti/backoff/LICENSE;md5=1571d94433e3f3aa05267efd4dbea68b',
791 'file://src/${GO_IMPORT}/vendor/github.com/davecgh/go-spew/LICENSE;md5=c06795ed54b2a35ebeeb543cd3a73e56',
792 'file://src/${GO_IMPORT}/vendor/github.com/eclipse/paho.mqtt.golang/LICENSE;md5=dcdb33474b60c38efd27356d8f2edec7',
793 'file://src/${GO_IMPORT}/vendor/github.com/eclipse/paho.mqtt.golang/edl-v10;md5=3adfcc70f5aeb7a44f3f9b495aa1fbf3',
794 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-bootstrap/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
795 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-configuration/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
796 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-core-contracts/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
797 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-messaging/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
798 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-registry/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
799 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-secrets/v3/LICENSE;md5=f9fa2f4f8e0ef8cc7b5dd150963eb457',
800 'file://src/${GO_IMPORT}/vendor/github.com/fatih/color/LICENSE.md;md5=316e6d590bdcde7993fb175662c0dd5a',
801 'file://src/${GO_IMPORT}/vendor/github.com/fxamacker/cbor/v2/LICENSE;md5=827f5a2fa861382d35a3943adf9ebb86',
802 'file://src/${GO_IMPORT}/vendor/github.com/go-jose/go-jose/v3/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
803 'file://src/${GO_IMPORT}/vendor/github.com/go-jose/go-jose/v3/json/LICENSE;md5=591778525c869cdde0ab5a1bf283cd81',
804 'file://src/${GO_IMPORT}/vendor/github.com/go-kit/log/LICENSE;md5=5b7c15ad5fffe2ff6e9d58a6c161f082',
805 'file://src/${GO_IMPORT}/vendor/github.com/go-logfmt/logfmt/LICENSE;md5=98e39517c38127f969de33057067091e',
806 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/locales/LICENSE;md5=3ccbda375ee345400ad1da85ba522301',
807 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/universal-translator/LICENSE;md5=2e2b21ef8f61057977d27c727c84bef1',
808 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/validator/v10/LICENSE;md5=a718a0f318d76f7c5d510cbae84f0b60',
809 'file://src/${GO_IMPORT}/vendor/github.com/go-redis/redis/v7/LICENSE;md5=58103aa5ea1ee9b7a369c9c4a95ef9b5',
810 'file://src/${GO_IMPORT}/vendor/github.com/golang/protobuf/LICENSE;md5=939cce1ec101726fa754e698ac871622',
811 'file://src/${GO_IMPORT}/vendor/github.com/gomodule/redigo/LICENSE;md5=2ee41112a44fe7014dce33e26468ba93',
812 'file://src/${GO_IMPORT}/vendor/github.com/google/uuid/LICENSE;md5=88073b6dd8ec00fe09da59e0b6dfded1',
813 'file://src/${GO_IMPORT}/vendor/github.com/gorilla/mux/LICENSE;md5=33fa1116c45f9e8de714033f99edde13',
814 'file://src/${GO_IMPORT}/vendor/github.com/gorilla/websocket/LICENSE;md5=c007b54a1743d596f46b2748d9f8c044',
815 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/consul/api/LICENSE;md5=b8a277a612171b7526e9be072f405ef4',
816 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/errwrap/LICENSE;md5=b278a92d2c1509760384428817710378',
817 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-cleanhttp/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
818 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-hclog/LICENSE;md5=ec7f605b74b9ad03347d0a93a5cc7eb8',
819 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-immutable-radix/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
820 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-multierror/LICENSE;md5=d44fdeb607e2d2614db9464dbedd4094',
821 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-rootcerts/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
822 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/golang-lru/LICENSE;md5=f27a50d2e878867827842f2c60e30bfc',
823 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/serf/LICENSE;md5=b278a92d2c1509760384428817710378',
824 'file://src/${GO_IMPORT}/vendor/github.com/leodido/go-urn/LICENSE;md5=8f50db5538ec1148a9b3d14ed96c3418',
825 'file://src/${GO_IMPORT}/vendor/github.com/mattn/go-colorable/LICENSE;md5=24ce168f90aec2456a73de1839037245',
826 'file://src/${GO_IMPORT}/vendor/github.com/mattn/go-isatty/LICENSE;md5=f509beadd5a11227c27b5d2ad6c9f2c6',
827 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/consulstructure/LICENSE;md5=96ada10a9e51c98c4656f2cede08c673',
828 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/copystructure/LICENSE;md5=56da355a12d4821cda57b8f23ec34bc4',
829 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/go-homedir/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
830 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/mapstructure/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
831 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/reflectwalk/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
832 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nats.go/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
833 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nkeys/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
834 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nuid/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
835 'file://src/${GO_IMPORT}/vendor/github.com/pmezard/go-difflib/LICENSE;md5=e9a2ebb8de779a07500ddecca806145e',
836 'file://src/${GO_IMPORT}/vendor/github.com/rcrowley/go-metrics/LICENSE;md5=1bdf5d819f50f141366dabce3be1460f',
837 'file://src/${GO_IMPORT}/vendor/github.com/spiffe/go-spiffe/v2/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
838 'file://src/${GO_IMPORT}/vendor/github.com/stretchr/objx/LICENSE;md5=d023fd31d3ca39ec61eec65a91732735',
839 'file://src/${GO_IMPORT}/vendor/github.com/stretchr/testify/LICENSE;md5=188f01994659f3c0d310612333d2a26f',
840 'file://src/${GO_IMPORT}/vendor/github.com/x448/float16/LICENSE;md5=de8f8e025d57fe7ee0b67f30d571323b',
841 'file://src/${GO_IMPORT}/vendor/github.com/zeebo/errs/LICENSE;md5=84914ab36fc0eb48edbaa53e66e8d326',
842 'file://src/${GO_IMPORT}/vendor/golang.org/x/crypto/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
843 'file://src/${GO_IMPORT}/vendor/golang.org/x/mod/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
844 'file://src/${GO_IMPORT}/vendor/golang.org/x/net/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
845 'file://src/${GO_IMPORT}/vendor/golang.org/x/sync/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
846 'file://src/${GO_IMPORT}/vendor/golang.org/x/sys/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
847 'file://src/${GO_IMPORT}/vendor/golang.org/x/text/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
848 'file://src/${GO_IMPORT}/vendor/golang.org/x/tools/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
849 'file://src/${GO_IMPORT}/vendor/google.golang.org/genproto/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
850 'file://src/${GO_IMPORT}/vendor/google.golang.org/grpc/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
851 'file://src/${GO_IMPORT}/vendor/google.golang.org/protobuf/LICENSE;md5=02d4002e9171d41a8fad93aa7faf3956',
852 'file://src/${GO_IMPORT}/vendor/gopkg.in/eapache/queue.v1/LICENSE;md5=1bfd4408d3de090ef6b908b0cc45a316',
853 'file://src/${GO_IMPORT}/vendor/gopkg.in/yaml.v3/LICENSE;md5=3c91c17266710e16afdbb2b6d15c761c'])
854
855 self.assertTrue(os.path.isfile(lics_require_file))
856 self._test_recipe_contents(lics_require_file, checkvars, []) 790 self._test_recipe_contents(lics_require_file, checkvars, [])
857 791
858 dependencies = \ 792 deps_require_file = os.path.join(temprecipe, 'recipetool-go-test-go-mods.inc')
859 [ ('github.com/eclipse/paho.mqtt.golang','v1.4.2', '', '', ''), 793 self.assertFileExists(deps_require_file)
860 ('github.com/edgexfoundry/go-mod-bootstrap','v3.0.1','github.com/edgexfoundry/go-mod-bootstrap/v3','/v3', ''),
861 ('github.com/edgexfoundry/go-mod-configuration','v3.0.0','github.com/edgexfoundry/go-mod-configuration/v3','/v3', ''),
862 ('github.com/edgexfoundry/go-mod-core-contracts','v3.0.0','github.com/edgexfoundry/go-mod-core-contracts/v3','/v3', ''),
863 ('github.com/edgexfoundry/go-mod-messaging','v3.0.0','github.com/edgexfoundry/go-mod-messaging/v3','/v3', ''),
864 ('github.com/edgexfoundry/go-mod-secrets','v3.0.1','github.com/edgexfoundry/go-mod-secrets/v3','/v3', ''),
865 ('github.com/fxamacker/cbor','v2.4.0','github.com/fxamacker/cbor/v2','/v2', ''),
866 ('github.com/gomodule/redigo','v1.8.9', '', '', ''),
867 ('github.com/google/uuid','v1.3.0', '', '', ''),
868 ('github.com/gorilla/mux','v1.8.0', '', '', ''),
869 ('github.com/rcrowley/go-metrics','v0.0.0-20201227073835-cf1acfcdf475', '', '', ''),
870 ('github.com/spiffe/go-spiffe','v2.1.4','github.com/spiffe/go-spiffe/v2','/v2', ''),
871 ('github.com/stretchr/testify','v1.8.2', '', '', ''),
872 ('go.googlesource.com/crypto','v0.8.0','golang.org/x/crypto', '', ''),
873 ('gopkg.in/eapache/queue.v1','v1.1.0', '', '', ''),
874 ('gopkg.in/yaml.v3','v3.0.1', '', '', ''),
875 ('github.com/microsoft/go-winio','v0.6.0','github.com/Microsoft/go-winio', '', ''),
876 ('github.com/hashicorp/go-metrics','v0.3.10','github.com/armon/go-metrics', '', ''),
877 ('github.com/cenkalti/backoff','v2.2.1+incompatible', '', '', ''),
878 ('github.com/davecgh/go-spew','v1.1.1', '', '', ''),
879 ('github.com/edgexfoundry/go-mod-registry','v3.0.0','github.com/edgexfoundry/go-mod-registry/v3','/v3', ''),
880 ('github.com/fatih/color','v1.9.0', '', '', ''),
881 ('github.com/go-jose/go-jose','v3.0.0','github.com/go-jose/go-jose/v3','/v3', ''),
882 ('github.com/go-kit/log','v0.2.1', '', '', ''),
883 ('github.com/go-logfmt/logfmt','v0.5.1', '', '', ''),
884 ('github.com/go-playground/locales','v0.14.1', '', '', ''),
885 ('github.com/go-playground/universal-translator','v0.18.1', '', '', ''),
886 ('github.com/go-playground/validator','v10.13.0','github.com/go-playground/validator/v10','/v10', ''),
887 ('github.com/go-redis/redis','v7.3.0','github.com/go-redis/redis/v7','/v7', ''),
888 ('github.com/golang/protobuf','v1.5.2', '', '', ''),
889 ('github.com/gorilla/websocket','v1.4.2', '', '', ''),
890 ('github.com/hashicorp/consul','v1.20.0','github.com/hashicorp/consul/api', '', 'api'),
891 ('github.com/hashicorp/errwrap','v1.0.0', '', '', ''),
892 ('github.com/hashicorp/go-cleanhttp','v0.5.1', '', '', ''),
893 ('github.com/hashicorp/go-hclog','v0.14.1', '', '', ''),
894 ('github.com/hashicorp/go-immutable-radix','v1.3.0', '', '', ''),
895 ('github.com/hashicorp/go-multierror','v1.1.1', '', '', ''),
896 ('github.com/hashicorp/go-rootcerts','v1.0.2', '', '', ''),
897 ('github.com/hashicorp/golang-lru','v0.5.4', '', '', ''),
898 ('github.com/hashicorp/serf','v0.10.1', '', '', ''),
899 ('github.com/leodido/go-urn','v1.2.3', '', '', ''),
900 ('github.com/mattn/go-colorable','v0.1.12', '', '', ''),
901 ('github.com/mattn/go-isatty','v0.0.14', '', '', ''),
902 ('github.com/mitchellh/consulstructure','v0.0.0-20190329231841-56fdc4d2da54', '', '', ''),
903 ('github.com/mitchellh/copystructure','v1.2.0', '', '', ''),
904 ('github.com/mitchellh/go-homedir','v1.1.0', '', '', ''),
905 ('github.com/mitchellh/mapstructure','v1.5.0', '', '', ''),
906 ('github.com/mitchellh/reflectwalk','v1.0.2', '', '', ''),
907 ('github.com/nats-io/nats.go','v1.25.0', '', '', ''),
908 ('github.com/nats-io/nkeys','v0.4.4', '', '', ''),
909 ('github.com/nats-io/nuid','v1.0.1', '', '', ''),
910 ('github.com/pmezard/go-difflib','v1.0.0', '', '', ''),
911 ('github.com/stretchr/objx','v0.5.0', '', '', ''),
912 ('github.com/x448/float16','v0.8.4', '', '', ''),
913 ('github.com/zeebo/errs','v1.3.0', '', '', ''),
914 ('go.googlesource.com/mod','v0.8.0','golang.org/x/mod', '', ''),
915 ('go.googlesource.com/net','v0.9.0','golang.org/x/net', '', ''),
916 ('go.googlesource.com/sync','v0.1.0','golang.org/x/sync', '', ''),
917 ('go.googlesource.com/sys','v0.7.0','golang.org/x/sys', '', ''),
918 ('go.googlesource.com/text','v0.9.0','golang.org/x/text', '', ''),
919 ('go.googlesource.com/tools','v0.6.0','golang.org/x/tools', '', ''),
920 ('github.com/googleapis/go-genproto','v0.0.0-20230223222841-637eb2293923','google.golang.org/genproto', '', ''),
921 ('github.com/grpc/grpc-go','v1.53.0','google.golang.org/grpc', '', ''),
922 ('go.googlesource.com/protobuf','v1.28.1','google.golang.org/protobuf', '', ''),
923 ]
924
925 src_uri = set()
926 for d in dependencies:
927 src_uri.add(self._go_urifiy(*d))
928
929 checkvars = {}
930 checkvars['GO_DEPENDENCIES_SRC_URI'] = src_uri
931
932 self.assertTrue(os.path.isfile(deps_require_file))
933 self._test_recipe_contents(deps_require_file, checkvars, [])
934
935 def test_recipetool_create_go_replace_modules(self):
936 # Check handling of replaced modules
937 temprecipe = os.path.join(self.tempdir, 'recipe')
938 os.makedirs(temprecipe)
939
940 recipefile = os.path.join(temprecipe, 'openapi-generator_git.bb')
941 deps_require_file = os.path.join(temprecipe, 'openapi-generator', 'go-modules.inc')
942 lics_require_file = os.path.join(temprecipe, 'openapi-generator', 'go-licenses.inc')
943 modules_txt_file = os.path.join(temprecipe, 'openapi-generator', 'modules.txt')
944
945 srcuri = 'https://github.com/OpenAPITools/openapi-generator.git'
946 srcrev = "v7.2.0"
947 srcbranch = "master"
948 srcsubdir = "samples/openapi3/client/petstore/go"
949
950 result = runCmd('recipetool create -o %s %s -S %s -B %s --src-subdir %s' % (temprecipe, srcuri, srcrev, srcbranch, srcsubdir))
951
952 self.maxDiff = None
953 inherits = ['go-vendor']
954
955 checkvars = {}
956 checkvars['GO_IMPORT'] = "github.com/OpenAPITools/openapi-generator/samples/openapi3/client/petstore/go"
957 checkvars['SRC_URI'] = {'git://${GO_IMPORT};destsuffix=git/src/${GO_IMPORT};nobranch=1;name=${BPN};protocol=https',
958 'file://modules.txt'}
959
960 self.assertNotIn('Traceback', result.output)
961 self.assertIn('No license file was detected for the main module', result.output)
962 self.assertTrue(os.path.isfile(recipefile))
963 self._test_recipe_contents(recipefile, checkvars, inherits)
964
965 # make sure that dependencies don't mention local directory ./go-petstore
966 dependencies = \
967 [ ('github.com/stretchr/testify','v1.8.4', '', '', ''),
968 ('go.googlesource.com/oauth2','v0.10.0','golang.org/x/oauth2', '', ''),
969 ('github.com/davecgh/go-spew','v1.1.1', '', '', ''),
970 ('github.com/golang/protobuf','v1.5.3', '', '', ''),
971 ('github.com/kr/pretty','v0.3.0', '', '', ''),
972 ('github.com/pmezard/go-difflib','v1.0.0', '', '', ''),
973 ('github.com/rogpeppe/go-internal','v1.9.0', '', '', ''),
974 ('go.googlesource.com/net','v0.12.0','golang.org/x/net', '', ''),
975 ('github.com/golang/appengine','v1.6.7','google.golang.org/appengine', '', ''),
976 ('go.googlesource.com/protobuf','v1.31.0','google.golang.org/protobuf', '', ''),
977 ('gopkg.in/check.v1','v1.0.0-20201130134442-10cb98267c6c', '', '', ''),
978 ('gopkg.in/yaml.v3','v3.0.1', '', '', ''),
979 ]
980
981 src_uri = set()
982 for d in dependencies:
983 src_uri.add(self._go_urifiy(*d))
984
985 checkvars = {} 794 checkvars = {}
986 checkvars['GO_DEPENDENCIES_SRC_URI'] = src_uri 795 checkvars['SRC_URI'] = {'gomod://github.com/godbus/dbus/v5;version=v5.1.0;sha256sum=03dfa8e71089a6f477310d15c4d3a036d82d028532881b50fee254358e782ad9'}
987
988 self.assertTrue(os.path.isfile(deps_require_file))
989 self._test_recipe_contents(deps_require_file, checkvars, []) 796 self._test_recipe_contents(deps_require_file, checkvars, [])
990 797
991class RecipetoolTests(RecipetoolBase): 798class RecipetoolTests(RecipetoolBase):
@@ -1068,6 +875,7 @@ class RecipetoolTests(RecipetoolBase):
1068 875
1069 d = DataConnectorCopy 876 d = DataConnectorCopy
1070 d.getVar = Mock(return_value=commonlicdir) 877 d.getVar = Mock(return_value=commonlicdir)
878 d.expand = Mock(side_effect=lambda x: x)
1071 879
1072 srctree = tempfile.mkdtemp(prefix='recipetoolqa') 880 srctree = tempfile.mkdtemp(prefix='recipetoolqa')
1073 self.track_for_cleanup(srctree) 881 self.track_for_cleanup(srctree)
@@ -1323,10 +1131,10 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase):
1323 1131
1324 def test_recipetool_appendsrcfile_srcdir_basic(self): 1132 def test_recipetool_appendsrcfile_srcdir_basic(self):
1325 testrecipe = 'bash' 1133 testrecipe = 'bash'
1326 bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe) 1134 bb_vars = get_bb_vars(['S', 'UNPACKDIR'], testrecipe)
1327 srcdir = bb_vars['S'] 1135 srcdir = bb_vars['S']
1328 workdir = bb_vars['WORKDIR'] 1136 unpackdir = bb_vars['UNPACKDIR']
1329 subdir = os.path.relpath(srcdir, workdir) 1137 subdir = os.path.relpath(srcdir, unpackdir)
1330 self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir) 1138 self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir)
1331 1139
1332 def test_recipetool_appendsrcfile_existing_in_src_uri(self): 1140 def test_recipetool_appendsrcfile_existing_in_src_uri(self):
@@ -1375,10 +1183,10 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase):
1375 def test_recipetool_appendsrcfile_replace_file_srcdir(self): 1183 def test_recipetool_appendsrcfile_replace_file_srcdir(self):
1376 testrecipe = 'bash' 1184 testrecipe = 'bash'
1377 filepath = 'Makefile.in' 1185 filepath = 'Makefile.in'
1378 bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe) 1186 bb_vars = get_bb_vars(['S', 'UNPACKDIR'], testrecipe)
1379 srcdir = bb_vars['S'] 1187 srcdir = bb_vars['S']
1380 workdir = bb_vars['WORKDIR'] 1188 unpackdir = bb_vars['UNPACKDIR']
1381 subdir = os.path.relpath(srcdir, workdir) 1189 subdir = os.path.relpath(srcdir, unpackdir)
1382 1190
1383 self._test_appendsrcfile(testrecipe, filepath, srcdir=subdir) 1191 self._test_appendsrcfile(testrecipe, filepath, srcdir=subdir)
1384 bitbake('%s:do_unpack' % testrecipe) 1192 bitbake('%s:do_unpack' % testrecipe)
diff --git a/meta/lib/oeqa/selftest/cases/recipeutils.py b/meta/lib/oeqa/selftest/cases/recipeutils.py
index 2cb4445f81..e697fd2920 100644
--- a/meta/lib/oeqa/selftest/cases/recipeutils.py
+++ b/meta/lib/oeqa/selftest/cases/recipeutils.py
@@ -72,7 +72,7 @@ class RecipeUtilsTests(OESelftestTestCase):
72 expected_patch = """ 72 expected_patch = """
73--- a/recipes-test/recipeutils/recipeutils-test_1.2.bb 73--- a/recipes-test/recipeutils/recipeutils-test_1.2.bb
74+++ b/recipes-test/recipeutils/recipeutils-test_1.2.bb 74+++ b/recipes-test/recipeutils/recipeutils-test_1.2.bb
75@@ -8,6 +8,4 @@ 75@@ -10,6 +10,4 @@
76 76
77 BBCLASSEXTEND = "native nativesdk" 77 BBCLASSEXTEND = "native nativesdk"
78 78
@@ -97,7 +97,7 @@ class RecipeUtilsTests(OESelftestTestCase):
97 expected_patch = """ 97 expected_patch = """
98--- a/recipes-test/recipeutils/recipeutils-test_1.2.bb 98--- a/recipes-test/recipeutils/recipeutils-test_1.2.bb
99+++ b/recipes-test/recipeutils/recipeutils-test_1.2.bb 99+++ b/recipes-test/recipeutils/recipeutils-test_1.2.bb
100@@ -8,6 +8,3 @@ 100@@ -10,6 +10,3 @@
101 101
102 BBCLASSEXTEND = "native nativesdk" 102 BBCLASSEXTEND = "native nativesdk"
103 103
diff --git a/meta/lib/oeqa/selftest/cases/reproducible.py b/meta/lib/oeqa/selftest/cases/reproducible.py
index 80e830136f..f06027cb03 100644
--- a/meta/lib/oeqa/selftest/cases/reproducible.py
+++ b/meta/lib/oeqa/selftest/cases/reproducible.py
@@ -97,8 +97,10 @@ def compare_file(reference, test, diffutils_sysroot):
97 result.status = SAME 97 result.status = SAME
98 return result 98 return result
99 99
100def run_diffoscope(a_dir, b_dir, html_dir, max_report_size=0, **kwargs): 100def run_diffoscope(a_dir, b_dir, html_dir, max_report_size=0, max_diff_block_lines=1024, max_diff_block_lines_saved=0, **kwargs):
101 return runCmd(['diffoscope', '--no-default-limits', '--max-report-size', str(max_report_size), 101 return runCmd(['diffoscope', '--no-default-limits', '--max-report-size', str(max_report_size),
102 '--max-diff-block-lines-saved', str(max_diff_block_lines_saved),
103 '--max-diff-block-lines', str(max_diff_block_lines),
102 '--exclude-directory-metadata', 'yes', '--html-dir', html_dir, a_dir, b_dir], 104 '--exclude-directory-metadata', 'yes', '--html-dir', html_dir, a_dir, b_dir],
103 **kwargs) 105 **kwargs)
104 106
@@ -132,8 +134,14 @@ class ReproducibleTests(OESelftestTestCase):
132 # Maximum report size, in bytes 134 # Maximum report size, in bytes
133 max_report_size = 250 * 1024 * 1024 135 max_report_size = 250 * 1024 * 1024
134 136
137 # Maximum diff blocks size, in lines
138 max_diff_block_lines = 1024
139 # Maximum diff blocks size (saved in memory), in lines
140 max_diff_block_lines_saved = max_diff_block_lines
141
135 # targets are the things we want to test the reproducibility of 142 # targets are the things we want to test the reproducibility of
136 targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'core-image-weston', 'world'] 143 # Have to add the virtual targets manually for now as builds may or may not include them as they're exclude from world
144 targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'core-image-weston', 'world', 'virtual/librpc', 'virtual/libsdl2', 'virtual/crypt']
137 145
138 # sstate targets are things to pull from sstate to potentially cut build/debugging time 146 # sstate targets are things to pull from sstate to potentially cut build/debugging time
139 sstate_targets = [] 147 sstate_targets = []
@@ -161,6 +169,7 @@ class ReproducibleTests(OESelftestTestCase):
161 'OEQA_REPRODUCIBLE_TEST_TARGET', 169 'OEQA_REPRODUCIBLE_TEST_TARGET',
162 'OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS', 170 'OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS',
163 'OEQA_REPRODUCIBLE_EXCLUDED_PACKAGES', 171 'OEQA_REPRODUCIBLE_EXCLUDED_PACKAGES',
172 'OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS',
164 ] 173 ]
165 bb_vars = get_bb_vars(needed_vars) 174 bb_vars = get_bb_vars(needed_vars)
166 for v in needed_vars: 175 for v in needed_vars:
@@ -169,19 +178,20 @@ class ReproducibleTests(OESelftestTestCase):
169 if bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE']: 178 if bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE']:
170 self.package_classes = bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE'].split() 179 self.package_classes = bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE'].split()
171 180
172 if bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET']: 181 if bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET'] or bb_vars['OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS']:
173 self.targets = bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET'].split() 182 self.targets = (bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET'] or "").split() + (bb_vars['OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS'] or "").split()
174 183
175 if bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS']: 184 if bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS']:
176 self.sstate_targets = bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS'].split() 185 self.sstate_targets = bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS'].split()
177 186
187 if bb_vars['OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS']:
188 # Setup to build every DEPENDS of leaf recipes using sstate
189 for leaf_recipe in bb_vars['OEQA_REPRODUCIBLE_TEST_LEAF_TARGETS'].split():
190 self.sstate_targets.extend(get_bb_var('DEPENDS', leaf_recipe).split())
191
178 self.extraresults = {} 192 self.extraresults = {}
179 self.extraresults.setdefault('reproducible.rawlogs', {})['log'] = ''
180 self.extraresults.setdefault('reproducible', {}).setdefault('files', {}) 193 self.extraresults.setdefault('reproducible', {}).setdefault('files', {})
181 194
182 def append_to_log(self, msg):
183 self.extraresults['reproducible.rawlogs']['log'] += msg
184
185 def compare_packages(self, reference_dir, test_dir, diffutils_sysroot): 195 def compare_packages(self, reference_dir, test_dir, diffutils_sysroot):
186 result = PackageCompareResults(self.oeqa_reproducible_excluded_packages) 196 result = PackageCompareResults(self.oeqa_reproducible_excluded_packages)
187 197
@@ -208,7 +218,7 @@ class ReproducibleTests(OESelftestTestCase):
208 218
209 def write_package_list(self, package_class, name, packages): 219 def write_package_list(self, package_class, name, packages):
210 self.extraresults['reproducible']['files'].setdefault(package_class, {})[name] = [ 220 self.extraresults['reproducible']['files'].setdefault(package_class, {})[name] = [
211 {'reference': p.reference, 'test': p.test} for p in packages] 221 p.reference.split("/./")[1] for p in packages]
212 222
213 def copy_file(self, source, dest): 223 def copy_file(self, source, dest):
214 bb.utils.mkdirhier(os.path.dirname(dest)) 224 bb.utils.mkdirhier(os.path.dirname(dest))
@@ -220,7 +230,6 @@ class ReproducibleTests(OESelftestTestCase):
220 tmpdir = os.path.join(self.topdir, name, 'tmp') 230 tmpdir = os.path.join(self.topdir, name, 'tmp')
221 if os.path.exists(tmpdir): 231 if os.path.exists(tmpdir):
222 bb.utils.remove(tmpdir, recurse=True) 232 bb.utils.remove(tmpdir, recurse=True)
223
224 config = textwrap.dedent('''\ 233 config = textwrap.dedent('''\
225 PACKAGE_CLASSES = "{package_classes}" 234 PACKAGE_CLASSES = "{package_classes}"
226 TMPDIR = "{tmpdir}" 235 TMPDIR = "{tmpdir}"
@@ -233,11 +242,41 @@ class ReproducibleTests(OESelftestTestCase):
233 ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes), 242 ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes),
234 tmpdir=tmpdir) 243 tmpdir=tmpdir)
235 244
245 # Export BB_CONSOLELOG to the calling function and make it constant to
246 # avoid a case where bitbake would get a timestamp-based filename but
247 # oe-selftest would, later, get another.
248 capture_vars.append("BB_CONSOLELOG")
249 config += 'BB_CONSOLELOG = "${LOG_DIR}/cooker/${MACHINE}/console.log"\n'
250
251 # We want different log files for each build, but a persistent bitbake
252 # may reuse the previous log file so restart the bitbake server.
253 bitbake("--kill-server")
254
255 def print_condensed_error_log(logs, context_lines=10, tail_lines=20):
256 """Prints errors with context and the end of the log."""
257
258 logs = logs.split("\n")
259 for i, line in enumerate(logs):
260 if line.startswith("ERROR"):
261 self.logger.info("Found ERROR (line %d):" % (i + 1))
262 for l in logs[i-context_lines:i+context_lines]:
263 self.logger.info(" " + l)
264
265 self.logger.info("End of log:")
266 for l in logs[-tail_lines:]:
267 self.logger.info(" " + l)
268
269 bitbake_failure_count = 0
236 if not use_sstate: 270 if not use_sstate:
237 if self.sstate_targets: 271 if self.sstate_targets:
238 self.logger.info("Building prebuild for %s (sstate allowed)..." % (name)) 272 self.logger.info("Building prebuild for %s (sstate allowed)..." % (name))
239 self.write_config(config) 273 self.write_config(config)
240 bitbake(' '.join(self.sstate_targets)) 274 try:
275 bitbake("--continue "+' '.join(self.sstate_targets))
276 except AssertionError as e:
277 bitbake_failure_count += 1
278 self.logger.error("Bitbake failed! but keep going... Log:")
279 print_condensed_error_log(str(e))
241 280
242 # This config fragment will disable using shared and the sstate 281 # This config fragment will disable using shared and the sstate
243 # mirror, forcing a complete build from scratch 282 # mirror, forcing a complete build from scratch
@@ -249,9 +288,24 @@ class ReproducibleTests(OESelftestTestCase):
249 self.logger.info("Building %s (sstate%s allowed)..." % (name, '' if use_sstate else ' NOT')) 288 self.logger.info("Building %s (sstate%s allowed)..." % (name, '' if use_sstate else ' NOT'))
250 self.write_config(config) 289 self.write_config(config)
251 d = get_bb_vars(capture_vars) 290 d = get_bb_vars(capture_vars)
252 # targets used to be called images 291 try:
253 bitbake(' '.join(getattr(self, 'images', self.targets))) 292 # targets used to be called images
254 return d 293 bitbake("--continue "+' '.join(getattr(self, 'images', self.targets)))
294 except AssertionError as e:
295 bitbake_failure_count += 1
296 self.logger.error("Bitbake failed! but keep going... Log:")
297 print_condensed_error_log(str(e))
298
299 # The calling function expects the existence of the deploy
300 # directories containing the packages.
301 # If bitbake failed to create them, do it manually
302 for c in self.package_classes:
303 deploy = d['DEPLOY_DIR_' + c.upper()]
304 if not os.path.exists(deploy):
305 self.logger.info("Manually creating %s" % deploy)
306 bb.utils.mkdirhier(deploy)
307
308 return (d, bitbake_failure_count)
255 309
256 def test_reproducible_builds(self): 310 def test_reproducible_builds(self):
257 def strip_topdir(s): 311 def strip_topdir(s):
@@ -273,15 +327,30 @@ class ReproducibleTests(OESelftestTestCase):
273 os.chmod(save_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) 327 os.chmod(save_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
274 self.logger.info('Non-reproducible packages will be copied to %s', save_dir) 328 self.logger.info('Non-reproducible packages will be copied to %s', save_dir)
275 329
276 vars_A = self.do_test_build('reproducibleA', self.build_from_sstate) 330 # The below bug shows that a few reproducible issues are depends on build dir path length.
331 # https://bugzilla.yoctoproject.org/show_bug.cgi?id=15554
332 # So, the reproducibleA & reproducibleB directories are changed to reproducibleA & reproducibleB-extended to have different size.
277 333
278 vars_B = self.do_test_build('reproducibleB', False) 334 fails = []
335 vars_list = [None, None]
336
337 for i, (name, use_sstate) in enumerate(
338 (('reproducibleA', self.build_from_sstate),
339 ('reproducibleB-extended', False))):
340 (variables, bitbake_failure_count) = self.do_test_build(name, use_sstate)
341 if bitbake_failure_count > 0:
342 self.logger.error('%s build failed. Trying to compute built packages differences but the test will fail.' % name)
343 fails.append("Bitbake %s failure" % name)
344 if self.save_results:
345 failure_log_path = os.path.join(save_dir, "bitbake-%s.log" % name)
346 self.logger.info('Failure log for %s will be copied to %s'% (name, failure_log_path))
347 self.copy_file(variables["BB_CONSOLELOG"], failure_log_path)
348 vars_list[i] = variables
279 349
350 vars_A, vars_B = vars_list
280 # NOTE: The temp directories from the reproducible build are purposely 351 # NOTE: The temp directories from the reproducible build are purposely
281 # kept after the build so it can be diffed for debugging. 352 # kept after the build so it can be diffed for debugging.
282 353
283 fails = []
284
285 for c in self.package_classes: 354 for c in self.package_classes:
286 with self.subTest(package_class=c): 355 with self.subTest(package_class=c):
287 package_class = 'package_' + c 356 package_class = 'package_' + c
@@ -294,8 +363,6 @@ class ReproducibleTests(OESelftestTestCase):
294 363
295 self.logger.info('Reproducibility summary for %s: %s' % (c, result)) 364 self.logger.info('Reproducibility summary for %s: %s' % (c, result))
296 365
297 self.append_to_log('\n'.join("%s: %s" % (r.status, r.test) for r in result.total))
298
299 self.write_package_list(package_class, 'missing', result.missing) 366 self.write_package_list(package_class, 'missing', result.missing)
300 self.write_package_list(package_class, 'different', result.different) 367 self.write_package_list(package_class, 'different', result.different)
301 self.write_package_list(package_class, 'different_excluded', result.different_excluded) 368 self.write_package_list(package_class, 'different_excluded', result.different_excluded)
@@ -330,7 +397,9 @@ class ReproducibleTests(OESelftestTestCase):
330 # Copy jquery to improve the diffoscope output usability 397 # Copy jquery to improve the diffoscope output usability
331 self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js')) 398 self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js'))
332 399
333 run_diffoscope('reproducibleA', 'reproducibleB', package_html_dir, max_report_size=self.max_report_size, 400 run_diffoscope('reproducibleA', 'reproducibleB-extended', package_html_dir, max_report_size=self.max_report_size,
401 max_diff_block_lines_saved=self.max_diff_block_lines_saved,
402 max_diff_block_lines=self.max_diff_block_lines,
334 native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir) 403 native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir)
335 404
336 if fails: 405 if fails:
diff --git a/meta/lib/oeqa/selftest/cases/retain.py b/meta/lib/oeqa/selftest/cases/retain.py
new file mode 100644
index 0000000000..892be45857
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/retain.py
@@ -0,0 +1,241 @@
1# Tests for retain.bbclass
2#
3# Copyright OpenEmbedded Contributors
4#
5# SPDX-License-Identifier: MIT
6#
7
8import os
9import glob
10import fnmatch
11import oe.path
12import shutil
13import tarfile
14from oeqa.utils.commands import bitbake, get_bb_vars
15from oeqa.selftest.case import OESelftestTestCase
16
17class Retain(OESelftestTestCase):
18
19 def test_retain_always(self):
20 """
21 Summary: Test retain class with RETAIN_DIRS_ALWAYS
22 Expected: Archive written to RETAIN_OUTDIR when build of test recipe completes
23 Product: oe-core
24 Author: Paul Eggleton <paul.eggleton@microsoft.com>
25 """
26
27 test_recipe = 'quilt-native'
28
29 features = 'INHERIT += "retain"\n'
30 features += 'RETAIN_DIRS_ALWAYS = "${T}"\n'
31 self.write_config(features)
32
33 bitbake('-c clean %s' % test_recipe)
34
35 bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR'])
36 retain_outdir = bb_vars['RETAIN_OUTDIR'] or ''
37 tmpdir = bb_vars['TMPDIR']
38 if len(retain_outdir) < 5:
39 self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir)
40 if not oe.path.is_path_parent(tmpdir, retain_outdir):
41 self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir))
42 try:
43 shutil.rmtree(retain_outdir)
44 except FileNotFoundError:
45 pass
46
47 bitbake(test_recipe)
48 if not glob.glob(os.path.join(retain_outdir, '%s_temp_*.tar.gz' % test_recipe)):
49 self.fail('No output archive for %s created' % test_recipe)
50
51
52 def test_retain_failure(self):
53 """
54 Summary: Test retain class default behaviour
55 Expected: Archive written to RETAIN_OUTDIR only when build of test
56 recipe fails, and archive contents are as expected
57 Product: oe-core
58 Author: Paul Eggleton <paul.eggleton@microsoft.com>
59 """
60
61 test_recipe_fail = 'error'
62
63 features = 'INHERIT += "retain"\n'
64 self.write_config(features)
65
66 bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR', 'RETAIN_DIRS_ALWAYS', 'RETAIN_DIRS_GLOBAL_ALWAYS'])
67 if bb_vars['RETAIN_DIRS_ALWAYS']:
68 self.fail('RETAIN_DIRS_ALWAYS is set, this interferes with the test')
69 if bb_vars['RETAIN_DIRS_GLOBAL_ALWAYS']:
70 self.fail('RETAIN_DIRS_GLOBAL_ALWAYS is set, this interferes with the test')
71 retain_outdir = bb_vars['RETAIN_OUTDIR'] or ''
72 tmpdir = bb_vars['TMPDIR']
73 if len(retain_outdir) < 5:
74 self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir)
75 if not oe.path.is_path_parent(tmpdir, retain_outdir):
76 self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir))
77
78 try:
79 shutil.rmtree(retain_outdir)
80 except FileNotFoundError:
81 pass
82
83 bitbake('-c clean %s' % test_recipe_fail)
84
85 if os.path.exists(retain_outdir):
86 retain_dirlist = os.listdir(retain_outdir)
87 if retain_dirlist:
88 self.fail('RETAIN_OUTDIR should be empty without failure, contents:\n%s' % '\n'.join(retain_dirlist))
89
90 result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True)
91 if result.status == 0:
92 self.fail('Build of %s did not fail as expected' % test_recipe_fail)
93
94 archives = glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % test_recipe_fail))
95 if not archives:
96 self.fail('No output archive for %s created' % test_recipe_fail)
97 if len(archives) > 1:
98 self.fail('More than one archive for %s created' % test_recipe_fail)
99 for archive in archives:
100 found = False
101 archive_prefix = os.path.basename(archive).split('.tar')[0]
102 expected_prefix_start = '%s_workdir' % test_recipe_fail
103 if not archive_prefix.startswith(expected_prefix_start):
104 self.fail('Archive %s name does not start with expected prefix "%s"' % (os.path.basename(archive), expected_prefix_start))
105 with tarfile.open(archive) as tf:
106 for ti in tf:
107 if not fnmatch.fnmatch(ti.name, '%s/*' % archive_prefix):
108 self.fail('File without tarball-named subdirectory within tarball %s: %s' % (os.path.basename(archive), ti.name))
109 if ti.name.endswith('/temp/log.do_compile'):
110 found = True
111 if not found:
112 self.fail('Did not find log.do_compile in output archive %s' % os.path.basename(archive))
113
114
115 def test_retain_global(self):
116 """
117 Summary: Test retain class RETAIN_DIRS_GLOBAL_* behaviour
118 Expected: Ensure RETAIN_DIRS_GLOBAL_ALWAYS always causes an
119 archive to be created, and RETAIN_DIRS_GLOBAL_FAILURE
120 only causes an archive to be created on failure.
121 Also test archive naming (with : character) as an
122 added bonus.
123 Product: oe-core
124 Author: Paul Eggleton <paul.eggleton@microsoft.com>
125 """
126
127 test_recipe = 'quilt-native'
128 test_recipe_fail = 'error'
129
130 features = 'INHERIT += "retain"\n'
131 features += 'RETAIN_DIRS_GLOBAL_ALWAYS = "${LOG_DIR};prefix=buildlogs"\n'
132 features += 'RETAIN_DIRS_GLOBAL_FAILURE = "${STAMPS_DIR}"\n'
133 self.write_config(features)
134
135 bitbake('-c clean %s' % test_recipe)
136
137 bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR', 'STAMPS_DIR'])
138 retain_outdir = bb_vars['RETAIN_OUTDIR'] or ''
139 tmpdir = bb_vars['TMPDIR']
140 if len(retain_outdir) < 5:
141 self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir)
142 if not oe.path.is_path_parent(tmpdir, retain_outdir):
143 self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir))
144 try:
145 shutil.rmtree(retain_outdir)
146 except FileNotFoundError:
147 pass
148
149 # Test success case
150 bitbake(test_recipe)
151 if not glob.glob(os.path.join(retain_outdir, 'buildlogs_*.tar.gz')):
152 self.fail('No output archive for LOG_DIR created')
153 stamps_dir = bb_vars['STAMPS_DIR']
154 if glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % os.path.basename(stamps_dir))):
155 self.fail('Output archive for STAMPS_DIR created when it should not have been')
156
157 # Test failure case
158 result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True)
159 if result.status == 0:
160 self.fail('Build of %s did not fail as expected' % test_recipe_fail)
161 if not glob.glob(os.path.join(retain_outdir, '%s_*.tar.gz' % os.path.basename(stamps_dir))):
162 self.fail('Output archive for STAMPS_DIR not created')
163 if len(glob.glob(os.path.join(retain_outdir, 'buildlogs_*.tar.gz'))) != 2:
164 self.fail('Should be exactly two buildlogs archives in output dir')
165
166
167 def test_retain_misc(self):
168 """
169 Summary: Test retain class with RETAIN_ENABLED and RETAIN_TARBALL_SUFFIX
170 Expected: Archive written to RETAIN_OUTDIR only when RETAIN_ENABLED is set
171 and archive contents are as expected. Also test archive naming
172 (with : character) as an added bonus.
173 Product: oe-core
174 Author: Paul Eggleton <paul.eggleton@microsoft.com>
175 """
176
177 test_recipe_fail = 'error'
178
179 features = 'INHERIT += "retain"\n'
180 features += 'RETAIN_DIRS_ALWAYS = "${T}"\n'
181 features += 'RETAIN_ENABLED = "0"\n'
182 self.write_config(features)
183
184 bb_vars = get_bb_vars(['RETAIN_OUTDIR', 'TMPDIR'])
185 retain_outdir = bb_vars['RETAIN_OUTDIR'] or ''
186 tmpdir = bb_vars['TMPDIR']
187 if len(retain_outdir) < 5:
188 self.fail('RETAIN_OUTDIR value "%s" is invalid' % retain_outdir)
189 if not oe.path.is_path_parent(tmpdir, retain_outdir):
190 self.fail('RETAIN_OUTDIR (%s) is not underneath TMPDIR (%s)' % (retain_outdir, tmpdir))
191
192 try:
193 shutil.rmtree(retain_outdir)
194 except FileNotFoundError:
195 pass
196
197 bitbake('-c clean %s' % test_recipe_fail)
198 result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True)
199 if result.status == 0:
200 self.fail('Build of %s did not fail as expected' % test_recipe_fail)
201
202 if os.path.exists(retain_outdir) and os.listdir(retain_outdir):
203 self.fail('RETAIN_OUTDIR should be empty with RETAIN_ENABLED = "0"')
204
205 features = 'INHERIT += "retain"\n'
206 features += 'RETAIN_DIRS_ALWAYS = "${T};prefix=recipelogs"\n'
207 features += 'RETAIN_TARBALL_SUFFIX = "${DATETIME}-testsuffix.tar.bz2"\n'
208 features += 'RETAIN_ENABLED = "1"\n'
209 self.write_config(features)
210
211 result = bitbake('-c compile %s' % test_recipe_fail, ignore_status=True)
212 if result.status == 0:
213 self.fail('Build of %s did not fail as expected' % test_recipe_fail)
214
215 archives = glob.glob(os.path.join(retain_outdir, '%s_*-testsuffix.tar.bz2' % test_recipe_fail))
216 if not archives:
217 self.fail('No output archive for %s created' % test_recipe_fail)
218 if len(archives) != 2:
219 self.fail('Two archives for %s expected, but %d exist' % (test_recipe_fail, len(archives)))
220 recipelogs_found = False
221 workdir_found = False
222 for archive in archives:
223 contents_found = False
224 archive_prefix = os.path.basename(archive).split('.tar')[0]
225 if archive_prefix.startswith('%s_recipelogs' % test_recipe_fail):
226 recipelogs_found = True
227 if archive_prefix.startswith('%s_workdir' % test_recipe_fail):
228 workdir_found = True
229 with tarfile.open(archive, 'r:bz2') as tf:
230 for ti in tf:
231 if not fnmatch.fnmatch(ti.name, '%s/*' % (archive_prefix)):
232 self.fail('File without tarball-named subdirectory within tarball %s: %s' % (os.path.basename(archive), ti.name))
233 if ti.name.endswith('/log.do_compile'):
234 contents_found = True
235 if not contents_found:
236 # Both archives should contain this file
237 self.fail('Did not find log.do_compile in output archive %s' % os.path.basename(archive))
238 if not recipelogs_found:
239 self.fail('No archive with expected "recipelogs" prefix found')
240 if not workdir_found:
241 self.fail('No archive with expected "workdir" prefix found')
diff --git a/meta/lib/oeqa/selftest/cases/runqemu.py b/meta/lib/oeqa/selftest/cases/runqemu.py
index f01e1eec66..7ed89e80de 100644
--- a/meta/lib/oeqa/selftest/cases/runqemu.py
+++ b/meta/lib/oeqa/selftest/cases/runqemu.py
@@ -31,9 +31,9 @@ class RunqemuTests(OESelftestTestCase):
31 if self.td["HOST_ARCH"] in ('i586', 'i686', 'x86_64'): 31 if self.td["HOST_ARCH"] in ('i586', 'i686', 'x86_64'):
32 self.fstypes += " iso hddimg" 32 self.fstypes += " iso hddimg"
33 if self.machine == "qemux86-64": 33 if self.machine == "qemux86-64":
34 self.fstypes += " wic.vmdk wic.qcow2 wic.vdi" 34 self.fstypes += " wic.vmdk wic.qcow2 wic.vdi wic.zst"
35 35
36 self.cmd_common = "runqemu nographic" 36 self.cmd_common = "runqemu nographic snapshot"
37 kvm = oe.types.qemu_use_kvm(get_bb_var('QEMU_USE_KVM'), self.td["TARGET_ARCH"]) 37 kvm = oe.types.qemu_use_kvm(get_bb_var('QEMU_USE_KVM'), self.td["TARGET_ARCH"])
38 if kvm: 38 if kvm:
39 self.cmd_common += " kvm" 39 self.cmd_common += " kvm"
@@ -152,6 +152,25 @@ SYSLINUX_TIMEOUT = "10"
152 with open(qemu.qemurunnerlog) as f: 152 with open(qemu.qemurunnerlog) as f:
153 self.assertTrue(qemu.runner.logged, "Failed: %s, %s" % (cmd, f.read())) 153 self.assertTrue(qemu.runner.logged, "Failed: %s, %s" % (cmd, f.read()))
154 154
155 @skipIfNotMachine("qemux86-64", "wic tests are qemux86-64 specific currently")
156 def test_boot_compressed_wic_by_path(self):
157 """Test runqemu /path/to/rootfs.wic.zst"""
158 rootfs = "%s.wic.zst" % (self.image_link_name)
159 rootfs = os.path.join(self.deploy_dir_image, rootfs)
160 if not os.path.exists(rootfs):
161 self.skipTest("%s not found" % rootfs)
162 cmd = "%s %s snapshot" % (self.cmd_common, rootfs)
163 with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
164 with open(qemu.qemurunnerlog) as f:
165 self.assertTrue(qemu.runner.logged, "Failed: %s, %s" % (cmd, f.read()))
166
167 @skipIfNotMachine("qemux86-64", "wic tests are qemux86-64 specific currently")
168 def test_boot_compressed_wic_by_recipe(self):
169 """Test runqemu recipe-image wic.zst"""
170 cmd = "%s %s snapshot wic.zst" % (self.cmd_common, self.recipe)
171 with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
172 with open(qemu.qemurunnerlog) as f:
173 self.assertTrue(qemu.runner.logged, "Failed: %s, %s" % (cmd, f.read()))
155 174
156# This test was designed as a separate class to test that shutdown 175# This test was designed as a separate class to test that shutdown
157# command will shutdown qemu as expected on each qemu architecture 176# command will shutdown qemu as expected on each qemu architecture
@@ -173,9 +192,15 @@ class QemuTest(OESelftestTestCase):
173 cls.machine = get_bb_var('MACHINE') 192 cls.machine = get_bb_var('MACHINE')
174 cls.deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') 193 cls.deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
175 cls.image_link_name = get_bb_var('IMAGE_LINK_NAME', cls.recipe) 194 cls.image_link_name = get_bb_var('IMAGE_LINK_NAME', cls.recipe)
176 cls.cmd_common = "runqemu nographic" 195 cls.cmd_common = "runqemu nographic snapshot"
177 cls.qemuboot_conf = "%s.qemuboot.conf" % (cls.image_link_name) 196 cls.qemuboot_conf = "%s.qemuboot.conf" % (cls.image_link_name)
178 cls.qemuboot_conf = os.path.join(cls.deploy_dir_image, cls.qemuboot_conf) 197 cls.qemuboot_conf = os.path.join(cls.deploy_dir_image, cls.qemuboot_conf)
198
199 cls.write_config(cls,
200"""
201IMAGE_FSTYPES += "tar.bz2"
202""")
203
179 bitbake(cls.recipe) 204 bitbake(cls.recipe)
180 205
181 def _start_qemu_shutdown_check_if_shutdown_succeeded(self, qemu, timeout): 206 def _start_qemu_shutdown_check_if_shutdown_succeeded(self, qemu, timeout):
@@ -199,7 +224,7 @@ class QemuTest(OESelftestTestCase):
199 224
200 def test_qemu_can_shutdown(self): 225 def test_qemu_can_shutdown(self):
201 self.assertExists(self.qemuboot_conf) 226 self.assertExists(self.qemuboot_conf)
202 cmd = "%s %s" % (self.cmd_common, self.qemuboot_conf) 227 cmd = "%s snapshot %s" % (self.cmd_common, self.qemuboot_conf)
203 shutdown_timeout = 120 228 shutdown_timeout = 120
204 with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: 229 with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
205 qemu_shutdown_succeeded = self._start_qemu_shutdown_check_if_shutdown_succeeded(qemu, shutdown_timeout) 230 qemu_shutdown_succeeded = self._start_qemu_shutdown_check_if_shutdown_succeeded(qemu, shutdown_timeout)
diff --git a/meta/lib/oeqa/selftest/cases/runtime_test.py b/meta/lib/oeqa/selftest/cases/runtime_test.py
index 12000aac16..d58ffa80f5 100644
--- a/meta/lib/oeqa/selftest/cases/runtime_test.py
+++ b/meta/lib/oeqa/selftest/cases/runtime_test.py
@@ -174,7 +174,6 @@ TEST_RUNQEMUPARAMS += " slirp"
174 features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n' 174 features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n'
175 features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase') 175 features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase')
176 features += 'GPG_PATH = "%s"\n' % self.gpg_home 176 features += 'GPG_PATH = "%s"\n' % self.gpg_home
177 features += 'PSEUDO_IGNORE_PATHS .= ",%s"\n' % self.gpg_home
178 self.write_config(features) 177 self.write_config(features)
179 178
180 bitbake('core-image-full-cmdline socat') 179 bitbake('core-image-full-cmdline socat')
@@ -211,7 +210,6 @@ TEST_RUNQEMUPARAMS += " slirp"
211 features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n' 210 features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n'
212 features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase') 211 features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase')
213 features += 'GPG_PATH = "%s"\n' % self.gpg_home 212 features += 'GPG_PATH = "%s"\n' % self.gpg_home
214 features += 'PSEUDO_IGNORE_PATHS .= ",%s"\n' % self.gpg_home
215 self.write_config(features) 213 self.write_config(features)
216 214
217 # Build core-image-sato and testimage 215 # Build core-image-sato and testimage
@@ -273,7 +271,9 @@ TEST_RUNQEMUPARAMS += " slirp"
273 import subprocess, os 271 import subprocess, os
274 272
275 distro = oe.lsb.distro_identifier() 273 distro = oe.lsb.distro_identifier()
276 if distro and (distro in ['debian-9', 'debian-10', 'centos-7', 'centos-8', 'ubuntu-16.04', 'ubuntu-18.04'] or 274 # Merge request to address the issue on centos/rhel/derivatives:
275 # https://gitlab.com/cki-project/kernel-ark/-/merge_requests/3449
276 if distro and (distro in ['debian-9', 'debian-10', 'centos-7', 'centos-8', 'centos-9', 'ubuntu-16.04', 'ubuntu-18.04'] or
277 distro.startswith('almalinux') or distro.startswith('rocky')): 277 distro.startswith('almalinux') or distro.startswith('rocky')):
278 self.skipTest('virgl headless cannot be tested with %s' %(distro)) 278 self.skipTest('virgl headless cannot be tested with %s' %(distro))
279 279
@@ -310,10 +310,7 @@ class Postinst(OESelftestTestCase):
310 features += 'IMAGE_FEATURES += "package-management empty-root-password"\n' 310 features += 'IMAGE_FEATURES += "package-management empty-root-password"\n'
311 features += 'PACKAGE_CLASSES = "%s"\n' % classes 311 features += 'PACKAGE_CLASSES = "%s"\n' % classes
312 if init_manager == "systemd": 312 if init_manager == "systemd":
313 features += 'DISTRO_FEATURES:append = " systemd usrmerge"\n' 313 features += 'INIT_MANAGER = "systemd"\n'
314 features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n'
315 features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n'
316 features += 'VIRTUAL-RUNTIME_initscripts = ""\n'
317 self.write_config(features) 314 self.write_config(features)
318 315
319 bitbake('core-image-minimal') 316 bitbake('core-image-minimal')
diff --git a/meta/lib/oeqa/selftest/cases/rust.py b/meta/lib/oeqa/selftest/cases/rust.py
index ad14189c6d..06acf53e9a 100644
--- a/meta/lib/oeqa/selftest/cases/rust.py
+++ b/meta/lib/oeqa/selftest/cases/rust.py
@@ -1,11 +1,11 @@
1# SPDX-License-Identifier: MIT 1# SPDX-License-Identifier: MIT
2import os
3import subprocess 2import subprocess
4import time 3import time
5from oeqa.core.decorator import OETestTag 4from oeqa.core.decorator import OETestTag
5from oeqa.core.decorator.data import skipIfArch
6from oeqa.core.case import OEPTestResultTestCase 6from oeqa.core.case import OEPTestResultTestCase
7from oeqa.selftest.case import OESelftestTestCase 7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu, Command 8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, runqemu
9from oeqa.utils.sshcontrol import SSHControl 9from oeqa.utils.sshcontrol import SSHControl
10 10
11def parse_results(filename): 11def parse_results(filename):
@@ -38,15 +38,9 @@ def parse_results(filename):
38@OETestTag("toolchain-user") 38@OETestTag("toolchain-user")
39@OETestTag("runqemu") 39@OETestTag("runqemu")
40class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase): 40class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
41 def test_rust(self, *args, **kwargs):
42 # Disable Rust Oe-selftest
43 #self.skipTest("The Rust Oe-selftest is disabled.")
44
45 # Skip mips32 target since it is unstable with rust tests
46 machine = get_bb_var('MACHINE')
47 if machine == "qemumips":
48 self.skipTest("The mips32 target is skipped for Rust Oe-selftest.")
49 41
42 @skipIfArch(['mips', 'mips64'])
43 def test_rust(self, *args, **kwargs):
50 # build remote-test-server before image build 44 # build remote-test-server before image build
51 recipe = "rust" 45 recipe = "rust"
52 start_time = time.time() 46 start_time = time.time()
@@ -66,136 +60,51 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
66 # bless: First runs rustfmt to format the codebase, 60 # bless: First runs rustfmt to format the codebase,
67 # then runs tidy checks. 61 # then runs tidy checks.
68 exclude_list = [ 62 exclude_list = [
69 'compiler/rustc', 63 'src/bootstrap',
70 'compiler/rustc_interface/src/tests.rs',
71 'library/panic_abort',
72 'library/panic_unwind',
73 'library/test/src/stats/tests.rs',
74 'src/bootstrap/builder/tests.rs',
75 'src/doc/rustc', 64 'src/doc/rustc',
76 'src/doc/rustdoc', 65 'src/doc/rustdoc',
77 'src/doc/unstable-book', 66 'src/doc/unstable-book',
67 'src/etc/test-float-parse',
78 'src/librustdoc', 68 'src/librustdoc',
79 'src/rustdoc-json-types', 69 'src/rustdoc-json-types',
80 'src/tools/compiletest/src/common.rs', 70 'src/tools/coverage-dump',
71 'src/tools/jsondoclint',
81 'src/tools/lint-docs', 72 'src/tools/lint-docs',
73 'src/tools/replace-version-placeholder',
82 'src/tools/rust-analyzer', 74 'src/tools/rust-analyzer',
83 'src/tools/rustdoc-themes', 75 'src/tools/rustdoc-themes',
76 'src/tools/rust-installer',
77 'src/tools/test-float-parse',
78 'src/tools/suggest-tests',
84 'src/tools/tidy', 79 'src/tools/tidy',
85 'tests/assembly/asm/aarch64-outline-atomics.rs', 80 'tests/assembly-llvm/asm/aarch64-outline-atomics.rs',
86 'tests/codegen/abi-main-signature-32bit-c-int.rs', 81 'tests/codegen-llvm/issues/issue-122805.rs',
87 'tests/codegen/abi-repr-ext.rs', 82 'tests/codegen-llvm/thread-local.rs',
88 'tests/codegen/abi-x86-interrupt.rs', 83 'tests/mir-opt/',
89 'tests/codegen/branch-protection.rs',
90 'tests/codegen/catch-unwind.rs',
91 'tests/codegen/cf-protection.rs',
92 'tests/codegen/enum-bounds-check-derived-idx.rs',
93 'tests/codegen/force-unwind-tables.rs',
94 'tests/codegen/intrinsic-no-unnamed-attr.rs',
95 'tests/codegen/issues/issue-103840.rs',
96 'tests/codegen/issues/issue-47278.rs',
97 'tests/codegen/issues/issue-73827-bounds-check-index-in-subexpr.rs',
98 'tests/codegen/lifetime_start_end.rs',
99 'tests/codegen/local-generics-in-exe-internalized.rs',
100 'tests/codegen/match-unoptimized.rs',
101 'tests/codegen/noalias-rwlockreadguard.rs',
102 'tests/codegen/non-terminate/nonempty-infinite-loop.rs',
103 'tests/codegen/noreturn-uninhabited.rs',
104 'tests/codegen/repr-transparent-aggregates-3.rs',
105 'tests/codegen/riscv-abi/call-llvm-intrinsics.rs',
106 'tests/codegen/riscv-abi/riscv64-lp64f-lp64d-abi.rs',
107 'tests/codegen/riscv-abi/riscv64-lp64d-abi.rs',
108 'tests/codegen/sse42-implies-crc32.rs',
109 'tests/codegen/thread-local.rs',
110 'tests/codegen/uninit-consts.rs',
111 'tests/pretty/raw-str-nonexpr.rs',
112 'tests/run-make', 84 'tests/run-make',
113 'tests/run-make-fulldeps', 85 'tests/run-make-fulldeps',
114 'tests/rustdoc', 86 'tests/rustdoc',
115 'tests/rustdoc-json', 87 'tests/rustdoc-json',
116 'tests/rustdoc-js-std', 88 'tests/rustdoc-js-std',
117 'tests/rustdoc-ui/cfg-test.rs',
118 'tests/rustdoc-ui/check-cfg-test.rs',
119 'tests/rustdoc-ui/display-output.rs',
120 'tests/rustdoc-ui/doc-comment-multi-line-attr.rs',
121 'tests/rustdoc-ui/doc-comment-multi-line-cfg-attr.rs',
122 'tests/rustdoc-ui/doc-test-doctest-feature.rs',
123 'tests/rustdoc-ui/doctest-multiline-crate-attribute.rs',
124 'tests/rustdoc-ui/doctest-output.rs',
125 'tests/rustdoc-ui/doc-test-rustdoc-feature.rs',
126 'tests/rustdoc-ui/failed-doctest-compile-fail.rs',
127 'tests/rustdoc-ui/issue-80992.rs',
128 'tests/rustdoc-ui/issue-91134.rs',
129 'tests/rustdoc-ui/nocapture-fail.rs',
130 'tests/rustdoc-ui/nocapture.rs',
131 'tests/rustdoc-ui/no-run-flag.rs',
132 'tests/rustdoc-ui/run-directory.rs',
133 'tests/rustdoc-ui/test-no_std.rs',
134 'tests/rustdoc-ui/test-type.rs',
135 'tests/rustdoc/unit-return.rs',
136 'tests/ui/abi/stack-probes-lto.rs', 89 'tests/ui/abi/stack-probes-lto.rs',
137 'tests/ui/abi/stack-probes.rs', 90 'tests/ui/abi/stack-probes.rs',
138 'tests/ui/array-slice-vec/subslice-patterns-const-eval-match.rs', 91 'tests/ui/codegen/mismatched-data-layouts.rs',
139 'tests/ui/asm/x86_64/sym.rs', 92 'tests/codegen-llvm/rust-abi-arch-specific-adjustment.rs',
140 'tests/ui/associated-type-bounds/fn-apit.rs',
141 'tests/ui/associated-type-bounds/fn-dyn-apit.rs',
142 'tests/ui/associated-type-bounds/fn-wrap-apit.rs',
143 'tests/ui/debuginfo/debuginfo-emit-llvm-ir-and-split-debuginfo.rs', 93 'tests/ui/debuginfo/debuginfo-emit-llvm-ir-and-split-debuginfo.rs',
144 'tests/ui/drop/dynamic-drop.rs', 94 'tests/ui/feature-gates/version_check.rs',
145 'tests/ui/empty_global_asm.rs',
146 'tests/ui/functions-closures/fn-help-with-err.rs',
147 'tests/ui/linkage-attr/issue-10755.rs',
148 'tests/ui/macros/restricted-shadowing-legacy.rs',
149 'tests/ui/process/nofile-limit.rs',
150 'tests/ui/process/process-panic-after-fork.rs',
151 'tests/ui/process/process-sigpipe.rs',
152 'tests/ui/simd/target-feature-mixup.rs',
153 'tests/ui/structs-enums/multiple-reprs.rs',
154 'src/tools/jsondoclint',
155 'src/tools/replace-version-placeholder',
156 'tests/codegen/abi-efiapi.rs',
157 'tests/codegen/abi-sysv64.rs',
158 'tests/codegen/align-byval.rs',
159 'tests/codegen/align-fn.rs',
160 'tests/codegen/asm-powerpc-clobbers.rs',
161 'tests/codegen/async-fn-debug-awaitee-field.rs',
162 'tests/codegen/binary-search-index-no-bound-check.rs',
163 'tests/codegen/call-metadata.rs',
164 'tests/codegen/debug-column.rs',
165 'tests/codegen/debug-limited.rs',
166 'tests/codegen/debuginfo-generic-closure-env-names.rs',
167 'tests/codegen/drop.rs',
168 'tests/codegen/dst-vtable-align-nonzero.rs',
169 'tests/codegen/enable-lto-unit-splitting.rs',
170 'tests/codegen/enum/enum-u128.rs',
171 'tests/codegen/fn-impl-trait-self.rs',
172 'tests/codegen/inherit_overflow.rs',
173 'tests/codegen/inline-function-args-debug-info.rs',
174 'tests/codegen/intrinsics/mask.rs',
175 'tests/codegen/intrinsics/transmute-niched.rs',
176 'tests/codegen/issues/issue-73258.rs',
177 'tests/codegen/issues/issue-75546.rs',
178 'tests/codegen/issues/issue-77812.rs',
179 'tests/codegen/issues/issue-98156-const-arg-temp-lifetime.rs',
180 'tests/codegen/llvm-ident.rs',
181 'tests/codegen/mainsubprogram.rs',
182 'tests/codegen/move-operands.rs',
183 'tests/codegen/repr/transparent-mips64.rs',
184 'tests/mir-opt/',
185 'tests/rustdoc-json',
186 'tests/rustdoc-ui/doc-test-rustdoc-feature.rs',
187 'tests/rustdoc-ui/no-run-flag.rs',
188 'tests/ui-fulldeps/', 95 'tests/ui-fulldeps/',
189 'tests/ui/numbers-arithmetic/u128.rs' 96 'tests/ui/process/nofile-limit.rs',
97 'tidyselftest'
190 ] 98 ]
191 99
192 exclude_fail_tests = " ".join([" --exclude " + item for item in exclude_list]) 100 exclude_fail_tests = " ".join([" --exclude " + item for item in exclude_list])
193 # Add exclude_fail_tests with other test arguments 101 # Add exclude_fail_tests with other test arguments
194 testargs = exclude_fail_tests + " --doc --no-fail-fast --bless" 102 testargs = exclude_fail_tests + " --no-doc --no-fail-fast --bless"
195 103
196 # wrap the execution with a qemu instance. 104 # wrap the execution with a qemu instance.
197 # Tests are run with 512 tasks in parallel to execute all tests very quickly 105 # Set QEMU RAM to 1024MB to support running unit tests for the compiler crate, including larger
198 with runqemu("core-image-minimal", runqemuparams = "nographic", qemuparams = "-m 512") as qemu: 106 # test cases and parallel execution in the test environment.
107 with runqemu("core-image-minimal", runqemuparams = "nographic", qemuparams = "-m 1024") as qemu:
199 # Copy remote-test-server to image through scp 108 # Copy remote-test-server to image through scp
200 host_sys = get_bb_var("RUST_BUILD_SYS", "rust") 109 host_sys = get_bb_var("RUST_BUILD_SYS", "rust")
201 ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user="root") 110 ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user="root")
@@ -210,9 +119,8 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
210 tmpdir = get_bb_var("TMPDIR", "rust") 119 tmpdir = get_bb_var("TMPDIR", "rust")
211 120
212 # Set path for target-poky-linux-gcc, RUST_TARGET_PATH and hosttools. 121 # Set path for target-poky-linux-gcc, RUST_TARGET_PATH and hosttools.
213 cmd = " export PATH=%s/recipe-sysroot-native/usr/bin:$PATH;" % rustlibpath 122 cmd = "export TARGET_VENDOR=\"-poky\";"
214 cmd = cmd + " export TARGET_VENDOR=\"-poky\";" 123 cmd = cmd + " export PATH=%s/recipe-sysroot-native/usr/bin/python3-native:%s/recipe-sysroot-native/usr/bin:%s/recipe-sysroot-native/usr/bin/%s:%s/hosttools:$PATH;" % (rustlibpath, rustlibpath, rustlibpath, tcpath, tmpdir)
215 cmd = cmd + " export PATH=%s/recipe-sysroot-native/usr/bin/%s:%s/hosttools:$PATH;" % (rustlibpath, tcpath, tmpdir)
216 cmd = cmd + " export RUST_TARGET_PATH=%s/rust-targets;" % rustlibpath 124 cmd = cmd + " export RUST_TARGET_PATH=%s/rust-targets;" % rustlibpath
217 # Trigger testing. 125 # Trigger testing.
218 cmd = cmd + " export TEST_DEVICE_ADDR=\"%s:12345\";" % qemu.ip 126 cmd = cmd + " export TEST_DEVICE_ADDR=\"%s:12345\";" % qemu.ip
diff --git a/meta/lib/oeqa/selftest/cases/sdk.py b/meta/lib/oeqa/selftest/cases/sdk.py
new file mode 100644
index 0000000000..3971365029
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/sdk.py
@@ -0,0 +1,39 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os.path
8
9from oeqa.selftest.case import OESelftestTestCase
10from oeqa.utils.commands import bitbake, get_bb_vars
11
12class SDKTests(OESelftestTestCase):
13
14 def load_manifest(self, filename):
15 manifest = {}
16 with open(filename) as f:
17 for line in f:
18 name, arch, version = line.split(maxsplit=3)
19 manifest[name] = (version, arch)
20 return manifest
21
22 def test_sdk_manifests(self):
23 image = "core-image-minimal"
24
25 self.write_config("""
26TOOLCHAIN_HOST_TASK:append = " nativesdk-selftest-hello"
27IMAGE_INSTALL:append = " selftest-hello"
28""")
29
30 bitbake(f"{image} -c populate_sdk")
31 vars = get_bb_vars(['SDK_DEPLOY', 'TOOLCHAIN_OUTPUTNAME'], image)
32
33 path = os.path.join(vars["SDK_DEPLOY"], vars["TOOLCHAIN_OUTPUTNAME"] + ".host.manifest")
34 self.assertNotEqual(os.path.getsize(path), 0, msg="Host manifest is empty")
35 self.assertIn("nativesdk-selftest-hello", self.load_manifest(path))
36
37 path = os.path.join(vars["SDK_DEPLOY"], vars["TOOLCHAIN_OUTPUTNAME"] + ".target.manifest")
38 self.assertNotEqual(os.path.getsize(path), 0, msg="Target manifest is empty")
39 self.assertIn("selftest-hello", self.load_manifest(path))
diff --git a/meta/lib/oeqa/selftest/cases/signing.py b/meta/lib/oeqa/selftest/cases/signing.py
index 18cce0ba25..4df45ba032 100644
--- a/meta/lib/oeqa/selftest/cases/signing.py
+++ b/meta/lib/oeqa/selftest/cases/signing.py
@@ -83,6 +83,8 @@ class Signing(OESelftestTestCase):
83 feature += 'RPM_GPG_PASSPHRASE = "test123"\n' 83 feature += 'RPM_GPG_PASSPHRASE = "test123"\n'
84 feature += 'RPM_GPG_NAME = "testuser"\n' 84 feature += 'RPM_GPG_NAME = "testuser"\n'
85 feature += 'GPG_PATH = "%s"\n' % self.gpg_dir 85 feature += 'GPG_PATH = "%s"\n' % self.gpg_dir
86 feature += 'PACKAGECONFIG:append:pn-rpm-native = " sequoia"\n'
87 feature += 'PACKAGECONFIG:append:pn-rpm = " sequoia"\n'
86 88
87 self.write_config(feature) 89 self.write_config(feature)
88 90
diff --git a/meta/lib/oeqa/selftest/cases/spdx.py b/meta/lib/oeqa/selftest/cases/spdx.py
index 05fc4e390b..8cd4e83ca2 100644
--- a/meta/lib/oeqa/selftest/cases/spdx.py
+++ b/meta/lib/oeqa/selftest/cases/spdx.py
@@ -6,29 +6,39 @@
6 6
7import json 7import json
8import os 8import os
9import textwrap
10import hashlib
11from pathlib import Path
9from oeqa.selftest.case import OESelftestTestCase 12from oeqa.selftest.case import OESelftestTestCase
10from oeqa.utils.commands import bitbake, get_bb_var, runCmd 13from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars, runCmd
14import oe.spdx30
11 15
12class SPDXCheck(OESelftestTestCase):
13 16
17class SPDX22Check(OESelftestTestCase):
14 @classmethod 18 @classmethod
15 def setUpClass(cls): 19 def setUpClass(cls):
16 super(SPDXCheck, cls).setUpClass() 20 super().setUpClass()
17 bitbake("python3-spdx-tools-native") 21 bitbake("python3-spdx-tools-native")
18 bitbake("-c addto_recipe_sysroot python3-spdx-tools-native") 22 bitbake("-c addto_recipe_sysroot python3-spdx-tools-native")
19 23
20 def check_recipe_spdx(self, high_level_dir, spdx_file, target_name): 24 def check_recipe_spdx(self, high_level_dir, spdx_file, target_name):
21 config = """ 25 config = textwrap.dedent(
22INHERIT += "create-spdx" 26 """\
23""" 27 INHERIT:remove = "create-spdx"
28 INHERIT += "create-spdx-2.2"
29 """
30 )
24 self.write_config(config) 31 self.write_config(config)
25 32
26 deploy_dir = get_bb_var("DEPLOY_DIR") 33 deploy_dir = get_bb_var("DEPLOY_DIR")
27 machine_var = get_bb_var("MACHINE") 34 arch_dir = get_bb_var("PACKAGE_ARCH", target_name)
35 spdx_version = get_bb_var("SPDX_VERSION")
28 # qemux86-64 creates the directory qemux86_64 36 # qemux86-64 creates the directory qemux86_64
29 machine_dir = machine_var.replace("-", "_") 37 #arch_dir = arch_var.replace("-", "_")
30 38
31 full_file_path = os.path.join(deploy_dir, "spdx", machine_dir, high_level_dir, spdx_file) 39 full_file_path = os.path.join(
40 deploy_dir, "spdx", spdx_version, arch_dir, high_level_dir, spdx_file
41 )
32 42
33 try: 43 try:
34 os.remove(full_file_path) 44 os.remove(full_file_path)
@@ -43,8 +53,13 @@ INHERIT += "create-spdx"
43 self.assertNotEqual(report, None) 53 self.assertNotEqual(report, None)
44 self.assertNotEqual(report["SPDXID"], None) 54 self.assertNotEqual(report["SPDXID"], None)
45 55
46 python = os.path.join(get_bb_var('STAGING_BINDIR', 'python3-spdx-tools-native'), 'nativepython3') 56 python = os.path.join(
47 validator = os.path.join(get_bb_var('STAGING_BINDIR', 'python3-spdx-tools-native'), 'pyspdxtools') 57 get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"),
58 "nativepython3",
59 )
60 validator = os.path.join(
61 get_bb_var("STAGING_BINDIR", "python3-spdx-tools-native"), "pyspdxtools"
62 )
48 result = runCmd("{} {} -i {}".format(python, validator, filename)) 63 result = runCmd("{} {} -i {}".format(python, validator, filename))
49 64
50 self.assertExists(full_file_path) 65 self.assertExists(full_file_path)
@@ -52,3 +67,222 @@ INHERIT += "create-spdx"
52 67
53 def test_spdx_base_files(self): 68 def test_spdx_base_files(self):
54 self.check_recipe_spdx("packages", "base-files.spdx.json", "base-files") 69 self.check_recipe_spdx("packages", "base-files.spdx.json", "base-files")
70
71 def test_spdx_tar(self):
72 self.check_recipe_spdx("packages", "tar.spdx.json", "tar")
73
74
75class SPDX3CheckBase(object):
76 """
77 Base class for checking SPDX 3 based tests
78 """
79
80 def check_spdx_file(self, filename):
81 self.assertExists(filename)
82
83 # Read the file
84 objset = oe.spdx30.SHACLObjectSet()
85 with open(filename, "r") as f:
86 d = oe.spdx30.JSONLDDeserializer()
87 d.read(f, objset)
88
89 return objset
90
91 def check_recipe_spdx(self, target_name, spdx_path, *, task=None, extraconf=""):
92 config = (
93 textwrap.dedent(
94 f"""\
95 INHERIT:remove = "create-spdx"
96 INHERIT += "{self.SPDX_CLASS}"
97 """
98 )
99 + textwrap.dedent(extraconf)
100 )
101
102 self.write_config(config)
103
104 if task:
105 bitbake(f"-c {task} {target_name}")
106 else:
107 bitbake(target_name)
108
109 filename = spdx_path.format(
110 **get_bb_vars(
111 [
112 "DEPLOY_DIR_IMAGE",
113 "DEPLOY_DIR_SPDX",
114 "MACHINE",
115 "MACHINE_ARCH",
116 "SDKMACHINE",
117 "SDK_DEPLOY",
118 "SPDX_VERSION",
119 "SSTATE_PKGARCH",
120 "TOOLCHAIN_OUTPUTNAME",
121 ],
122 target_name,
123 )
124 )
125
126 return self.check_spdx_file(filename)
127
128 def check_objset_missing_ids(self, objset):
129 for o in objset.foreach_type(oe.spdx30.SpdxDocument):
130 doc = o
131 break
132 else:
133 self.assertTrue(False, "Unable to find SpdxDocument")
134
135 missing_ids = objset.missing_ids - set(i.externalSpdxId for i in doc.import_)
136 if missing_ids:
137 self.assertTrue(
138 False,
139 "The following SPDXIDs are unresolved:\n " + "\n ".join(missing_ids),
140 )
141
142
143class SPDX30Check(SPDX3CheckBase, OESelftestTestCase):
144 SPDX_CLASS = "create-spdx-3.0"
145
146 def test_base_files(self):
147 self.check_recipe_spdx(
148 "base-files",
149 "{DEPLOY_DIR_SPDX}/{MACHINE_ARCH}/packages/package-base-files.spdx.json",
150 )
151
152 def test_gcc_include_source(self):
153 objset = self.check_recipe_spdx(
154 "gcc",
155 "{DEPLOY_DIR_SPDX}/{SSTATE_PKGARCH}/recipes/recipe-gcc.spdx.json",
156 extraconf="""\
157 SPDX_INCLUDE_SOURCES = "1"
158 """,
159 )
160
161 gcc_pv = get_bb_var("PV", "gcc")
162 filename = f"gcc-{gcc_pv}/README"
163 found = False
164 for software_file in objset.foreach_type(oe.spdx30.software_File):
165 if software_file.name == filename:
166 found = True
167 self.logger.info(
168 f"The spdxId of {filename} in recipe-gcc.spdx.json is {software_file.spdxId}"
169 )
170 break
171
172 self.assertTrue(
173 found, f"Not found source file {filename} in recipe-gcc.spdx.json\n"
174 )
175
176 def test_core_image_minimal(self):
177 objset = self.check_recipe_spdx(
178 "core-image-minimal",
179 "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json",
180 )
181
182 # Document should be fully linked
183 self.check_objset_missing_ids(objset)
184
185 def test_core_image_minimal_sdk(self):
186 objset = self.check_recipe_spdx(
187 "core-image-minimal",
188 "{SDK_DEPLOY}/{TOOLCHAIN_OUTPUTNAME}.spdx.json",
189 task="populate_sdk",
190 )
191
192 # Document should be fully linked
193 self.check_objset_missing_ids(objset)
194
195 def test_baremetal_helloworld(self):
196 objset = self.check_recipe_spdx(
197 "baremetal-helloworld",
198 "{DEPLOY_DIR_IMAGE}/baremetal-helloworld-image-{MACHINE}.spdx.json",
199 extraconf="""\
200 TCLIBC = "baremetal"
201 """,
202 )
203
204 # Document should be fully linked
205 self.check_objset_missing_ids(objset)
206
207 def test_extra_opts(self):
208 HOST_SPDXID = "http://foo.bar/spdx/bar2"
209
210 EXTRACONF = textwrap.dedent(
211 f"""\
212 SPDX_INVOKED_BY_name = "CI Tool"
213 SPDX_INVOKED_BY_type = "software"
214
215 SPDX_ON_BEHALF_OF_name = "John Doe"
216 SPDX_ON_BEHALF_OF_type = "person"
217 SPDX_ON_BEHALF_OF_id_email = "John.Doe@noreply.com"
218
219 SPDX_PACKAGE_SUPPLIER_name = "ACME Embedded Widgets"
220 SPDX_PACKAGE_SUPPLIER_type = "organization"
221
222 SPDX_AUTHORS += "authorA"
223 SPDX_AUTHORS_authorA_ref = "SPDX_ON_BEHALF_OF"
224
225 SPDX_BUILD_HOST = "host"
226
227 SPDX_IMPORTS += "host"
228 SPDX_IMPORTS_host_spdxid = "{HOST_SPDXID}"
229
230 SPDX_INCLUDE_BUILD_VARIABLES = "1"
231 SPDX_INCLUDE_BITBAKE_PARENT_BUILD = "1"
232 SPDX_INCLUDE_TIMESTAMPS = "1"
233
234 SPDX_PRETTY = "1"
235 """
236 )
237 extraconf_hash = hashlib.sha1(EXTRACONF.encode("utf-8")).hexdigest()
238
239 objset = self.check_recipe_spdx(
240 "core-image-minimal",
241 "{DEPLOY_DIR_IMAGE}/core-image-minimal-{MACHINE}.rootfs.spdx.json",
242 # Many SPDX variables do not trigger a rebuild, since they are
243 # intended to record information at the time of the build. As such,
244 # the extra configuration alone may not trigger a rebuild, and even
245 # if it does, the task hash won't necessarily be unique. In order
246 # to make sure rebuilds happen, but still allow these test objects
247 # to be pulled from sstate (e.g. remain reproducible), change the
248 # namespace prefix to include the hash of the extra configuration
249 extraconf=textwrap.dedent(
250 f"""\
251 SPDX_NAMESPACE_PREFIX = "http://spdx.org/spdxdocs/{extraconf_hash}"
252 """
253 )
254 + EXTRACONF,
255 )
256
257 # Document should be fully linked
258 self.check_objset_missing_ids(objset)
259
260 for o in objset.foreach_type(oe.spdx30.SoftwareAgent):
261 if o.name == "CI Tool":
262 break
263 else:
264 self.assertTrue(False, "Unable to find software tool")
265
266 for o in objset.foreach_type(oe.spdx30.Person):
267 if o.name == "John Doe":
268 break
269 else:
270 self.assertTrue(False, "Unable to find person")
271
272 for o in objset.foreach_type(oe.spdx30.Organization):
273 if o.name == "ACME Embedded Widgets":
274 break
275 else:
276 self.assertTrue(False, "Unable to find organization")
277
278 for o in objset.foreach_type(oe.spdx30.SpdxDocument):
279 doc = o
280 break
281 else:
282 self.assertTrue(False, "Unable to find SpdxDocument")
283
284 for i in doc.import_:
285 if i.externalSpdxId == HOST_SPDXID:
286 break
287 else:
288 self.assertTrue(False, "Unable to find imported Host SpdxID")
diff --git a/meta/lib/oeqa/selftest/cases/sstatetests.py b/meta/lib/oeqa/selftest/cases/sstatetests.py
index 86d6cd7464..44dd674a32 100644
--- a/meta/lib/oeqa/selftest/cases/sstatetests.py
+++ b/meta/lib/oeqa/selftest/cases/sstatetests.py
@@ -27,17 +27,15 @@ class SStateBase(OESelftestTestCase):
27 def setUpLocal(self): 27 def setUpLocal(self):
28 super(SStateBase, self).setUpLocal() 28 super(SStateBase, self).setUpLocal()
29 self.temp_sstate_location = None 29 self.temp_sstate_location = None
30 needed_vars = ['SSTATE_DIR', 'NATIVELSBSTRING', 'TCLIBC', 'TUNE_ARCH', 30 needed_vars = ['SSTATE_DIR', 'TCLIBC', 'TUNE_ARCH',
31 'TOPDIR', 'TARGET_VENDOR', 'TARGET_OS'] 31 'TOPDIR', 'TARGET_VENDOR', 'TARGET_OS']
32 bb_vars = get_bb_vars(needed_vars) 32 bb_vars = get_bb_vars(needed_vars)
33 self.sstate_path = bb_vars['SSTATE_DIR'] 33 self.sstate_path = bb_vars['SSTATE_DIR']
34 self.hostdistro = bb_vars['NATIVELSBSTRING']
35 self.tclibc = bb_vars['TCLIBC'] 34 self.tclibc = bb_vars['TCLIBC']
36 self.tune_arch = bb_vars['TUNE_ARCH'] 35 self.tune_arch = bb_vars['TUNE_ARCH']
37 self.topdir = bb_vars['TOPDIR'] 36 self.topdir = bb_vars['TOPDIR']
38 self.target_vendor = bb_vars['TARGET_VENDOR'] 37 self.target_vendor = bb_vars['TARGET_VENDOR']
39 self.target_os = bb_vars['TARGET_OS'] 38 self.target_os = bb_vars['TARGET_OS']
40 self.distro_specific_sstate = os.path.join(self.sstate_path, self.hostdistro)
41 39
42 def track_for_cleanup(self, path): 40 def track_for_cleanup(self, path):
43 if not keep_temp_files: 41 if not keep_temp_files:
@@ -52,10 +50,7 @@ class SStateBase(OESelftestTestCase):
52 config_temp_sstate = "SSTATE_DIR = \"%s\"" % temp_sstate_path 50 config_temp_sstate = "SSTATE_DIR = \"%s\"" % temp_sstate_path
53 self.append_config(config_temp_sstate) 51 self.append_config(config_temp_sstate)
54 self.track_for_cleanup(temp_sstate_path) 52 self.track_for_cleanup(temp_sstate_path)
55 bb_vars = get_bb_vars(['SSTATE_DIR', 'NATIVELSBSTRING']) 53 self.sstate_path = get_bb_var('SSTATE_DIR')
56 self.sstate_path = bb_vars['SSTATE_DIR']
57 self.hostdistro = bb_vars['NATIVELSBSTRING']
58 self.distro_specific_sstate = os.path.join(self.sstate_path, self.hostdistro)
59 54
60 if add_local_mirrors: 55 if add_local_mirrors:
61 config_set_sstate_if_not_set = 'SSTATE_MIRRORS ?= ""' 56 config_set_sstate_if_not_set = 'SSTATE_MIRRORS ?= ""'
@@ -65,8 +60,16 @@ class SStateBase(OESelftestTestCase):
65 config_sstate_mirror = "SSTATE_MIRRORS += \"file://.* file:///%s/PATH\"" % local_mirror 60 config_sstate_mirror = "SSTATE_MIRRORS += \"file://.* file:///%s/PATH\"" % local_mirror
66 self.append_config(config_sstate_mirror) 61 self.append_config(config_sstate_mirror)
67 62
63 def set_hostdistro(self):
64 # This needs to be read after a BuildStarted event in case it gets changed by event
65 # handling in uninative.bbclass
66 self.hostdistro = get_bb_var('NATIVELSBSTRING')
67 self.distro_specific_sstate = os.path.join(self.sstate_path, self.hostdistro)
68
68 # Returns a list containing sstate files 69 # Returns a list containing sstate files
69 def search_sstate(self, filename_regex, distro_specific=True, distro_nonspecific=True): 70 def search_sstate(self, filename_regex, distro_specific=True, distro_nonspecific=True):
71 self.set_hostdistro()
72
70 result = [] 73 result = []
71 for root, dirs, files in os.walk(self.sstate_path): 74 for root, dirs, files in os.walk(self.sstate_path):
72 if distro_specific and re.search(r"%s/%s/[a-z0-9]{2}/[a-z0-9]{2}$" % (self.sstate_path, self.hostdistro), root): 75 if distro_specific and re.search(r"%s/%s/[a-z0-9]{2}/[a-z0-9]{2}$" % (self.sstate_path, self.hostdistro), root):
@@ -80,55 +83,43 @@ class SStateBase(OESelftestTestCase):
80 return result 83 return result
81 84
82 # Test sstate files creation and their location and directory perms 85 # Test sstate files creation and their location and directory perms
83 def run_test_sstate_creation(self, targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True, should_pass=True): 86 def run_test_sstate_creation(self, targets, hostdistro_specific):
84 self.config_sstate(temp_sstate_location, [self.sstate_path]) 87 self.config_sstate(True, [self.sstate_path])
88
89 bitbake(['-cclean'] + targets)
85 90
86 if self.temp_sstate_location:
87 bitbake(['-cclean'] + targets)
88 else:
89 bitbake(['-ccleansstate'] + targets)
90
91 # We need to test that the env umask have does not effect sstate directory creation
92 # So, first, we'll get the current umask and set it to something we know incorrect
93 # See: sstate_task_postfunc for correct umask of os.umask(0o002)
94 import os
95 def current_umask():
96 current_umask = os.umask(0)
97 os.umask(current_umask)
98 return current_umask
99
100 orig_umask = current_umask()
101 # Set it to a umask we know will be 'wrong' 91 # Set it to a umask we know will be 'wrong'
102 os.umask(0o022) 92 with bb.utils.umask(0o022):
93 bitbake(targets)
103 94
104 bitbake(targets) 95 # Distro specific files
105 file_tracker = [] 96 distro_specific_files = self.search_sstate('|'.join(map(str, targets)), True, False)
106 results = self.search_sstate('|'.join(map(str, targets)), distro_specific, distro_nonspecific)
107 if distro_nonspecific:
108 for r in results:
109 if r.endswith(("_populate_lic.tar.zst", "_populate_lic.tar.zst.siginfo", "_fetch.tar.zst.siginfo", "_unpack.tar.zst.siginfo", "_patch.tar.zst.siginfo")):
110 continue
111 file_tracker.append(r)
112 else:
113 file_tracker = results
114 97
115 if should_pass: 98 # Distro non-specific
116 self.assertTrue(file_tracker , msg="Could not find sstate files for: %s" % ', '.join(map(str, targets))) 99 distro_non_specific_files = []
100 results = self.search_sstate('|'.join(map(str, targets)), False, True)
101 for r in results:
102 if r.endswith(("_populate_lic.tar.zst", "_populate_lic.tar.zst.siginfo", "_fetch.tar.zst.siginfo", "_unpack.tar.zst.siginfo", "_patch.tar.zst.siginfo")):
103 continue
104 distro_non_specific_files.append(r)
105
106 if hostdistro_specific:
107 self.assertTrue(distro_specific_files , msg="Could not find sstate files for: %s" % ', '.join(map(str, targets)))
108 self.assertFalse(distro_non_specific_files, msg="Found sstate files in the wrong place for: %s (found %s)" % (', '.join(map(str, targets)), str(distro_non_specific_files)))
117 else: 109 else:
118 self.assertTrue(not file_tracker , msg="Found sstate files in the wrong place for: %s (found %s)" % (', '.join(map(str, targets)), str(file_tracker))) 110 self.assertTrue(distro_non_specific_files , msg="Could not find sstate files for: %s" % ', '.join(map(str, targets)))
111 self.assertFalse(distro_specific_files, msg="Found sstate files in the wrong place for: %s (found %s)" % (', '.join(map(str, targets)), str(distro_specific_files)))
119 112
120 # Now we'll walk the tree to check the mode and see if things are incorrect. 113 # Now we'll walk the tree to check the mode and see if things are incorrect.
121 badperms = [] 114 badperms = []
122 for root, dirs, files in os.walk(self.sstate_path): 115 for root, dirs, files in os.walk(self.sstate_path):
123 for directory in dirs: 116 for directory in dirs:
124 if (os.stat(os.path.join(root, directory)).st_mode & 0o777) != 0o775: 117 mode = os.stat(os.path.join(root, directory)).st_mode & 0o777
125 badperms.append(os.path.join(root, directory)) 118 if mode != 0o775:
126 119 badperms.append("%s: %s vs %s" % (os.path.join(root, directory), mode, 0o775))
127 # Return to original umask
128 os.umask(orig_umask)
129 120
130 if should_pass: 121 # Check badperms is empty
131 self.assertTrue(badperms , msg="Found sstate directories with the wrong permissions: %s (found %s)" % (', '.join(map(str, targets)), str(badperms))) 122 self.assertFalse(badperms , msg="Found sstate directories with the wrong permissions: %s (found %s)" % (', '.join(map(str, targets)), str(badperms)))
132 123
133 # Test the sstate files deletion part of the do_cleansstate task 124 # Test the sstate files deletion part of the do_cleansstate task
134 def run_test_cleansstate_task(self, targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True): 125 def run_test_cleansstate_task(self, targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True):
@@ -153,6 +144,8 @@ class SStateBase(OESelftestTestCase):
153 144
154 bitbake(['-ccleansstate'] + targets) 145 bitbake(['-ccleansstate'] + targets)
155 146
147 self.set_hostdistro()
148
156 bitbake(targets) 149 bitbake(targets)
157 results = self.search_sstate('|'.join(map(str, [s + r'.*?\.tar.zst$' for s in targets])), distro_specific=False, distro_nonspecific=True) 150 results = self.search_sstate('|'.join(map(str, [s + r'.*?\.tar.zst$' for s in targets])), distro_specific=False, distro_nonspecific=True)
158 filtered_results = [] 151 filtered_results = []
@@ -251,17 +244,11 @@ class SStateTests(SStateBase):
251 bitbake("dbus-wait-test -c unpack") 244 bitbake("dbus-wait-test -c unpack")
252 245
253class SStateCreation(SStateBase): 246class SStateCreation(SStateBase):
254 def test_sstate_creation_distro_specific_pass(self): 247 def test_sstate_creation_distro_specific(self):
255 self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True) 248 self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], hostdistro_specific=True)
256
257 def test_sstate_creation_distro_specific_fail(self):
258 self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True, should_pass=False)
259 249
260 def test_sstate_creation_distro_nonspecific_pass(self): 250 def test_sstate_creation_distro_nonspecific(self):
261 self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) 251 self.run_test_sstate_creation(['linux-libc-headers'], hostdistro_specific=False)
262
263 def test_sstate_creation_distro_nonspecific_fail(self):
264 self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True, should_pass=False)
265 252
266class SStateCleanup(SStateBase): 253class SStateCleanup(SStateBase):
267 def test_cleansstate_task_distro_specific_nonspecific(self): 254 def test_cleansstate_task_distro_specific_nonspecific(self):
@@ -349,36 +336,29 @@ class SStateCacheManagement(SStateBase):
349 def test_sstate_cache_management_script_using_pr_3(self): 336 def test_sstate_cache_management_script_using_pr_3(self):
350 global_config = [] 337 global_config = []
351 target_config = [] 338 target_config = []
352 global_config.append('MACHINE = "qemux86-64"') 339 global_config.append('MACHINE:forcevariable = "qemux86-64"')
353 target_config.append('PR = "0"') 340 target_config.append('PR = "0"')
354 global_config.append(global_config[0]) 341 global_config.append(global_config[0])
355 target_config.append('PR = "1"') 342 target_config.append('PR = "1"')
356 global_config.append('MACHINE = "qemux86"') 343 global_config.append('MACHINE:forcevariable = "qemux86"')
357 target_config.append('PR = "1"') 344 target_config.append('PR = "1"')
358 self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) 345 self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic'])
359 346
360 def test_sstate_cache_management_script_using_machine(self): 347 def test_sstate_cache_management_script_using_machine(self):
361 global_config = [] 348 global_config = []
362 target_config = [] 349 target_config = []
363 global_config.append('MACHINE = "qemux86-64"') 350 global_config.append('MACHINE:forcevariable = "qemux86-64"')
364 target_config.append('') 351 target_config.append('')
365 global_config.append('MACHINE = "qemux86"') 352 global_config.append('MACHINE:forcevariable = "qemux86"')
366 target_config.append('') 353 target_config.append('')
367 self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) 354 self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic'])
368 355
369class SStateHashSameSigs(SStateBase): 356class SStateHashSameSigs(SStateBase):
370 def test_sstate_32_64_same_hash(self): 357 def sstate_hashtest(self, sdkmachine):
371 """
372 The sstate checksums for both native and target should not vary whether
373 they're built on a 32 or 64 bit system. Rather than requiring two different
374 build machines and running a builds, override the variables calling uname()
375 manually and check using bitbake -S.
376 """
377 358
378 self.write_config(""" 359 self.write_config("""
379MACHINE = "qemux86" 360MACHINE:forcevariable = "qemux86"
380TMPDIR = "${TOPDIR}/tmp-sstatesamehash" 361TMPDIR = "${TOPDIR}/tmp-sstatesamehash"
381TCLIBCAPPEND = ""
382BUILD_ARCH = "x86_64" 362BUILD_ARCH = "x86_64"
383BUILD_OS = "linux" 363BUILD_OS = "linux"
384SDKMACHINE = "x86_64" 364SDKMACHINE = "x86_64"
@@ -388,15 +368,14 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
388 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash") 368 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash")
389 bitbake("core-image-weston -S none") 369 bitbake("core-image-weston -S none")
390 self.write_config(""" 370 self.write_config("""
391MACHINE = "qemux86" 371MACHINE:forcevariable = "qemux86"
392TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" 372TMPDIR = "${TOPDIR}/tmp-sstatesamehash2"
393TCLIBCAPPEND = ""
394BUILD_ARCH = "i686" 373BUILD_ARCH = "i686"
395BUILD_OS = "linux" 374BUILD_OS = "linux"
396SDKMACHINE = "i686" 375SDKMACHINE = "%s"
397PACKAGE_CLASSES = "package_rpm package_ipk package_deb" 376PACKAGE_CLASSES = "package_rpm package_ipk package_deb"
398BB_SIGNATURE_HANDLER = "OEBasicHash" 377BB_SIGNATURE_HANDLER = "OEBasicHash"
399""") 378""" % sdkmachine)
400 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash2") 379 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash2")
401 bitbake("core-image-weston -S none") 380 bitbake("core-image-weston -S none")
402 381
@@ -416,6 +395,20 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
416 self.maxDiff = None 395 self.maxDiff = None
417 self.assertCountEqual(files1, files2) 396 self.assertCountEqual(files1, files2)
418 397
398 def test_sstate_32_64_same_hash(self):
399 """
400 The sstate checksums for both native and target should not vary whether
401 they're built on a 32 or 64 bit system. Rather than requiring two different
402 build machines and running a builds, override the variables calling uname()
403 manually and check using bitbake -S.
404 """
405 self.sstate_hashtest("i686")
406
407 def test_sstate_sdk_arch_same_hash(self):
408 """
409 Similarly, test an arm SDK has the same hashes
410 """
411 self.sstate_hashtest("aarch64")
419 412
420 def test_sstate_nativelsbstring_same_hash(self): 413 def test_sstate_nativelsbstring_same_hash(self):
421 """ 414 """
@@ -426,7 +419,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
426 419
427 self.write_config(""" 420 self.write_config("""
428TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 421TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
429TCLIBCAPPEND = \"\"
430NATIVELSBSTRING = \"DistroA\" 422NATIVELSBSTRING = \"DistroA\"
431BB_SIGNATURE_HANDLER = "OEBasicHash" 423BB_SIGNATURE_HANDLER = "OEBasicHash"
432""") 424""")
@@ -434,7 +426,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
434 bitbake("core-image-weston -S none") 426 bitbake("core-image-weston -S none")
435 self.write_config(""" 427 self.write_config("""
436TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 428TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
437TCLIBCAPPEND = \"\"
438NATIVELSBSTRING = \"DistroB\" 429NATIVELSBSTRING = \"DistroB\"
439BB_SIGNATURE_HANDLER = "OEBasicHash" 430BB_SIGNATURE_HANDLER = "OEBasicHash"
440""") 431""")
@@ -463,17 +454,17 @@ class SStateHashSameSigs2(SStateBase):
463 454
464 configA = """ 455 configA = """
465TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 456TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
466TCLIBCAPPEND = \"\" 457MACHINE:forcevariable = \"qemux86-64\"
467MACHINE = \"qemux86-64\"
468BB_SIGNATURE_HANDLER = "OEBasicHash" 458BB_SIGNATURE_HANDLER = "OEBasicHash"
469""" 459"""
470 #OLDEST_KERNEL is arch specific so set to a different value here for testing 460 #OLDEST_KERNEL is arch specific so set to a different value here for testing
471 configB = """ 461 configB = """
472TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 462TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
473TCLIBCAPPEND = \"\" 463MACHINE:forcevariable = \"qemuarm\"
474MACHINE = \"qemuarm\"
475OLDEST_KERNEL = \"3.3.0\" 464OLDEST_KERNEL = \"3.3.0\"
476BB_SIGNATURE_HANDLER = "OEBasicHash" 465BB_SIGNATURE_HANDLER = "OEBasicHash"
466ERROR_QA:append = " somenewoption"
467WARN_QA:append = " someotheroption"
477""" 468"""
478 self.sstate_common_samesigs(configA, configB, allarch=True) 469 self.sstate_common_samesigs(configA, configB, allarch=True)
479 470
@@ -484,8 +475,7 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
484 475
485 configA = """ 476 configA = """
486TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 477TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
487TCLIBCAPPEND = \"\" 478MACHINE:forcevariable = \"qemux86-64\"
488MACHINE = \"qemux86-64\"
489require conf/multilib.conf 479require conf/multilib.conf
490MULTILIBS = \"multilib:lib32\" 480MULTILIBS = \"multilib:lib32\"
491DEFAULTTUNE:virtclass-multilib-lib32 = \"x86\" 481DEFAULTTUNE:virtclass-multilib-lib32 = \"x86\"
@@ -493,8 +483,7 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
493""" 483"""
494 configB = """ 484 configB = """
495TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 485TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
496TCLIBCAPPEND = \"\" 486MACHINE:forcevariable = \"qemuarm\"
497MACHINE = \"qemuarm\"
498require conf/multilib.conf 487require conf/multilib.conf
499MULTILIBS = \"\" 488MULTILIBS = \"\"
500BB_SIGNATURE_HANDLER = "OEBasicHash" 489BB_SIGNATURE_HANDLER = "OEBasicHash"
@@ -511,8 +500,7 @@ class SStateHashSameSigs3(SStateBase):
511 500
512 self.write_config(""" 501 self.write_config("""
513TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 502TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
514TCLIBCAPPEND = \"\" 503MACHINE:forcevariable = \"qemux86\"
515MACHINE = \"qemux86\"
516require conf/multilib.conf 504require conf/multilib.conf
517MULTILIBS = "multilib:lib32" 505MULTILIBS = "multilib:lib32"
518DEFAULTTUNE:virtclass-multilib-lib32 = "x86" 506DEFAULTTUNE:virtclass-multilib-lib32 = "x86"
@@ -522,8 +510,7 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
522 bitbake("world meta-toolchain -S none") 510 bitbake("world meta-toolchain -S none")
523 self.write_config(""" 511 self.write_config("""
524TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 512TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
525TCLIBCAPPEND = \"\" 513MACHINE:forcevariable = \"qemux86copy\"
526MACHINE = \"qemux86copy\"
527require conf/multilib.conf 514require conf/multilib.conf
528MULTILIBS = "multilib:lib32" 515MULTILIBS = "multilib:lib32"
529DEFAULTTUNE:virtclass-multilib-lib32 = "x86" 516DEFAULTTUNE:virtclass-multilib-lib32 = "x86"
@@ -559,8 +546,7 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
559 546
560 self.write_config(""" 547 self.write_config("""
561TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 548TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
562TCLIBCAPPEND = \"\" 549MACHINE:forcevariable = \"qemux86\"
563MACHINE = \"qemux86\"
564require conf/multilib.conf 550require conf/multilib.conf
565MULTILIBS = "multilib:lib32" 551MULTILIBS = "multilib:lib32"
566DEFAULTTUNE:virtclass-multilib-lib32 = "x86" 552DEFAULTTUNE:virtclass-multilib-lib32 = "x86"
@@ -570,8 +556,7 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
570 bitbake("binutils-native -S none") 556 bitbake("binutils-native -S none")
571 self.write_config(""" 557 self.write_config("""
572TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 558TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
573TCLIBCAPPEND = \"\" 559MACHINE:forcevariable = \"qemux86copy\"
574MACHINE = \"qemux86copy\"
575BB_SIGNATURE_HANDLER = "OEBasicHash" 560BB_SIGNATURE_HANDLER = "OEBasicHash"
576""") 561""")
577 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash2") 562 self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash2")
@@ -598,7 +583,6 @@ class SStateHashSameSigs4(SStateBase):
598 583
599 self.write_config(""" 584 self.write_config("""
600TMPDIR = "${TOPDIR}/tmp-sstatesamehash" 585TMPDIR = "${TOPDIR}/tmp-sstatesamehash"
601TCLIBCAPPEND = ""
602BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}" 586BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}"
603PARALLEL_MAKE = "-j 1" 587PARALLEL_MAKE = "-j 1"
604DL_DIR = "${TOPDIR}/download1" 588DL_DIR = "${TOPDIR}/download1"
@@ -613,7 +597,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
613 bitbake("world meta-toolchain -S none") 597 bitbake("world meta-toolchain -S none")
614 self.write_config(""" 598 self.write_config("""
615TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" 599TMPDIR = "${TOPDIR}/tmp-sstatesamehash2"
616TCLIBCAPPEND = ""
617BB_NUMBER_THREADS = "${@oe.utils.cpu_count()+1}" 600BB_NUMBER_THREADS = "${@oe.utils.cpu_count()+1}"
618PARALLEL_MAKE = "-j 2" 601PARALLEL_MAKE = "-j 2"
619DL_DIR = "${TOPDIR}/download2" 602DL_DIR = "${TOPDIR}/download2"
@@ -724,8 +707,7 @@ class SStateFindSiginfo(SStateBase):
724 """ 707 """
725 self.write_config(""" 708 self.write_config("""
726TMPDIR = \"${TOPDIR}/tmp-sstates-findsiginfo\" 709TMPDIR = \"${TOPDIR}/tmp-sstates-findsiginfo\"
727TCLIBCAPPEND = \"\" 710MACHINE:forcevariable = \"qemux86-64\"
728MACHINE = \"qemux86-64\"
729require conf/multilib.conf 711require conf/multilib.conf
730MULTILIBS = "multilib:lib32" 712MULTILIBS = "multilib:lib32"
731DEFAULTTUNE:virtclass-multilib-lib32 = "x86" 713DEFAULTTUNE:virtclass-multilib-lib32 = "x86"
@@ -917,15 +899,24 @@ INHERIT += "base-do-configure-modified"
917""", 899""",
918expected_sametmp_output, expected_difftmp_output) 900expected_sametmp_output, expected_difftmp_output)
919 901
920@OETestTag("yocto-mirrors") 902class SStateCheckObjectPresence(SStateBase):
921class SStateMirrors(SStateBase): 903 def check_bb_output(self, output, targets, exceptions, check_cdn):
922 def check_bb_output(self, output, exceptions, check_cdn):
923 def is_exception(object, exceptions): 904 def is_exception(object, exceptions):
924 for e in exceptions: 905 for e in exceptions:
925 if re.search(e, object): 906 if re.search(e, object):
926 return True 907 return True
927 return False 908 return False
928 909
910 # sstate is checked for existence of these, but they never get written out to begin with
911 exceptions += ["{}.*image_qa".format(t) for t in targets.split()]
912 exceptions += ["{}.*deploy_source_date_epoch".format(t) for t in targets.split()]
913 exceptions += ["{}.*image_complete".format(t) for t in targets.split()]
914 exceptions += ["linux-yocto.*shared_workdir"]
915 # these get influnced by IMAGE_FSTYPES tweaks in yocto-autobuilder-helper's config.json (on x86-64)
916 # additionally, they depend on noexec (thus, absent stamps) package, install, etc. image tasks,
917 # which makes tracing other changes difficult
918 exceptions += ["{}.*create_.*spdx".format(t) for t in targets.split()]
919
929 output_l = output.splitlines() 920 output_l = output.splitlines()
930 for l in output_l: 921 for l in output_l:
931 if l.startswith("Sstate summary"): 922 if l.startswith("Sstate summary"):
@@ -960,34 +951,25 @@ class SStateMirrors(SStateBase):
960 self.assertEqual(len(failed_urls), missing_objects, "Amount of reported missing objects does not match failed URLs: {}\nFailed URLs:\n{}\nFetcher diagnostics:\n{}".format(missing_objects, "\n".join(failed_urls), "\n".join(failed_urls_extrainfo))) 951 self.assertEqual(len(failed_urls), missing_objects, "Amount of reported missing objects does not match failed URLs: {}\nFailed URLs:\n{}\nFetcher diagnostics:\n{}".format(missing_objects, "\n".join(failed_urls), "\n".join(failed_urls_extrainfo)))
961 self.assertEqual(len(failed_urls), 0, "Missing objects in the cache:\n{}\nFetcher diagnostics:\n{}".format("\n".join(failed_urls), "\n".join(failed_urls_extrainfo))) 952 self.assertEqual(len(failed_urls), 0, "Missing objects in the cache:\n{}\nFetcher diagnostics:\n{}".format("\n".join(failed_urls), "\n".join(failed_urls_extrainfo)))
962 953
954@OETestTag("yocto-mirrors")
955class SStateMirrors(SStateCheckObjectPresence):
963 def run_test(self, machine, targets, exceptions, check_cdn = True, ignore_errors = False): 956 def run_test(self, machine, targets, exceptions, check_cdn = True, ignore_errors = False):
964 # sstate is checked for existence of these, but they never get written out to begin with
965 exceptions += ["{}.*image_qa".format(t) for t in targets.split()]
966 exceptions += ["{}.*deploy_source_date_epoch".format(t) for t in targets.split()]
967 exceptions += ["{}.*image_complete".format(t) for t in targets.split()]
968 exceptions += ["linux-yocto.*shared_workdir"]
969 # these get influnced by IMAGE_FSTYPES tweaks in yocto-autobuilder-helper's config.json (on x86-64)
970 # additionally, they depend on noexec (thus, absent stamps) package, install, etc. image tasks,
971 # which makes tracing other changes difficult
972 exceptions += ["{}.*create_spdx".format(t) for t in targets.split()]
973 exceptions += ["{}.*create_runtime_spdx".format(t) for t in targets.split()]
974
975 if check_cdn: 957 if check_cdn:
976 self.config_sstate(True) 958 self.config_sstate(True)
977 self.append_config(""" 959 self.append_config("""
978MACHINE = "{}" 960MACHINE:forcevariable = "{}"
979BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687" 961BB_HASHSERVE_UPSTREAM = "hashserv.yoctoproject.org:8686"
980SSTATE_MIRRORS ?= "file://.* http://cdn.jsdelivr.net/yocto/sstate/all/PATH;downloadfilename=PATH" 962SSTATE_MIRRORS ?= "file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH"
981""".format(machine)) 963""".format(machine))
982 else: 964 else:
983 self.append_config(""" 965 self.append_config("""
984MACHINE = "{}" 966MACHINE:forcevariable = "{}"
985""".format(machine)) 967""".format(machine))
986 result = bitbake("-DD -n {}".format(targets)) 968 result = bitbake("-DD -n {}".format(targets))
987 bitbake("-S none {}".format(targets)) 969 bitbake("-S none {}".format(targets))
988 if ignore_errors: 970 if ignore_errors:
989 return 971 return
990 self.check_bb_output(result.output, exceptions, check_cdn) 972 self.check_bb_output(result.output, targets, exceptions, check_cdn)
991 973
992 def test_cdn_mirror_qemux86_64(self): 974 def test_cdn_mirror_qemux86_64(self):
993 exceptions = [] 975 exceptions = []
diff --git a/meta/lib/oeqa/selftest/cases/toolchain.py b/meta/lib/oeqa/selftest/cases/toolchain.py
new file mode 100644
index 0000000000..b4b280d037
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/toolchain.py
@@ -0,0 +1,71 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7import shutil
8import subprocess
9import tempfile
10from types import SimpleNamespace
11
12import oe.path
13from oeqa.selftest.case import OESelftestTestCase
14from oeqa.utils.commands import bitbake, get_bb_var, get_bb_vars
15
16class ToolchainTests(OESelftestTestCase):
17
18 def test_toolchain_switching(self):
19 """
20 Test that a configuration that uses GCC by default but clang for one
21 specific recipe does infact do that.
22 """
23
24 def extract_comment(objcopy, filename):
25 """
26 Using the specified `objcopy`, return the .comment segment from
27 `filename` as a bytes().
28 """
29 with tempfile.NamedTemporaryFile(prefix="comment-") as f:
30 cmd = [objcopy, "--dump-section", ".comment=" + f.name, filename]
31 subprocess.run(cmd, check=True)
32 # clang's objcopy writes to a temporary file and renames, so we need to re-open.
33 with open(f.name, "rb") as f2:
34 return f2.read()
35
36 def check_recipe(recipe, filename, override, comment_present, comment_absent=None):
37 """
38 Check that `filename` in `recipe`'s bindir contains `comment`, and
39 the overrides contain `override`.
40 """
41 d = SimpleNamespace(**get_bb_vars(("D", "bindir", "OBJCOPY", "OVERRIDES", "PATH"), target=recipe))
42
43 self.assertIn(override, d.OVERRIDES)
44
45 binary = oe.path.join(d.D, d.bindir, filename)
46
47 objcopy = shutil.which(d.OBJCOPY, path=d.PATH)
48 self.assertIsNotNone(objcopy)
49
50 comment = extract_comment(objcopy, binary)
51 self.assertIn(comment_present, comment)
52 if comment_absent:
53 self.assertNotIn(comment_absent, comment)
54
55
56 # GCC by default, clang for selftest-hello.
57 self.write_config("""
58TOOLCHAIN = "gcc"
59TOOLCHAIN:pn-selftest-hello = "clang"
60 """)
61
62 # Force these recipes to re-install so we can extract the .comments from
63 # the install directory, as they're stripped out of the final packages.
64 bitbake("m4 selftest-hello -C install")
65
66 # m4 should be built with GCC and only GCC
67 check_recipe("m4", "m4", "toolchain-gcc", b"GCC: (GNU)", b"clang")
68
69 # helloworld should be built with clang. We can't assert that GCC is not
70 # present as it will be linked against glibc which is built with GCC.
71 check_recipe("selftest-hello", "helloworld", "toolchain-clang", b"clang version")
diff --git a/meta/lib/oeqa/selftest/cases/uboot.py b/meta/lib/oeqa/selftest/cases/uboot.py
new file mode 100644
index 0000000000..980ea327f0
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/uboot.py
@@ -0,0 +1,98 @@
1# Qemu-based u-boot bootloader integration testing
2#
3# Copyright OpenEmbedded Contributors
4#
5# SPDX-License-Identifier: MIT
6#
7
8from oeqa.selftest.case import OESelftestTestCase
9from oeqa.utils.commands import bitbake, runqemu, get_bb_var, get_bb_vars, runCmd
10from oeqa.core.decorator.data import skipIfNotArch, skipIfNotBuildArch
11from oeqa.core.decorator import OETestTag
12
13uboot_boot_patterns = {
14 'search_reached_prompt': "stop autoboot",
15 'search_login_succeeded': "=>",
16 'search_cmd_finished': "=>"
17 }
18
19
20class UBootTest(OESelftestTestCase):
21
22 @skipIfNotArch(['arm', 'aarch64'])
23 @OETestTag("runqemu")
24 def test_boot_uboot(self):
25 """
26 Tests building u-boot and booting it with QEMU
27 """
28
29 self.write_config("""
30QB_DEFAULT_BIOS = "u-boot.bin"
31PREFERRED_PROVIDER_virtual/bootloader = "u-boot"
32QEMU_USE_KVM = "False"
33""")
34 bitbake("virtual/bootloader core-image-minimal")
35
36 with runqemu('core-image-minimal', ssh=False, runqemuparams='nographic',
37 boot_patterns=uboot_boot_patterns) as qemu:
38
39 # test if u-boot console works
40 cmd = "version"
41 status, output = qemu.run_serial(cmd)
42 self.assertEqual(status, 1, msg=output)
43 self.assertTrue("U-Boot" in output, msg=output)
44
45 @skipIfNotArch(['aarch64'])
46 @skipIfNotBuildArch(['aarch64'])
47 @OETestTag("runqemu")
48 def test_boot_uboot_kvm_to_full_target(self):
49 """
50 Tests building u-boot and booting it with QEMU and KVM.
51 Requires working KVM on build host. See "kvm-ok" output.
52 """
53
54 runCmd("kvm-ok")
55
56 image = "core-image-minimal"
57 vars = get_bb_vars(['HOST_ARCH', 'BUILD_ARCH'], image)
58 host_arch = vars['HOST_ARCH']
59 build_arch = vars['BUILD_ARCH']
60
61 self.assertEqual(host_arch, build_arch, 'HOST_ARCH %s and BUILD_ARCH %s must match for KVM' % (host_arch, build_arch))
62
63 self.write_config("""
64QEMU_USE_KVM = "1"
65
66# Using u-boot in EFI mode, need ESP partition for grub/systemd-boot/kernel etc
67IMAGE_FSTYPES:pn-core-image-minimal:append = " wic"
68
69# easiest to follow genericarm64 setup with wks file, initrd and EFI loader
70INITRAMFS_IMAGE = "core-image-initramfs-boot"
71EFI_PROVIDER = "${@bb.utils.contains("DISTRO_FEATURES", "systemd", "systemd-boot", "grub-efi", d)}"
72WKS_FILE = "genericarm64.wks.in"
73
74# use wic image with ESP for u-boot, not ext4
75QB_DEFAULT_FSTYPE = "wic"
76
77PREFERRED_PROVIDER_virtual/bootloader = "u-boot"
78QB_DEFAULT_BIOS = "u-boot.bin"
79
80# let u-boot or EFI loader load kernel from ESP
81QB_DEFAULT_KERNEL = "none"
82
83# virt pci, not scsi because support not in u-boot to find ESP
84QB_DRIVE_TYPE = "/dev/vd"
85""")
86 bitbake("virtual/bootloader %s" % image)
87
88 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', image) or ""
89 with runqemu(image, ssh=False, runqemuparams='nographic kvm %s' % runqemu_params) as qemu:
90
91 # boot to target and login worked, should have been fast with kvm
92 cmd = "dmesg"
93 status, output = qemu.run_serial(cmd)
94 self.assertEqual(status, 1, msg=output)
95 # Machine is qemu
96 self.assertTrue("Machine model: linux,dummy-virt" in output, msg=output)
97 # with KVM enabled
98 self.assertTrue("KVM: hypervisor services detected" in output, msg=output)
diff --git a/meta/lib/oeqa/selftest/cases/uki.py b/meta/lib/oeqa/selftest/cases/uki.py
new file mode 100644
index 0000000000..9a1aa4e269
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/uki.py
@@ -0,0 +1,141 @@
1# Based on runqemu.py test file
2#
3# Copyright (c) 2017 Wind River Systems, Inc.
4#
5# SPDX-License-Identifier: MIT
6#
7
8from oeqa.selftest.case import OESelftestTestCase
9from oeqa.utils.commands import bitbake, runqemu, get_bb_var
10from oeqa.core.decorator.data import skipIfNotArch
11from oeqa.core.decorator import OETestTag
12import oe.types
13
14class UkiTest(OESelftestTestCase):
15 """Boot Unified Kernel Image (UKI) generated with uki.bbclass on UEFI firmware (omvf/edk2)"""
16
17 @skipIfNotArch(['i586', 'i686', 'x86_64'])
18 @OETestTag("runqemu")
19 def test_uki_boot_systemd(self):
20 """Build and boot into UEFI firmware (omvf/edk2), systemd-boot, initrd without systemd, rootfs with systemd"""
21 image = "core-image-minimal"
22 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', image) or ""
23 cmd = "runqemu %s nographic serial wic ovmf" % (runqemu_params)
24 if oe.types.qemu_use_kvm(self.td.get('QEMU_USE_KVM', 0), self.td["TARGET_ARCH"]):
25 cmd += " kvm"
26
27 self.write_config("""
28# efi firmware must load systemd-boot, not grub
29EFI_PROVIDER = "systemd-boot"
30
31# image format must be wic, needs esp partition for firmware etc
32IMAGE_FSTYPES:pn-%s:append = " wic"
33WKS_FILE = "efi-uki-bootdisk.wks.in"
34
35# efi, uki and systemd features must be enabled
36INIT_MANAGER = "systemd"
37MACHINE_FEATURES:append = " efi"
38IMAGE_CLASSES:append:pn-core-image-minimal = " uki"
39
40# uki embeds also an initrd
41INITRAMFS_IMAGE = "core-image-minimal-initramfs"
42
43# runqemu must not load kernel separately, it's in the uki
44QB_KERNEL_ROOT = ""
45QB_DEFAULT_KERNEL = "none"
46
47# boot command line provided via uki, not via bootloader
48UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
49
50# disable kvm, breaks boot
51QEMU_USE_KVM = ""
52
53IMAGE_CLASSES:remove = 'testimage'
54""" % (image))
55
56 uki_filename = get_bb_var('UKI_FILENAME', image)
57
58 bitbake(image + " ovmf")
59 with runqemu(image, ssh=False, launch_cmd=cmd) as qemu:
60 self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd)
61
62 # Verify from efivars that firmware was:
63 # x86_64, qemux86_64, ovmf = edk2
64 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderFirmwareInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep 'EDK II'"
65 status, output = qemu.run_serial(cmd)
66 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
67
68 # Check that systemd-boot was the loader
69 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep systemd-boot"
70 status, output = qemu.run_serial(cmd)
71 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
72
73 # Check that systemd-stub was used
74 cmd = "echo $( cat /sys/firmware/efi/efivars/StubInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep systemd-stub"
75 status, output = qemu.run_serial(cmd)
76 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
77
78 # Check that the compiled uki file was booted into
79 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep '%s'" % (uki_filename)
80 status, output = qemu.run_serial(cmd)
81 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
82
83 @skipIfNotArch(['i586', 'i686', 'x86_64'])
84 @OETestTag("runqemu")
85 def test_uki_sysvinit(self):
86 """Build and boot into UEFI firmware (omvf/edk2), systemd-boot, initrd with sysvinit, rootfs with sysvinit"""
87 config = """
88# efi firmware must load systemd-boot, not grub
89EFI_PROVIDER = "systemd-boot"
90
91# image format must be wic, needs esp partition for firmware etc
92IMAGE_FSTYPES:pn-core-image-base:append = " wic"
93WKS_FILE = "efi-uki-bootdisk.wks.in"
94
95# efi, uki and systemd features must be enabled
96MACHINE_FEATURES:append = " efi"
97IMAGE_CLASSES:append:pn-core-image-base = " uki"
98
99# uki embeds also an initrd, no systemd or udev
100INITRAMFS_IMAGE = "core-image-initramfs-boot"
101
102# runqemu must not load kernel separately, it's in the uki
103QB_KERNEL_ROOT = ""
104QB_DEFAULT_KERNEL = "none"
105
106# boot command line provided via uki, not via bootloader
107UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
108
109# disable kvm, breaks boot
110QEMU_USE_KVM = ""
111
112IMAGE_CLASSES:remove = 'testimage'
113"""
114 self.append_config(config)
115 bitbake('core-image-base ovmf')
116 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
117 uki_filename = get_bb_var('UKI_FILENAME', 'core-image-base')
118 self.remove_config(config)
119
120 with runqemu('core-image-base', ssh=False,
121 runqemuparams='%s slirp nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
122 # Verify from efivars that firmware was:
123 # x86_64, qemux86_64, ovmf = edk2
124 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderFirmwareInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep 'EDK II'"
125 status, output = qemu.run_serial(cmd)
126 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
127
128 # Check that systemd-boot was the loader
129 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep systemd-boot"
130 status, output = qemu.run_serial(cmd)
131 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
132
133 # Check that systemd-stub was used
134 cmd = "echo $( cat /sys/firmware/efi/efivars/StubInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep systemd-stub"
135 status, output = qemu.run_serial(cmd)
136 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
137
138 # Check that the compiled uki file was booted into
139 cmd = "echo $( cat /sys/firmware/efi/efivars/LoaderEntrySelected-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ) | grep '%s'" % (uki_filename)
140 status, output = qemu.run_serial(cmd)
141 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
diff --git a/meta/lib/oeqa/selftest/cases/wic.py b/meta/lib/oeqa/selftest/cases/wic.py
index b616759209..bb4ac23ebf 100644
--- a/meta/lib/oeqa/selftest/cases/wic.py
+++ b/meta/lib/oeqa/selftest/cases/wic.py
@@ -12,11 +12,13 @@ import os
12import sys 12import sys
13import unittest 13import unittest
14import hashlib 14import hashlib
15import subprocess
15 16
16from glob import glob 17from glob import glob
17from shutil import rmtree, copy 18from shutil import rmtree, copy
18from tempfile import NamedTemporaryFile 19from tempfile import NamedTemporaryFile
19from tempfile import TemporaryDirectory 20from tempfile import TemporaryDirectory
21from textwrap import dedent
20 22
21from oeqa.selftest.case import OESelftestTestCase 23from oeqa.selftest.case import OESelftestTestCase
22from oeqa.core.decorator import OETestTag 24from oeqa.core.decorator import OETestTag
@@ -152,7 +154,7 @@ class Wic(WicTestCase):
152 # create a temporary file for the WKS content 154 # create a temporary file for the WKS content
153 with NamedTemporaryFile("w", suffix=".wks") as wks: 155 with NamedTemporaryFile("w", suffix=".wks") as wks:
154 wks.write( 156 wks.write(
155 'part --source bootimg-efi ' 157 'part --source bootimg_efi '
156 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=false" ' 158 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=false" '
157 '--label boot --active\n' 159 '--label boot --active\n'
158 ) 160 )
@@ -185,7 +187,7 @@ class Wic(WicTestCase):
185 # create a temporary file for the WKS content 187 # create a temporary file for the WKS content
186 with NamedTemporaryFile("w", suffix=".wks") as wks: 188 with NamedTemporaryFile("w", suffix=".wks") as wks:
187 wks.write( 189 wks.write(
188 'part --source bootimg-efi ' 190 'part --source bootimg_efi '
189 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=true" ' 191 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=true" '
190 '--label boot --active\n' 192 '--label boot --active\n'
191 ) 193 )
@@ -214,6 +216,47 @@ class Wic(WicTestCase):
214 found, "The kernel image '{}' was not found in the boot partition".format(kimgtype) 216 found, "The kernel image '{}' was not found in the boot partition".format(kimgtype)
215 ) 217 )
216 218
219 @skipIfNotArch(['x86_64'])
220 def test_grub_install_pcbios(self):
221 """
222 Test the installation of the grub modules + config
223 into the boot directory in the resulting wic image.
224 """
225
226 # create a temporary file for the WKS content
227 with NamedTemporaryFile("w", suffix=".wks") as wks:
228 wks.write(
229 'part --source bootimg_pcbios --sourceparams="loader-bios=grub" '
230 '--offset 1024 --fixed-size 78M --label boot --active\n'
231 'bootloader --ptable msdos --source bootimg_pcbios\n'
232 )
233 wks.flush()
234 # create a temporary directory to extract the disk image to
235 with TemporaryDirectory() as tmpdir:
236 img = "core-image-minimal"
237 config = 'DEPENDS:pn-%s += "grub-native grub"' % (img)
238
239 self.append_config(config)
240 bitbake(img)
241 self.remove_config(config)
242
243 cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
244 runCmd(cmd)
245
246 wksname = os.path.splitext(os.path.basename(wks.name))[0]
247 out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
248 self.assertEqual(1, len(out))
249
250 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
251
252 # Check if grub.cfg is installed
253 result = runCmd("wic ls %s:1/boot/grub -n %s" % (out[0], sysroot))
254 self.assertIn('grub', result.output)
255
256 # Check if normal.mod is installed
257 result = runCmd("wic ls %s:1/boot/grub/i386-pc -n %s" % (out[0], sysroot))
258 self.assertIn('normal', result.output)
259
217 def test_build_image_name(self): 260 def test_build_image_name(self):
218 """Test wic create wictestdisk --image-name=core-image-minimal""" 261 """Test wic create wictestdisk --image-name=core-image-minimal"""
219 cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir 262 cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir
@@ -445,8 +488,9 @@ class Wic(WicTestCase):
445 wks.write(""" 488 wks.write("""
446part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr 489part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr
447part /usr --source rootfs --ondisk mmcblk0 --fstype=ext4 --rootfs-dir %s/usr 490part /usr --source rootfs --ondisk mmcblk0 --fstype=ext4 --rootfs-dir %s/usr
448part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --rootfs-dir %s/usr""" 491part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --rootfs-dir %s/usr
449 % (rootfs_dir, rootfs_dir)) 492part /mnt --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/whoami --rootfs-dir %s/usr"""
493 % (rootfs_dir, rootfs_dir, rootfs_dir))
450 runCmd("wic create %s -e core-image-minimal -o %s" \ 494 runCmd("wic create %s -e core-image-minimal -o %s" \
451 % (wks_file, self.resultdir)) 495 % (wks_file, self.resultdir))
452 496
@@ -457,7 +501,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
457 wicimg = wicout[0] 501 wicimg = wicout[0]
458 502
459 # verify partition size with wic 503 # verify partition size with wic
460 res = runCmd("parted -m %s unit b p 2>/dev/null" % wicimg) 504 res = runCmd("parted -m %s unit b p" % wicimg, stderr=subprocess.PIPE)
461 505
462 # parse parted output which looks like this: 506 # parse parted output which looks like this:
463 # BYT;\n 507 # BYT;\n
@@ -465,9 +509,9 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
465 # 1:0.00MiB:200MiB:200MiB:ext4::;\n 509 # 1:0.00MiB:200MiB:200MiB:ext4::;\n
466 partlns = res.output.splitlines()[2:] 510 partlns = res.output.splitlines()[2:]
467 511
468 self.assertEqual(3, len(partlns)) 512 self.assertEqual(4, len(partlns))
469 513
470 for part in [1, 2, 3]: 514 for part in [1, 2, 3, 4]:
471 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part) 515 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
472 partln = partlns[part-1].split(":") 516 partln = partlns[part-1].split(":")
473 self.assertEqual(7, len(partln)) 517 self.assertEqual(7, len(partln))
@@ -478,16 +522,16 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
478 522
479 # Test partition 1, should contain the normal root directories, except 523 # Test partition 1, should contain the normal root directories, except
480 # /usr. 524 # /usr.
481 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 525 res = runCmd("debugfs -R 'ls -p' %s" % \
482 os.path.join(self.resultdir, "selftest_img.part1")) 526 os.path.join(self.resultdir, "selftest_img.part1"), stderr=subprocess.PIPE)
483 files = extract_files(res.output) 527 files = extract_files(res.output)
484 self.assertIn("etc", files) 528 self.assertIn("etc", files)
485 self.assertNotIn("usr", files) 529 self.assertNotIn("usr", files)
486 530
487 # Partition 2, should contain common directories for /usr, not root 531 # Partition 2, should contain common directories for /usr, not root
488 # directories. 532 # directories.
489 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 533 res = runCmd("debugfs -R 'ls -p' %s" % \
490 os.path.join(self.resultdir, "selftest_img.part2")) 534 os.path.join(self.resultdir, "selftest_img.part2"), stderr=subprocess.PIPE)
491 files = extract_files(res.output) 535 files = extract_files(res.output)
492 self.assertNotIn("etc", files) 536 self.assertNotIn("etc", files)
493 self.assertNotIn("usr", files) 537 self.assertNotIn("usr", files)
@@ -495,27 +539,78 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
495 539
496 # Partition 3, should contain the same as partition 2, including the bin 540 # Partition 3, should contain the same as partition 2, including the bin
497 # directory, but not the files inside it. 541 # directory, but not the files inside it.
498 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 542 res = runCmd("debugfs -R 'ls -p' %s" % \
499 os.path.join(self.resultdir, "selftest_img.part3")) 543 os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
500 files = extract_files(res.output) 544 files = extract_files(res.output)
501 self.assertNotIn("etc", files) 545 self.assertNotIn("etc", files)
502 self.assertNotIn("usr", files) 546 self.assertNotIn("usr", files)
503 self.assertIn("share", files) 547 self.assertIn("share", files)
504 self.assertIn("bin", files) 548 self.assertIn("bin", files)
505 res = runCmd("debugfs -R 'ls -p bin' %s 2>/dev/null" % \ 549 res = runCmd("debugfs -R 'ls -p bin' %s" % \
506 os.path.join(self.resultdir, "selftest_img.part3")) 550 os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
507 files = extract_files(res.output) 551 files = extract_files(res.output)
508 self.assertIn(".", files) 552 self.assertIn(".", files)
509 self.assertIn("..", files) 553 self.assertIn("..", files)
510 self.assertEqual(2, len(files)) 554 self.assertEqual(2, len(files))
511 555
512 for part in [1, 2, 3]: 556 # Partition 4, should contain the same as partition 2, including the bin
557 # directory, but not whoami (a symlink to busybox.nosuid) inside it.
558 res = runCmd("debugfs -R 'ls -p' %s" % \
559 os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
560 files = extract_files(res.output)
561 self.assertNotIn("etc", files)
562 self.assertNotIn("usr", files)
563 self.assertIn("share", files)
564 self.assertIn("bin", files)
565 res = runCmd("debugfs -R 'ls -p bin' %s" % \
566 os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
567 files = extract_files(res.output)
568 self.assertIn(".", files)
569 self.assertIn("..", files)
570 self.assertIn("who", files)
571 self.assertNotIn("whoami", files)
572
573 for part in [1, 2, 3, 4]:
513 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part) 574 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
514 os.remove(part_file) 575 os.remove(part_file)
515 576
516 finally: 577 finally:
517 os.environ['PATH'] = oldpath 578 os.environ['PATH'] = oldpath
518 579
580 def test_exclude_path_with_extra_space(self):
581 """Test having --exclude-path with IMAGE_ROOTFS_EXTRA_SPACE. [Yocto #15555]"""
582
583 with NamedTemporaryFile("w", suffix=".wks") as wks:
584 wks.writelines(
585 ['bootloader --ptable gpt\n',
586 'part /boot --size=100M --active --fstype=ext4 --label boot\n',
587 'part / --source rootfs --fstype=ext4 --label root --exclude-path boot/\n'])
588 wks.flush()
589 config = 'IMAGE_ROOTFS_EXTRA_SPACE = "500000"\n'\
590 'DEPENDS:pn-core-image-minimal += "wic-tools"\n'\
591 'IMAGE_FSTYPES += "wic ext4"\n'\
592 'WKS_FILE = "%s"\n' % wks.name
593 self.append_config(config)
594 bitbake('core-image-minimal')
595
596 """
597 the output of "wic ls <image>.wic" will look something like:
598 Num Start End Size Fstype
599 1 17408 136332287 136314880 ext4
600 2 136332288 171464703 35132416 ext4
601 we are looking for the size of partition 2
602 i.e. in this case the number 35,132,416
603 without the fix the size will be around 85,403,648
604 with the fix the size should be around 799,960,064
605 """
606 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'MACHINE'], 'core-image-minimal')
607 deploy_dir = bb_vars['DEPLOY_DIR_IMAGE']
608 machine = bb_vars['MACHINE']
609 nativesysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
610 wicout = glob(os.path.join(deploy_dir, "core-image-minimal-%s.rootfs-*.wic" % machine))[0]
611 size_of_root_partition = int(runCmd("wic ls %s --native-sysroot %s" % (wicout, nativesysroot)).output.split('\n')[2].split()[3])
612 self.assertGreater(size_of_root_partition, 500000000)
613
519 def test_include_path(self): 614 def test_include_path(self):
520 """Test --include-path wks option.""" 615 """Test --include-path wks option."""
521 616
@@ -541,13 +636,13 @@ part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s"""
541 part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0] 636 part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0]
542 637
543 # Test partition 1, should not contain 'test-file' 638 # Test partition 1, should not contain 'test-file'
544 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 639 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
545 files = extract_files(res.output) 640 files = extract_files(res.output)
546 self.assertNotIn('test-file', files) 641 self.assertNotIn('test-file', files)
547 self.assertEqual(True, files_own_by_root(res.output)) 642 self.assertEqual(True, files_own_by_root(res.output))
548 643
549 # Test partition 2, should contain 'test-file' 644 # Test partition 2, should contain 'test-file'
550 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part2)) 645 res = runCmd("debugfs -R 'ls -p' %s" % (part2), stderr=subprocess.PIPE)
551 files = extract_files(res.output) 646 files = extract_files(res.output)
552 self.assertIn('test-file', files) 647 self.assertIn('test-file', files)
553 self.assertEqual(True, files_own_by_root(res.output)) 648 self.assertEqual(True, files_own_by_root(res.output))
@@ -576,12 +671,12 @@ part / --source rootfs --fstype=ext4 --include-path %s --include-path core-imag
576 671
577 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] 672 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
578 673
579 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 674 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
580 files = extract_files(res.output) 675 files = extract_files(res.output)
581 self.assertIn('test-file', files) 676 self.assertIn('test-file', files)
582 self.assertEqual(True, files_own_by_root(res.output)) 677 self.assertEqual(True, files_own_by_root(res.output))
583 678
584 res = runCmd("debugfs -R 'ls -p /export/etc/' %s 2>/dev/null" % (part1)) 679 res = runCmd("debugfs -R 'ls -p /export/etc/' %s" % (part1), stderr=subprocess.PIPE)
585 files = extract_files(res.output) 680 files = extract_files(res.output)
586 self.assertIn('passwd', files) 681 self.assertIn('passwd', files)
587 self.assertEqual(True, files_own_by_root(res.output)) 682 self.assertEqual(True, files_own_by_root(res.output))
@@ -668,7 +763,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
668 % (wks_file, self.resultdir)) 763 % (wks_file, self.resultdir))
669 764
670 for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')): 765 for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')):
671 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part)) 766 res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
672 self.assertEqual(True, files_own_by_root(res.output)) 767 self.assertEqual(True, files_own_by_root(res.output))
673 768
674 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file 769 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file
@@ -678,7 +773,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
678 773
679 # check each partition for permission 774 # check each partition for permission
680 for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')): 775 for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')):
681 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part)) 776 res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
682 self.assertTrue(files_own_by_root(res.output) 777 self.assertTrue(files_own_by_root(res.output)
683 ,msg='Files permission incorrect using wks set "%s"' % test) 778 ,msg='Files permission incorrect using wks set "%s"' % test)
684 779
@@ -706,7 +801,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
706 801
707 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] 802 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
708 803
709 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 804 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
710 files = extract_files(res.output) 805 files = extract_files(res.output)
711 self.assertIn('passwd', files) 806 self.assertIn('passwd', files)
712 807
@@ -741,7 +836,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
741 bitbake('base-files -c do_install') 836 bitbake('base-files -c do_install')
742 bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab') 837 bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab')
743 self.assertEqual(True, os.path.exists(bf_fstab)) 838 self.assertEqual(True, os.path.exists(bf_fstab))
744 bf_fstab_md5sum = runCmd('md5sum %s 2>/dev/null' % bf_fstab).output.split(" ")[0] 839 bf_fstab_md5sum = runCmd('md5sum %s ' % bf_fstab).output.split(" ")[0]
745 840
746 try: 841 try:
747 no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update') 842 no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update')
@@ -757,7 +852,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
757 part_fstab_md5sum = [] 852 part_fstab_md5sum = []
758 for i in range(1, 3): 853 for i in range(1, 3):
759 part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0] 854 part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0]
760 part_fstab = runCmd("debugfs -R 'cat etc/fstab' %s 2>/dev/null" % (part)) 855 part_fstab = runCmd("debugfs -R 'cat etc/fstab' %s" % (part), stderr=subprocess.PIPE)
761 part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest()) 856 part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest())
762 857
763 # '/etc/fstab' in partition 2 should contain the same stock fstab file 858 # '/etc/fstab' in partition 2 should contain the same stock fstab file
@@ -839,6 +934,61 @@ bootloader --ptable gpt""")
839 finally: 934 finally:
840 os.remove(wks_file) 935 os.remove(wks_file)
841 936
937 def test_wic_sector_size(self):
938 """Test generation image sector size"""
939
940 oldpath = os.environ['PATH']
941 os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
942
943 try:
944 # Add WIC_SECTOR_SIZE into config
945 config = 'WIC_SECTOR_SIZE = "4096"\n'\
946 'WICVARS:append = " WIC_SECTOR_SIZE"\n'
947 self.append_config(config)
948 bitbake('core-image-minimal')
949
950 # Check WIC_SECTOR_SIZE apply to bitbake variable
951 wic_sector_size_str = get_bb_var('WIC_SECTOR_SIZE', 'core-image-minimal')
952 wic_sector_size = int(wic_sector_size_str)
953 self.assertEqual(4096, wic_sector_size)
954
955 self.logger.info("Test wic_sector_size: %d \n" % wic_sector_size)
956
957 with NamedTemporaryFile("w", suffix=".wks") as wks:
958 wks.writelines(
959 ['bootloader --ptable gpt\n',
960 'part --fstype ext4 --source rootfs --label rofs-a --mkfs-extraopts "-b 4096"\n',
961 'part --fstype ext4 --source rootfs --use-uuid --mkfs-extraopts "-b 4096"\n'])
962 wks.flush()
963 cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
964 runCmd(cmd)
965 wksname = os.path.splitext(os.path.basename(wks.name))[0]
966 images = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
967 self.assertEqual(1, len(images))
968
969 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
970 # list partitions
971 result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
972 self.assertEqual(3, len(result.output.split('\n')))
973
974 # verify partition size with wic
975 res = runCmd("export PARTED_SECTOR_SIZE=%d; parted -m %s unit b p" % (wic_sector_size, images[0]),
976 stderr=subprocess.PIPE)
977
978 # parse parted output which looks like this:
979 # BYT;\n
980 # /var/tmp/wic/build/tmpgjzzefdd-202410281021-sda.direct:78569472B:file:4096:4096:gpt::;\n
981 # 1:139264B:39284735B:39145472B:ext4:rofs-a:;\n
982 # 2:39284736B:78430207B:39145472B:ext4:primary:;\n
983 disk_info = res.output.splitlines()[1]
984 # Check sector sizes
985 sector_size_logical = int(disk_info.split(":")[3])
986 sector_size_physical = int(disk_info.split(":")[4])
987 self.assertEqual(wic_sector_size, sector_size_logical, "Logical sector size is not %d." % wic_sector_size)
988 self.assertEqual(wic_sector_size, sector_size_physical, "Physical sector size is not %d." % wic_sector_size)
989
990 finally:
991 os.environ['PATH'] = oldpath
842 992
843class Wic2(WicTestCase): 993class Wic2(WicTestCase):
844 994
@@ -872,7 +1022,7 @@ class Wic2(WicTestCase):
872 wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES', 1022 wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES',
873 'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE', 1023 'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE',
874 'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME', 1024 'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME',
875 'APPEND', 'IMAGE_EFI_BOOT_FILES')) 1025 'APPEND', 'IMAGE_EFI_BOOT_FILES', 'IMAGE_EXTRA_PARTITION_FILES'))
876 with open(path) as envfile: 1026 with open(path) as envfile:
877 content = dict(line.split("=", 1) for line in envfile) 1027 content = dict(line.split("=", 1) for line in envfile)
878 # test if variables used by wic present in the .env file 1028 # test if variables used by wic present in the .env file
@@ -913,6 +1063,18 @@ class Wic2(WicTestCase):
913 """Test building wic images by bitbake""" 1063 """Test building wic images by bitbake"""
914 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\ 1064 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
915 'MACHINE_FEATURES:append = " efi"\n' 1065 'MACHINE_FEATURES:append = " efi"\n'
1066 image_recipe_append = """
1067do_image_wic[postfuncs] += "run_wic_cmd"
1068run_wic_cmd() {
1069 echo "test" >> ${WORKDIR}/test.wic-cp
1070 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1071 wic ls --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1072 wic rm --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/test.wic-cp
1073 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1074}
1075"""
1076 self.write_recipeinc('images', image_recipe_append)
1077
916 self.append_config(config) 1078 self.append_config(config)
917 image = 'wic-image-minimal' 1079 image = 'wic-image-minimal'
918 bitbake(image) 1080 bitbake(image)
@@ -921,6 +1083,11 @@ class Wic2(WicTestCase):
921 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image) 1083 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image)
922 prefix = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.' % bb_vars['IMAGE_LINK_NAME']) 1084 prefix = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.' % bb_vars['IMAGE_LINK_NAME'])
923 1085
1086 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1087 # check if file is there
1088 result = runCmd("wic ls %s:1/ -n %s" % (prefix+"wic", sysroot))
1089 self.assertIn("test.wic-cp", result.output)
1090
924 # check if we have result image and manifests symlinks 1091 # check if we have result image and manifests symlinks
925 # pointing to existing files 1092 # pointing to existing files
926 for suffix in ('wic', 'manifest'): 1093 for suffix in ('wic', 'manifest'):
@@ -936,10 +1103,29 @@ class Wic2(WicTestCase):
936 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\ 1103 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
937 'MACHINE_FEATURES:append = " efi"\n' 1104 'MACHINE_FEATURES:append = " efi"\n'
938 self.append_config(config) 1105 self.append_config(config)
1106 image_recipe_append = """
1107do_image_wic[postfuncs] += "run_wic_cmd"
1108run_wic_cmd() {
1109 echo "test" >> ${WORKDIR}/test.wic-cp
1110 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1111 wic ls --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1112 wic rm --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/test.wic-cp
1113 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1114}
1115"""
1116 self.write_recipeinc('images', image_recipe_append)
939 bitbake('wic-image-minimal') 1117 bitbake('wic-image-minimal')
1118
1119 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1120 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], "wic-image-minimal")
1121 image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], bb_vars['IMAGE_LINK_NAME'])
1122 # check if file is there
1123 result = runCmd("wic ls %s:1/ -n %s" % (image_path+".wic", sysroot))
1124 self.assertIn("test.wic-cp", result.output)
940 self.remove_config(config) 1125 self.remove_config(config)
941 1126
942 with runqemu('wic-image-minimal', ssh=False, runqemuparams='nographic') as qemu: 1127 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'wic-image-minimal') or ""
1128 with runqemu('wic-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
943 cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' " \ 1129 cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' " \
944 "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'" 1130 "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'"
945 status, output = qemu.run_serial(cmd) 1131 status, output = qemu.run_serial(cmd)
@@ -959,8 +1145,9 @@ class Wic2(WicTestCase):
959 bitbake('core-image-minimal ovmf') 1145 bitbake('core-image-minimal ovmf')
960 self.remove_config(config) 1146 self.remove_config(config)
961 1147
1148 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
962 with runqemu('core-image-minimal', ssh=False, 1149 with runqemu('core-image-minimal', ssh=False,
963 runqemuparams='nographic ovmf', image_fstype='wic') as qemu: 1150 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
964 cmd = "grep sda. /proc/partitions |wc -l" 1151 cmd = "grep sda. /proc/partitions |wc -l"
965 status, output = qemu.run_serial(cmd) 1152 status, output = qemu.run_serial(cmd)
966 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1153 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
@@ -980,7 +1167,7 @@ class Wic2(WicTestCase):
980 1167
981 return wkspath 1168 return wkspath
982 1169
983 def _get_wic_partitions(self, wkspath, native_sysroot=None, ignore_status=False): 1170 def _get_wic(self, wkspath, ignore_status=False):
984 p = runCmd("wic create %s -e core-image-minimal -o %s" % (wkspath, self.resultdir), 1171 p = runCmd("wic create %s -e core-image-minimal -o %s" % (wkspath, self.resultdir),
985 ignore_status=ignore_status) 1172 ignore_status=ignore_status)
986 1173
@@ -994,14 +1181,20 @@ class Wic2(WicTestCase):
994 if not wicout: 1181 if not wicout:
995 return (p, None) 1182 return (p, None)
996 1183
997 wicimg = wicout[0] 1184 return (p, wicout[0])
1185
1186 def _get_wic_partitions(self, wkspath, native_sysroot=None, ignore_status=False):
1187 p, wicimg = self._get_wic(wkspath, ignore_status)
1188
1189 if wicimg is None:
1190 return (p, None)
998 1191
999 if not native_sysroot: 1192 if not native_sysroot:
1000 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools") 1193 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1001 1194
1002 # verify partition size with wic 1195 # verify partition size with wic
1003 res = runCmd("parted -m %s unit kib p 2>/dev/null" % wicimg, 1196 res = runCmd("parted -m %s unit kib p" % wicimg,
1004 native_sysroot=native_sysroot) 1197 native_sysroot=native_sysroot, stderr=subprocess.PIPE)
1005 1198
1006 # parse parted output which looks like this: 1199 # parse parted output which looks like this:
1007 # BYT;\n 1200 # BYT;\n
@@ -1040,71 +1233,71 @@ class Wic2(WicTestCase):
1040 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1233 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1041 # Test that partitions are placed at the correct offsets, default KB 1234 # Test that partitions are placed at the correct offsets, default KB
1042 tempf.write("bootloader --ptable gpt\n" \ 1235 tempf.write("bootloader --ptable gpt\n" \
1043 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 100M --fstype=ext4\n" \ 1236 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 200M --fstype=ext4\n" \
1044 "part /bar --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n") 1237 "part /bar --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1045 tempf.flush() 1238 tempf.flush()
1046 1239
1047 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1240 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1048 self.assertEqual(partlns, [ 1241 self.assertEqual(partlns, [
1049 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1242 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1050 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1243 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1051 ]) 1244 ])
1052 1245
1053 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1246 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1054 # Test that partitions are placed at the correct offsets, same with explicit KB 1247 # Test that partitions are placed at the correct offsets, same with explicit KB
1055 tempf.write("bootloader --ptable gpt\n" \ 1248 tempf.write("bootloader --ptable gpt\n" \
1056 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 100M --fstype=ext4\n" \ 1249 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 200M --fstype=ext4\n" \
1057 "part /bar --ondisk hda --offset 102432K --fixed-size 100M --fstype=ext4\n") 1250 "part /bar --ondisk hda --offset 204832K --fixed-size 100M --fstype=ext4\n")
1058 tempf.flush() 1251 tempf.flush()
1059 1252
1060 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1253 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1061 self.assertEqual(partlns, [ 1254 self.assertEqual(partlns, [
1062 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1255 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1063 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1256 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1064 ]) 1257 ])
1065 1258
1066 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1259 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1067 # Test that partitions are placed at the correct offsets using MB 1260 # Test that partitions are placed at the correct offsets using MB
1068 tempf.write("bootloader --ptable gpt\n" \ 1261 tempf.write("bootloader --ptable gpt\n" \
1069 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 100M --fstype=ext4\n" \ 1262 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 200M --fstype=ext4\n" \
1070 "part /bar --ondisk hda --offset 101M --fixed-size 100M --fstype=ext4\n") 1263 "part /bar --ondisk hda --offset 201M --fixed-size 100M --fstype=ext4\n")
1071 tempf.flush() 1264 tempf.flush()
1072 1265
1073 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1266 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1074 self.assertEqual(partlns, [ 1267 self.assertEqual(partlns, [
1075 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1268 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1076 "2:103424kiB:205824kiB:102400kiB:ext4:primary:;", 1269 "2:205824kiB:308224kiB:102400kiB:ext4:primary:;",
1077 ]) 1270 ])
1078 1271
1079 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1272 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1080 # Test that partitions can be placed on a 512 byte sector boundary 1273 # Test that partitions can be placed on a 512 byte sector boundary
1081 tempf.write("bootloader --ptable gpt\n" \ 1274 tempf.write("bootloader --ptable gpt\n" \
1082 "part / --source rootfs --ondisk hda --offset 65s --fixed-size 99M --fstype=ext4\n" \ 1275 "part / --source rootfs --ondisk hda --offset 65s --fixed-size 199M --fstype=ext4\n" \
1083 "part /bar --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n") 1276 "part /bar --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1084 tempf.flush() 1277 tempf.flush()
1085 1278
1086 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1279 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1087 self.assertEqual(partlns, [ 1280 self.assertEqual(partlns, [
1088 "1:32.5kiB:101408kiB:101376kiB:ext4:primary:;", 1281 "1:32.5kiB:203808kiB:203776kiB:ext4:primary:;",
1089 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1282 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1090 ]) 1283 ])
1091 1284
1092 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1285 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1093 # Test that a partition can be placed immediately after a MSDOS partition table 1286 # Test that a partition can be placed immediately after a MSDOS partition table
1094 tempf.write("bootloader --ptable msdos\n" \ 1287 tempf.write("bootloader --ptable msdos\n" \
1095 "part / --source rootfs --ondisk hda --offset 1s --fixed-size 100M --fstype=ext4\n") 1288 "part / --source rootfs --ondisk hda --offset 1s --fixed-size 200M --fstype=ext4\n")
1096 tempf.flush() 1289 tempf.flush()
1097 1290
1098 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1291 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1099 self.assertEqual(partlns, [ 1292 self.assertEqual(partlns, [
1100 "1:0.50kiB:102400kiB:102400kiB:ext4::;", 1293 "1:0.50kiB:204800kiB:204800kiB:ext4::;",
1101 ]) 1294 ])
1102 1295
1103 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1296 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1104 # Test that image creation fails if the partitions would overlap 1297 # Test that image creation fails if the partitions would overlap
1105 tempf.write("bootloader --ptable gpt\n" \ 1298 tempf.write("bootloader --ptable gpt\n" \
1106 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 100M --fstype=ext4\n" \ 1299 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 200M --fstype=ext4\n" \
1107 "part /bar --ondisk hda --offset 102431 --fixed-size 100M --fstype=ext4\n") 1300 "part /bar --ondisk hda --offset 204831 --fixed-size 100M --fstype=ext4\n")
1108 tempf.flush() 1301 tempf.flush()
1109 1302
1110 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True) 1303 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
@@ -1113,18 +1306,18 @@ class Wic2(WicTestCase):
1113 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1306 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1114 # Test that partitions are not allowed to overlap with the booloader 1307 # Test that partitions are not allowed to overlap with the booloader
1115 tempf.write("bootloader --ptable gpt\n" \ 1308 tempf.write("bootloader --ptable gpt\n" \
1116 "part / --source rootfs --ondisk hda --offset 8 --fixed-size 100M --fstype=ext4\n") 1309 "part / --source rootfs --ondisk hda --offset 8 --fixed-size 200M --fstype=ext4\n")
1117 tempf.flush() 1310 tempf.flush()
1118 1311
1119 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True) 1312 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1120 self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output) 1313 self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1121 1314
1122 def test_extra_space(self): 1315 def test_extra_filesystem_space(self):
1123 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools") 1316 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1124 1317
1125 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1318 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1126 tempf.write("bootloader --ptable gpt\n" \ 1319 tempf.write("bootloader --ptable gpt\n" \
1127 "part / --source rootfs --ondisk hda --extra-space 200M --fstype=ext4\n") 1320 "part / --source rootfs --ondisk hda --extra-filesystem-space 200M --fstype=ext4\n")
1128 tempf.flush() 1321 tempf.flush()
1129 1322
1130 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1323 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
@@ -1134,6 +1327,51 @@ class Wic2(WicTestCase):
1134 size = int(size[:-3]) 1327 size = int(size[:-3])
1135 self.assertGreaterEqual(size, 204800) 1328 self.assertGreaterEqual(size, 204800)
1136 1329
1330 def test_extra_partition_space(self):
1331 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1332
1333 oldpath = os.environ['PATH']
1334 os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
1335
1336 try:
1337 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1338 tempf.write("bootloader --ptable gpt\n" \
1339 "part --ondisk hda --size 10M --extra-partition-space 10M --fstype=ext4\n" \
1340 "part --ondisk hda --fixed-size 20M --extra-partition-space 10M --fstype=ext4\n" \
1341 "part --source rootfs --ondisk hda --extra-partition-space 10M --fstype=ext4\n" \
1342 "part --source rootfs --ondisk hda --fixed-size 200M --extra-partition-space 10M --fstype=ext4\n")
1343 tempf.flush()
1344
1345 _, wicimg = self._get_wic(tempf.name)
1346
1347 res = runCmd("parted -m %s unit b p" % wicimg,
1348 native_sysroot=native_sysroot, stderr=subprocess.PIPE)
1349
1350 # parse parted output which looks like this:
1351 # BYT;\n
1352 # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
1353 # 1:0.00MiB:200MiB:200MiB:ext4::;\n
1354 partlns = res.output.splitlines()[2:]
1355
1356 self.assertEqual(4, len(partlns))
1357
1358 # Test for each partitions that the extra part space exists
1359 for part in range(0, len(partlns)):
1360 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % (part + 1))
1361 partln = partlns[part].split(":")
1362 self.assertEqual(7, len(partln))
1363 self.assertRegex(partln[3], r'^[0-9]+B$')
1364 part_size = int(partln[3].rstrip("B"))
1365 start = int(partln[1].rstrip("B")) / 512
1366 length = part_size / 512
1367 runCmd("dd if=%s of=%s skip=%d count=%d" %
1368 (wicimg, part_file, start, length))
1369 res = runCmd("dumpe2fs %s -h | grep \"^Block count\"" % part_file)
1370 fs_size = int(res.output.split(":")[1].strip()) * 1024
1371 self.assertLessEqual(fs_size + 10485760, part_size, "part file: %s" % part_file)
1372 finally:
1373 os.environ['PATH'] = oldpath
1374
1137 # TODO this test could also work on aarch64 1375 # TODO this test could also work on aarch64
1138 @skipIfNotArch(['i586', 'i686', 'x86_64']) 1376 @skipIfNotArch(['i586', 'i686', 'x86_64'])
1139 @OETestTag("runqemu") 1377 @OETestTag("runqemu")
@@ -1154,8 +1392,9 @@ class Wic2(WicTestCase):
1154 bitbake('core-image-minimal-mtdutils') 1392 bitbake('core-image-minimal-mtdutils')
1155 self.remove_config(config) 1393 self.remove_config(config)
1156 1394
1395 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal-mtdutils') or ""
1157 with runqemu('core-image-minimal-mtdutils', ssh=False, 1396 with runqemu('core-image-minimal-mtdutils', ssh=False,
1158 runqemuparams='nographic', image_fstype='wic') as qemu: 1397 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1159 cmd = "grep sda. /proc/partitions |wc -l" 1398 cmd = "grep sda. /proc/partitions |wc -l"
1160 status, output = qemu.run_serial(cmd) 1399 status, output = qemu.run_serial(cmd)
1161 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1400 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
@@ -1177,6 +1416,10 @@ class Wic2(WicTestCase):
1177 self.assertEqual(1, len(out)) 1416 self.assertEqual(1, len(out))
1178 1417
1179 def test_rawcopy_plugin(self): 1418 def test_rawcopy_plugin(self):
1419 config = 'IMAGE_FSTYPES = "ext4"\n'
1420 self.append_config(config)
1421 self.assertEqual(0, bitbake('core-image-minimal').status)
1422 self.remove_config(config)
1180 self._rawcopy_plugin('ext4') 1423 self._rawcopy_plugin('ext4')
1181 1424
1182 def test_rawcopy_plugin_unpack(self): 1425 def test_rawcopy_plugin_unpack(self):
@@ -1214,8 +1457,9 @@ class Wic2(WicTestCase):
1214 bitbake('core-image-minimal') 1457 bitbake('core-image-minimal')
1215 self.remove_config(config) 1458 self.remove_config(config)
1216 1459
1460 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1217 with runqemu('core-image-minimal', ssh=False, 1461 with runqemu('core-image-minimal', ssh=False,
1218 runqemuparams='nographic', image_fstype='wic') as qemu: 1462 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1219 # Check that we have ONLY two /dev/sda* partitions (/boot and /) 1463 # Check that we have ONLY two /dev/sda* partitions (/boot and /)
1220 cmd = "grep sda. /proc/partitions | wc -l" 1464 cmd = "grep sda. /proc/partitions | wc -l"
1221 status, output = qemu.run_serial(cmd) 1465 status, output = qemu.run_serial(cmd)
@@ -1242,7 +1486,7 @@ class Wic2(WicTestCase):
1242 def test_biosplusefi_plugin(self): 1486 def test_biosplusefi_plugin(self):
1243 """Test biosplusefi plugin""" 1487 """Test biosplusefi plugin"""
1244 # Wic generation below may fail depending on the order of the unittests 1488 # Wic generation below may fail depending on the order of the unittests
1245 # This is because bootimg-pcbios (that bootimg-biosplusefi uses) generate its MBR inside STAGING_DATADIR directory 1489 # This is because bootimg_pcbios (that bootimg_biosplusefi uses) generate its MBR inside STAGING_DATADIR directory
1246 # which may or may not exists depending on what was built already 1490 # which may or may not exists depending on what was built already
1247 # If an image hasn't been built yet, directory ${STAGING_DATADIR}/syslinux won't exists and _get_bootimg_dir() 1491 # If an image hasn't been built yet, directory ${STAGING_DATADIR}/syslinux won't exists and _get_bootimg_dir()
1248 # will raise with "Couldn't find correct bootimg_dir" 1492 # will raise with "Couldn't find correct bootimg_dir"
@@ -1254,7 +1498,7 @@ class Wic2(WicTestCase):
1254 1498
1255 img = 'core-image-minimal' 1499 img = 'core-image-minimal'
1256 with NamedTemporaryFile("w", suffix=".wks") as wks: 1500 with NamedTemporaryFile("w", suffix=".wks") as wks:
1257 wks.writelines(['part /boot --active --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\n', 1501 wks.writelines(['part /boot --active --source bootimg_biosplusefi --sourceparams="loader=grub-efi"\n',
1258 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\ 1502 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1259 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n']) 1503 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1260 wks.flush() 1504 wks.flush()
@@ -1274,7 +1518,7 @@ class Wic2(WicTestCase):
1274 1518
1275 img = 'core-image-minimal' 1519 img = 'core-image-minimal'
1276 with NamedTemporaryFile("w", suffix=".wks") as wks: 1520 with NamedTemporaryFile("w", suffix=".wks") as wks:
1277 wks.writelines(['part /boot --source bootimg-efi --sourceparams="loader=uefi-kernel"\n' 1521 wks.writelines(['part /boot --source bootimg_efi --sourceparams="loader=uefi-kernel"\n'
1278 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\ 1522 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1279 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n']) 1523 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1280 wks.flush() 1524 wks.flush()
@@ -1288,24 +1532,45 @@ class Wic2(WicTestCase):
1288 @skipIfNotArch(['i586', 'i686', 'x86_64']) 1532 @skipIfNotArch(['i586', 'i686', 'x86_64'])
1289 @OETestTag("runqemu") 1533 @OETestTag("runqemu")
1290 def test_efi_plugin_unified_kernel_image_qemu(self): 1534 def test_efi_plugin_unified_kernel_image_qemu(self):
1291 """Test efi plugin's Unified Kernel Image feature in qemu""" 1535 """Test Unified Kernel Image feature in qemu without systemd in initramfs or rootfs"""
1292 config = 'IMAGE_FSTYPES = "wic"\n'\ 1536 config = """
1293 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\ 1537# efi firmware must load systemd-boot, not grub
1294 'WKS_FILE = "test_efi_plugin.wks"\n'\ 1538EFI_PROVIDER = "systemd-boot"
1295 'MACHINE_FEATURES:append = " efi"\n' 1539
1540# image format must be wic, needs esp partition for firmware etc
1541IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1542WKS_FILE = "test_efi_plugin.wks"
1543
1544# efi, uki and systemd features must be enabled
1545MACHINE_FEATURES:append = " efi"
1546IMAGE_CLASSES:append:pn-core-image-base = " uki"
1547
1548# uki embeds also an initrd, no systemd or udev
1549INITRAMFS_IMAGE = "core-image-initramfs-boot"
1550
1551# runqemu must not load kernel separately, it's in the uki
1552QB_KERNEL_ROOT = ""
1553QB_DEFAULT_KERNEL = "none"
1554
1555# boot command line provided via uki, not via bootloader
1556UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
1557
1558"""
1296 self.append_config(config) 1559 self.append_config(config)
1297 bitbake('core-image-minimal core-image-minimal-initramfs ovmf') 1560 bitbake('core-image-base ovmf')
1561 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1562 uki_filename = get_bb_var('UKI_FILENAME', 'core-image-base')
1298 self.remove_config(config) 1563 self.remove_config(config)
1299 1564
1300 with runqemu('core-image-minimal', ssh=False, 1565 with runqemu('core-image-base', ssh=False,
1301 runqemuparams='nographic ovmf', image_fstype='wic') as qemu: 1566 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1302 # Check that /boot has EFI bootx64.efi (required for EFI) 1567 # Check that /boot has EFI boot*.efi (required for EFI)
1303 cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l" 1568 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1304 status, output = qemu.run_serial(cmd) 1569 status, output = qemu.run_serial(cmd)
1305 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1570 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1306 self.assertEqual(output, '1') 1571 self.assertEqual(output, '1')
1307 # Check that /boot has EFI/Linux/linux.efi (required for Unified Kernel Images auto detection) 1572 # Check that /boot has EFI/Linux/${UKI_FILENAME} (required for Unified Kernel Images auto detection)
1308 cmd = "ls /boot/EFI/Linux/linux.efi | wc -l" 1573 cmd = "ls /boot/EFI/Linux/%s | wc -l" % (uki_filename)
1309 status, output = qemu.run_serial(cmd) 1574 status, output = qemu.run_serial(cmd)
1310 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1575 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1311 self.assertEqual(output, '1') 1576 self.assertEqual(output, '1')
@@ -1315,6 +1580,127 @@ class Wic2(WicTestCase):
1315 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1580 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1316 self.assertEqual(output, '0') 1581 self.assertEqual(output, '0')
1317 1582
1583 @skipIfNotArch(['aarch64'])
1584 @OETestTag("runqemu")
1585 def test_efi_plugin_plain_systemd_boot_qemu_aarch64(self):
1586 """Test plain systemd-boot in qemu with systemd"""
1587 config = """
1588INIT_MANAGER = "systemd"
1589EFI_PROVIDER = "systemd-boot"
1590
1591# image format must be wic, needs esp partition for firmware etc
1592IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1593WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1594
1595INITRAMFS_IMAGE = "core-image-initramfs-boot"
1596
1597# to configure runqemu
1598IMAGE_CLASSES += "qemuboot"
1599# u-boot efi firmware
1600QB_DEFAULT_BIOS = "u-boot.bin"
1601# need to use virtio, scsi not supported by u-boot by default
1602QB_DRIVE_TYPE = "/dev/vd"
1603
1604# disable kvm, breaks boot
1605QEMU_USE_KVM = ""
1606
1607IMAGE_CLASSES:remove = 'testimage'
1608"""
1609 self.append_config(config)
1610 bitbake('core-image-base u-boot')
1611 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1612
1613 with runqemu('core-image-base', ssh=False,
1614 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1615 # Check that /boot has EFI boot*.efi (required for EFI)
1616 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1617 status, output = qemu.run_serial(cmd)
1618 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1619 self.assertEqual(output, '1')
1620 # Check that boot.conf exists
1621 cmd = "cat /boot/loader/entries/boot.conf"
1622 status, output = qemu.run_serial(cmd)
1623 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1624 self.remove_config(config)
1625
1626 @skipIfNotArch(['i586', 'i686', 'x86_64'])
1627 @OETestTag("runqemu")
1628 def test_efi_plugin_plain_systemd_boot_qemu_x86(self):
1629 """Test plain systemd-boot to systemd in qemu"""
1630 config = """
1631INIT_MANAGER = "systemd"
1632EFI_PROVIDER = "systemd-boot"
1633
1634# image format must be wic, needs esp partition for firmware etc
1635IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1636WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1637
1638INITRAMFS_IMAGE = "core-image-initramfs-boot"
1639"""
1640 self.append_config(config)
1641 bitbake('core-image-base ovmf')
1642 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1643 self.remove_config(config)
1644
1645 with runqemu('core-image-base', ssh=False,
1646 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1647 # Check that /boot has EFI boot*.efi (required for EFI)
1648 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1649 status, output = qemu.run_serial(cmd)
1650 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1651 self.assertEqual(output, '1')
1652 # Check that boot.conf exists
1653 cmd = "cat /boot/loader/entries/boot.conf"
1654 status, output = qemu.run_serial(cmd)
1655 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1656
1657 def test_extra_partition_plugin(self):
1658 """Test extra partition plugin"""
1659 config = dedent("""\
1660 IMAGE_EXTRA_PARTITION_FILES_label-foo = "bar.conf;foo.conf"
1661 IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d = "bar.conf;foobar.conf"
1662 IMAGE_EXTRA_PARTITION_FILES = "foo/*"
1663 WICVARS:append = "\
1664 IMAGE_EXTRA_PARTITION_FILES_label-foo \
1665 IMAGE_EXTRA_PARTITION_FILES_uuid-e7d0824e-cda3-4bed-9f54-9ef5312d105d \
1666 "
1667 """)
1668 self.append_config(config)
1669
1670 deploy_dir = get_bb_var('DEPLOY_DIR_IMAGE')
1671
1672 testfile = open(os.path.join(deploy_dir, "bar.conf"), "w")
1673 testfile.write("test")
1674 testfile.close()
1675
1676 os.mkdir(os.path.join(deploy_dir, "foo"))
1677 testfile = open(os.path.join(deploy_dir, "foo", "bar.conf"), "w")
1678 testfile.write("test")
1679 testfile.close()
1680
1681 oldpath = os.environ['PATH']
1682 os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
1683
1684 try:
1685 with NamedTemporaryFile("w", suffix=".wks") as wks:
1686 wks.writelines(['part / --source extra_partition --ondisk sda --fstype=ext4 --label foo --align 4 --size 5M\n',
1687 'part / --source extra_partition --ondisk sda --fstype=ext4 --uuid e7d0824e-cda3-4bed-9f54-9ef5312d105d --align 4 --size 5M\n',
1688 'part / --source extra_partition --ondisk sda --fstype=ext4 --label bar --align 4 --size 5M\n'])
1689 wks.flush()
1690 _, wicimg = self._get_wic(wks.name)
1691
1692 result = runCmd("wic ls %s | wc -l" % wicimg)
1693 self.assertEqual('4', result.output, msg="Expect 3 partitions, not %s" % result.output)
1694
1695 for part, file in enumerate(["foo.conf", "foobar.conf", "bar.conf"]):
1696 result = runCmd("wic ls %s:%d | grep -q \"%s\"" % (wicimg, part + 1, file))
1697 self.assertEqual(0, result.status, msg="File '%s' not found in the partition #%d" % (file, part))
1698
1699 self.remove_config(config)
1700
1701 finally:
1702 os.environ['PATH'] = oldpath
1703
1318 def test_fs_types(self): 1704 def test_fs_types(self):
1319 """Test filesystem types for empty and not empty partitions""" 1705 """Test filesystem types for empty and not empty partitions"""
1320 img = 'core-image-minimal' 1706 img = 'core-image-minimal'
@@ -1446,8 +1832,8 @@ class Wic2(WicTestCase):
1446 os.rename(image_path, image_path + '.bak') 1832 os.rename(image_path, image_path + '.bak')
1447 os.rename(new_image_path, image_path) 1833 os.rename(new_image_path, image_path)
1448 1834
1449 # Check if it boots in qemu 1835 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1450 with runqemu('core-image-minimal', ssh=False, runqemuparams='nographic') as qemu: 1836 with runqemu('core-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1451 cmd = "ls /etc/" 1837 cmd = "ls /etc/"
1452 status, output = qemu.run_serial('true') 1838 status, output = qemu.run_serial('true')
1453 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1839 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
diff --git a/meta/lib/oeqa/selftest/cases/yoctotestresultsquerytests.py b/meta/lib/oeqa/selftest/cases/yoctotestresultsquerytests.py
index 312edb6431..b1015d60ef 100644
--- a/meta/lib/oeqa/selftest/cases/yoctotestresultsquerytests.py
+++ b/meta/lib/oeqa/selftest/cases/yoctotestresultsquerytests.py
@@ -18,8 +18,8 @@ sys.path = sys.path + [lib_path]
18class TestResultsQueryTests(OESelftestTestCase): 18class TestResultsQueryTests(OESelftestTestCase):
19 def test_get_sha1(self): 19 def test_get_sha1(self):
20 test_data_get_sha1 = [ 20 test_data_get_sha1 = [
21 {"input": "yocto-4.0", "expected": "00cfdde791a0176c134f31e5a09eff725e75b905"}, 21 {"input": "yocto-4.0", "expected": "92fcb6570bddd0c5717d8cfdf38ecf3e44942b0f"},
22 {"input": "4.1_M1", "expected": "95066dde6861ee08fdb505ab3e0422156cc24fae"}, 22 {"input": "yocto-5.2", "expected": "6ec2c52b938302b894f119f701ffcf0a847eee85"},
23 ] 23 ]
24 for data in test_data_get_sha1: 24 for data in test_data_get_sha1:
25 test_name = data["input"] 25 test_name = data["input"]
diff --git a/meta/lib/oeqa/selftest/context.py b/meta/lib/oeqa/selftest/context.py
index 99186175e5..c9eb481725 100644
--- a/meta/lib/oeqa/selftest/context.py
+++ b/meta/lib/oeqa/selftest/context.py
@@ -44,9 +44,13 @@ class NonConcurrentTestSuite(unittest.TestSuite):
44 self.bb_vars = bb_vars 44 self.bb_vars = bb_vars
45 45
46 def run(self, result): 46 def run(self, result):
47 origenv = os.environ.copy()
47 (builddir, newbuilddir) = self.setupfunc("-st", None, self.suite) 48 (builddir, newbuilddir) = self.setupfunc("-st", None, self.suite)
48 ret = super().run(result) 49 ret = super().run(result)
50 # In forks we don't have to restore but in a single process, restore cwd and the env
49 os.chdir(builddir) 51 os.chdir(builddir)
52 for e in origenv:
53 os.environ[e] = origenv[e]
50 if newbuilddir and ret.wasSuccessful() and self.removefunc: 54 if newbuilddir and ret.wasSuccessful() and self.removefunc:
51 self.removefunc(newbuilddir) 55 self.removefunc(newbuilddir)
52 56
@@ -102,6 +106,13 @@ class OESelftestTestContext(OETestContext):
102 oe.path.copytree(builddir + "/cache", newbuilddir + "/cache") 106 oe.path.copytree(builddir + "/cache", newbuilddir + "/cache")
103 oe.path.copytree(selftestdir, newselftestdir) 107 oe.path.copytree(selftestdir, newselftestdir)
104 108
109 # if the last line of local.conf in newbuilddir is not empty and does not end with newline then add one
110 localconf_path = newbuilddir + "/conf/local.conf"
111 with open(localconf_path, "r+", encoding="utf-8") as f:
112 last_line = f.readlines()[-1]
113 if last_line and not last_line.endswith("\n"):
114 f.write("\n")
115
105 subprocess.check_output("git init && git add * && git commit -a -m 'initial'", cwd=newselftestdir, shell=True) 116 subprocess.check_output("git init && git add * && git commit -a -m 'initial'", cwd=newselftestdir, shell=True)
106 117
107 # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow 118 # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow
@@ -114,11 +125,15 @@ class OESelftestTestContext(OETestContext):
114 bblayers_abspath = [os.path.abspath(path) for path in bblayers.split()] 125 bblayers_abspath = [os.path.abspath(path) for path in bblayers.split()]
115 with open("%s/conf/bblayers.conf" % newbuilddir, "a") as f: 126 with open("%s/conf/bblayers.conf" % newbuilddir, "a") as f:
116 newbblayers = "# new bblayers to be used by selftest in the new build dir '%s'\n" % newbuilddir 127 newbblayers = "# new bblayers to be used by selftest in the new build dir '%s'\n" % newbuilddir
128 newbblayers += 'unset BBLAYERS\n'
117 newbblayers += 'BBLAYERS = "%s"\n' % ' '.join(bblayers_abspath) 129 newbblayers += 'BBLAYERS = "%s"\n' % ' '.join(bblayers_abspath)
118 f.write(newbblayers) 130 f.write(newbblayers)
119 131
132 # Rewrite builddir paths seen in environment variables
120 for e in os.environ: 133 for e in os.environ:
121 if builddir + "/" in os.environ[e]: 134 # Rewrite paths that absolutely point inside builddir
135 # (e.g $builddir/conf/ would be rewritten but not $builddir/../bitbake/)
136 if builddir + "/" in os.environ[e] and builddir + "/" in os.path.abspath(os.environ[e]):
122 os.environ[e] = os.environ[e].replace(builddir + "/", newbuilddir + "/") 137 os.environ[e] = os.environ[e].replace(builddir + "/", newbuilddir + "/")
123 if os.environ[e].endswith(builddir): 138 if os.environ[e].endswith(builddir):
124 os.environ[e] = os.environ[e].replace(builddir, newbuilddir) 139 os.environ[e] = os.environ[e].replace(builddir, newbuilddir)
diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py
index 6e8b781973..a9080077e2 100644
--- a/meta/lib/oeqa/targetcontrol.py
+++ b/meta/lib/oeqa/targetcontrol.py
@@ -86,9 +86,9 @@ class BaseTarget(object, metaclass=ABCMeta):
86 86
87class QemuTarget(BaseTarget): 87class QemuTarget(BaseTarget):
88 88
89 supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] 89 supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic', 'ext3.zst', 'ext4.zst', 'wic.zst']
90 90
91 def __init__(self, d, logger, image_fstype=None): 91 def __init__(self, d, logger, image_fstype=None, boot_patterns=None):
92 92
93 import oe.types 93 import oe.types
94 94
@@ -141,7 +141,8 @@ class QemuTarget(BaseTarget):
141 dump_dir = dump_dir, 141 dump_dir = dump_dir,
142 logger = logger, 142 logger = logger,
143 tmpfsdir = d.getVar("RUNQEMU_TMPFS_DIR"), 143 tmpfsdir = d.getVar("RUNQEMU_TMPFS_DIR"),
144 serial_ports = len(d.getVar("SERIAL_CONSOLES").split())) 144 serial_ports = len(d.getVar("SERIAL_CONSOLES").split()),
145 boot_patterns = boot_patterns)
145 146
146 self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner) 147 self.monitor_dumper = MonitorDumper(dump_monitor_cmds, dump_dir, self.runner)
147 if (self.monitor_dumper): 148 if (self.monitor_dumper):
diff --git a/meta/lib/oeqa/utils/__init__.py b/meta/lib/oeqa/utils/__init__.py
index 53bdcbf266..e03f7e33bb 100644
--- a/meta/lib/oeqa/utils/__init__.py
+++ b/meta/lib/oeqa/utils/__init__.py
@@ -96,4 +96,10 @@ def get_json_result_dir(d):
96 custom_json_result_dir = d.getVar("OEQA_JSON_RESULT_DIR") 96 custom_json_result_dir = d.getVar("OEQA_JSON_RESULT_DIR")
97 if custom_json_result_dir: 97 if custom_json_result_dir:
98 json_result_dir = custom_json_result_dir 98 json_result_dir = custom_json_result_dir
99 return json_result_dir \ No newline at end of file 99 return json_result_dir
100
101def get_artefact_dir(d):
102 custom_json_result_dir = d.getVar("OEQA_ARTEFACT_DIR")
103 if custom_json_result_dir:
104 return custom_json_result_dir
105 return os.path.join(d.getVar("LOG_DIR"), 'oeqa-artefacts')
diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py
index 575e380017..9154220968 100644
--- a/meta/lib/oeqa/utils/commands.py
+++ b/meta/lib/oeqa/utils/commands.py
@@ -203,6 +203,8 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=T
203 203
204 if result.status and not ignore_status: 204 if result.status and not ignore_status:
205 exc_output = result.output 205 exc_output = result.output
206 if result.error:
207 exc_output = exc_output + result.error
206 if limit_exc_output > 0: 208 if limit_exc_output > 0:
207 split = result.output.splitlines() 209 split = result.output.splitlines()
208 if len(split) > limit_exc_output: 210 if len(split) > limit_exc_output:
@@ -283,7 +285,20 @@ def get_bb_vars(variables=None, target=None, postconfig=None):
283 return values 285 return values
284 286
285def get_bb_var(var, target=None, postconfig=None): 287def get_bb_var(var, target=None, postconfig=None):
286 return get_bb_vars([var], target, postconfig)[var] 288 if postconfig:
289 return bitbake("-e %s" % target or "", postconfig=postconfig).output
290 else:
291 # Fast-path for the non-postconfig case
292 cmd = ["bitbake-getvar", "--quiet", "--value", var]
293 if target:
294 cmd.extend(["--recipe", target])
295 try:
296 return subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE).stdout.strip()
297 except subprocess.CalledProcessError as e:
298 # We need to return None not the empty string if the variable hasn't been set.
299 if e.returncode == 1:
300 return None
301 raise
287 302
288def get_test_layer(bblayers=None): 303def get_test_layer(bblayers=None):
289 if bblayers is None: 304 if bblayers is None:
@@ -312,9 +327,26 @@ def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec=
312 f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames)) 327 f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames))
313 328
314@contextlib.contextmanager 329@contextlib.contextmanager
315def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, discard_writes=True): 330def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, boot_patterns = {}, discard_writes=True):
316 """ 331 """
317 launch_cmd means directly run the command, don't need set rootfs or env vars. 332 Starts a context manager for a 'oeqa.targetcontrol.QemuTarget' resource.
333 The underlying Qemu will be booted into a shell when the generator yields
334 and stopped when the 'with' block exits.
335
336 Usage:
337
338 with runqemu('core-image-minimal') as qemu:
339 qemu.run_serial('cat /proc/cpuinfo')
340
341 Args:
342 pn (str): (image) recipe to run on
343 ssh (boolean): whether or not to enable SSH (network access)
344 runqemuparams (str): space-separated list of params to pass to 'runqemu' script (like 'nographics', 'ovmf', etc.)
345 image_fstype (str): IMAGE_FSTYPE to use
346 launch_cmd (str): directly run this command and bypass automatic runqemu parameter generation
347 overrides (dict): dict of "'<bitbake-variable>': value" pairs that allows overriding bitbake variables
348 boot_patterns (dict): dict of "'<pattern-name>': value" pairs to override default boot patterns, e.g. when not booting Linux
349 discard_writes (boolean): enables qemu -snapshot feature to prevent modifying original image
318 """ 350 """
319 351
320 import bb.tinfoil 352 import bb.tinfoil
@@ -345,7 +377,7 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None,
345 377
346 logdir = recipedata.getVar("TEST_LOG_DIR") 378 logdir = recipedata.getVar("TEST_LOG_DIR")
347 379
348 qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype) 380 qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype, boot_patterns=boot_patterns)
349 finally: 381 finally:
350 # We need to shut down tinfoil early here in case we actually want 382 # We need to shut down tinfoil early here in case we actually want
351 # to run tinfoil-using utilities with the running QEMU instance. 383 # to run tinfoil-using utilities with the running QEMU instance.
@@ -369,6 +401,20 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None,
369 targetlogger.removeHandler(handler) 401 targetlogger.removeHandler(handler)
370 qemu.stop() 402 qemu.stop()
371 403
404def runqemu_check_taps():
405 """Check if tap devices for runqemu are available"""
406 if not os.path.exists('/etc/runqemu-nosudo'):
407 return False
408 result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ip tuntap show mode tap', ignore_status=True)
409 if result.status != 0:
410 return False
411 for line in result.output.splitlines():
412 if 'tap' in line:
413 break
414 else:
415 return False
416 return True
417
372def updateEnv(env_file): 418def updateEnv(env_file):
373 """ 419 """
374 Source a file and update environment. 420 Source a file and update environment.
diff --git a/meta/lib/oeqa/utils/gitarchive.py b/meta/lib/oeqa/utils/gitarchive.py
index 10cb267dfa..7e1d505748 100644
--- a/meta/lib/oeqa/utils/gitarchive.py
+++ b/meta/lib/oeqa/utils/gitarchive.py
@@ -67,7 +67,7 @@ def git_commit_data(repo, data_dir, branch, message, exclude, notes, log):
67 67
68 # Remove files that are excluded 68 # Remove files that are excluded
69 if exclude: 69 if exclude:
70 repo.run_cmd(['rm', '--cached'] + [f for f in exclude], env_update) 70 repo.run_cmd(['rm', '--cached', '--ignore-unmatch'] + [f for f in exclude], env_update)
71 71
72 tree = repo.run_cmd('write-tree', env_update) 72 tree = repo.run_cmd('write-tree', env_update)
73 73
@@ -146,7 +146,7 @@ def expand_tag_strings(repo, name_pattern, msg_subj_pattern, msg_body_pattern,
146 keyws['tag_number'] = '{tag_number}' 146 keyws['tag_number'] = '{tag_number}'
147 tag_re = format_str(name_pattern, keyws) 147 tag_re = format_str(name_pattern, keyws)
148 # Replace parentheses for proper regex matching 148 # Replace parentheses for proper regex matching
149 tag_re = tag_re.replace('(', '\(').replace(')', '\)') + '$' 149 tag_re = tag_re.replace('(', r'\(').replace(')', r'\)') + '$'
150 # Inject regex group pattern for 'tag_number' 150 # Inject regex group pattern for 'tag_number'
151 tag_re = tag_re.format(tag_number='(?P<tag_number>[0-9]{1,5})') 151 tag_re = tag_re.format(tag_number='(?P<tag_number>[0-9]{1,5})')
152 152
@@ -202,6 +202,8 @@ def gitarchive(data_dir, git_dir, no_create, bare, commit_msg_subject, commit_ms
202 log.info("Pushing data to remote") 202 log.info("Pushing data to remote")
203 data_repo.run_cmd(cmd) 203 data_repo.run_cmd(cmd)
204 204
205 return tag_name
206
205# Container class for tester revisions 207# Container class for tester revisions
206TestedRev = namedtuple('TestedRev', 'commit commit_number tags') 208TestedRev = namedtuple('TestedRev', 'commit commit_number tags')
207 209
diff --git a/meta/lib/oeqa/utils/metadata.py b/meta/lib/oeqa/utils/metadata.py
index 15ec190c4a..b320df67e0 100644
--- a/meta/lib/oeqa/utils/metadata.py
+++ b/meta/lib/oeqa/utils/metadata.py
@@ -76,6 +76,10 @@ def git_rev_info(path):
76 info['commit_count'] = int(subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=path).decode('utf-8').strip()) 76 info['commit_count'] = int(subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=path).decode('utf-8').strip())
77 except subprocess.CalledProcessError: 77 except subprocess.CalledProcessError:
78 pass 78 pass
79 try:
80 info['commit_time'] = int(subprocess.check_output(["git", "show", "--no-patch", "--format=%ct", "HEAD"], cwd=path).decode('utf-8').strip())
81 except subprocess.CalledProcessError:
82 pass
79 return info 83 return info
80 try: 84 try:
81 repo = Repo(path, search_parent_directories=True) 85 repo = Repo(path, search_parent_directories=True)
@@ -83,6 +87,7 @@ def git_rev_info(path):
83 return info 87 return info
84 info['commit'] = repo.head.commit.hexsha 88 info['commit'] = repo.head.commit.hexsha
85 info['commit_count'] = repo.head.commit.count() 89 info['commit_count'] = repo.head.commit.count()
90 info['commit_time'] = repo.head.commit.committed_date
86 try: 91 try:
87 info['branch'] = repo.active_branch.name 92 info['branch'] = repo.active_branch.name
88 except TypeError: 93 except TypeError:
diff --git a/meta/lib/oeqa/utils/postactions.py b/meta/lib/oeqa/utils/postactions.py
index ecdddd2d40..7b967edaad 100644
--- a/meta/lib/oeqa/utils/postactions.py
+++ b/meta/lib/oeqa/utils/postactions.py
@@ -7,35 +7,32 @@
7# Run a set of actions after tests. The runner provides internal data 7# Run a set of actions after tests. The runner provides internal data
8# dictionary as well as test context to any action to run. 8# dictionary as well as test context to any action to run.
9 9
10from oeqa.utils import get_json_result_dir 10import datetime
11 11import io
12def create_artifacts_directory(d, tc): 12import os
13 import shutil 13import stat
14 14import subprocess
15 local_artifacts_dir = os.path.join(get_json_result_dir(d), "artifacts") 15import tempfile
16 if os.path.isdir(local_artifacts_dir): 16from oeqa.utils import get_artefact_dir
17 shutil.rmtree(local_artifacts_dir)
18
19 os.makedirs(local_artifacts_dir)
20 17
21################################################################## 18##################################################################
22# Host/target statistics 19# Host/target statistics
23################################################################## 20##################################################################
24 21
25def get_target_disk_usage(d, tc): 22def get_target_disk_usage(d, tc, artifacts_list, outputdir):
26 output_file = os.path.join(get_json_result_dir(d), "artifacts", "target_disk_usage.txt") 23 output_file = os.path.join(outputdir, "target_disk_usage.txt")
27 try: 24 try:
28 (status, output) = tc.target.run('df -h') 25 (status, output) = tc.target.run('df -h', ignore_ssh_fails=True)
29 with open(output_file, 'w') as f: 26 with open(output_file, 'w') as f:
30 f.write(output) 27 f.write(output)
31 f.write("\n") 28 f.write("\n")
32 except Exception as e: 29 except Exception as e:
33 bb.warn(f"Can not get target disk usage: {e}") 30 bb.warn(f"Can not get target disk usage: {e}")
34 31
35def get_host_disk_usage(d, tc): 32def get_host_disk_usage(d, tc, artifacts_list, outputdir):
36 import subprocess 33 import subprocess
37 34
38 output_file = os.path.join(get_json_result_dir(d), "artifacts", "host_disk_usage.txt") 35 output_file = os.path.join(outputdir, "host_disk_usage.txt")
39 try: 36 try:
40 with open(output_file, 'w') as f: 37 with open(output_file, 'w') as f:
41 output = subprocess.run(['df', '-hl'], check=True, text=True, stdout=f, env={}) 38 output = subprocess.run(['df', '-hl'], check=True, text=True, stdout=f, env={})
@@ -52,7 +49,7 @@ def get_artifacts_list(target, raw_list):
52 for raw_path in raw_list.split(): 49 for raw_path in raw_list.split():
53 cmd = f"for p in {raw_path}; do if [ -e $p ]; then echo $p; fi; done" 50 cmd = f"for p in {raw_path}; do if [ -e $p ]; then echo $p; fi; done"
54 try: 51 try:
55 status, output = target.run(cmd) 52 status, output = target.run(cmd, ignore_ssh_fails=True)
56 if status != 0 or not output: 53 if status != 0 or not output:
57 raise Exception() 54 raise Exception()
58 result += output.split() 55 result += output.split()
@@ -61,25 +58,22 @@ def get_artifacts_list(target, raw_list):
61 58
62 return result 59 return result
63 60
64def retrieve_test_artifacts(target, artifacts_list, target_dir): 61def list_and_fetch_failed_tests_artifacts(d, tc, artifacts_list, outputdir):
65 local_artifacts_dir = os.path.join(target_dir, "artifacts") 62 artifacts_list = get_artifacts_list(tc.target, artifacts_list)
66 for artifact_path in artifacts_list:
67 if not os.path.isabs(artifact_path):
68 bb.warn(f"{artifact_path} is not an absolute path")
69 continue
70 try:
71 dest_dir = os.path.join(local_artifacts_dir, os.path.dirname(artifact_path[1:]))
72 os.makedirs(dest_dir, exist_ok=True)
73 target.copyFrom(artifact_path, dest_dir)
74 except Exception as e:
75 bb.warn(f"Can not retrieve {artifact_path} from test target: {e}")
76
77def list_and_fetch_failed_tests_artifacts(d, tc):
78 artifacts_list = get_artifacts_list(tc.target, d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS"))
79 if not artifacts_list: 63 if not artifacts_list:
80 bb.warn("Could not load artifacts list, skip artifacts retrieval") 64 bb.warn("Could not load artifacts list, skip artifacts retrieval")
81 else: 65 return
82 retrieve_test_artifacts(tc.target, artifacts_list, get_json_result_dir(d)) 66 try:
67 # We need gnu tar for sparse files, not busybox
68 cmd = "tar --sparse -zcf - " + " ".join(artifacts_list)
69 (status, output) = tc.target.run(cmd, raw = True)
70 if status != 0 or not output:
71 raise Exception("Error while fetching compressed artifacts")
72 archive_name = os.path.join(outputdir, "tests_artifacts.tar.gz")
73 with open(archive_name, "wb") as f:
74 f.write(output)
75 except Exception as e:
76 bb.warn(f"Can not retrieve artifacts from test target: {e}")
83 77
84 78
85################################################################## 79##################################################################
@@ -87,12 +81,22 @@ def list_and_fetch_failed_tests_artifacts(d, tc):
87################################################################## 81##################################################################
88 82
89def run_failed_tests_post_actions(d, tc): 83def run_failed_tests_post_actions(d, tc):
84 artifacts = d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS")
85 # Allow all the code to be disabled by having no artifacts set, e.g. for systems with no ssh support
86 if not artifacts:
87 return
88
89 outputdir = get_artefact_dir(d)
90 os.makedirs(outputdir, exist_ok=True)
91 datestr = datetime.datetime.now().strftime('%Y%m%d')
92 outputdir = tempfile.mkdtemp(prefix='oeqa-target-artefacts-%s-' % datestr, dir=outputdir)
93 os.chmod(outputdir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
94
90 post_actions=[ 95 post_actions=[
91 create_artifacts_directory,
92 list_and_fetch_failed_tests_artifacts, 96 list_and_fetch_failed_tests_artifacts,
93 get_target_disk_usage, 97 get_target_disk_usage,
94 get_host_disk_usage 98 get_host_disk_usage
95 ] 99 ]
96 100
97 for action in post_actions: 101 for action in post_actions:
98 action(d, tc) 102 action(d, tc, artifacts, outputdir)
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py
index cda43aad8c..c4db0cf038 100644
--- a/meta/lib/oeqa/utils/qemurunner.py
+++ b/meta/lib/oeqa/utils/qemurunner.py
@@ -30,6 +30,8 @@ control_range = list(range(0,32))+list(range(127,160))
30control_chars = [chr(x) for x in control_range 30control_chars = [chr(x) for x in control_range
31 if chr(x) not in string.printable] 31 if chr(x) not in string.printable]
32re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) 32re_control_char = re.compile('[%s]' % re.escape("".join(control_chars)))
33# Regex to remove the ANSI (color) control codes from console strings in order to match the text only
34re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_a-z]*[@-_a-z]|\x1b[@-_a-z]')
33 35
34def getOutput(o): 36def getOutput(o):
35 import fcntl 37 import fcntl
@@ -101,7 +103,7 @@ class QemuRunner:
101 103
102 # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" 104 # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n"
103 for pattern in accepted_patterns: 105 for pattern in accepted_patterns:
104 if not self.boot_patterns[pattern]: 106 if pattern not in self.boot_patterns or not self.boot_patterns[pattern]:
105 self.boot_patterns[pattern] = default_boot_patterns[pattern] 107 self.boot_patterns[pattern] = default_boot_patterns[pattern]
106 108
107 def create_socket(self): 109 def create_socket(self):
@@ -265,12 +267,15 @@ class QemuRunner:
265 self.monitorpipe = os.fdopen(w, "w") 267 self.monitorpipe = os.fdopen(w, "w")
266 else: 268 else:
267 # child process 269 # child process
268 os.setpgrp() 270 try:
269 os.close(w) 271 os.setpgrp()
270 r = os.fdopen(r) 272 os.close(w)
271 x = r.read() 273 r = os.fdopen(r)
272 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 274 x = r.read()
273 os._exit(0) 275 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
276 finally:
277 # We must exit under all circumstances
278 os._exit(0)
274 279
275 self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) 280 self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
276 self.logger.debug("waiting at most %d seconds for qemu pid (%s)" % 281 self.logger.debug("waiting at most %d seconds for qemu pid (%s)" %
@@ -519,7 +524,6 @@ class QemuRunner:
519 except Exception as e: 524 except Exception as e:
520 self.logger.warning('Extra log data exception %s' % repr(e)) 525 self.logger.warning('Extra log data exception %s' % repr(e))
521 data = None 526 data = None
522 self.thread.serial_lock.release()
523 return False 527 return False
524 528
525 with self.thread.serial_lock: 529 with self.thread.serial_lock:
@@ -533,7 +537,7 @@ class QemuRunner:
533 self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", "")) 537 self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", ""))
534 if netconf: 538 if netconf:
535 # configure guest networking 539 # configure guest networking
536 cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) 540 cmd = "ip addr add %s/%s dev eth0\nip link set dev eth0 up\n" % (self.ip, self.netmask)
537 output = self.run_serial(cmd, raw=True)[1] 541 output = self.run_serial(cmd, raw=True)[1]
538 if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): 542 if re.search(r"root@[a-zA-Z0-9\-]+:~#", output):
539 self.logger.debug("configured ip address %s", self.ip) 543 self.logger.debug("configured ip address %s", self.ip)
@@ -681,7 +685,7 @@ class QemuRunner:
681 time.sleep(0.1) 685 time.sleep(0.1)
682 answer = self.server_socket.recv(1024) 686 answer = self.server_socket.recv(1024)
683 if answer: 687 if answer:
684 data += answer.decode('utf-8') 688 data += re_vt100.sub("", answer.decode('utf-8'))
685 # Search the prompt to stop 689 # Search the prompt to stop
686 if re.search(self.boot_patterns['search_cmd_finished'], data): 690 if re.search(self.boot_patterns['search_cmd_finished'], data):
687 break 691 break
@@ -745,8 +749,10 @@ class LoggingThread(threading.Thread):
745 def threadtarget(self): 749 def threadtarget(self):
746 try: 750 try:
747 self.eventloop() 751 self.eventloop()
748 except Exception as e: 752 except Exception:
749 self.logger.warning("Exception %s in logging thread" % traceback.format_exception(e)) 753 exc_type, exc_value, exc_traceback = sys.exc_info()
754 self.logger.warning("Exception %s in logging thread" %
755 traceback.format_exception(exc_type, exc_value, exc_traceback))
750 finally: 756 finally:
751 self.teardown() 757 self.teardown()
752 758
@@ -822,10 +828,12 @@ class LoggingThread(threading.Thread):
822 self.logfunc(data, ".stdout") 828 self.logfunc(data, ".stdout")
823 elif self.serialsock and self.serialsock.fileno() == fd: 829 elif self.serialsock and self.serialsock.fileno() == fd:
824 if self.serial_lock.acquire(blocking=False): 830 if self.serial_lock.acquire(blocking=False):
825 data = self.recv(1024, self.serialsock) 831 try:
826 self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) 832 data = self.recv(1024, self.serialsock)
827 self.logfunc(data, ".2") 833 self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace'))
828 self.serial_lock.release() 834 self.logfunc(data, ".2")
835 finally:
836 self.serial_lock.release()
829 else: 837 else:
830 serial_registered = False 838 serial_registered = False
831 poll.unregister(self.serialsock.fileno()) 839 poll.unregister(self.serialsock.fileno())
diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py
index 36c2ecb3db..88a61aff63 100644
--- a/meta/lib/oeqa/utils/sshcontrol.py
+++ b/meta/lib/oeqa/utils/sshcontrol.py
@@ -57,8 +57,10 @@ class SSHProcess(object):
57 if select.select([self.process.stdout], [], [], 5)[0] != []: 57 if select.select([self.process.stdout], [], [], 5)[0] != []:
58 data = os.read(self.process.stdout.fileno(), 1024) 58 data = os.read(self.process.stdout.fileno(), 1024)
59 if not data: 59 if not data:
60 self.process.stdout.close() 60 self.process.poll()
61 eof = True 61 if self.process.returncode is not None:
62 self.process.stdout.close()
63 eof = True
62 else: 64 else:
63 data = data.decode("utf-8") 65 data = data.decode("utf-8")
64 output += data 66 output += data
diff --git a/meta/lib/oeqa/utils/subprocesstweak.py b/meta/lib/oeqa/utils/subprocesstweak.py
index 3e43ed547b..046667faa9 100644
--- a/meta/lib/oeqa/utils/subprocesstweak.py
+++ b/meta/lib/oeqa/utils/subprocesstweak.py
@@ -8,15 +8,12 @@ import subprocess
8class OETestCalledProcessError(subprocess.CalledProcessError): 8class OETestCalledProcessError(subprocess.CalledProcessError):
9 def __str__(self): 9 def __str__(self):
10 def strify(o): 10 def strify(o):
11 if isinstance(o, bytes): 11 return o.decode("utf-8", errors="replace") if isinstance(o, bytes) else o
12 return o.decode("utf-8", errors="replace")
13 else:
14 return o
15 12
16 s = "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) 13 s = super().__str__()
17 if hasattr(self, "output") and self.output: 14 s = s + "\nStandard Output: " + strify(self.output)
18 s = s + "\nStandard Output: " + strify(self.output) 15 # stderr is not available for check_output method
19 if hasattr(self, "stderr") and self.stderr: 16 if self.stderr != None:
20 s = s + "\nStandard Error: " + strify(self.stderr) 17 s = s + "\nStandard Error: " + strify(self.stderr)
21 return s 18 return s
22 19
diff --git a/meta/lib/oeqa/utils/testexport.py b/meta/lib/oeqa/utils/testexport.py
index e89d130a9c..3ab024d9e9 100644
--- a/meta/lib/oeqa/utils/testexport.py
+++ b/meta/lib/oeqa/utils/testexport.py
@@ -60,17 +60,17 @@ def process_binaries(d, params):
60 export_env = d.getVar("TEST_EXPORT_ONLY") 60 export_env = d.getVar("TEST_EXPORT_ONLY")
61 61
62 def extract_binary(pth_to_pkg, dest_pth=None): 62 def extract_binary(pth_to_pkg, dest_pth=None):
63 cpio_command = runCmd("which cpio") 63 tar_command = runCmd("which tar")
64 rpm2cpio_command = runCmd("ls /usr/bin/rpm2cpio") 64 rpm2archive_command = runCmd("ls /usr/bin/rpm2archive")
65 if (cpio_command.status != 0) and (rpm2cpio_command.status != 0): 65 if (tar_command.status != 0) and (rpm2archive_command.status != 0):
66 bb.fatal("Either \"rpm2cpio\" or \"cpio\" tools are not available on your system." 66 bb.fatal("Either \"rpm2archive\" or \"tar\" tools are not available on your system."
67 "All binaries extraction processes will not be available, crashing all related tests." 67 "All binaries extraction processes will not be available, crashing all related tests."
68 "Please install them according to your OS recommendations") # will exit here 68 "Please install them according to your OS recommendations") # will exit here
69 if dest_pth: 69 if dest_pth:
70 os.chdir(dest_pth) 70 os.chdir(dest_pth)
71 else: 71 else:
72 os.chdir("%s" % os.sep)# this is for native package 72 os.chdir("%s" % os.sep)# this is for native package
73 extract_bin_command = runCmd("%s %s | %s -idm" % (rpm2cpio_command.output, pth_to_pkg, cpio_command.output)) # semi-hardcoded because of a bug on poky's rpm2cpio 73 extract_bin_command = runCmd("%s -n %s | %s xv" % (rpm2archive_command.output, pth_to_pkg, tar_command.output)) # semi-hardcoded because of a bug on poky's rpm2cpio
74 return extract_bin_command 74 return extract_bin_command
75 75
76 if determine_if_poky_env(): # machine with poky environment 76 if determine_if_poky_env(): # machine with poky environment