summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/core/case.py8
-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/serial.py315
-rw-r--r--meta/lib/oeqa/core/target/ssh.py56
-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/manual/crops.json294
-rw-r--r--meta/lib/oeqa/manual/eclipse-plugin.json322
-rw-r--r--meta/lib/oeqa/runtime/case.py16
-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/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.py5
-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/context.py12
-rw-r--r--meta/lib/oeqa/sdk/case.py67
-rw-r--r--meta/lib/oeqa/sdk/cases/autotools.py (renamed from meta/lib/oeqa/sdk/cases/buildcpio.py)17
-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.py (renamed from meta/lib/oeqa/sdk/cases/assimp.py)18
-rw-r--r--meta/lib/oeqa/sdk/cases/gcc.py4
-rw-r--r--meta/lib/oeqa/sdk/cases/gtk3.py40
-rw-r--r--meta/lib/oeqa/sdk/cases/kmod.py39
-rw-r--r--meta/lib/oeqa/sdk/cases/makefile.py (renamed from meta/lib/oeqa/sdk/cases/buildlzip.py)10
-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.py3
-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/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.py31
-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.py61
-rw-r--r--meta/lib/oeqa/selftest/cases/buildoptions.py51
-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.py214
-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.py2117
-rw-r--r--meta/lib/oeqa/selftest/cases/gcc.py4
-rw-r--r--meta/lib/oeqa/selftest/cases/gdbserver.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/imagefeatures.py15
-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.py8
-rw-r--r--meta/lib/oeqa/selftest/cases/minidebuginfo.py16
-rw-r--r--meta/lib/oeqa/selftest/cases/oescripts.py26
-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.py261
-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/runtime_test.py11
-rw-r--r--meta/lib/oeqa/selftest/cases/rust.py138
-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.py174
-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.py384
-rw-r--r--meta/lib/oeqa/selftest/context.py30
-rw-r--r--meta/lib/oeqa/targetcontrol.py5
-rw-r--r--meta/lib/oeqa/utils/__init__.py8
-rw-r--r--meta/lib/oeqa/utils/commands.py40
-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.py72
-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
100 files changed, 4759 insertions, 2280 deletions
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/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/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..8b5c450a05 100644
--- a/meta/lib/oeqa/core/target/ssh.py
+++ b/meta/lib/oeqa/core/target/ssh.py
@@ -55,14 +55,14 @@ 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):
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
@@ -72,7 +72,7 @@ class OESSHTarget(OETarget):
72 72
73 return (status, output) 73 return (status, output)
74 74
75 def run(self, command, timeout=None, ignore_status=True): 75 def run(self, command, timeout=None, ignore_status=True, raw=False):
76 """ 76 """
77 Runs command in target. 77 Runs command in target.
78 78
@@ -91,8 +91,11 @@ class OESSHTarget(OETarget):
91 else: 91 else:
92 processTimeout = self.timeout 92 processTimeout = self.timeout
93 93
94 status, output = self._run(sshCmd, processTimeout, ignore_status) 94 status, output = self._run(sshCmd, processTimeout, ignore_status, raw)
95 self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output)) 95 if len(output) > (64 * 1024):
96 self.logger.debug('Command: %s\nStatus: %d Output length: %s\n' % (command, status, len(output)))
97 else:
98 self.logger.debug('Command: %s\nStatus: %d Output: %s\n' % (command, status, output))
96 99
97 return (status, output) 100 return (status, output)
98 101
@@ -206,22 +209,23 @@ class OESSHTarget(OETarget):
206 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/")) 209 remoteDir = os.path.join(remotePath, tmpDir.lstrip("/"))
207 self.deleteDir(remoteDir) 210 self.deleteDir(remoteDir)
208 211
209def SSHCall(command, logger, timeout=None, **opts): 212def SSHCall(command, logger, timeout=None, raw=False, **opts):
210 213
211 def run(): 214 def run():
212 nonlocal output 215 nonlocal output
213 nonlocal process 216 nonlocal process
214 output_raw = b'' 217 output_raw = bytearray()
215 starttime = time.time() 218 starttime = time.time()
219 progress = time.time()
216 process = subprocess.Popen(command, **options) 220 process = subprocess.Popen(command, **options)
217 has_timeout = False 221 has_timeout = False
222 appendline = None
218 if timeout: 223 if timeout:
219 endtime = starttime + timeout 224 endtime = starttime + timeout
220 eof = False 225 eof = False
221 os.set_blocking(process.stdout.fileno(), False) 226 os.set_blocking(process.stdout.fileno(), False)
222 while not has_timeout and not eof: 227 while not has_timeout and not eof:
223 try: 228 try:
224 logger.debug('Waiting for process output: time: %s, endtime: %s' % (time.time(), endtime))
225 if select.select([process.stdout], [], [], 5)[0] != []: 229 if select.select([process.stdout], [], [], 5)[0] != []:
226 # wait a bit for more data, tries to avoid reading single characters 230 # wait a bit for more data, tries to avoid reading single characters
227 time.sleep(0.2) 231 time.sleep(0.2)
@@ -229,9 +233,9 @@ def SSHCall(command, logger, timeout=None, **opts):
229 if not data: 233 if not data:
230 eof = True 234 eof = True
231 else: 235 else:
232 output_raw += data 236 output_raw.extend(data)
233 # ignore errors to capture as much as possible 237 # ignore errors to capture as much as possible
234 logger.debug('Partial data from SSH call:\n%s' % data.decode('utf-8', errors='ignore')) 238 #logger.debug('Partial data from SSH call:\n%s' % data.decode('utf-8', errors='ignore'))
235 endtime = time.time() + timeout 239 endtime = time.time() + timeout
236 except InterruptedError: 240 except InterruptedError:
237 logger.debug('InterruptedError') 241 logger.debug('InterruptedError')
@@ -244,6 +248,10 @@ def SSHCall(command, logger, timeout=None, **opts):
244 logger.debug('SSHCall has timeout! Time: %s, endtime: %s' % (time.time(), endtime)) 248 logger.debug('SSHCall has timeout! Time: %s, endtime: %s' % (time.time(), endtime))
245 has_timeout = True 249 has_timeout = True
246 250
251 if time.time() >= (progress + 60):
252 logger.debug('Waiting for process output at time: %s with datasize: %s' % (time.time(), len(output_raw)))
253 progress = time.time()
254
247 process.stdout.close() 255 process.stdout.close()
248 256
249 # process hasn't returned yet 257 # process hasn't returned yet
@@ -256,17 +264,29 @@ def SSHCall(command, logger, timeout=None, **opts):
256 logger.debug('OSError when killing process') 264 logger.debug('OSError when killing process')
257 pass 265 pass
258 endtime = time.time() - starttime 266 endtime = time.time() - starttime
259 lastline = ("\nProcess killed - no output for %d seconds. Total" 267 appendline = ("\nProcess killed - no output for %d seconds. Total"
260 " running time: %d seconds." % (timeout, endtime)) 268 " running time: %d seconds." % (timeout, endtime))
261 logger.debug('Received data from SSH call:\n%s ' % lastline) 269 logger.debug('Received data from SSH call:\n%s ' % appendline)
262 output += lastline
263 process.wait() 270 process.wait()
264 271
272 if raw:
273 output = bytes(output_raw)
274 if appendline:
275 output += bytes(appendline, "utf-8")
276 else:
277 output = output_raw.decode('utf-8', errors='ignore')
278 if appendline:
279 output += appendline
265 else: 280 else:
266 output_raw = process.communicate()[0] 281 output = output_raw = process.communicate()[0]
282 if not raw:
283 output = output_raw.decode('utf-8', errors='ignore')
267 284
268 output = output_raw.decode('utf-8', errors='ignore') 285 if len(output) < (64 * 1024):
269 logger.debug('Data from SSH call:\n%s' % output.rstrip()) 286 if output.rstrip():
287 logger.debug('Data from SSH call:\n%s' % output.rstrip())
288 else:
289 logger.debug('No output from SSH call')
270 290
271 # timout or not, make sure process exits and is not hanging 291 # timout or not, make sure process exits and is not hanging
272 if process.returncode == None: 292 if process.returncode == None:
@@ -292,7 +312,7 @@ def SSHCall(command, logger, timeout=None, **opts):
292 312
293 options = { 313 options = {
294 "stdout": subprocess.PIPE, 314 "stdout": subprocess.PIPE,
295 "stderr": subprocess.STDOUT, 315 "stderr": subprocess.STDOUT if not raw else None,
296 "stdin": None, 316 "stdin": None,
297 "shell": False, 317 "shell": False,
298 "bufsize": -1, 318 "bufsize": -1,
@@ -320,4 +340,4 @@ def SSHCall(command, logger, timeout=None, **opts):
320 logger.debug('Something went wrong, killing SSH process') 340 logger.debug('Something went wrong, killing SSH process')
321 raise 341 raise
322 342
323 return (process.returncode, output.rstrip()) 343 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/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..9515ca2f3d 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,16 @@ 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 status, output = runner.run_serial("ip addr")
27 print("ip addr on target: %s %s" % (output, status))
28 status, output = runner.run_serial("ping -c 1 %s" % self.target.server_ip)
29 print("ping on target for %s: %s %s" % (self.target.server_ip, output, status))
30 status, output = runner.run_serial("ping -c 1 %s" % self.target.ip)
31 print("ping on target for %s: %s %s" % (self.target.ip, output, status))
32 # Have to use a full path for netstat which isn't in HOSTTOOLS
33 subprocess.call(["/usr/bin/netstat", "-tunape"])
34 subprocess.call(["/usr/bin/netstat", "-ei"])
35 subprocess.call(["ps", "-awx"], shell=True)
36 print("PID: %s %s" % (str(os.getpid()), time.time()))
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/ltp.py b/meta/lib/oeqa/runtime/cases/ltp.py
index f588a93200..e81360670c 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", "ipc", "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..b632a29a01 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)
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..6b55e7de50 100644
--- a/meta/lib/oeqa/runtime/cases/stap.py
+++ b/meta/lib/oeqa/runtime/cases/stap.py
@@ -21,11 +21,12 @@ class StapTest(OERuntimeTestCase):
21 status, output = self.target.run(cmd, 900) 21 status, output = self.target.run(cmd, 900)
22 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 22 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
23 23
24 cmd = 'stap -v -p4 -m stap-hello --disable-cache -DSTP_NO_VERREL_CHECK -e \'probe oneshot { print("Hello, "); println("SystemTap!") }\'' 24 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) 25 status, output = self.target.run(cmd, 900)
26 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 26 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
27 27
28 cmd = 'staprun -v -R -b1 stap-hello.ko' 28 cmd = 'staprun -v -R -b1 stap_hello.ko'
29 status, output = self.target.run(cmd, 60)
29 self.assertEqual(status, 0, msg='\n'.join([cmd, output])) 30 self.assertEqual(status, 0, msg='\n'.join([cmd, output]))
30 self.assertIn('Hello, SystemTap!', output, msg='\n'.join([cmd, output])) 31 self.assertIn('Hello, SystemTap!', output, msg='\n'.join([cmd, output]))
31 except: 32 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/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..1fd3b3b569 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,76 @@ 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 self._run('devtool sdk-install %s' % recipe)
48 else:
49 raise unittest.SkipTest("Test %s needs one of %s" % (self.id(), ", ".join(packages)))
50
51 def ensure_target_package(self, *packages, multilib=False, recipe=None):
52 """
53 Check that at least one of the packages listed is available in the SDK,
54 adding the multilib prefix if required. The target package is a list for
55 the case where debian-renaming may have occured, and the manifest could
56 contain 'foo' or 'libfoo'.
57
58 If testing an eSDK and the package is not found, then try to install the
59 specified recipe to install it from sstate.
60 """
61
62 # In a SDK the manifest is correct. In an eSDK the manifest may be
63 # correct (type=full) or not include packages that exist in sstate but
64 # not installed yet (minimal) so we should try to install the recipe.
65 for package in packages:
66 if self.tc.hasTargetPackage(package, multilib=multilib):
67 break
68 else:
69 if isinstance(self.tc, OESDKExtTestContext):
70 recipe = recipe or packages[0]
71 print("Trying to install %s..." % recipe)
72 self._run('devtool sdk-install %s' % recipe)
73 else:
74 raise unittest.SkipTest("Test %s needs one of %s" % (self.id(), ", ".join(packages)))
75
76
18 def fetch(self, workdir, dl_dir, url, archive=None): 77 def fetch(self, workdir, dl_dir, url, archive=None):
19 if not archive: 78 if not archive:
20 from urllib.parse import urlparse 79 from urllib.parse import urlparse
21 archive = os.path.basename(urlparse(url).path) 80 archive = os.path.basename(urlparse(url).path)
22 81
23 if dl_dir: 82 if dl_dir:
24 tarball = os.path.join(dl_dir, archive) 83 archive_tarball = os.path.join(dl_dir, archive)
25 if os.path.exists(tarball): 84 if os.path.exists(archive_tarball):
26 return tarball 85 return archive_tarball
27 86
28 tarball = os.path.join(workdir, archive) 87 tarball = os.path.join(workdir, archive)
29 subprocess.check_output(["wget", "-O", tarball, url], stderr=subprocess.STDOUT) 88 subprocess.check_output(["wget", "-O", tarball, url], stderr=subprocess.STDOUT)
89 if dl_dir and not os.path.exists(archive_tarball):
90 shutil.copyfile(tarball, archive_tarball)
30 return tarball 91 return tarball
31 92
32 def check_elf(self, path, target_os=None, target_arch=None): 93 def check_elf(self, path, target_os=None, target_arch=None):
diff --git a/meta/lib/oeqa/sdk/cases/buildcpio.py b/meta/lib/oeqa/sdk/cases/autotools.py
index 51003b19cd..ee6c522551 100644
--- a/meta/lib/oeqa/sdk/cases/buildcpio.py
+++ b/meta/lib/oeqa/sdk/cases/autotools.py
@@ -13,10 +13,15 @@ from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output 13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output() 14errors_have_output()
15 15
16class BuildCpioTest(OESDKTestCase): 16class AutotoolsTest(OESDKTestCase):
17 """ 17 """
18 Check that autotools will cross-compile correctly. 18 Check that autotools will cross-compile correctly.
19 """ 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
20 def test_cpio(self): 25 def test_cpio(self):
21 with tempfile.TemporaryDirectory(prefix="cpio-", dir=self.tc.sdk_dir) as testdir: 26 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") 27 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftp.gnu.org/gnu/cpio/cpio-2.15.tar.gz")
@@ -30,8 +35,14 @@ class BuildCpioTest(OESDKTestCase):
30 self.assertTrue(os.path.isdir(dirs["source"])) 35 self.assertTrue(os.path.isdir(dirs["source"]))
31 os.makedirs(dirs["build"]) 36 os.makedirs(dirs["build"])
32 37
33 self._run("cd {build} && {source}/configure $CONFIGURE_FLAGS".format(**dirs)) 38 self._run("cd {build} && {source}/configure CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration' $CONFIGURE_FLAGS".format(**dirs))
34 self._run("cd {build} && make -j".format(**dirs)) 39
40 # Check that configure detected the target correctly
41 with open(os.path.join(dirs["build"], "config.log")) as f:
42 host_sys = self.td["HOST_SYS"]
43 self.assertIn(f"host_alias='{host_sys}'\n", f.readlines())
44
45 self._run("cd {build} && make CFLAGS='-std=gnu17 -Dbool=int -Dtrue=1 -Dfalse=0 -Wno-error=implicit-function-declaration' -j".format(**dirs))
35 self._run("cd {build} && make install DESTDIR={install}".format(**dirs)) 46 self._run("cd {build} && make install DESTDIR={install}".format(**dirs))
36 47
37 self.check_elf(os.path.join(dirs["install"], "usr", "local", "bin", "cpio")) 48 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/assimp.py b/meta/lib/oeqa/sdk/cases/cmake.py
index e986838aea..070682ef08 100644
--- a/meta/lib/oeqa/sdk/cases/assimp.py
+++ b/meta/lib/oeqa/sdk/cases/cmake.py
@@ -13,22 +13,24 @@ from oeqa.sdk.case import OESDKTestCase
13from oeqa.utils.subprocesstweak import errors_have_output 13from oeqa.utils.subprocesstweak import errors_have_output
14errors_have_output() 14errors_have_output()
15 15
16class BuildAssimp(OESDKTestCase): 16class CMakeTest(OESDKTestCase):
17 """ 17 """
18 Test case to build a project using cmake. 18 Test case to build a project using cmake.
19 """ 19 """
20 20
21 def setUp(self): 21 def setUp(self):
22 if not (self.tc.hasHostPackage("nativesdk-cmake") or 22 libc = self.td.get("TCLIBC")
23 self.tc.hasHostPackage("cmake-native")): 23 if libc in [ 'newlib' ]:
24 raise unittest.SkipTest("Needs cmake") 24 raise unittest.SkipTest("CMakeTest class: SDK doesn't contain a supported C library")
25
26 self.ensure_host_package("cmake")
25 27
26 def test_assimp(self): 28 def test_assimp(self):
27 with tempfile.TemporaryDirectory(prefix="assimp", dir=self.tc.sdk_dir) as testdir: 29 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") 30 tarball = self.fetch(testdir, self.td["DL_DIR"], "https://github.com/assimp/assimp/archive/v5.4.1.tar.gz")
29 31
30 dirs = {} 32 dirs = {}
31 dirs["source"] = os.path.join(testdir, "assimp-5.3.1") 33 dirs["source"] = os.path.join(testdir, "assimp-5.4.1")
32 dirs["build"] = os.path.join(testdir, "build") 34 dirs["build"] = os.path.join(testdir, "build")
33 dirs["install"] = os.path.join(testdir, "install") 35 dirs["install"] = os.path.join(testdir, "install")
34 36
@@ -39,7 +41,7 @@ class BuildAssimp(OESDKTestCase):
39 self._run("sed -i '/# ifdef _FILE_OFFSET_BITS/I,+2 d' {source}/contrib/zlib/gzguts.h".format(**dirs)) 41 self._run("sed -i '/# ifdef _FILE_OFFSET_BITS/I,+2 d' {source}/contrib/zlib/gzguts.h".format(**dirs))
40 os.makedirs(dirs["build"]) 42 os.makedirs(dirs["build"])
41 43
42 self._run("cd {build} && cmake -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DASSIMP_BUILD_ZLIB=ON {source}".format(**dirs)) 44 self._run("cd {build} && cmake -DASSIMP_WARNINGS_AS_ERRORS=OFF -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DASSIMP_BUILD_ZLIB=ON {source}".format(**dirs))
43 self._run("cmake --build {build} -- -j".format(**dirs)) 45 self._run("cmake --build {build} -- -j".format(**dirs))
44 self._run("cmake --build {build} --target install -- DESTDIR={install}".format(**dirs)) 46 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")) 47 self.check_elf(os.path.join(dirs["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/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..0aa6f702e4
--- /dev/null
+++ b/meta/lib/oeqa/sdk/cases/kmod.py
@@ -0,0 +1,39 @@
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 self.ensure_target_package("kernel-devsrc")
25 # These targets need to be built before kernel modules can be built.
26 self._run("make -j -C $OECORE_TARGET_SYSROOT/usr/src/kernel prepare scripts")
27
28 with tempfile.TemporaryDirectory(prefix="cryptodev", dir=self.tc.sdk_dir) as testdir:
29 git_url = "https://github.com/cryptodev-linux/cryptodev-linux"
30 # This is a knnown-good commit post-1.13 that builds with kernel 6.7+
31 git_sha = "bb8bc7cf60d2c0b097c8b3b0e807f805b577a53f"
32
33 sourcedir = os.path.join(testdir, "cryptodev-linux")
34 subprocess.check_output(["git", "clone", git_url, sourcedir], stderr=subprocess.STDOUT)
35 self.assertTrue(os.path.isdir(sourcedir))
36 subprocess.check_output(["git", "-C", sourcedir, "checkout", git_sha], stderr=subprocess.STDOUT)
37
38 self._run("make -C %s V=1 KERNEL_DIR=$OECORE_TARGET_SYSROOT/usr/src/kernel" % sourcedir)
39 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..e1e2484820 100644
--- a/meta/lib/oeqa/sdk/cases/buildlzip.py
+++ b/meta/lib/oeqa/sdk/cases/makefile.py
@@ -4,15 +4,21 @@
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):
17 with tempfile.TemporaryDirectory(prefix="lzip", dir=self.tc.sdk_dir) as testdir: 23 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") 24 tarball = self.fetch(testdir, self.td["DL_DIR"], "http://downloads.yoctoproject.org/mirror/sources/lzip-1.19.tar.gz")
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..52b702b6a2 100644
--- a/meta/lib/oeqa/sdk/testsdk.py
+++ b/meta/lib/oeqa/sdk/testsdk.py
@@ -114,7 +114,8 @@ class TestSDK(TestSDKBase):
114 host_pkg_manifest=host_pkg_manifest, **context_args) 114 host_pkg_manifest=host_pkg_manifest, **context_args)
115 115
116 try: 116 try:
117 tc.loadTests(self.context_executor_class.default_cases) 117 modules = (d.getVar("TESTSDK_SUITES") or "").split()
118 tc.loadTests(self.context_executor_class.default_cases, modules)
118 except Exception as e: 119 except Exception as e:
119 import traceback 120 import traceback
120 bb.fatal("Loading tests failed:\n%s" % traceback.format_exc()) 121 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/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..68b0377720 100644
--- a/meta/lib/oeqa/selftest/cases/bblayers.py
+++ b/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -240,3 +240,34 @@ class BitbakeLayers(OESelftestTestCase):
240 self.assertEqual(first_desc_2, '', "Describe not cleared: '{}'".format(first_desc_2)) 240 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)) 241 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)) 242 self.assertEqual(second_desc_2, second_desc_1, "Describe should not be updated: '{}'".format(second_desc_2))
243
244class BitbakeConfigBuild(OESelftestTestCase):
245 def test_enable_disable_fragments(self):
246 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
247 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
248
249 runCmd('bitbake-config-build enable-fragment selftest/test-fragment')
250 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue')
251 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
252
253 runCmd('bitbake-config-build enable-fragment selftest/more-fragments-here/test-another-fragment')
254 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), 'somevalue')
255 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue')
256
257 fragment_metadata_command = "bitbake-getvar -f {} --value {}"
258 result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_SUMMARY"))
259 self.assertIn("This is a configuration fragment intended for testing in oe-selftest context", result.output)
260 result = runCmd(fragment_metadata_command.format("selftest/test-fragment", "BB_CONF_FRAGMENT_DESCRIPTION"))
261 self.assertIn("It defines a variable that can be checked inside the test.", result.output)
262 result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_SUMMARY"))
263 self.assertIn("This is a second configuration fragment intended for testing in oe-selftest context", result.output)
264 result = runCmd(fragment_metadata_command.format("selftest/more-fragments-here/test-another-fragment", "BB_CONF_FRAGMENT_DESCRIPTION"))
265 self.assertIn("It defines another variable that can be checked inside the test.", result.output)
266
267 runCmd('bitbake-config-build disable-fragment selftest/test-fragment')
268 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
269 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), 'someothervalue')
270
271 runCmd('bitbake-config-build disable-fragment selftest/more-fragments-here/test-another-fragment')
272 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_VARIABLE'), None)
273 self.assertEqual(get_bb_var('SELFTEST_FRAGMENT_ANOTHER_VARIABLE'), None)
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..511c666554 100644
--- a/meta/lib/oeqa/selftest/cases/buildhistory.py
+++ b/meta/lib/oeqa/selftest/cases/buildhistory.py
@@ -9,10 +9,10 @@ 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'])
@@ -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..767e19bd88 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 """
@@ -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 bc1e40ef83..74a7727cc0 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -64,11 +64,15 @@ def setUpModule():
64 # under COREBASE and we don't want to copy that, so we have 64 # under COREBASE and we don't want to copy that, so we have
65 # to be selective. 65 # to be selective.
66 result = runCmd('git status --porcelain', cwd=oldreporoot) 66 result = runCmd('git status --porcelain', cwd=oldreporoot)
67
68 # Also copy modifications to the 'scripts/' directory
69 canonical_layerpath_scripts = os.path.normpath(canonical_layerpath + "../scripts")
70
67 for line in result.output.splitlines(): 71 for line in result.output.splitlines():
68 if line.startswith(' M ') or line.startswith('?? '): 72 if line.startswith(' M ') or line.startswith('?? '):
69 relpth = line.split()[1] 73 relpth = line.split()[1]
70 pth = os.path.join(oldreporoot, relpth) 74 pth = os.path.join(oldreporoot, relpth)
71 if pth.startswith(canonical_layerpath): 75 if pth.startswith(canonical_layerpath) or pth.startswith(canonical_layerpath_scripts):
72 if relpth.endswith('/'): 76 if relpth.endswith('/'):
73 destdir = os.path.join(corecopydir, relpth) 77 destdir = os.path.join(corecopydir, relpth)
74 # avoid race condition by not copying .pyc files YPBZ#13421,13803 78 # avoid race condition by not copying .pyc files YPBZ#13421,13803
@@ -286,10 +290,13 @@ class DevtoolTestCase(OESelftestTestCase):
286 else: 290 else:
287 self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') 291 self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test')
288 292
289 def _test_devtool_add_git_url(self, git_url, version, pn, resulting_src_uri): 293 def _test_devtool_add_git_url(self, git_url, version, pn, resulting_src_uri, srcrev=None):
290 self.track_for_cleanup(self.workspacedir) 294 self.track_for_cleanup(self.workspacedir)
291 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') 295 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
292 result = runCmd('devtool add --version %s %s %s' % (version, pn, git_url)) 296 command = 'devtool add --version %s %s %s' % (version, pn, git_url)
297 if srcrev :
298 command += ' --srcrev %s' %srcrev
299 result = runCmd(command)
293 self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') 300 self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
294 # Check the recipe name is correct 301 # Check the recipe name is correct
295 recipefile = get_bb_var('FILE', pn) 302 recipefile = get_bb_var('FILE', pn)
@@ -314,7 +321,7 @@ class DevtoolBase(DevtoolTestCase):
314 cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate 321 cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate
315 cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n' 322 cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n'
316 % cls.original_sstate) 323 % cls.original_sstate)
317 cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687"\n') 324 cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yoctoproject.org:8686"\n')
318 325
319 @classmethod 326 @classmethod
320 def tearDownClass(cls): 327 def tearDownClass(cls):
@@ -462,7 +469,7 @@ class DevtoolAddTests(DevtoolBase):
462 checkvars = {} 469 checkvars = {}
463 checkvars['LICENSE'] = 'GPL-2.0-only' 470 checkvars['LICENSE'] = 'GPL-2.0-only'
464 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263' 471 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263'
465 checkvars['S'] = '${WORKDIR}/git' 472 checkvars['S'] = None
466 checkvars['PV'] = '0.1+git' 473 checkvars['PV'] = '0.1+git'
467 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master' 474 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master'
468 checkvars['SRCREV'] = srcrev 475 checkvars['SRCREV'] = srcrev
@@ -479,11 +486,12 @@ class DevtoolAddTests(DevtoolBase):
479 486
480 def test_devtool_add_git_style2(self): 487 def test_devtool_add_git_style2(self):
481 version = 'v3.1.0' 488 version = 'v3.1.0'
489 srcrev = 'v3.1.0'
482 pn = 'mbedtls' 490 pn = 'mbedtls'
483 # this will trigger reformat_git_uri with branch parameter in url 491 # this will trigger reformat_git_uri with branch parameter in url
484 git_url = "'git://git@github.com/ARMmbed/mbedtls.git;protocol=https'" 492 git_url = "'git://git@github.com/ARMmbed/mbedtls.git;protocol=https'"
485 resulting_src_uri = "gitsm://git@github.com/ARMmbed/mbedtls.git;protocol=https;branch=master" 493 resulting_src_uri = "git://git@github.com/ARMmbed/mbedtls.git;protocol=https;branch=master"
486 self._test_devtool_add_git_url(git_url, version, pn, resulting_src_uri) 494 self._test_devtool_add_git_url(git_url, version, pn, resulting_src_uri, srcrev)
487 495
488 def test_devtool_add_library(self): 496 def test_devtool_add_library(self):
489 # Fetch source 497 # Fetch source
@@ -557,7 +565,7 @@ class DevtoolAddTests(DevtoolBase):
557 recipefile = get_bb_var('FILE', testrecipe) 565 recipefile = get_bb_var('FILE', testrecipe)
558 self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named') 566 self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named')
559 checkvars = {} 567 checkvars = {}
560 checkvars['S'] = '${WORKDIR}/MarkupSafe-${PV}' 568 checkvars['S'] = '${UNPACKDIR}/MarkupSafe-${PV}'
561 checkvars['SRC_URI'] = url.replace(testver, '${PV}') 569 checkvars['SRC_URI'] = url.replace(testver, '${PV}')
562 self._test_recipe_contents(recipefile, checkvars, []) 570 self._test_recipe_contents(recipefile, checkvars, [])
563 # Try with version specified 571 # Try with version specified
@@ -574,7 +582,7 @@ class DevtoolAddTests(DevtoolBase):
574 recipefile = get_bb_var('FILE', testrecipe) 582 recipefile = get_bb_var('FILE', testrecipe)
575 self.assertIn('%s_%s.bb' % (testrecipe, fakever), recipefile, 'Recipe file incorrectly named') 583 self.assertIn('%s_%s.bb' % (testrecipe, fakever), recipefile, 'Recipe file incorrectly named')
576 checkvars = {} 584 checkvars = {}
577 checkvars['S'] = '${WORKDIR}/MarkupSafe-%s' % testver 585 checkvars['S'] = '${UNPACKDIR}/MarkupSafe-%s' % testver
578 checkvars['SRC_URI'] = url 586 checkvars['SRC_URI'] = url
579 self._test_recipe_contents(recipefile, checkvars, []) 587 self._test_recipe_contents(recipefile, checkvars, [])
580 588
@@ -601,7 +609,7 @@ class DevtoolAddTests(DevtoolBase):
601 recipefile = get_bb_var('FILE', testrecipe) 609 recipefile = get_bb_var('FILE', testrecipe)
602 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') 610 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
603 checkvars = {} 611 checkvars = {}
604 checkvars['S'] = '${WORKDIR}/git' 612 checkvars['S'] = None
605 checkvars['PV'] = '1.0+git' 613 checkvars['PV'] = '1.0+git'
606 checkvars['SRC_URI'] = url_branch 614 checkvars['SRC_URI'] = url_branch
607 checkvars['SRCREV'] = '${AUTOREV}' 615 checkvars['SRCREV'] = '${AUTOREV}'
@@ -620,7 +628,7 @@ class DevtoolAddTests(DevtoolBase):
620 recipefile = get_bb_var('FILE', testrecipe) 628 recipefile = get_bb_var('FILE', testrecipe)
621 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') 629 self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
622 checkvars = {} 630 checkvars = {}
623 checkvars['S'] = '${WORKDIR}/git' 631 checkvars['S'] = None
624 checkvars['PV'] = '1.5+git' 632 checkvars['PV'] = '1.5+git'
625 checkvars['SRC_URI'] = url_branch 633 checkvars['SRC_URI'] = url_branch
626 checkvars['SRCREV'] = checkrev 634 checkvars['SRCREV'] = checkrev
@@ -749,6 +757,25 @@ class DevtoolModifyTests(DevtoolBase):
749 result = runCmd('devtool status') 757 result = runCmd('devtool status')
750 self.assertNotIn('mdadm', result.output) 758 self.assertNotIn('mdadm', result.output)
751 759
760 def test_devtool_modify_go(self):
761 import oe.path
762 from tempfile import TemporaryDirectory
763 with TemporaryDirectory(prefix='devtoolqa') as tempdir:
764 self.track_for_cleanup(self.workspacedir)
765 self.add_command_to_tearDown('bitbake -c clean go-helloworld')
766 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
767 result = runCmd('devtool modify go-helloworld -x %s' % tempdir)
768 self.assertExists(
769 oe.path.join(tempdir, 'src', 'golang.org', 'x', 'example', 'go.mod'),
770 'Extracted source could not be found'
771 )
772 self.assertExists(
773 oe.path.join(self.workspacedir, 'conf', 'layer.conf'),
774 'Workspace directory not created'
775 )
776 matches = glob.glob(oe.path.join(self.workspacedir, 'appends', 'go-helloworld_*.bbappend'))
777 self.assertTrue(matches, 'bbappend not created %s' % result.output)
778
752 def test_devtool_buildclean(self): 779 def test_devtool_buildclean(self):
753 def assertFile(path, *paths): 780 def assertFile(path, *paths):
754 f = os.path.join(path, *paths) 781 f = os.path.join(path, *paths)
@@ -875,13 +902,8 @@ class DevtoolModifyTests(DevtoolBase):
875 self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) 902 self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
876 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') 903 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
877 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) 904 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
878 srcfile = os.path.join(tempdir, 'oe-local-files/share/dot.bashrc') 905 srcfile = os.path.join(tempdir, 'share/dot.bashrc')
879 srclink = os.path.join(tempdir, 'share/dot.bashrc')
880 self.assertExists(srcfile, 'Extracted source could not be found') 906 self.assertExists(srcfile, 'Extracted source could not be found')
881 if os.path.islink(srclink) and os.path.exists(srclink) and os.path.samefile(srcfile, srclink):
882 correct_symlink = True
883 self.assertTrue(correct_symlink, 'Source symlink to oe-local-files is broken')
884
885 matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe)) 907 matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe))
886 self.assertTrue(matches, 'bbappend not created') 908 self.assertTrue(matches, 'bbappend not created')
887 # Test devtool status 909 # Test devtool status
@@ -952,9 +974,9 @@ class DevtoolModifyTests(DevtoolBase):
952 # others git:// in SRC_URI 974 # others git:// in SRC_URI
953 # cointains a patch 975 # cointains a patch
954 testrecipe = 'hello-rs' 976 testrecipe = 'hello-rs'
955 bb_vars = get_bb_vars(['SRC_URI', 'FILE', 'WORKDIR', 'CARGO_HOME'], testrecipe) 977 bb_vars = get_bb_vars(['SRC_URI', 'FILE', 'UNPACKDIR', 'CARGO_HOME'], testrecipe)
956 recipefile = bb_vars['FILE'] 978 recipefile = bb_vars['FILE']
957 workdir = bb_vars['WORKDIR'] 979 unpackdir = bb_vars['UNPACKDIR']
958 cargo_home = bb_vars['CARGO_HOME'] 980 cargo_home = bb_vars['CARGO_HOME']
959 src_uri = bb_vars['SRC_URI'].split() 981 src_uri = bb_vars['SRC_URI'].split()
960 self.assertTrue(src_uri[0].startswith('git://'), 982 self.assertTrue(src_uri[0].startswith('git://'),
@@ -1005,7 +1027,7 @@ class DevtoolModifyTests(DevtoolBase):
1005 # Configure the recipe to check that the git dependencies are correctly patched in cargo config 1027 # Configure the recipe to check that the git dependencies are correctly patched in cargo config
1006 bitbake('-c configure %s' % testrecipe) 1028 bitbake('-c configure %s' % testrecipe)
1007 1029
1008 cargo_config_path = os.path.join(cargo_home, 'config') 1030 cargo_config_path = os.path.join(cargo_home, 'config.toml')
1009 with open(cargo_config_path, "r") as f: 1031 with open(cargo_config_path, "r") as f:
1010 cargo_config_contents = [line.strip('\n') for line in f.readlines()] 1032 cargo_config_contents = [line.strip('\n') for line in f.readlines()]
1011 1033
@@ -1025,7 +1047,7 @@ class DevtoolModifyTests(DevtoolBase):
1025 self.assertEqual(parms['type'], 'git-dependency', 'git dependencies uri should have "type=git-dependency"') 1047 self.assertEqual(parms['type'], 'git-dependency', 'git dependencies uri should have "type=git-dependency"')
1026 raw_url = raw_url.replace("git://", '%s://' % parms['protocol']) 1048 raw_url = raw_url.replace("git://", '%s://' % parms['protocol'])
1027 patch_line = '[patch."%s"]' % raw_url 1049 patch_line = '[patch."%s"]' % raw_url
1028 path_patched = os.path.join(workdir, parms['destsuffix']) 1050 path_patched = os.path.join(unpackdir, parms['destsuffix'])
1029 path_override_line = '%s = { path = "%s" }' % (parms['name'], path_patched) 1051 path_override_line = '%s = { path = "%s" }' % (parms['name'], path_patched)
1030 # Would have been better to use tomllib to read this file :/ 1052 # Would have been better to use tomllib to read this file :/
1031 self.assertIn(patch_line, cargo_config_contents) 1053 self.assertIn(patch_line, cargo_config_contents)
@@ -1163,13 +1185,16 @@ class DevtoolUpdateTests(DevtoolBase):
1163 result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir) 1185 result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir)
1164 result = runCmd('git add devtool-new-file', cwd=tempdir) 1186 result = runCmd('git add devtool-new-file', cwd=tempdir)
1165 result = runCmd('git commit -m "Add a new file"', cwd=tempdir) 1187 result = runCmd('git commit -m "Add a new file"', cwd=tempdir)
1166 self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) 1188 cleanup_cmd = 'cd %s; rm %s/*.patch; git add %s; git checkout %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))
1189 self.add_command_to_tearDown(cleanup_cmd)
1167 result = runCmd('devtool update-recipe %s' % testrecipe) 1190 result = runCmd('devtool update-recipe %s' % testrecipe)
1168 result = runCmd('git add minicom', cwd=os.path.dirname(recipefile)) 1191 result = runCmd('git add minicom', cwd=os.path.dirname(recipefile))
1169 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1192 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1170 ('A ', '.*/0001-Change-the-README.patch$'), 1193 ('A ', '.*/0001-Change-the-README.patch$'),
1171 ('A ', '.*/0002-Add-a-new-file.patch$')] 1194 ('A ', '.*/0002-Add-a-new-file.patch$')]
1172 self._check_repo_status(os.path.dirname(recipefile), expected_status) 1195 self._check_repo_status(os.path.dirname(recipefile), expected_status)
1196 result = runCmd(cleanup_cmd)
1197 self._check_repo_status(os.path.dirname(recipefile), [])
1173 1198
1174 def test_devtool_update_recipe_git(self): 1199 def test_devtool_update_recipe_git(self):
1175 # Check preconditions 1200 # Check preconditions
@@ -1226,7 +1251,7 @@ class DevtoolUpdateTests(DevtoolBase):
1226 1251
1227 def test_devtool_update_recipe_append(self): 1252 def test_devtool_update_recipe_append(self):
1228 # Check preconditions 1253 # Check preconditions
1229 testrecipe = 'mdadm' 1254 testrecipe = 'minicom'
1230 bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe) 1255 bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1231 recipefile = bb_vars['FILE'] 1256 recipefile = bb_vars['FILE']
1232 src_uri = bb_vars['SRC_URI'] 1257 src_uri = bb_vars['SRC_URI']
@@ -1244,7 +1269,7 @@ class DevtoolUpdateTests(DevtoolBase):
1244 # Check git repo 1269 # Check git repo
1245 self._check_src_repo(tempsrcdir) 1270 self._check_src_repo(tempsrcdir)
1246 # Add a commit 1271 # Add a commit
1247 result = runCmd("sed 's!\\(#define VERSION\\W*\"[^\"]*\\)\"!\\1-custom\"!' -i ReadMe.c", cwd=tempsrcdir) 1272 result = runCmd('echo "Additional line" >> README', cwd=tempsrcdir)
1248 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir) 1273 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
1249 self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe)) 1274 self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe))
1250 # Create a temporary layer and add it to bblayers.conf 1275 # Create a temporary layer and add it to bblayers.conf
@@ -1274,7 +1299,7 @@ class DevtoolUpdateTests(DevtoolBase):
1274 with open(bbappendfile, 'r') as f: 1299 with open(bbappendfile, 'r') as f:
1275 self.assertEqual(expectedlines, f.readlines()) 1300 self.assertEqual(expectedlines, f.readlines())
1276 # Drop new commit and check patch gets deleted 1301 # Drop new commit and check patch gets deleted
1277 result = runCmd('git reset HEAD^', cwd=tempsrcdir) 1302 result = runCmd('git reset HEAD^ --hard', cwd=tempsrcdir)
1278 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) 1303 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1279 self.assertNotExists(patchfile, 'Patch file not deleted') 1304 self.assertNotExists(patchfile, 'Patch file not deleted')
1280 expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n', 1305 expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n',
@@ -1283,6 +1308,7 @@ class DevtoolUpdateTests(DevtoolBase):
1283 self.assertEqual(expectedlines2, f.readlines()) 1308 self.assertEqual(expectedlines2, f.readlines())
1284 # Put commit back and check we can run it if layer isn't in bblayers.conf 1309 # Put commit back and check we can run it if layer isn't in bblayers.conf
1285 os.remove(bbappendfile) 1310 os.remove(bbappendfile)
1311 result = runCmd('echo "Additional line" >> README', cwd=tempsrcdir)
1286 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir) 1312 result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
1287 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) 1313 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1288 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) 1314 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
@@ -1357,7 +1383,7 @@ class DevtoolUpdateTests(DevtoolBase):
1357 with open(bbappendfile, 'r') as f: 1383 with open(bbappendfile, 'r') as f:
1358 self.assertEqual(expectedlines, set(f.readlines())) 1384 self.assertEqual(expectedlines, set(f.readlines()))
1359 # Drop new commit and check SRCREV changes 1385 # Drop new commit and check SRCREV changes
1360 result = runCmd('git reset HEAD^', cwd=tempsrcdir) 1386 result = runCmd('git reset HEAD^ --hard', cwd=tempsrcdir)
1361 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) 1387 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1362 self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created') 1388 self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
1363 result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) 1389 result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
@@ -1369,6 +1395,7 @@ class DevtoolUpdateTests(DevtoolBase):
1369 self.assertEqual(expectedlines, set(f.readlines())) 1395 self.assertEqual(expectedlines, set(f.readlines()))
1370 # Put commit back and check we can run it if layer isn't in bblayers.conf 1396 # Put commit back and check we can run it if layer isn't in bblayers.conf
1371 os.remove(bbappendfile) 1397 os.remove(bbappendfile)
1398 result = runCmd('echo "# Additional line" >> Makefile.am', cwd=tempsrcdir)
1372 result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir) 1399 result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir)
1373 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) 1400 result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1374 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) 1401 result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
@@ -1400,22 +1427,39 @@ class DevtoolUpdateTests(DevtoolBase):
1400 # Try building just to ensure we haven't broken that 1427 # Try building just to ensure we haven't broken that
1401 bitbake("%s" % testrecipe) 1428 bitbake("%s" % testrecipe)
1402 # Edit / commit local source 1429 # Edit / commit local source
1403 runCmd('echo "/* Foobar */" >> oe-local-files/makedevs.c', cwd=tempdir) 1430 runCmd('echo "/* Foobar */" >> makedevs.c', cwd=tempdir)
1404 runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir) 1431 runCmd('echo "Foo" > new-local', cwd=tempdir)
1405 runCmd('echo "Bar" > new-file', cwd=tempdir) 1432 runCmd('echo "Bar" > new-file', cwd=tempdir)
1406 runCmd('git add new-file', cwd=tempdir) 1433 runCmd('git add new-file', cwd=tempdir)
1407 runCmd('git commit -m "Add new file"', cwd=tempdir) 1434 runCmd('git commit -m "Add new file"', cwd=tempdir)
1408 self.add_command_to_tearDown('cd %s; git clean -fd .; git checkout .' % 1435 runCmd('git add new-local', cwd=tempdir)
1409 os.path.dirname(recipefile))
1410 runCmd('devtool update-recipe %s' % testrecipe) 1436 runCmd('devtool update-recipe %s' % testrecipe)
1411 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1437 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1412 (' M', '.*/makedevs/makedevs.c$'), 1438 (' M', '.*/makedevs/makedevs.c$'),
1413 ('??', '.*/makedevs/new-local$'), 1439 ('??', '.*/makedevs/new-local$'),
1414 ('??', '.*/makedevs/0001-Add-new-file.patch$')] 1440 ('??', '.*/makedevs/0001-Add-new-file.patch$')]
1415 self._check_repo_status(os.path.dirname(recipefile), expected_status) 1441 self._check_repo_status(os.path.dirname(recipefile), expected_status)
1416 1442 # Now try to update recipe in another layer, so first, clean it
1417 def test_devtool_update_recipe_local_files_2(self): 1443 runCmd('cd %s; git clean -fd .; git checkout .' % os.path.dirname(recipefile))
1418 """Check local source files support when oe-local-files is in Git""" 1444 # Create a temporary layer and add it to bblayers.conf
1445 self._create_temp_layer(templayerdir, True, 'templayer')
1446 # Update recipe in templayer
1447 result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1448 self.assertNotIn('WARNING:', result.output)
1449 # Check recipe is still clean
1450 self._check_repo_status(os.path.dirname(recipefile), [])
1451 splitpath = os.path.dirname(recipefile).split(os.sep)
1452 appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1])
1453 bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir)
1454 patchfile = os.path.join(appenddir, testrecipe, '0001-Add-new-file.patch')
1455 new_local_file = os.path.join(appenddir, testrecipe, 'new_local')
1456 local_file = os.path.join(appenddir, testrecipe, 'makedevs.c')
1457 self.assertExists(patchfile, 'Patch file 0001-Add-new-file.patch not created')
1458 self.assertExists(local_file, 'File makedevs.c not created')
1459 self.assertExists(patchfile, 'File new_local not created')
1460
1461 def _test_devtool_update_recipe_local_files_2(self):
1462 """Check local source files support when editing local files in Git"""
1419 testrecipe = 'devtool-test-local' 1463 testrecipe = 'devtool-test-local'
1420 recipefile = get_bb_var('FILE', testrecipe) 1464 recipefile = get_bb_var('FILE', testrecipe)
1421 recipedir = os.path.dirname(recipefile) 1465 recipedir = os.path.dirname(recipefile)
@@ -1430,17 +1474,13 @@ class DevtoolUpdateTests(DevtoolBase):
1430 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) 1474 result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1431 # Check git repo 1475 # Check git repo
1432 self._check_src_repo(tempdir) 1476 self._check_src_repo(tempdir)
1433 # Add oe-local-files to Git
1434 runCmd('rm oe-local-files/.gitignore', cwd=tempdir)
1435 runCmd('git add oe-local-files', cwd=tempdir)
1436 runCmd('git commit -m "Add local sources"', cwd=tempdir)
1437 # Edit / commit local sources 1477 # Edit / commit local sources
1438 runCmd('echo "# Foobar" >> oe-local-files/file1', cwd=tempdir) 1478 runCmd('echo "# Foobar" >> file1', cwd=tempdir)
1439 runCmd('git commit -am "Edit existing file"', cwd=tempdir) 1479 runCmd('git commit -am "Edit existing file"', cwd=tempdir)
1440 runCmd('git rm oe-local-files/file2', cwd=tempdir) 1480 runCmd('git rm file2', cwd=tempdir)
1441 runCmd('git commit -m"Remove file"', cwd=tempdir) 1481 runCmd('git commit -m"Remove file"', cwd=tempdir)
1442 runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir) 1482 runCmd('echo "Foo" > new-local', cwd=tempdir)
1443 runCmd('git add oe-local-files/new-local', cwd=tempdir) 1483 runCmd('git add new-local', cwd=tempdir)
1444 runCmd('git commit -m "Add new local file"', cwd=tempdir) 1484 runCmd('git commit -m "Add new local file"', cwd=tempdir)
1445 runCmd('echo "Gar" > new-file', cwd=tempdir) 1485 runCmd('echo "Gar" > new-file', cwd=tempdir)
1446 runCmd('git add new-file', cwd=tempdir) 1486 runCmd('git add new-file', cwd=tempdir)
@@ -1449,7 +1489,7 @@ class DevtoolUpdateTests(DevtoolBase):
1449 os.path.dirname(recipefile)) 1489 os.path.dirname(recipefile))
1450 # Checkout unmodified file to working copy -> devtool should still pick 1490 # Checkout unmodified file to working copy -> devtool should still pick
1451 # the modified version from HEAD 1491 # the modified version from HEAD
1452 runCmd('git checkout HEAD^ -- oe-local-files/file1', cwd=tempdir) 1492 runCmd('git checkout HEAD^ -- file1', cwd=tempdir)
1453 runCmd('devtool update-recipe %s' % testrecipe) 1493 runCmd('devtool update-recipe %s' % testrecipe)
1454 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), 1494 expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1455 (' M', '.*/file1$'), 1495 (' M', '.*/file1$'),
@@ -1524,7 +1564,7 @@ class DevtoolUpdateTests(DevtoolBase):
1524 # (don't bother with cleaning the recipe on teardown, we won't be building it) 1564 # (don't bother with cleaning the recipe on teardown, we won't be building it)
1525 result = runCmd('devtool modify %s' % testrecipe) 1565 result = runCmd('devtool modify %s' % testrecipe)
1526 # Modify one file 1566 # Modify one file
1527 runCmd('echo "Another line" >> file2', cwd=os.path.join(self.workspacedir, 'sources', testrecipe, 'oe-local-files')) 1567 runCmd('echo "Another line" >> file2', cwd=os.path.join(self.workspacedir, 'sources', testrecipe))
1528 self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) 1568 self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1529 result = runCmd('devtool update-recipe %s' % testrecipe) 1569 result = runCmd('devtool update-recipe %s' % testrecipe)
1530 expected_status = [(' M', '.*/%s/file2$' % testrecipe)] 1570 expected_status = [(' M', '.*/%s/file2$' % testrecipe)]
@@ -1587,12 +1627,12 @@ class DevtoolUpdateTests(DevtoolBase):
1587 # Check preconditions 1627 # Check preconditions
1588 testrecipe = 'dos2unix' 1628 testrecipe = 'dos2unix'
1589 self.append_config('ERROR_QA:remove:pn-dos2unix = "patch-status"\n') 1629 self.append_config('ERROR_QA:remove:pn-dos2unix = "patch-status"\n')
1590 bb_vars = get_bb_vars(['SRC_URI', 'S', 'WORKDIR', 'FILE'], testrecipe) 1630 bb_vars = get_bb_vars(['SRC_URI', 'S', 'UNPACKDIR', 'FILE', 'BB_GIT_DEFAULT_DESTSUFFIX'], testrecipe)
1591 self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe) 1631 self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe)
1592 workdir_git = '%s/git/' % bb_vars['WORKDIR'] 1632 unpackdir_git = '%s/%s/' % (bb_vars['UNPACKDIR'], bb_vars['BB_GIT_DEFAULT_DESTSUFFIX'])
1593 if not bb_vars['S'].startswith(workdir_git): 1633 if not bb_vars['S'].startswith(unpackdir_git):
1594 self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe) 1634 self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe)
1595 subdir = bb_vars['S'].split(workdir_git, 1)[1] 1635 subdir = bb_vars['S'].split(unpackdir_git, 1)[1]
1596 # Clean up anything in the workdir/sysroot/sstate cache 1636 # Clean up anything in the workdir/sysroot/sstate cache
1597 bitbake('%s -c cleansstate' % testrecipe) 1637 bitbake('%s -c cleansstate' % testrecipe)
1598 # Try modifying a recipe 1638 # Try modifying a recipe
@@ -1720,6 +1760,8 @@ class DevtoolExtractTests(DevtoolBase):
1720 self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found') 1760 self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
1721 self._check_src_repo(tempdir) 1761 self._check_src_repo(tempdir)
1722 1762
1763class DevtoolResetTests(DevtoolBase):
1764
1723 def test_devtool_reset_all(self): 1765 def test_devtool_reset_all(self):
1724 tempdir = tempfile.mkdtemp(prefix='devtoolqa') 1766 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1725 self.track_for_cleanup(tempdir) 1767 self.track_for_cleanup(tempdir)
@@ -1746,6 +1788,21 @@ class DevtoolExtractTests(DevtoolBase):
1746 matches2 = glob.glob(stampprefix2 + '*') 1788 matches2 = glob.glob(stampprefix2 + '*')
1747 self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) 1789 self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2)
1748 1790
1791 def test_devtool_reset_re_plus_plus(self):
1792 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1793 self.track_for_cleanup(tempdir)
1794 self.track_for_cleanup(self.workspacedir)
1795 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1796 testrecipe = 'devtool-test-reset-re++'
1797 result = runCmd('devtool modify %s' % testrecipe)
1798 result = runCmd('devtool reset -n %s' % testrecipe)
1799 self.assertIn(testrecipe, result.output)
1800 result = runCmd('devtool status')
1801 self.assertNotIn(testrecipe, result.output)
1802 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', testrecipe), 'Recipe directory should not exist after resetting')
1803
1804class DevtoolDeployTargetTests(DevtoolBase):
1805
1749 @OETestTag("runqemu") 1806 @OETestTag("runqemu")
1750 def test_devtool_deploy_target(self): 1807 def test_devtool_deploy_target(self):
1751 self._check_runqemu_prerequisites() 1808 self._check_runqemu_prerequisites()
@@ -1753,6 +1810,8 @@ class DevtoolExtractTests(DevtoolBase):
1753 # Definitions 1810 # Definitions
1754 testrecipe = 'mdadm' 1811 testrecipe = 'mdadm'
1755 testfile = '/sbin/mdadm' 1812 testfile = '/sbin/mdadm'
1813 if "usrmerge" in get_bb_var('DISTRO_FEATURES'):
1814 testfile = '/usr/sbin/mdadm'
1756 testimage = 'oe-selftest-image' 1815 testimage = 'oe-selftest-image'
1757 testcommand = '/sbin/mdadm --help' 1816 testcommand = '/sbin/mdadm --help'
1758 # Build an image to run 1817 # Build an image to run
@@ -1811,6 +1870,8 @@ class DevtoolExtractTests(DevtoolBase):
1811 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True) 1870 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
1812 self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have') 1871 self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
1813 1872
1873class DevtoolBuildImageTests(DevtoolBase):
1874
1814 def test_devtool_build_image(self): 1875 def test_devtool_build_image(self):
1815 """Test devtool build-image plugin""" 1876 """Test devtool build-image plugin"""
1816 # Check preconditions 1877 # Check preconditions
@@ -1982,6 +2043,52 @@ class DevtoolUpgradeTests(DevtoolBase):
1982 newlines = f.readlines() 2043 newlines = f.readlines()
1983 self.assertEqual(desiredlines, newlines) 2044 self.assertEqual(desiredlines, newlines)
1984 2045
2046 def test_devtool_upgrade_recipe_upgrade_extra_tasks(self):
2047 # Check preconditions
2048 self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
2049 self.track_for_cleanup(self.workspacedir)
2050 self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
2051 recipe = 'python3-guessing-game'
2052 version = '0.2.0'
2053 commit = '40cf004c2772ffa20ea803fa3be1528a75be3e98'
2054 oldrecipefile = get_bb_var('FILE', recipe)
2055 oldcratesincfile = os.path.join(os.path.dirname(oldrecipefile), os.path.basename(oldrecipefile).strip('_git.bb') + '-crates.inc')
2056 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
2057 self.track_for_cleanup(tempdir)
2058 # Check that recipe is not already under devtool control
2059 result = runCmd('devtool status')
2060 self.assertNotIn(recipe, result.output)
2061 # Check upgrade
2062 result = runCmd('devtool upgrade %s %s --version %s --srcrev %s' % (recipe, tempdir, version, commit))
2063 # Check if srctree at least is populated
2064 self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, commit))
2065 # Check new recipe file and new -crates.inc files are present
2066 newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldrecipefile))
2067 newcratesincfile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldcratesincfile))
2068 self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
2069 self.assertExists(newcratesincfile, 'Recipe crates.inc file should exist after upgrade')
2070 # Check devtool status and make sure recipe is present
2071 result = runCmd('devtool status')
2072 self.assertIn(recipe, result.output)
2073 self.assertIn(tempdir, result.output)
2074 # Check recipe got changed as expected
2075 with open(oldrecipefile + '.upgraded', 'r') as f:
2076 desiredlines = f.readlines()
2077 with open(newrecipefile, 'r') as f:
2078 newlines = f.readlines()
2079 self.assertEqual(desiredlines, newlines)
2080 # Check crates.inc got changed as expected
2081 with open(oldcratesincfile + '.upgraded', 'r') as f:
2082 desiredlines = f.readlines()
2083 with open(newcratesincfile, 'r') as f:
2084 newlines = f.readlines()
2085 self.assertEqual(desiredlines, newlines)
2086 # Check devtool reset recipe
2087 result = runCmd('devtool reset %s -n' % recipe)
2088 result = runCmd('devtool status')
2089 self.assertNotIn(recipe, result.output)
2090 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
2091
1985 def test_devtool_layer_plugins(self): 2092 def test_devtool_layer_plugins(self):
1986 """Test that devtool can use plugins from other layers. 2093 """Test that devtool can use plugins from other layers.
1987 2094
@@ -2307,7 +2414,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2307 newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename) 2414 newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename)
2308 self.assertExists(newsrctree, 'Source directory not renamed') 2415 self.assertExists(newsrctree, 'Source directory not renamed')
2309 checkvars = {} 2416 checkvars = {}
2310 checkvars['S'] = '${WORKDIR}/%s-%s' % (recipename, recipever) 2417 checkvars['S'] = '${UNPACKDIR}/%s-%s' % (recipename, recipever)
2311 checkvars['SRC_URI'] = url 2418 checkvars['SRC_URI'] = url
2312 self._test_recipe_contents(newrecipefile, checkvars, []) 2419 self._test_recipe_contents(newrecipefile, checkvars, [])
2313 # Try again - change just name this time 2420 # Try again - change just name this time
@@ -2319,7 +2426,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2319 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists') 2426 self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
2320 self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed') 2427 self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed')
2321 checkvars = {} 2428 checkvars = {}
2322 checkvars['S'] = '${WORKDIR}/%s-${PV}' % recipename 2429 checkvars['S'] = '${UNPACKDIR}/%s-${PV}' % recipename
2323 checkvars['SRC_URI'] = url.replace(recipever, '${PV}') 2430 checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
2324 self._test_recipe_contents(newrecipefile, checkvars, []) 2431 self._test_recipe_contents(newrecipefile, checkvars, [])
2325 # Try again - change just version this time 2432 # Try again - change just version this time
@@ -2330,7 +2437,7 @@ class DevtoolUpgradeTests(DevtoolBase):
2330 self.assertExists(newrecipefile, 'Recipe file not renamed') 2437 self.assertExists(newrecipefile, 'Recipe file not renamed')
2331 self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists') 2438 self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists')
2332 checkvars = {} 2439 checkvars = {}
2333 checkvars['S'] = '${WORKDIR}/${BPN}-%s' % recipever 2440 checkvars['S'] = '${UNPACKDIR}/${BPN}-%s' % recipever
2334 checkvars['SRC_URI'] = url 2441 checkvars['SRC_URI'] = url
2335 self._test_recipe_contents(newrecipefile, checkvars, []) 2442 self._test_recipe_contents(newrecipefile, checkvars, [])
2336 2443
@@ -2452,7 +2559,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
2452 self.track_for_cleanup(tempdir) 2559 self.track_for_cleanup(tempdir)
2453 self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name) 2560 self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name)
2454 2561
2455 result = runCmd('devtool modify %s -x %s' % (recipe_name, tempdir)) 2562 result = runCmd('devtool modify %s -x %s --debug-build' % (recipe_name, tempdir))
2456 self.assertExists(os.path.join(tempdir, build_file), 2563 self.assertExists(os.path.join(tempdir, build_file),
2457 'Extracted source could not be found') 2564 'Extracted source could not be found')
2458 self.assertExists(os.path.join(self.workspacedir, 'conf', 2565 self.assertExists(os.path.join(self.workspacedir, 'conf',
@@ -2504,11 +2611,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
2504 i_and_d_script_path = os.path.join( 2611 i_and_d_script_path = os.path.join(
2505 self._workspace_scripts_dir(recipe_name), i_and_d_script) 2612 self._workspace_scripts_dir(recipe_name), i_and_d_script)
2506 self.assertExists(i_and_d_script_path) 2613 self.assertExists(i_and_d_script_path)
2507 del_script = "delete_package_dirs_" + recipe_id
2508 del_script_path = os.path.join(
2509 self._workspace_scripts_dir(recipe_name), del_script)
2510 self.assertExists(del_script_path)
2511 runCmd(del_script_path, cwd=tempdir)
2512 2614
2513 def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): 2615 def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe):
2514 """Verify deployment and execution in Qemu system work for one recipe. 2616 """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..3c40857747 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 """
78
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
11 448
12class FitImageTests(OESelftestTestCase): 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 = "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 = "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 = "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 = "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 922
150 itspath = [] 923 self._test_fitimage(bb_vars)
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 924
172 for reqpath in req_itspaths: 925 def test_fit_image_sign_initramfs(self):
173 if not reqpath in itspaths:
174 self.fail('Missing section in its file: %s' % reqpath)
175
176 reqsigvalues_image = {
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
186 for itspath, values in sigs.items():
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 = "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 = "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"""
279 1127
280 # The U-Boot fitImage is created as part of the U-Boot recipe 1128 BOOTLOADER_RECIPE = "virtual/bootloader"
281 bitbake("virtual/bootloader") 1129
282 1130 def _fit_get_bb_vars(self, additional_vars=[]):
283 deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') 1131 """Get bb_vars as needed by _test_sign_fit_image
284 machine = get_bb_var('MACHINE') 1132
285 fitimage_its_path = os.path.join(deploy_dir_image, 1133 Call the get_bb_vars function once and get all variables needed by the test case.
286 "u-boot-its-%s" % (machine,)) 1134 """
287 fitimage_path = os.path.join(deploy_dir_image, 1135 internal_used = {
288 "u-boot-fitImage-%s" % (machine,)) 1136 'DEPLOY_DIR_IMAGE',
289 1137 'FIT_HASH_ALG',
290 self.assertTrue(os.path.exists(fitimage_its_path), 1138 'FIT_KEY_GENRSA_ARGS',
291 "%s image tree source doesn't exist" % (fitimage_its_path)) 1139 'FIT_KEY_REQ_ARGS',
292 self.assertTrue(os.path.exists(fitimage_path), 1140 'FIT_KEY_SIGN_PKCS',
293 "%s FIT image doesn't exist" % (fitimage_path)) 1141 'FIT_SIGN_ALG',
294 1142 'FIT_SIGN_INDIVIDUAL',
295 # Check that the type, load address, entrypoint address and default 1143 'FIT_SIGN_NUMBITS',
296 # values for kernel and ramdisk in Image Tree Source are as expected. 1144 'MACHINE',
297 # The order of fields in the below array is important. Not all the 1145 'SPL_MKIMAGE_SIGN_ARGS',
298 # fields are tested, only the key fields that wont vary between 1146 'SPL_SIGN_ENABLE',
299 # different architectures. 1147 'SPL_SIGN_KEYNAME',
1148 'UBOOT_ARCH',
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";',
308 ] 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";',
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: 1272
313 if field_index == len(its_field_check): 1273 The dict generated by this function is supposed to be compared against
314 break 1274 the dict which is generated by the _dump_fitimage function.
315 if its_field_check[field_index] in line: 1275 """
316 field_index +=1 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 }
1306
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)
317 1396
318 if field_index != len(its_field_check): # if its equal, the test passed
319 self.assertTrue(field_index == len(its_field_check),
320 "Fields in Image Tree Source File %s did not match, error in finding %s"
321 % (fitimage_its_path, its_field_check[field_index]))
322 1397
323 def test_uboot_sign_fit_image(self): 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
@@ -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 = "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
@@ -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 = "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)
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)
727 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 = "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 = "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 = "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..1bda29a72b 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"')
@@ -83,6 +83,8 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
83 # validate that SSH is working 83 # validate that SSH is working
84 status, _ = qemu.run("uname") 84 status, _ = qemu.run("uname")
85 self.assertEqual(status, 0) 85 self.assertEqual(status, 0)
86 qemu.run('echo "MaxStartups 75:30:100" >> /etc/ssh/sshd_config')
87 qemu.run('service sshd restart')
86 88
87 return self.run_check(*args, ssh=qemu.ip, **kwargs) 89 return self.run_check(*args, ssh=qemu.ip, **kwargs)
88 90
diff --git a/meta/lib/oeqa/selftest/cases/gdbserver.py b/meta/lib/oeqa/selftest/cases/gdbserver.py
index 9da97ae780..b6b7c5c473 100644
--- a/meta/lib/oeqa/selftest/cases/gdbserver.py
+++ b/meta/lib/oeqa/selftest/cases/gdbserver.py
@@ -54,7 +54,7 @@ CORE_IMAGE_EXTRA_INSTALL = "gdbserver"
54 self.logger.warning("starting gdb %s" % cmd) 54 self.logger.warning("starting gdb %s" % cmd)
55 r = runCmd(cmd, native_sysroot=native_sysroot, target_sys=target_sys) 55 r = runCmd(cmd, native_sysroot=native_sysroot, target_sys=target_sys)
56 self.assertEqual(0, r.status) 56 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>" 57 line_re = r"Line \d+ of \".*\" starts at address 0x[0-9A-Fa-f]+ <kmod_help>"
58 self.assertRegex(r.output, line_re) 58 self.assertRegex(r.output, line_re)
59 break 59 break
60 else: 60 else:
diff --git a/meta/lib/oeqa/selftest/cases/imagefeatures.py b/meta/lib/oeqa/selftest/cases/imagefeatures.py
index dc88c222bd..94d01ba116 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"
@@ -319,7 +314,7 @@ SKIP_RECIPE[busybox] = "Don't build this"
319 """ 314 """
320 config = """ 315 config = """
321DISTRO_FEATURES:append = " api-documentation" 316DISTRO_FEATURES:append = " api-documentation"
322CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc" 317CORE_IMAGE_EXTRA_INSTALL = "man-pages"
323""" 318"""
324 self.write_config(config) 319 self.write_config(config)
325 bitbake("core-image-minimal") 320 bitbake("core-image-minimal")
@@ -330,7 +325,7 @@ CORE_IMAGE_EXTRA_INSTALL = "man-pages kmod-doc"
330 self.assertEqual(status, 1, 'Failed to run apropos: %s' % (output)) 325 self.assertEqual(status, 1, 'Failed to run apropos: %s' % (output))
331 self.assertIn("iso_8859_15", output) 326 self.assertIn("iso_8859_15", output)
332 327
333 # This manpage is provided by kmod 328 # This manpage is provided by man-pages
334 status, output = qemu.run_serial("man --pager=cat modprobe") 329 status, output = qemu.run_serial("man --pager=cat intro")
335 self.assertEqual(status, 1, 'Failed to run man: %s' % (output)) 330 self.assertEqual(status, 1, 'Failed to run man: %s' % (output))
336 self.assertIn("force-modversion", output) 331 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..c3a7df4cdf 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']
@@ -47,9 +47,9 @@ class MetaIDE(OESelftestTestCase):
47 "https://ftp.gnu.org/gnu/cpio/cpio-2.15.tar.gz", 47 "https://ftp.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..3f9899b289 100644
--- a/meta/lib/oeqa/selftest/cases/oescripts.py
+++ b/meta/lib/oeqa/selftest/cases/oescripts.py
@@ -9,33 +9,9 @@ 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
@@ -175,7 +151,7 @@ class OEListPackageconfigTests(OESelftestTestCase):
175 def test_packageconfig_flags_option_all(self): 151 def test_packageconfig_flags_option_all(self):
176 results = runCmd('%s/contrib/list-packageconfig-flags.py -a' % self.scripts_dir) 152 results = runCmd('%s/contrib/list-packageconfig-flags.py -a' % self.scripts_dir)
177 expected_endlines = [] 153 expected_endlines = []
178 expected_endlines.append("pinentry-1.2.1") 154 expected_endlines.append("pinentry-1.3.1")
179 expected_endlines.append("PACKAGECONFIG ncurses") 155 expected_endlines.append("PACKAGECONFIG ncurses")
180 expected_endlines.append("PACKAGECONFIG[qt] --enable-pinentry-qt, --disable-pinentry-qt, qtbase-native qtbase") 156 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") 157 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 aebea42502..2a91f6c7ae 100644
--- a/meta/lib/oeqa/selftest/cases/recipetool.py
+++ b/meta/lib/oeqa/selftest/cases/recipetool.py
@@ -120,9 +120,15 @@ class RecipetoolAppendTests(RecipetoolBase):
120 self._try_recipetool_appendfile_fail('/dev/console', self.testfile, ['ERROR: /dev/console cannot be handled by this tool']) 120 self._try_recipetool_appendfile_fail('/dev/console', self.testfile, ['ERROR: /dev/console cannot be handled by this tool'])
121 121
122 def test_recipetool_appendfile_alternatives(self): 122 def test_recipetool_appendfile_alternatives(self):
123 lspath = '/bin/ls'
124 dirname = "base_bindir"
125 if "usrmerge" in get_bb_var('DISTRO_FEATURES'):
126 lspath = '/usr/bin/ls'
127 dirname = "bindir"
128
123 # Now try with a file we know should be an alternative 129 # Now try with a file we know should be an alternative
124 # (this is very much a fake example, but one we know is reliably an alternative) 130 # (this is very much a fake example, but one we know is reliably an alternative)
125 self._try_recipetool_appendfile_fail('/bin/ls', self.testfile, ['ERROR: File /bin/ls is an alternative possibly provided by the following recipes:', 'coreutils', 'busybox']) 131 self._try_recipetool_appendfile_fail(lspath, self.testfile, ['ERROR: File %s is an alternative possibly provided by the following recipes:' % lspath, 'coreutils', 'busybox'])
126 # Need a test file - should be executable 132 # Need a test file - should be executable
127 testfile2 = os.path.join(self.corebase, 'oe-init-build-env') 133 testfile2 = os.path.join(self.corebase, 'oe-init-build-env')
128 testfile2name = os.path.basename(testfile2) 134 testfile2name = os.path.basename(testfile2)
@@ -131,12 +137,12 @@ class RecipetoolAppendTests(RecipetoolBase):
131 'SRC_URI += "file://%s"\n' % testfile2name, 137 'SRC_URI += "file://%s"\n' % testfile2name,
132 '\n', 138 '\n',
133 'do_install:append() {\n', 139 'do_install:append() {\n',
134 ' install -d ${D}${base_bindir}\n', 140 ' install -d ${D}${%s}\n' % dirname,
135 ' install -m 0755 ${WORKDIR}/%s ${D}${base_bindir}/ls\n' % testfile2name, 141 ' install -m 0755 ${UNPACKDIR}/%s ${D}${%s}/ls\n' % (testfile2name, dirname),
136 '}\n'] 142 '}\n']
137 self._try_recipetool_appendfile('coreutils', '/bin/ls', testfile2, '-r coreutils', expectedlines, [testfile2name]) 143 self._try_recipetool_appendfile('coreutils', lspath, testfile2, '-r coreutils', expectedlines, [testfile2name])
138 # Now try bbappending the same file again, contents should not change 144 # Now try bbappending the same file again, contents should not change
139 bbappendfile, _ = self._try_recipetool_appendfile('coreutils', '/bin/ls', self.testfile, '-r coreutils', expectedlines, [testfile2name]) 145 bbappendfile, _ = self._try_recipetool_appendfile('coreutils', lspath, self.testfile, '-r coreutils', expectedlines, [testfile2name])
140 # But file should have 146 # But file should have
141 copiedfile = os.path.join(os.path.dirname(bbappendfile), 'coreutils', testfile2name) 147 copiedfile = os.path.join(os.path.dirname(bbappendfile), 'coreutils', testfile2name)
142 result = runCmd('diff -q %s %s' % (testfile2, copiedfile), ignore_status=True) 148 result = runCmd('diff -q %s %s' % (testfile2, copiedfile), ignore_status=True)
@@ -158,7 +164,7 @@ class RecipetoolAppendTests(RecipetoolBase):
158 '\n', 164 '\n',
159 'do_install:append() {\n', 165 'do_install:append() {\n',
160 ' install -d ${D}${datadir}\n', 166 ' install -d ${D}${datadir}\n',
161 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 167 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
162 '}\n'] 168 '}\n']
163 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'])
164 # 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
@@ -173,8 +179,8 @@ class RecipetoolAppendTests(RecipetoolBase):
173 '\n', 179 '\n',
174 'do_install:append() {\n', 180 'do_install:append() {\n',
175 ' install -d ${D}${datadir}\n', 181 ' install -d ${D}${datadir}\n',
176 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 182 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
177 ' install -m 0755 ${WORKDIR}/%s ${D}${datadir}/scriptname\n' % testfile2name, 183 ' install -m 0755 ${UNPACKDIR}/%s ${D}${datadir}/scriptname\n' % testfile2name,
178 '}\n'] 184 '}\n']
179 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])
180 186
@@ -186,7 +192,7 @@ class RecipetoolAppendTests(RecipetoolBase):
186 '\n', 192 '\n',
187 'do_install:append() {\n', 193 'do_install:append() {\n',
188 ' install -d ${D}${bindir}\n', 194 ' install -d ${D}${bindir}\n',
189 ' install -m 0755 ${WORKDIR}/testfile ${D}${bindir}/selftest-recipetool-testbin\n', 195 ' install -m 0755 ${UNPACKDIR}/testfile ${D}${bindir}/selftest-recipetool-testbin\n',
190 '}\n'] 196 '}\n']
191 _, 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'])
192 self.assertNotIn('WARNING: ', output) 198 self.assertNotIn('WARNING: ', output)
@@ -201,7 +207,7 @@ class RecipetoolAppendTests(RecipetoolBase):
201 '\n', 207 '\n',
202 'do_install:append:mymachine() {\n', 208 'do_install:append:mymachine() {\n',
203 ' install -d ${D}${datadir}\n', 209 ' install -d ${D}${datadir}\n',
204 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/something\n', 210 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/something\n',
205 '}\n'] 211 '}\n']
206 _, 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'])
207 self.assertNotIn('WARNING: ', output) 213 self.assertNotIn('WARNING: ', output)
@@ -235,7 +241,7 @@ class RecipetoolAppendTests(RecipetoolBase):
235 '\n', 241 '\n',
236 'do_install:append() {\n', 242 'do_install:append() {\n',
237 ' install -d ${D}${datadir}\n', 243 ' install -d ${D}${datadir}\n',
238 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-subdir\n', 244 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-subdir\n',
239 '}\n'] 245 '}\n']
240 _, 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'])
241 self.assertNotIn('WARNING: ', output) 247 self.assertNotIn('WARNING: ', output)
@@ -262,7 +268,7 @@ class RecipetoolAppendTests(RecipetoolBase):
262 '\n', 268 '\n',
263 'do_install:append() {\n', 269 'do_install:append() {\n',
264 ' install -d ${D}${sysconfdir}\n', 270 ' install -d ${D}${sysconfdir}\n',
265 ' install -m 0644 ${WORKDIR}/testfile ${D}${sysconfdir}/selftest-replaceme-patched\n', 271 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${sysconfdir}/selftest-replaceme-patched\n',
266 '}\n'] 272 '}\n']
267 _, 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'])
268 for line in output.splitlines(): 274 for line in output.splitlines():
@@ -280,7 +286,7 @@ class RecipetoolAppendTests(RecipetoolBase):
280 '\n', 286 '\n',
281 'do_install:append() {\n', 287 'do_install:append() {\n',
282 ' install -d ${D}${datadir}\n', 288 ' install -d ${D}${datadir}\n',
283 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-scripted\n', 289 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-scripted\n',
284 '}\n'] 290 '}\n']
285 _, 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'])
286 self.assertNotIn('WARNING: ', output) 292 self.assertNotIn('WARNING: ', output)
@@ -303,7 +309,7 @@ class RecipetoolAppendTests(RecipetoolBase):
303 '\n', 309 '\n',
304 'do_install:append() {\n', 310 'do_install:append() {\n',
305 ' install -d ${D}${datadir}\n', 311 ' install -d ${D}${datadir}\n',
306 ' install -m 0644 ${WORKDIR}/testfile ${D}${datadir}/selftest-replaceme-postinst\n', 312 ' install -m 0644 ${UNPACKDIR}/testfile ${D}${datadir}/selftest-replaceme-postinst\n',
307 '}\n'] 313 '}\n']
308 _, 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'])
309 315
@@ -379,7 +385,7 @@ class RecipetoolCreateTests(RecipetoolBase):
379 checkvars = {} 385 checkvars = {}
380 checkvars['LICENSE'] = 'LGPL-2.1-only' 386 checkvars['LICENSE'] = 'LGPL-2.1-only'
381 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=7fbc338309ac38fefcd64b04bb903e34' 387 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=7fbc338309ac38fefcd64b04bb903e34'
382 checkvars['S'] = '${WORKDIR}/git' 388 checkvars['S'] = None
383 checkvars['PV'] = '1.11+git' 389 checkvars['PV'] = '1.11+git'
384 checkvars['SRC_URI'] = srcuri + ';branch=master' 390 checkvars['SRC_URI'] = srcuri + ';branch=master'
385 checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxext', 'pango']) 391 checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxext', 'pango'])
@@ -754,13 +760,13 @@ class RecipetoolCreateTests(RecipetoolBase):
754 temprecipe = os.path.join(self.tempdir, 'recipe') 760 temprecipe = os.path.join(self.tempdir, 'recipe')
755 os.makedirs(temprecipe) 761 os.makedirs(temprecipe)
756 762
757 recipefile = os.path.join(temprecipe, 'edgex-go_git.bb') 763 recipefile = os.path.join(temprecipe, 'recipetool-go-test_git.bb')
758 deps_require_file = os.path.join(temprecipe, 'edgex-go', 'edgex-go-modules.inc') 764 deps_require_file = os.path.join(temprecipe, 'recipetool-go-test', 'recipetool-go-test-modules.inc')
759 lics_require_file = os.path.join(temprecipe, 'edgex-go', 'edgex-go-licenses.inc') 765 lics_require_file = os.path.join(temprecipe, 'recipetool-go-test', 'recipetool-go-test-licenses.inc')
760 modules_txt_file = os.path.join(temprecipe, 'edgex-go', 'modules.txt') 766 modules_txt_file = os.path.join(temprecipe, 'recipetool-go-test', 'modules.txt')
761 767
762 srcuri = 'https://github.com/edgexfoundry/edgex-go.git' 768 srcuri = 'https://git.yoctoproject.org/recipetool-go-test.git'
763 srcrev = "v3.0.0" 769 srcrev = "c3e213c01b6c1406b430df03ef0d1ae77de5d2f7"
764 srcbranch = "main" 770 srcbranch = "main"
765 771
766 result = runCmd('recipetool create -o %s %s -S %s -B %s' % (temprecipe, srcuri, srcrev, srcbranch)) 772 result = runCmd('recipetool create -o %s %s -S %s -B %s' % (temprecipe, srcuri, srcrev, srcbranch))
@@ -769,207 +775,27 @@ class RecipetoolCreateTests(RecipetoolBase):
769 inherits = ['go-vendor'] 775 inherits = ['go-vendor']
770 776
771 checkvars = {} 777 checkvars = {}
772 checkvars['GO_IMPORT'] = "github.com/edgexfoundry/edgex-go" 778 checkvars['GO_IMPORT'] = "git.yoctoproject.org/recipetool-go-test"
773 checkvars['SRC_URI'] = {'git://${GO_IMPORT};destsuffix=git/src/${GO_IMPORT};nobranch=1;name=${BPN};protocol=https', 779 checkvars['SRC_URI'] = {'git://${GO_IMPORT};destsuffix=git/src/${GO_IMPORT};nobranch=1;name=${BPN};protocol=https',
774 'file://modules.txt'} 780 'file://modules.txt'}
775 checkvars['LIC_FILES_CHKSUM'] = {'file://src/${GO_IMPORT}/LICENSE;md5=8f8bc924cf73f6a32381e5fd4c58d603'} 781 checkvars['LIC_FILES_CHKSUM'] = {
782 'file://src/${GO_IMPORT}/LICENSE;md5=4e3933dd47afbf115e484d11385fb3bd',
783 'file://src/${GO_IMPORT}/is/LICENSE;md5=62beaee5a116dd1e80161667b1df39ab'
784 }
776 785
777 self.assertTrue(os.path.isfile(recipefile))
778 self._test_recipe_contents(recipefile, checkvars, inherits) 786 self._test_recipe_contents(recipefile, checkvars, inherits)
787 self.assertNotIn('Traceback', result.output)
779 788
780 checkvars = {} 789 checkvars = {}
781 checkvars['VENDORED_LIC_FILES_CHKSUM'] = set( 790 checkvars['VENDORED_LIC_FILES_CHKSUM'] = set(
782 ['file://src/${GO_IMPORT}/vendor/github.com/Microsoft/go-winio/LICENSE;md5=69205ff73858f2c22b2ca135b557e8ef', 791 ['file://src/${GO_IMPORT}/vendor/github.com/godbus/dbus/v5/LICENSE;md5=09042bd5c6c96a2b9e45ddf1bc517eed',
783 'file://src/${GO_IMPORT}/vendor/github.com/armon/go-metrics/LICENSE;md5=d2d77030c0183e3d1e66d26dc1f243be', 792 'file://src/${GO_IMPORT}/vendor/github.com/matryer/is/LICENSE;md5=62beaee5a116dd1e80161667b1df39ab'])
784 'file://src/${GO_IMPORT}/vendor/github.com/cenkalti/backoff/LICENSE;md5=1571d94433e3f3aa05267efd4dbea68b',
785 'file://src/${GO_IMPORT}/vendor/github.com/davecgh/go-spew/LICENSE;md5=c06795ed54b2a35ebeeb543cd3a73e56',
786 'file://src/${GO_IMPORT}/vendor/github.com/eclipse/paho.mqtt.golang/LICENSE;md5=dcdb33474b60c38efd27356d8f2edec7',
787 'file://src/${GO_IMPORT}/vendor/github.com/eclipse/paho.mqtt.golang/edl-v10;md5=3adfcc70f5aeb7a44f3f9b495aa1fbf3',
788 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-bootstrap/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
789 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-configuration/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
790 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-core-contracts/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
791 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-messaging/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
792 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-registry/v3/LICENSE;md5=0d6dae39976133b2851fba4c1e1275ff',
793 'file://src/${GO_IMPORT}/vendor/github.com/edgexfoundry/go-mod-secrets/v3/LICENSE;md5=f9fa2f4f8e0ef8cc7b5dd150963eb457',
794 'file://src/${GO_IMPORT}/vendor/github.com/fatih/color/LICENSE.md;md5=316e6d590bdcde7993fb175662c0dd5a',
795 'file://src/${GO_IMPORT}/vendor/github.com/fxamacker/cbor/v2/LICENSE;md5=827f5a2fa861382d35a3943adf9ebb86',
796 'file://src/${GO_IMPORT}/vendor/github.com/go-jose/go-jose/v3/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
797 'file://src/${GO_IMPORT}/vendor/github.com/go-jose/go-jose/v3/json/LICENSE;md5=591778525c869cdde0ab5a1bf283cd81',
798 'file://src/${GO_IMPORT}/vendor/github.com/go-kit/log/LICENSE;md5=5b7c15ad5fffe2ff6e9d58a6c161f082',
799 'file://src/${GO_IMPORT}/vendor/github.com/go-logfmt/logfmt/LICENSE;md5=98e39517c38127f969de33057067091e',
800 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/locales/LICENSE;md5=3ccbda375ee345400ad1da85ba522301',
801 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/universal-translator/LICENSE;md5=2e2b21ef8f61057977d27c727c84bef1',
802 'file://src/${GO_IMPORT}/vendor/github.com/go-playground/validator/v10/LICENSE;md5=a718a0f318d76f7c5d510cbae84f0b60',
803 'file://src/${GO_IMPORT}/vendor/github.com/go-redis/redis/v7/LICENSE;md5=58103aa5ea1ee9b7a369c9c4a95ef9b5',
804 'file://src/${GO_IMPORT}/vendor/github.com/golang/protobuf/LICENSE;md5=939cce1ec101726fa754e698ac871622',
805 'file://src/${GO_IMPORT}/vendor/github.com/gomodule/redigo/LICENSE;md5=2ee41112a44fe7014dce33e26468ba93',
806 'file://src/${GO_IMPORT}/vendor/github.com/google/uuid/LICENSE;md5=88073b6dd8ec00fe09da59e0b6dfded1',
807 'file://src/${GO_IMPORT}/vendor/github.com/gorilla/mux/LICENSE;md5=33fa1116c45f9e8de714033f99edde13',
808 'file://src/${GO_IMPORT}/vendor/github.com/gorilla/websocket/LICENSE;md5=c007b54a1743d596f46b2748d9f8c044',
809 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/consul/api/LICENSE;md5=b8a277a612171b7526e9be072f405ef4',
810 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/errwrap/LICENSE;md5=b278a92d2c1509760384428817710378',
811 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-cleanhttp/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
812 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-hclog/LICENSE;md5=ec7f605b74b9ad03347d0a93a5cc7eb8',
813 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-immutable-radix/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
814 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-multierror/LICENSE;md5=d44fdeb607e2d2614db9464dbedd4094',
815 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/go-rootcerts/LICENSE;md5=65d26fcc2f35ea6a181ac777e42db1ea',
816 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/golang-lru/LICENSE;md5=f27a50d2e878867827842f2c60e30bfc',
817 'file://src/${GO_IMPORT}/vendor/github.com/hashicorp/serf/LICENSE;md5=b278a92d2c1509760384428817710378',
818 'file://src/${GO_IMPORT}/vendor/github.com/leodido/go-urn/LICENSE;md5=8f50db5538ec1148a9b3d14ed96c3418',
819 'file://src/${GO_IMPORT}/vendor/github.com/mattn/go-colorable/LICENSE;md5=24ce168f90aec2456a73de1839037245',
820 'file://src/${GO_IMPORT}/vendor/github.com/mattn/go-isatty/LICENSE;md5=f509beadd5a11227c27b5d2ad6c9f2c6',
821 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/consulstructure/LICENSE;md5=96ada10a9e51c98c4656f2cede08c673',
822 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/copystructure/LICENSE;md5=56da355a12d4821cda57b8f23ec34bc4',
823 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/go-homedir/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
824 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/mapstructure/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
825 'file://src/${GO_IMPORT}/vendor/github.com/mitchellh/reflectwalk/LICENSE;md5=3f7765c3d4f58e1f84c4313cecf0f5bd',
826 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nats.go/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
827 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nkeys/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
828 'file://src/${GO_IMPORT}/vendor/github.com/nats-io/nuid/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
829 'file://src/${GO_IMPORT}/vendor/github.com/pmezard/go-difflib/LICENSE;md5=e9a2ebb8de779a07500ddecca806145e',
830 'file://src/${GO_IMPORT}/vendor/github.com/rcrowley/go-metrics/LICENSE;md5=1bdf5d819f50f141366dabce3be1460f',
831 'file://src/${GO_IMPORT}/vendor/github.com/spiffe/go-spiffe/v2/LICENSE;md5=86d3f3a95c324c9479bd8986968f4327',
832 'file://src/${GO_IMPORT}/vendor/github.com/stretchr/objx/LICENSE;md5=d023fd31d3ca39ec61eec65a91732735',
833 'file://src/${GO_IMPORT}/vendor/github.com/stretchr/testify/LICENSE;md5=188f01994659f3c0d310612333d2a26f',
834 'file://src/${GO_IMPORT}/vendor/github.com/x448/float16/LICENSE;md5=de8f8e025d57fe7ee0b67f30d571323b',
835 'file://src/${GO_IMPORT}/vendor/github.com/zeebo/errs/LICENSE;md5=84914ab36fc0eb48edbaa53e66e8d326',
836 'file://src/${GO_IMPORT}/vendor/golang.org/x/crypto/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
837 'file://src/${GO_IMPORT}/vendor/golang.org/x/mod/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
838 'file://src/${GO_IMPORT}/vendor/golang.org/x/net/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
839 'file://src/${GO_IMPORT}/vendor/golang.org/x/sync/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
840 'file://src/${GO_IMPORT}/vendor/golang.org/x/sys/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
841 'file://src/${GO_IMPORT}/vendor/golang.org/x/text/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
842 'file://src/${GO_IMPORT}/vendor/golang.org/x/tools/LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707',
843 'file://src/${GO_IMPORT}/vendor/google.golang.org/genproto/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
844 'file://src/${GO_IMPORT}/vendor/google.golang.org/grpc/LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57',
845 'file://src/${GO_IMPORT}/vendor/google.golang.org/protobuf/LICENSE;md5=02d4002e9171d41a8fad93aa7faf3956',
846 'file://src/${GO_IMPORT}/vendor/gopkg.in/eapache/queue.v1/LICENSE;md5=1bfd4408d3de090ef6b908b0cc45a316',
847 'file://src/${GO_IMPORT}/vendor/gopkg.in/yaml.v3/LICENSE;md5=3c91c17266710e16afdbb2b6d15c761c'])
848
849 self.assertTrue(os.path.isfile(lics_require_file)) 793 self.assertTrue(os.path.isfile(lics_require_file))
850 self._test_recipe_contents(lics_require_file, checkvars, []) 794 self._test_recipe_contents(lics_require_file, checkvars, [])
851 795
796 # make sure that dependencies don't mention local directory ./matryer/is
852 dependencies = \ 797 dependencies = \
853 [ ('github.com/eclipse/paho.mqtt.golang','v1.4.2', '', '', ''), 798 [ ('github.com/godbus/dbus','v5.1.0', 'github.com/godbus/dbus/v5', '/v5', ''),
854 ('github.com/edgexfoundry/go-mod-bootstrap','v3.0.1','github.com/edgexfoundry/go-mod-bootstrap/v3','/v3', ''),
855 ('github.com/edgexfoundry/go-mod-configuration','v3.0.0','github.com/edgexfoundry/go-mod-configuration/v3','/v3', ''),
856 ('github.com/edgexfoundry/go-mod-core-contracts','v3.0.0','github.com/edgexfoundry/go-mod-core-contracts/v3','/v3', ''),
857 ('github.com/edgexfoundry/go-mod-messaging','v3.0.0','github.com/edgexfoundry/go-mod-messaging/v3','/v3', ''),
858 ('github.com/edgexfoundry/go-mod-secrets','v3.0.1','github.com/edgexfoundry/go-mod-secrets/v3','/v3', ''),
859 ('github.com/fxamacker/cbor','v2.4.0','github.com/fxamacker/cbor/v2','/v2', ''),
860 ('github.com/gomodule/redigo','v1.8.9', '', '', ''),
861 ('github.com/google/uuid','v1.3.0', '', '', ''),
862 ('github.com/gorilla/mux','v1.8.0', '', '', ''),
863 ('github.com/rcrowley/go-metrics','v0.0.0-20201227073835-cf1acfcdf475', '', '', ''),
864 ('github.com/spiffe/go-spiffe','v2.1.4','github.com/spiffe/go-spiffe/v2','/v2', ''),
865 ('github.com/stretchr/testify','v1.8.2', '', '', ''),
866 ('go.googlesource.com/crypto','v0.8.0','golang.org/x/crypto', '', ''),
867 ('gopkg.in/eapache/queue.v1','v1.1.0', '', '', ''),
868 ('gopkg.in/yaml.v3','v3.0.1', '', '', ''),
869 ('github.com/microsoft/go-winio','v0.6.0','github.com/Microsoft/go-winio', '', ''),
870 ('github.com/hashicorp/go-metrics','v0.3.10','github.com/armon/go-metrics', '', ''),
871 ('github.com/cenkalti/backoff','v2.2.1+incompatible', '', '', ''),
872 ('github.com/davecgh/go-spew','v1.1.1', '', '', ''),
873 ('github.com/edgexfoundry/go-mod-registry','v3.0.0','github.com/edgexfoundry/go-mod-registry/v3','/v3', ''),
874 ('github.com/fatih/color','v1.9.0', '', '', ''),
875 ('github.com/go-jose/go-jose','v3.0.0','github.com/go-jose/go-jose/v3','/v3', ''),
876 ('github.com/go-kit/log','v0.2.1', '', '', ''),
877 ('github.com/go-logfmt/logfmt','v0.5.1', '', '', ''),
878 ('github.com/go-playground/locales','v0.14.1', '', '', ''),
879 ('github.com/go-playground/universal-translator','v0.18.1', '', '', ''),
880 ('github.com/go-playground/validator','v10.13.0','github.com/go-playground/validator/v10','/v10', ''),
881 ('github.com/go-redis/redis','v7.3.0','github.com/go-redis/redis/v7','/v7', ''),
882 ('github.com/golang/protobuf','v1.5.2', '', '', ''),
883 ('github.com/gorilla/websocket','v1.4.2', '', '', ''),
884 ('github.com/hashicorp/consul','v1.20.0','github.com/hashicorp/consul/api', '', 'api'),
885 ('github.com/hashicorp/errwrap','v1.0.0', '', '', ''),
886 ('github.com/hashicorp/go-cleanhttp','v0.5.1', '', '', ''),
887 ('github.com/hashicorp/go-hclog','v0.14.1', '', '', ''),
888 ('github.com/hashicorp/go-immutable-radix','v1.3.0', '', '', ''),
889 ('github.com/hashicorp/go-multierror','v1.1.1', '', '', ''),
890 ('github.com/hashicorp/go-rootcerts','v1.0.2', '', '', ''),
891 ('github.com/hashicorp/golang-lru','v0.5.4', '', '', ''),
892 ('github.com/hashicorp/serf','v0.10.1', '', '', ''),
893 ('github.com/leodido/go-urn','v1.2.3', '', '', ''),
894 ('github.com/mattn/go-colorable','v0.1.12', '', '', ''),
895 ('github.com/mattn/go-isatty','v0.0.14', '', '', ''),
896 ('github.com/mitchellh/consulstructure','v0.0.0-20190329231841-56fdc4d2da54', '', '', ''),
897 ('github.com/mitchellh/copystructure','v1.2.0', '', '', ''),
898 ('github.com/mitchellh/go-homedir','v1.1.0', '', '', ''),
899 ('github.com/mitchellh/mapstructure','v1.5.0', '', '', ''),
900 ('github.com/mitchellh/reflectwalk','v1.0.2', '', '', ''),
901 ('github.com/nats-io/nats.go','v1.25.0', '', '', ''),
902 ('github.com/nats-io/nkeys','v0.4.4', '', '', ''),
903 ('github.com/nats-io/nuid','v1.0.1', '', '', ''),
904 ('github.com/pmezard/go-difflib','v1.0.0', '', '', ''),
905 ('github.com/stretchr/objx','v0.5.0', '', '', ''),
906 ('github.com/x448/float16','v0.8.4', '', '', ''),
907 ('github.com/zeebo/errs','v1.3.0', '', '', ''),
908 ('go.googlesource.com/mod','v0.8.0','golang.org/x/mod', '', ''),
909 ('go.googlesource.com/net','v0.9.0','golang.org/x/net', '', ''),
910 ('go.googlesource.com/sync','v0.1.0','golang.org/x/sync', '', ''),
911 ('go.googlesource.com/sys','v0.7.0','golang.org/x/sys', '', ''),
912 ('go.googlesource.com/text','v0.9.0','golang.org/x/text', '', ''),
913 ('go.googlesource.com/tools','v0.6.0','golang.org/x/tools', '', ''),
914 ('github.com/googleapis/go-genproto','v0.0.0-20230223222841-637eb2293923','google.golang.org/genproto', '', ''),
915 ('github.com/grpc/grpc-go','v1.53.0','google.golang.org/grpc', '', ''),
916 ('go.googlesource.com/protobuf','v1.28.1','google.golang.org/protobuf', '', ''),
917 ]
918
919 src_uri = set()
920 for d in dependencies:
921 src_uri.add(self._go_urifiy(*d))
922
923 checkvars = {}
924 checkvars['GO_DEPENDENCIES_SRC_URI'] = src_uri
925
926 self.assertTrue(os.path.isfile(deps_require_file))
927 self._test_recipe_contents(deps_require_file, checkvars, [])
928
929 def test_recipetool_create_go_replace_modules(self):
930 # Check handling of replaced modules
931 temprecipe = os.path.join(self.tempdir, 'recipe')
932 os.makedirs(temprecipe)
933
934 recipefile = os.path.join(temprecipe, 'openapi-generator_git.bb')
935 deps_require_file = os.path.join(temprecipe, 'openapi-generator', 'go-modules.inc')
936 lics_require_file = os.path.join(temprecipe, 'openapi-generator', 'go-licenses.inc')
937 modules_txt_file = os.path.join(temprecipe, 'openapi-generator', 'modules.txt')
938
939 srcuri = 'https://github.com/OpenAPITools/openapi-generator.git'
940 srcrev = "v7.2.0"
941 srcbranch = "master"
942 srcsubdir = "samples/openapi3/client/petstore/go"
943
944 result = runCmd('recipetool create -o %s %s -S %s -B %s --src-subdir %s' % (temprecipe, srcuri, srcrev, srcbranch, srcsubdir))
945
946 self.maxDiff = None
947 inherits = ['go-vendor']
948
949 checkvars = {}
950 checkvars['GO_IMPORT'] = "github.com/OpenAPITools/openapi-generator/samples/openapi3/client/petstore/go"
951 checkvars['SRC_URI'] = {'git://${GO_IMPORT};destsuffix=git/src/${GO_IMPORT};nobranch=1;name=${BPN};protocol=https',
952 'file://modules.txt'}
953
954 self.assertNotIn('Traceback', result.output)
955 self.assertIn('No license file was detected for the main module', result.output)
956 self.assertTrue(os.path.isfile(recipefile))
957 self._test_recipe_contents(recipefile, checkvars, inherits)
958
959 # make sure that dependencies don't mention local directory ./go-petstore
960 dependencies = \
961 [ ('github.com/stretchr/testify','v1.8.4', '', '', ''),
962 ('go.googlesource.com/oauth2','v0.10.0','golang.org/x/oauth2', '', ''),
963 ('github.com/davecgh/go-spew','v1.1.1', '', '', ''),
964 ('github.com/golang/protobuf','v1.5.3', '', '', ''),
965 ('github.com/kr/pretty','v0.3.0', '', '', ''),
966 ('github.com/pmezard/go-difflib','v1.0.0', '', '', ''),
967 ('github.com/rogpeppe/go-internal','v1.9.0', '', '', ''),
968 ('go.googlesource.com/net','v0.12.0','golang.org/x/net', '', ''),
969 ('github.com/golang/appengine','v1.6.7','google.golang.org/appengine', '', ''),
970 ('go.googlesource.com/protobuf','v1.31.0','google.golang.org/protobuf', '', ''),
971 ('gopkg.in/check.v1','v1.0.0-20201130134442-10cb98267c6c', '', '', ''),
972 ('gopkg.in/yaml.v3','v3.0.1', '', '', ''),
973 ] 799 ]
974 800
975 src_uri = set() 801 src_uri = set()
@@ -1062,6 +888,7 @@ class RecipetoolTests(RecipetoolBase):
1062 888
1063 d = DataConnectorCopy 889 d = DataConnectorCopy
1064 d.getVar = Mock(return_value=commonlicdir) 890 d.getVar = Mock(return_value=commonlicdir)
891 d.expand = Mock(side_effect=lambda x: x)
1065 892
1066 srctree = tempfile.mkdtemp(prefix='recipetoolqa') 893 srctree = tempfile.mkdtemp(prefix='recipetoolqa')
1067 self.track_for_cleanup(srctree) 894 self.track_for_cleanup(srctree)
@@ -1317,10 +1144,10 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase):
1317 1144
1318 def test_recipetool_appendsrcfile_srcdir_basic(self): 1145 def test_recipetool_appendsrcfile_srcdir_basic(self):
1319 testrecipe = 'bash' 1146 testrecipe = 'bash'
1320 bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe) 1147 bb_vars = get_bb_vars(['S', 'UNPACKDIR'], testrecipe)
1321 srcdir = bb_vars['S'] 1148 srcdir = bb_vars['S']
1322 workdir = bb_vars['WORKDIR'] 1149 unpackdir = bb_vars['UNPACKDIR']
1323 subdir = os.path.relpath(srcdir, workdir) 1150 subdir = os.path.relpath(srcdir, unpackdir)
1324 self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir) 1151 self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir)
1325 1152
1326 def test_recipetool_appendsrcfile_existing_in_src_uri(self): 1153 def test_recipetool_appendsrcfile_existing_in_src_uri(self):
@@ -1369,10 +1196,10 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase):
1369 def test_recipetool_appendsrcfile_replace_file_srcdir(self): 1196 def test_recipetool_appendsrcfile_replace_file_srcdir(self):
1370 testrecipe = 'bash' 1197 testrecipe = 'bash'
1371 filepath = 'Makefile.in' 1198 filepath = 'Makefile.in'
1372 bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe) 1199 bb_vars = get_bb_vars(['S', 'UNPACKDIR'], testrecipe)
1373 srcdir = bb_vars['S'] 1200 srcdir = bb_vars['S']
1374 workdir = bb_vars['WORKDIR'] 1201 unpackdir = bb_vars['UNPACKDIR']
1375 subdir = os.path.relpath(srcdir, workdir) 1202 subdir = os.path.relpath(srcdir, unpackdir)
1376 1203
1377 self._test_appendsrcfile(testrecipe, filepath, srcdir=subdir) 1204 self._test_appendsrcfile(testrecipe, filepath, srcdir=subdir)
1378 bitbake('%s:do_unpack' % testrecipe) 1205 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/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..d99a58d6b9 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,132 +60,43 @@ 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/jsondoclint',
81 'src/tools/lint-docs', 71 'src/tools/lint-docs',
72 'src/tools/replace-version-placeholder',
82 'src/tools/rust-analyzer', 73 'src/tools/rust-analyzer',
83 'src/tools/rustdoc-themes', 74 'src/tools/rustdoc-themes',
84 'src/tools/tidy', 75 'src/tools/rust-installer',
76 'src/tools/suggest-tests',
85 'tests/assembly/asm/aarch64-outline-atomics.rs', 77 'tests/assembly/asm/aarch64-outline-atomics.rs',
86 'tests/codegen/abi-main-signature-32bit-c-int.rs', 78 'tests/codegen/issues/issue-122805.rs',
87 'tests/codegen/abi-repr-ext.rs',
88 'tests/codegen/abi-x86-interrupt.rs',
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', 79 'tests/codegen/thread-local.rs',
110 'tests/codegen/uninit-consts.rs', 80 'tests/mir-opt/',
111 'tests/pretty/raw-str-nonexpr.rs',
112 'tests/run-make', 81 'tests/run-make',
113 'tests/run-make-fulldeps', 82 'tests/run-make-fulldeps',
114 'tests/rustdoc', 83 'tests/rustdoc',
115 'tests/rustdoc-json', 84 'tests/rustdoc-json',
116 'tests/rustdoc-js-std', 85 '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', 86 'tests/ui/abi/stack-probes-lto.rs',
137 'tests/ui/abi/stack-probes.rs', 87 'tests/ui/abi/stack-probes.rs',
138 'tests/ui/array-slice-vec/subslice-patterns-const-eval-match.rs', 88 'tests/ui/codegen/mismatched-data-layouts.rs',
139 'tests/ui/asm/x86_64/sym.rs', 89 'tests/codegen/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', 90 'tests/ui/debuginfo/debuginfo-emit-llvm-ir-and-split-debuginfo.rs',
144 'tests/ui/drop/dynamic-drop.rs', 91 '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/', 92 'tests/ui-fulldeps/',
189 'tests/ui/numbers-arithmetic/u128.rs' 93 'tests/ui/process/nofile-limit.rs',
94 'tidyselftest'
190 ] 95 ]
191 96
192 exclude_fail_tests = " ".join([" --exclude " + item for item in exclude_list]) 97 exclude_fail_tests = " ".join([" --exclude " + item for item in exclude_list])
193 # Add exclude_fail_tests with other test arguments 98 # Add exclude_fail_tests with other test arguments
194 testargs = exclude_fail_tests + " --doc --no-fail-fast --bless" 99 testargs = exclude_fail_tests + " --no-fail-fast --bless"
195 100
196 # wrap the execution with a qemu instance. 101 # wrap the execution with a qemu instance.
197 # Tests are run with 512 tasks in parallel to execute all tests very quickly 102 # Tests are run with 512 tasks in parallel to execute all tests very quickly
@@ -199,7 +104,7 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
199 # Copy remote-test-server to image through scp 104 # Copy remote-test-server to image through scp
200 host_sys = get_bb_var("RUST_BUILD_SYS", "rust") 105 host_sys = get_bb_var("RUST_BUILD_SYS", "rust")
201 ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user="root") 106 ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user="root")
202 ssh.copy_to(builddir + "/build/" + host_sys + "/stage1-tools-bin/remote-test-server","~/") 107 ssh.copy_to(builddir + "/build/" + host_sys + "/stage2-tools-bin/remote-test-server","~/")
203 # Execute remote-test-server on image through background ssh 108 # Execute remote-test-server on image through background ssh
204 command = '~/remote-test-server --bind 0.0.0.0:12345 -v' 109 command = '~/remote-test-server --bind 0.0.0.0:12345 -v'
205 sshrun=subprocess.Popen(("ssh", '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-f', "root@%s" % qemu.ip, command), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 110 sshrun=subprocess.Popen(("ssh", '-o', 'UserKnownHostsFile=/dev/null', '-o', 'StrictHostKeyChecking=no', '-f', "root@%s" % qemu.ip, command), shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -210,9 +115,8 @@ class RustSelfTestSystemEmulated(OESelftestTestCase, OEPTestResultTestCase):
210 tmpdir = get_bb_var("TMPDIR", "rust") 115 tmpdir = get_bb_var("TMPDIR", "rust")
211 116
212 # Set path for target-poky-linux-gcc, RUST_TARGET_PATH and hosttools. 117 # Set path for target-poky-linux-gcc, RUST_TARGET_PATH and hosttools.
213 cmd = " export PATH=%s/recipe-sysroot-native/usr/bin:$PATH;" % rustlibpath 118 cmd = "export TARGET_VENDOR=\"-poky\";"
214 cmd = cmd + " export TARGET_VENDOR=\"-poky\";" 119 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 120 cmd = cmd + " export RUST_TARGET_PATH=%s/rust-targets;" % rustlibpath
217 # Trigger testing. 121 # Trigger testing.
218 cmd = cmd + " export TEST_DEVICE_ADDR=\"%s:12345\";" % qemu.ip 122 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..08f94b168a 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):
@@ -367,18 +354,11 @@ class SStateCacheManagement(SStateBase):
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 = "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"
@@ -390,13 +370,12 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
390 self.write_config(""" 370 self.write_config("""
391MACHINE = "qemux86" 371MACHINE = "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 = \"\"
467MACHINE = \"qemux86-64\" 457MACHINE = \"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 = \"\"
474MACHINE = \"qemuarm\" 463MACHINE = \"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,7 +475,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
484 475
485 configA = """ 476 configA = """
486TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 477TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
487TCLIBCAPPEND = \"\"
488MACHINE = \"qemux86-64\" 478MACHINE = \"qemux86-64\"
489require conf/multilib.conf 479require conf/multilib.conf
490MULTILIBS = \"multilib:lib32\" 480MULTILIBS = \"multilib:lib32\"
@@ -493,7 +483,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
493""" 483"""
494 configB = """ 484 configB = """
495TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" 485TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\"
496TCLIBCAPPEND = \"\"
497MACHINE = \"qemuarm\" 486MACHINE = \"qemuarm\"
498require conf/multilib.conf 487require conf/multilib.conf
499MULTILIBS = \"\" 488MULTILIBS = \"\"
@@ -511,7 +500,6 @@ class SStateHashSameSigs3(SStateBase):
511 500
512 self.write_config(""" 501 self.write_config("""
513TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 502TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
514TCLIBCAPPEND = \"\"
515MACHINE = \"qemux86\" 503MACHINE = \"qemux86\"
516require conf/multilib.conf 504require conf/multilib.conf
517MULTILIBS = "multilib:lib32" 505MULTILIBS = "multilib:lib32"
@@ -522,7 +510,6 @@ 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 = \"\"
526MACHINE = \"qemux86copy\" 513MACHINE = \"qemux86copy\"
527require conf/multilib.conf 514require conf/multilib.conf
528MULTILIBS = "multilib:lib32" 515MULTILIBS = "multilib:lib32"
@@ -559,7 +546,6 @@ BB_SIGNATURE_HANDLER = "OEBasicHash"
559 546
560 self.write_config(""" 547 self.write_config("""
561TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" 548TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\"
562TCLIBCAPPEND = \"\"
563MACHINE = \"qemux86\" 549MACHINE = \"qemux86\"
564require conf/multilib.conf 550require conf/multilib.conf
565MULTILIBS = "multilib:lib32" 551MULTILIBS = "multilib:lib32"
@@ -570,7 +556,6 @@ 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 = \"\"
574MACHINE = \"qemux86copy\" 559MACHINE = \"qemux86copy\"
575BB_SIGNATURE_HANDLER = "OEBasicHash" 560BB_SIGNATURE_HANDLER = "OEBasicHash"
576""") 561""")
@@ -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,7 +707,6 @@ class SStateFindSiginfo(SStateBase):
724 """ 707 """
725 self.write_config(""" 708 self.write_config("""
726TMPDIR = \"${TOPDIR}/tmp-sstates-findsiginfo\" 709TMPDIR = \"${TOPDIR}/tmp-sstates-findsiginfo\"
727TCLIBCAPPEND = \"\"
728MACHINE = \"qemux86-64\" 710MACHINE = \"qemux86-64\"
729require conf/multilib.conf 711require conf/multilib.conf
730MULTILIBS = "multilib:lib32" 712MULTILIBS = "multilib:lib32"
@@ -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,24 +951,15 @@ 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 = "{}"
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("""
@@ -987,7 +969,7 @@ MACHINE = "{}"
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..680f99d381 100644
--- a/meta/lib/oeqa/selftest/cases/wic.py
+++ b/meta/lib/oeqa/selftest/cases/wic.py
@@ -12,6 +12,7 @@ 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
@@ -152,7 +153,7 @@ class Wic(WicTestCase):
152 # create a temporary file for the WKS content 153 # create a temporary file for the WKS content
153 with NamedTemporaryFile("w", suffix=".wks") as wks: 154 with NamedTemporaryFile("w", suffix=".wks") as wks:
154 wks.write( 155 wks.write(
155 'part --source bootimg-efi ' 156 'part --source bootimg_efi '
156 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=false" ' 157 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=false" '
157 '--label boot --active\n' 158 '--label boot --active\n'
158 ) 159 )
@@ -185,7 +186,7 @@ class Wic(WicTestCase):
185 # create a temporary file for the WKS content 186 # create a temporary file for the WKS content
186 with NamedTemporaryFile("w", suffix=".wks") as wks: 187 with NamedTemporaryFile("w", suffix=".wks") as wks:
187 wks.write( 188 wks.write(
188 'part --source bootimg-efi ' 189 'part --source bootimg_efi '
189 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=true" ' 190 '--sourceparams="loader=grub-efi,install-kernel-into-boot-dir=true" '
190 '--label boot --active\n' 191 '--label boot --active\n'
191 ) 192 )
@@ -445,8 +446,9 @@ class Wic(WicTestCase):
445 wks.write(""" 446 wks.write("""
446part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr 447part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr
447part /usr --source rootfs --ondisk mmcblk0 --fstype=ext4 --rootfs-dir %s/usr 448part /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""" 449part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --rootfs-dir %s/usr
449 % (rootfs_dir, rootfs_dir)) 450part /mnt --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/whoami --rootfs-dir %s/usr"""
451 % (rootfs_dir, rootfs_dir, rootfs_dir))
450 runCmd("wic create %s -e core-image-minimal -o %s" \ 452 runCmd("wic create %s -e core-image-minimal -o %s" \
451 % (wks_file, self.resultdir)) 453 % (wks_file, self.resultdir))
452 454
@@ -457,7 +459,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
457 wicimg = wicout[0] 459 wicimg = wicout[0]
458 460
459 # verify partition size with wic 461 # verify partition size with wic
460 res = runCmd("parted -m %s unit b p 2>/dev/null" % wicimg) 462 res = runCmd("parted -m %s unit b p" % wicimg, stderr=subprocess.PIPE)
461 463
462 # parse parted output which looks like this: 464 # parse parted output which looks like this:
463 # BYT;\n 465 # BYT;\n
@@ -465,9 +467,9 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
465 # 1:0.00MiB:200MiB:200MiB:ext4::;\n 467 # 1:0.00MiB:200MiB:200MiB:ext4::;\n
466 partlns = res.output.splitlines()[2:] 468 partlns = res.output.splitlines()[2:]
467 469
468 self.assertEqual(3, len(partlns)) 470 self.assertEqual(4, len(partlns))
469 471
470 for part in [1, 2, 3]: 472 for part in [1, 2, 3, 4]:
471 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part) 473 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
472 partln = partlns[part-1].split(":") 474 partln = partlns[part-1].split(":")
473 self.assertEqual(7, len(partln)) 475 self.assertEqual(7, len(partln))
@@ -478,16 +480,16 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
478 480
479 # Test partition 1, should contain the normal root directories, except 481 # Test partition 1, should contain the normal root directories, except
480 # /usr. 482 # /usr.
481 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 483 res = runCmd("debugfs -R 'ls -p' %s" % \
482 os.path.join(self.resultdir, "selftest_img.part1")) 484 os.path.join(self.resultdir, "selftest_img.part1"), stderr=subprocess.PIPE)
483 files = extract_files(res.output) 485 files = extract_files(res.output)
484 self.assertIn("etc", files) 486 self.assertIn("etc", files)
485 self.assertNotIn("usr", files) 487 self.assertNotIn("usr", files)
486 488
487 # Partition 2, should contain common directories for /usr, not root 489 # Partition 2, should contain common directories for /usr, not root
488 # directories. 490 # directories.
489 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 491 res = runCmd("debugfs -R 'ls -p' %s" % \
490 os.path.join(self.resultdir, "selftest_img.part2")) 492 os.path.join(self.resultdir, "selftest_img.part2"), stderr=subprocess.PIPE)
491 files = extract_files(res.output) 493 files = extract_files(res.output)
492 self.assertNotIn("etc", files) 494 self.assertNotIn("etc", files)
493 self.assertNotIn("usr", files) 495 self.assertNotIn("usr", files)
@@ -495,27 +497,78 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r
495 497
496 # Partition 3, should contain the same as partition 2, including the bin 498 # Partition 3, should contain the same as partition 2, including the bin
497 # directory, but not the files inside it. 499 # directory, but not the files inside it.
498 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ 500 res = runCmd("debugfs -R 'ls -p' %s" % \
499 os.path.join(self.resultdir, "selftest_img.part3")) 501 os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
500 files = extract_files(res.output) 502 files = extract_files(res.output)
501 self.assertNotIn("etc", files) 503 self.assertNotIn("etc", files)
502 self.assertNotIn("usr", files) 504 self.assertNotIn("usr", files)
503 self.assertIn("share", files) 505 self.assertIn("share", files)
504 self.assertIn("bin", files) 506 self.assertIn("bin", files)
505 res = runCmd("debugfs -R 'ls -p bin' %s 2>/dev/null" % \ 507 res = runCmd("debugfs -R 'ls -p bin' %s" % \
506 os.path.join(self.resultdir, "selftest_img.part3")) 508 os.path.join(self.resultdir, "selftest_img.part3"), stderr=subprocess.PIPE)
507 files = extract_files(res.output) 509 files = extract_files(res.output)
508 self.assertIn(".", files) 510 self.assertIn(".", files)
509 self.assertIn("..", files) 511 self.assertIn("..", files)
510 self.assertEqual(2, len(files)) 512 self.assertEqual(2, len(files))
511 513
512 for part in [1, 2, 3]: 514 # Partition 4, should contain the same as partition 2, including the bin
515 # directory, but not whoami (a symlink to busybox.nosuid) inside it.
516 res = runCmd("debugfs -R 'ls -p' %s" % \
517 os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
518 files = extract_files(res.output)
519 self.assertNotIn("etc", files)
520 self.assertNotIn("usr", files)
521 self.assertIn("share", files)
522 self.assertIn("bin", files)
523 res = runCmd("debugfs -R 'ls -p bin' %s" % \
524 os.path.join(self.resultdir, "selftest_img.part4"), stderr=subprocess.PIPE)
525 files = extract_files(res.output)
526 self.assertIn(".", files)
527 self.assertIn("..", files)
528 self.assertIn("who", files)
529 self.assertNotIn("whoami", files)
530
531 for part in [1, 2, 3, 4]:
513 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part) 532 part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
514 os.remove(part_file) 533 os.remove(part_file)
515 534
516 finally: 535 finally:
517 os.environ['PATH'] = oldpath 536 os.environ['PATH'] = oldpath
518 537
538 def test_exclude_path_with_extra_space(self):
539 """Test having --exclude-path with IMAGE_ROOTFS_EXTRA_SPACE. [Yocto #15555]"""
540
541 with NamedTemporaryFile("w", suffix=".wks") as wks:
542 wks.writelines(
543 ['bootloader --ptable gpt\n',
544 'part /boot --size=100M --active --fstype=ext4 --label boot\n',
545 'part / --source rootfs --fstype=ext4 --label root --exclude-path boot/\n'])
546 wks.flush()
547 config = 'IMAGE_ROOTFS_EXTRA_SPACE = "500000"\n'\
548 'DEPENDS:pn-core-image-minimal += "wic-tools"\n'\
549 'IMAGE_FSTYPES += "wic ext4"\n'\
550 'WKS_FILE = "%s"\n' % wks.name
551 self.append_config(config)
552 bitbake('core-image-minimal')
553
554 """
555 the output of "wic ls <image>.wic" will look something like:
556 Num Start End Size Fstype
557 1 17408 136332287 136314880 ext4
558 2 136332288 171464703 35132416 ext4
559 we are looking for the size of partition 2
560 i.e. in this case the number 35,132,416
561 without the fix the size will be around 85,403,648
562 with the fix the size should be around 799,960,064
563 """
564 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'MACHINE'], 'core-image-minimal')
565 deploy_dir = bb_vars['DEPLOY_DIR_IMAGE']
566 machine = bb_vars['MACHINE']
567 nativesysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
568 wicout = glob(os.path.join(deploy_dir, "core-image-minimal-%s.rootfs-*.wic" % machine))[0]
569 size_of_root_partition = int(runCmd("wic ls %s --native-sysroot %s" % (wicout, nativesysroot)).output.split('\n')[2].split()[3])
570 self.assertGreater(size_of_root_partition, 500000000)
571
519 def test_include_path(self): 572 def test_include_path(self):
520 """Test --include-path wks option.""" 573 """Test --include-path wks option."""
521 574
@@ -541,13 +594,13 @@ part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s"""
541 part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0] 594 part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0]
542 595
543 # Test partition 1, should not contain 'test-file' 596 # Test partition 1, should not contain 'test-file'
544 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 597 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
545 files = extract_files(res.output) 598 files = extract_files(res.output)
546 self.assertNotIn('test-file', files) 599 self.assertNotIn('test-file', files)
547 self.assertEqual(True, files_own_by_root(res.output)) 600 self.assertEqual(True, files_own_by_root(res.output))
548 601
549 # Test partition 2, should contain 'test-file' 602 # Test partition 2, should contain 'test-file'
550 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part2)) 603 res = runCmd("debugfs -R 'ls -p' %s" % (part2), stderr=subprocess.PIPE)
551 files = extract_files(res.output) 604 files = extract_files(res.output)
552 self.assertIn('test-file', files) 605 self.assertIn('test-file', files)
553 self.assertEqual(True, files_own_by_root(res.output)) 606 self.assertEqual(True, files_own_by_root(res.output))
@@ -576,12 +629,12 @@ part / --source rootfs --fstype=ext4 --include-path %s --include-path core-imag
576 629
577 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] 630 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
578 631
579 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 632 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
580 files = extract_files(res.output) 633 files = extract_files(res.output)
581 self.assertIn('test-file', files) 634 self.assertIn('test-file', files)
582 self.assertEqual(True, files_own_by_root(res.output)) 635 self.assertEqual(True, files_own_by_root(res.output))
583 636
584 res = runCmd("debugfs -R 'ls -p /export/etc/' %s 2>/dev/null" % (part1)) 637 res = runCmd("debugfs -R 'ls -p /export/etc/' %s" % (part1), stderr=subprocess.PIPE)
585 files = extract_files(res.output) 638 files = extract_files(res.output)
586 self.assertIn('passwd', files) 639 self.assertIn('passwd', files)
587 self.assertEqual(True, files_own_by_root(res.output)) 640 self.assertEqual(True, files_own_by_root(res.output))
@@ -668,7 +721,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
668 % (wks_file, self.resultdir)) 721 % (wks_file, self.resultdir))
669 722
670 for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')): 723 for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')):
671 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part)) 724 res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
672 self.assertEqual(True, files_own_by_root(res.output)) 725 self.assertEqual(True, files_own_by_root(res.output))
673 726
674 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file 727 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file
@@ -678,7 +731,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
678 731
679 # check each partition for permission 732 # check each partition for permission
680 for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')): 733 for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')):
681 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part)) 734 res = runCmd("debugfs -R 'ls -p' %s" % (part), stderr=subprocess.PIPE)
682 self.assertTrue(files_own_by_root(res.output) 735 self.assertTrue(files_own_by_root(res.output)
683 ,msg='Files permission incorrect using wks set "%s"' % test) 736 ,msg='Files permission incorrect using wks set "%s"' % test)
684 737
@@ -706,7 +759,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
706 759
707 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] 760 part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
708 761
709 res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) 762 res = runCmd("debugfs -R 'ls -p' %s" % (part1), stderr=subprocess.PIPE)
710 files = extract_files(res.output) 763 files = extract_files(res.output)
711 self.assertIn('passwd', files) 764 self.assertIn('passwd', files)
712 765
@@ -741,7 +794,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
741 bitbake('base-files -c do_install') 794 bitbake('base-files -c do_install')
742 bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab') 795 bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab')
743 self.assertEqual(True, os.path.exists(bf_fstab)) 796 self.assertEqual(True, os.path.exists(bf_fstab))
744 bf_fstab_md5sum = runCmd('md5sum %s 2>/dev/null' % bf_fstab).output.split(" ")[0] 797 bf_fstab_md5sum = runCmd('md5sum %s ' % bf_fstab).output.split(" ")[0]
745 798
746 try: 799 try:
747 no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update') 800 no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update')
@@ -757,7 +810,7 @@ part /etc --source rootfs --fstype=ext4 --change-directory=etc
757 part_fstab_md5sum = [] 810 part_fstab_md5sum = []
758 for i in range(1, 3): 811 for i in range(1, 3):
759 part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0] 812 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)) 813 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()) 814 part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest())
762 815
763 # '/etc/fstab' in partition 2 should contain the same stock fstab file 816 # '/etc/fstab' in partition 2 should contain the same stock fstab file
@@ -839,6 +892,61 @@ bootloader --ptable gpt""")
839 finally: 892 finally:
840 os.remove(wks_file) 893 os.remove(wks_file)
841 894
895 def test_wic_sector_size(self):
896 """Test generation image sector size"""
897
898 oldpath = os.environ['PATH']
899 os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
900
901 try:
902 # Add WIC_SECTOR_SIZE into config
903 config = 'WIC_SECTOR_SIZE = "4096"\n'\
904 'WICVARS:append = " WIC_SECTOR_SIZE"\n'
905 self.append_config(config)
906 bitbake('core-image-minimal')
907
908 # Check WIC_SECTOR_SIZE apply to bitbake variable
909 wic_sector_size_str = get_bb_var('WIC_SECTOR_SIZE', 'core-image-minimal')
910 wic_sector_size = int(wic_sector_size_str)
911 self.assertEqual(4096, wic_sector_size)
912
913 self.logger.info("Test wic_sector_size: %d \n" % wic_sector_size)
914
915 with NamedTemporaryFile("w", suffix=".wks") as wks:
916 wks.writelines(
917 ['bootloader --ptable gpt\n',
918 'part --fstype ext4 --source rootfs --label rofs-a --mkfs-extraopts "-b 4096"\n',
919 'part --fstype ext4 --source rootfs --use-uuid --mkfs-extraopts "-b 4096"\n'])
920 wks.flush()
921 cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
922 runCmd(cmd)
923 wksname = os.path.splitext(os.path.basename(wks.name))[0]
924 images = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
925 self.assertEqual(1, len(images))
926
927 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
928 # list partitions
929 result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
930 self.assertEqual(3, len(result.output.split('\n')))
931
932 # verify partition size with wic
933 res = runCmd("export PARTED_SECTOR_SIZE=%d; parted -m %s unit b p" % (wic_sector_size, images[0]),
934 stderr=subprocess.PIPE)
935
936 # parse parted output which looks like this:
937 # BYT;\n
938 # /var/tmp/wic/build/tmpgjzzefdd-202410281021-sda.direct:78569472B:file:4096:4096:gpt::;\n
939 # 1:139264B:39284735B:39145472B:ext4:rofs-a:;\n
940 # 2:39284736B:78430207B:39145472B:ext4:primary:;\n
941 disk_info = res.output.splitlines()[1]
942 # Check sector sizes
943 sector_size_logical = int(disk_info.split(":")[3])
944 sector_size_physical = int(disk_info.split(":")[4])
945 self.assertEqual(wic_sector_size, sector_size_logical, "Logical sector size is not %d." % wic_sector_size)
946 self.assertEqual(wic_sector_size, sector_size_physical, "Physical sector size is not %d." % wic_sector_size)
947
948 finally:
949 os.environ['PATH'] = oldpath
842 950
843class Wic2(WicTestCase): 951class Wic2(WicTestCase):
844 952
@@ -913,6 +1021,18 @@ class Wic2(WicTestCase):
913 """Test building wic images by bitbake""" 1021 """Test building wic images by bitbake"""
914 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\ 1022 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
915 'MACHINE_FEATURES:append = " efi"\n' 1023 'MACHINE_FEATURES:append = " efi"\n'
1024 image_recipe_append = """
1025do_image_wic[postfuncs] += "run_wic_cmd"
1026run_wic_cmd() {
1027 echo "test" >> ${WORKDIR}/test.wic-cp
1028 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1029 wic ls --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1030 wic rm --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/test.wic-cp
1031 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1032}
1033"""
1034 self.write_recipeinc('images', image_recipe_append)
1035
916 self.append_config(config) 1036 self.append_config(config)
917 image = 'wic-image-minimal' 1037 image = 'wic-image-minimal'
918 bitbake(image) 1038 bitbake(image)
@@ -921,6 +1041,11 @@ class Wic2(WicTestCase):
921 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], image) 1041 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']) 1042 prefix = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], '%s.' % bb_vars['IMAGE_LINK_NAME'])
923 1043
1044 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1045 # check if file is there
1046 result = runCmd("wic ls %s:1/ -n %s" % (prefix+"wic", sysroot))
1047 self.assertIn("test.wic-cp", result.output)
1048
924 # check if we have result image and manifests symlinks 1049 # check if we have result image and manifests symlinks
925 # pointing to existing files 1050 # pointing to existing files
926 for suffix in ('wic', 'manifest'): 1051 for suffix in ('wic', 'manifest'):
@@ -936,10 +1061,29 @@ class Wic2(WicTestCase):
936 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\ 1061 config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
937 'MACHINE_FEATURES:append = " efi"\n' 1062 'MACHINE_FEATURES:append = " efi"\n'
938 self.append_config(config) 1063 self.append_config(config)
1064 image_recipe_append = """
1065do_image_wic[postfuncs] += "run_wic_cmd"
1066run_wic_cmd() {
1067 echo "test" >> ${WORKDIR}/test.wic-cp
1068 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1069 wic ls --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1070 wic rm --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/test.wic-cp
1071 wic cp --vars "${STAGING_DIR}/${MACHINE}/imgdata/" -e "${IMAGE_BASENAME}" ${WORKDIR}/test.wic-cp ${IMGDEPLOYDIR}/${IMAGE_NAME}.wic:1/
1072}
1073"""
1074 self.write_recipeinc('images', image_recipe_append)
939 bitbake('wic-image-minimal') 1075 bitbake('wic-image-minimal')
1076
1077 sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1078 bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME'], "wic-image-minimal")
1079 image_path = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], bb_vars['IMAGE_LINK_NAME'])
1080 # check if file is there
1081 result = runCmd("wic ls %s:1/ -n %s" % (image_path+".wic", sysroot))
1082 self.assertIn("test.wic-cp", result.output)
940 self.remove_config(config) 1083 self.remove_config(config)
941 1084
942 with runqemu('wic-image-minimal', ssh=False, runqemuparams='nographic') as qemu: 1085 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'wic-image-minimal') or ""
1086 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' " \ 1087 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'" 1088 "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'"
945 status, output = qemu.run_serial(cmd) 1089 status, output = qemu.run_serial(cmd)
@@ -959,8 +1103,9 @@ class Wic2(WicTestCase):
959 bitbake('core-image-minimal ovmf') 1103 bitbake('core-image-minimal ovmf')
960 self.remove_config(config) 1104 self.remove_config(config)
961 1105
1106 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
962 with runqemu('core-image-minimal', ssh=False, 1107 with runqemu('core-image-minimal', ssh=False,
963 runqemuparams='nographic ovmf', image_fstype='wic') as qemu: 1108 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
964 cmd = "grep sda. /proc/partitions |wc -l" 1109 cmd = "grep sda. /proc/partitions |wc -l"
965 status, output = qemu.run_serial(cmd) 1110 status, output = qemu.run_serial(cmd)
966 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1111 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
@@ -1000,8 +1145,8 @@ class Wic2(WicTestCase):
1000 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools") 1145 native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1001 1146
1002 # verify partition size with wic 1147 # verify partition size with wic
1003 res = runCmd("parted -m %s unit kib p 2>/dev/null" % wicimg, 1148 res = runCmd("parted -m %s unit kib p" % wicimg,
1004 native_sysroot=native_sysroot) 1149 native_sysroot=native_sysroot, stderr=subprocess.PIPE)
1005 1150
1006 # parse parted output which looks like this: 1151 # parse parted output which looks like this:
1007 # BYT;\n 1152 # BYT;\n
@@ -1040,71 +1185,71 @@ class Wic2(WicTestCase):
1040 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1185 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1041 # Test that partitions are placed at the correct offsets, default KB 1186 # Test that partitions are placed at the correct offsets, default KB
1042 tempf.write("bootloader --ptable gpt\n" \ 1187 tempf.write("bootloader --ptable gpt\n" \
1043 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 100M --fstype=ext4\n" \ 1188 "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") 1189 "part /bar --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1045 tempf.flush() 1190 tempf.flush()
1046 1191
1047 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1192 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1048 self.assertEqual(partlns, [ 1193 self.assertEqual(partlns, [
1049 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1194 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1050 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1195 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1051 ]) 1196 ])
1052 1197
1053 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1198 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1054 # Test that partitions are placed at the correct offsets, same with explicit KB 1199 # Test that partitions are placed at the correct offsets, same with explicit KB
1055 tempf.write("bootloader --ptable gpt\n" \ 1200 tempf.write("bootloader --ptable gpt\n" \
1056 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 100M --fstype=ext4\n" \ 1201 "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") 1202 "part /bar --ondisk hda --offset 204832K --fixed-size 100M --fstype=ext4\n")
1058 tempf.flush() 1203 tempf.flush()
1059 1204
1060 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1205 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1061 self.assertEqual(partlns, [ 1206 self.assertEqual(partlns, [
1062 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1207 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1063 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1208 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1064 ]) 1209 ])
1065 1210
1066 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1211 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1067 # Test that partitions are placed at the correct offsets using MB 1212 # Test that partitions are placed at the correct offsets using MB
1068 tempf.write("bootloader --ptable gpt\n" \ 1213 tempf.write("bootloader --ptable gpt\n" \
1069 "part / --source rootfs --ondisk hda --offset 32K --fixed-size 100M --fstype=ext4\n" \ 1214 "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") 1215 "part /bar --ondisk hda --offset 201M --fixed-size 100M --fstype=ext4\n")
1071 tempf.flush() 1216 tempf.flush()
1072 1217
1073 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1218 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1074 self.assertEqual(partlns, [ 1219 self.assertEqual(partlns, [
1075 "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;", 1220 "1:32.0kiB:204832kiB:204800kiB:ext4:primary:;",
1076 "2:103424kiB:205824kiB:102400kiB:ext4:primary:;", 1221 "2:205824kiB:308224kiB:102400kiB:ext4:primary:;",
1077 ]) 1222 ])
1078 1223
1079 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1224 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1080 # Test that partitions can be placed on a 512 byte sector boundary 1225 # Test that partitions can be placed on a 512 byte sector boundary
1081 tempf.write("bootloader --ptable gpt\n" \ 1226 tempf.write("bootloader --ptable gpt\n" \
1082 "part / --source rootfs --ondisk hda --offset 65s --fixed-size 99M --fstype=ext4\n" \ 1227 "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") 1228 "part /bar --ondisk hda --offset 204832 --fixed-size 100M --fstype=ext4\n")
1084 tempf.flush() 1229 tempf.flush()
1085 1230
1086 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1231 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1087 self.assertEqual(partlns, [ 1232 self.assertEqual(partlns, [
1088 "1:32.5kiB:101408kiB:101376kiB:ext4:primary:;", 1233 "1:32.5kiB:203808kiB:203776kiB:ext4:primary:;",
1089 "2:102432kiB:204832kiB:102400kiB:ext4:primary:;", 1234 "2:204832kiB:307232kiB:102400kiB:ext4:primary:;",
1090 ]) 1235 ])
1091 1236
1092 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1237 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1093 # Test that a partition can be placed immediately after a MSDOS partition table 1238 # Test that a partition can be placed immediately after a MSDOS partition table
1094 tempf.write("bootloader --ptable msdos\n" \ 1239 tempf.write("bootloader --ptable msdos\n" \
1095 "part / --source rootfs --ondisk hda --offset 1s --fixed-size 100M --fstype=ext4\n") 1240 "part / --source rootfs --ondisk hda --offset 1s --fixed-size 200M --fstype=ext4\n")
1096 tempf.flush() 1241 tempf.flush()
1097 1242
1098 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot) 1243 _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1099 self.assertEqual(partlns, [ 1244 self.assertEqual(partlns, [
1100 "1:0.50kiB:102400kiB:102400kiB:ext4::;", 1245 "1:0.50kiB:204800kiB:204800kiB:ext4::;",
1101 ]) 1246 ])
1102 1247
1103 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1248 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1104 # Test that image creation fails if the partitions would overlap 1249 # Test that image creation fails if the partitions would overlap
1105 tempf.write("bootloader --ptable gpt\n" \ 1250 tempf.write("bootloader --ptable gpt\n" \
1106 "part / --source rootfs --ondisk hda --offset 32 --fixed-size 100M --fstype=ext4\n" \ 1251 "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") 1252 "part /bar --ondisk hda --offset 204831 --fixed-size 100M --fstype=ext4\n")
1108 tempf.flush() 1253 tempf.flush()
1109 1254
1110 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True) 1255 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
@@ -1113,7 +1258,7 @@ class Wic2(WicTestCase):
1113 with NamedTemporaryFile("w", suffix=".wks") as tempf: 1258 with NamedTemporaryFile("w", suffix=".wks") as tempf:
1114 # Test that partitions are not allowed to overlap with the booloader 1259 # Test that partitions are not allowed to overlap with the booloader
1115 tempf.write("bootloader --ptable gpt\n" \ 1260 tempf.write("bootloader --ptable gpt\n" \
1116 "part / --source rootfs --ondisk hda --offset 8 --fixed-size 100M --fstype=ext4\n") 1261 "part / --source rootfs --ondisk hda --offset 8 --fixed-size 200M --fstype=ext4\n")
1117 tempf.flush() 1262 tempf.flush()
1118 1263
1119 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True) 1264 p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
@@ -1154,8 +1299,9 @@ class Wic2(WicTestCase):
1154 bitbake('core-image-minimal-mtdutils') 1299 bitbake('core-image-minimal-mtdutils')
1155 self.remove_config(config) 1300 self.remove_config(config)
1156 1301
1302 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal-mtdutils') or ""
1157 with runqemu('core-image-minimal-mtdutils', ssh=False, 1303 with runqemu('core-image-minimal-mtdutils', ssh=False,
1158 runqemuparams='nographic', image_fstype='wic') as qemu: 1304 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1159 cmd = "grep sda. /proc/partitions |wc -l" 1305 cmd = "grep sda. /proc/partitions |wc -l"
1160 status, output = qemu.run_serial(cmd) 1306 status, output = qemu.run_serial(cmd)
1161 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1307 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
@@ -1177,6 +1323,10 @@ class Wic2(WicTestCase):
1177 self.assertEqual(1, len(out)) 1323 self.assertEqual(1, len(out))
1178 1324
1179 def test_rawcopy_plugin(self): 1325 def test_rawcopy_plugin(self):
1326 config = 'IMAGE_FSTYPES = "ext4"\n'
1327 self.append_config(config)
1328 self.assertEqual(0, bitbake('core-image-minimal').status)
1329 self.remove_config(config)
1180 self._rawcopy_plugin('ext4') 1330 self._rawcopy_plugin('ext4')
1181 1331
1182 def test_rawcopy_plugin_unpack(self): 1332 def test_rawcopy_plugin_unpack(self):
@@ -1214,8 +1364,9 @@ class Wic2(WicTestCase):
1214 bitbake('core-image-minimal') 1364 bitbake('core-image-minimal')
1215 self.remove_config(config) 1365 self.remove_config(config)
1216 1366
1367 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1217 with runqemu('core-image-minimal', ssh=False, 1368 with runqemu('core-image-minimal', ssh=False,
1218 runqemuparams='nographic', image_fstype='wic') as qemu: 1369 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1219 # Check that we have ONLY two /dev/sda* partitions (/boot and /) 1370 # Check that we have ONLY two /dev/sda* partitions (/boot and /)
1220 cmd = "grep sda. /proc/partitions | wc -l" 1371 cmd = "grep sda. /proc/partitions | wc -l"
1221 status, output = qemu.run_serial(cmd) 1372 status, output = qemu.run_serial(cmd)
@@ -1242,7 +1393,7 @@ class Wic2(WicTestCase):
1242 def test_biosplusefi_plugin(self): 1393 def test_biosplusefi_plugin(self):
1243 """Test biosplusefi plugin""" 1394 """Test biosplusefi plugin"""
1244 # Wic generation below may fail depending on the order of the unittests 1395 # 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 1396 # 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 1397 # 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() 1398 # 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" 1399 # will raise with "Couldn't find correct bootimg_dir"
@@ -1254,7 +1405,7 @@ class Wic2(WicTestCase):
1254 1405
1255 img = 'core-image-minimal' 1406 img = 'core-image-minimal'
1256 with NamedTemporaryFile("w", suffix=".wks") as wks: 1407 with NamedTemporaryFile("w", suffix=".wks") as wks:
1257 wks.writelines(['part /boot --active --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\n', 1408 wks.writelines(['part /boot --active --source bootimg_biosplusefi --sourceparams="loader=grub-efi"\n',
1258 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\ 1409 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1259 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n']) 1410 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1260 wks.flush() 1411 wks.flush()
@@ -1274,7 +1425,7 @@ class Wic2(WicTestCase):
1274 1425
1275 img = 'core-image-minimal' 1426 img = 'core-image-minimal'
1276 with NamedTemporaryFile("w", suffix=".wks") as wks: 1427 with NamedTemporaryFile("w", suffix=".wks") as wks:
1277 wks.writelines(['part /boot --source bootimg-efi --sourceparams="loader=uefi-kernel"\n' 1428 wks.writelines(['part /boot --source bootimg_efi --sourceparams="loader=uefi-kernel"\n'
1278 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\ 1429 'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1279 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n']) 1430 'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1280 wks.flush() 1431 wks.flush()
@@ -1288,24 +1439,45 @@ class Wic2(WicTestCase):
1288 @skipIfNotArch(['i586', 'i686', 'x86_64']) 1439 @skipIfNotArch(['i586', 'i686', 'x86_64'])
1289 @OETestTag("runqemu") 1440 @OETestTag("runqemu")
1290 def test_efi_plugin_unified_kernel_image_qemu(self): 1441 def test_efi_plugin_unified_kernel_image_qemu(self):
1291 """Test efi plugin's Unified Kernel Image feature in qemu""" 1442 """Test Unified Kernel Image feature in qemu without systemd in initramfs or rootfs"""
1292 config = 'IMAGE_FSTYPES = "wic"\n'\ 1443 config = """
1293 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\ 1444# efi firmware must load systemd-boot, not grub
1294 'WKS_FILE = "test_efi_plugin.wks"\n'\ 1445EFI_PROVIDER = "systemd-boot"
1295 'MACHINE_FEATURES:append = " efi"\n' 1446
1447# image format must be wic, needs esp partition for firmware etc
1448IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1449WKS_FILE = "test_efi_plugin.wks"
1450
1451# efi, uki and systemd features must be enabled
1452MACHINE_FEATURES:append = " efi"
1453IMAGE_CLASSES:append:pn-core-image-base = " uki"
1454
1455# uki embeds also an initrd, no systemd or udev
1456INITRAMFS_IMAGE = "core-image-initramfs-boot"
1457
1458# runqemu must not load kernel separately, it's in the uki
1459QB_KERNEL_ROOT = ""
1460QB_DEFAULT_KERNEL = "none"
1461
1462# boot command line provided via uki, not via bootloader
1463UKI_CMDLINE = "rootwait root=LABEL=root console=${KERNEL_CONSOLE}"
1464
1465"""
1296 self.append_config(config) 1466 self.append_config(config)
1297 bitbake('core-image-minimal core-image-minimal-initramfs ovmf') 1467 bitbake('core-image-base ovmf')
1468 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1469 uki_filename = get_bb_var('UKI_FILENAME', 'core-image-base')
1298 self.remove_config(config) 1470 self.remove_config(config)
1299 1471
1300 with runqemu('core-image-minimal', ssh=False, 1472 with runqemu('core-image-base', ssh=False,
1301 runqemuparams='nographic ovmf', image_fstype='wic') as qemu: 1473 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1302 # Check that /boot has EFI bootx64.efi (required for EFI) 1474 # Check that /boot has EFI boot*.efi (required for EFI)
1303 cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l" 1475 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1304 status, output = qemu.run_serial(cmd) 1476 status, output = qemu.run_serial(cmd)
1305 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1477 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1306 self.assertEqual(output, '1') 1478 self.assertEqual(output, '1')
1307 # Check that /boot has EFI/Linux/linux.efi (required for Unified Kernel Images auto detection) 1479 # 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" 1480 cmd = "ls /boot/EFI/Linux/%s | wc -l" % (uki_filename)
1309 status, output = qemu.run_serial(cmd) 1481 status, output = qemu.run_serial(cmd)
1310 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1482 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1311 self.assertEqual(output, '1') 1483 self.assertEqual(output, '1')
@@ -1315,6 +1487,80 @@ class Wic2(WicTestCase):
1315 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1487 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1316 self.assertEqual(output, '0') 1488 self.assertEqual(output, '0')
1317 1489
1490 @skipIfNotArch(['aarch64'])
1491 @OETestTag("runqemu")
1492 def test_efi_plugin_plain_systemd_boot_qemu_aarch64(self):
1493 """Test plain systemd-boot in qemu with systemd"""
1494 config = """
1495INIT_MANAGER = "systemd"
1496EFI_PROVIDER = "systemd-boot"
1497
1498# image format must be wic, needs esp partition for firmware etc
1499IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1500WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1501
1502INITRAMFS_IMAGE = "core-image-initramfs-boot"
1503
1504# to configure runqemu
1505IMAGE_CLASSES += "qemuboot"
1506# u-boot efi firmware
1507QB_DEFAULT_BIOS = "u-boot.bin"
1508# need to use virtio, scsi not supported by u-boot by default
1509QB_DRIVE_TYPE = "/dev/vd"
1510
1511# disable kvm, breaks boot
1512QEMU_USE_KVM = ""
1513
1514IMAGE_CLASSES:remove = 'testimage'
1515"""
1516 self.append_config(config)
1517 bitbake('core-image-base u-boot')
1518 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1519
1520 with runqemu('core-image-base', ssh=False,
1521 runqemuparams='%s nographic' % (runqemu_params), image_fstype='wic') as qemu:
1522 # Check that /boot has EFI boot*.efi (required for EFI)
1523 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1524 status, output = qemu.run_serial(cmd)
1525 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1526 self.assertEqual(output, '1')
1527 # Check that boot.conf exists
1528 cmd = "cat /boot/loader/entries/boot.conf"
1529 status, output = qemu.run_serial(cmd)
1530 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1531 self.remove_config(config)
1532
1533 @skipIfNotArch(['i586', 'i686', 'x86_64'])
1534 @OETestTag("runqemu")
1535 def test_efi_plugin_plain_systemd_boot_qemu_x86(self):
1536 """Test plain systemd-boot to systemd in qemu"""
1537 config = """
1538INIT_MANAGER = "systemd"
1539EFI_PROVIDER = "systemd-boot"
1540
1541# image format must be wic, needs esp partition for firmware etc
1542IMAGE_FSTYPES:pn-core-image-base:append = " wic"
1543WKS_FILE = "test_efi_plugin_plain_systemd-boot.wks"
1544
1545INITRAMFS_IMAGE = "core-image-initramfs-boot"
1546"""
1547 self.append_config(config)
1548 bitbake('core-image-base ovmf')
1549 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-base') or ""
1550 self.remove_config(config)
1551
1552 with runqemu('core-image-base', ssh=False,
1553 runqemuparams='%s nographic ovmf' % (runqemu_params), image_fstype='wic') as qemu:
1554 # Check that /boot has EFI boot*.efi (required for EFI)
1555 cmd = "ls /boot/EFI/BOOT/boot*.efi | wc -l"
1556 status, output = qemu.run_serial(cmd)
1557 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1558 self.assertEqual(output, '1')
1559 # Check that boot.conf exists
1560 cmd = "cat /boot/loader/entries/boot.conf"
1561 status, output = qemu.run_serial(cmd)
1562 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1563
1318 def test_fs_types(self): 1564 def test_fs_types(self):
1319 """Test filesystem types for empty and not empty partitions""" 1565 """Test filesystem types for empty and not empty partitions"""
1320 img = 'core-image-minimal' 1566 img = 'core-image-minimal'
@@ -1446,8 +1692,8 @@ class Wic2(WicTestCase):
1446 os.rename(image_path, image_path + '.bak') 1692 os.rename(image_path, image_path + '.bak')
1447 os.rename(new_image_path, image_path) 1693 os.rename(new_image_path, image_path)
1448 1694
1449 # Check if it boots in qemu 1695 runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', 'core-image-minimal') or ""
1450 with runqemu('core-image-minimal', ssh=False, runqemuparams='nographic') as qemu: 1696 with runqemu('core-image-minimal', ssh=False, runqemuparams='%s nographic' % (runqemu_params)) as qemu:
1451 cmd = "ls /etc/" 1697 cmd = "ls /etc/"
1452 status, output = qemu.run_serial('true') 1698 status, output = qemu.run_serial('true')
1453 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 1699 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
diff --git a/meta/lib/oeqa/selftest/context.py b/meta/lib/oeqa/selftest/context.py
index 57844b289a..16f82c6737 100644
--- a/meta/lib/oeqa/selftest/context.py
+++ b/meta/lib/oeqa/selftest/context.py
@@ -102,6 +102,13 @@ class OESelftestTestContext(OETestContext):
102 oe.path.copytree(builddir + "/cache", newbuilddir + "/cache") 102 oe.path.copytree(builddir + "/cache", newbuilddir + "/cache")
103 oe.path.copytree(selftestdir, newselftestdir) 103 oe.path.copytree(selftestdir, newselftestdir)
104 104
105 # if the last line of local.conf in newbuilddir is not empty and does not end with newline then add one
106 localconf_path = newbuilddir + "/conf/local.conf"
107 with open(localconf_path, "r+", encoding="utf-8") as f:
108 last_line = f.readlines()[-1]
109 if last_line and not last_line.endswith("\n"):
110 f.write("\n")
111
105 subprocess.check_output("git init && git add * && git commit -a -m 'initial'", cwd=newselftestdir, shell=True) 112 subprocess.check_output("git init && git add * && git commit -a -m 'initial'", cwd=newselftestdir, shell=True)
106 113
107 # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow 114 # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow
@@ -114,11 +121,15 @@ class OESelftestTestContext(OETestContext):
114 bblayers_abspath = [os.path.abspath(path) for path in bblayers.split()] 121 bblayers_abspath = [os.path.abspath(path) for path in bblayers.split()]
115 with open("%s/conf/bblayers.conf" % newbuilddir, "a") as f: 122 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 123 newbblayers = "# new bblayers to be used by selftest in the new build dir '%s'\n" % newbuilddir
124 newbblayers += 'unset BBLAYERS\n'
117 newbblayers += 'BBLAYERS = "%s"\n' % ' '.join(bblayers_abspath) 125 newbblayers += 'BBLAYERS = "%s"\n' % ' '.join(bblayers_abspath)
118 f.write(newbblayers) 126 f.write(newbblayers)
119 127
128 # Rewrite builddir paths seen in environment variables
120 for e in os.environ: 129 for e in os.environ:
121 if builddir + "/" in os.environ[e]: 130 # Rewrite paths that absolutely point inside builddir
131 # (e.g $builddir/conf/ would be rewritten but not $builddir/../bitbake/)
132 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 + "/") 133 os.environ[e] = os.environ[e].replace(builddir + "/", newbuilddir + "/")
123 if os.environ[e].endswith(builddir): 134 if os.environ[e].endswith(builddir):
124 os.environ[e] = os.environ[e].replace(builddir, newbuilddir) 135 os.environ[e] = os.environ[e].replace(builddir, newbuilddir)
@@ -194,8 +205,23 @@ class OESelftestTestContextExecutor(OETestContextExecutor):
194 parser.add_argument('-R', '--skip-tests', required=False, action='store', 205 parser.add_argument('-R', '--skip-tests', required=False, action='store',
195 nargs='+', dest="skips", default=None, 206 nargs='+', dest="skips", default=None,
196 help='Skip the tests specified. Format should be <module>[.<class>[.<test_method>]]') 207 help='Skip the tests specified. Format should be <module>[.<class>[.<test_method>]]')
208
209 def check_parallel_support(parameter):
210 if not parameter.isdigit():
211 import argparse
212 raise argparse.ArgumentTypeError("argument -j/--num-processes: invalid int value: '%s' " % str(parameter))
213
214 processes = int(parameter)
215 if processes:
216 try:
217 import testtools, subunit
218 except ImportError:
219 print("Failed to import testtools or subunit, the testcases will run serially")
220 processes = None
221 return processes
222
197 parser.add_argument('-j', '--num-processes', dest='processes', action='store', 223 parser.add_argument('-j', '--num-processes', dest='processes', action='store',
198 type=int, help="number of processes to execute in parallel with") 224 type=check_parallel_support, help="number of processes to execute in parallel with")
199 225
200 parser.add_argument('-t', '--select-tag', dest="select_tags", 226 parser.add_argument('-t', '--select-tag', dest="select_tags",
201 action='append', default=None, 227 action='append', default=None,
diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py
index 6e8b781973..cdf382ee21 100644
--- a/meta/lib/oeqa/targetcontrol.py
+++ b/meta/lib/oeqa/targetcontrol.py
@@ -88,7 +88,7 @@ class QemuTarget(BaseTarget):
88 88
89 supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] 89 supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic']
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..b60a6e6c38 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.
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 8104400ac2..c69481db6c 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 -hl') 25 (status, output) = tc.target.run('df -h')
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={})
@@ -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..1774513023 100644
--- a/meta/lib/oeqa/utils/subprocesstweak.py
+++ b/meta/lib/oeqa/utils/subprocesstweak.py
@@ -8,16 +8,11 @@ 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 s = s + "\nStandard Error: " + strify(self.stderr)
19 if hasattr(self, "stderr") and self.stderr:
20 s = s + "\nStandard Error: " + strify(self.stderr)
21 return s 16 return s
22 17
23def errors_have_output(): 18def errors_have_output():
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