diff options
Diffstat (limited to 'meta/lib/oeqa/selftest/cases/devtool.py')
-rw-r--r-- | meta/lib/oeqa/selftest/cases/devtool.py | 1260 |
1 files changed, 1146 insertions, 114 deletions
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 3385546e8e..882225dde3 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py | |||
@@ -1,18 +1,23 @@ | |||
1 | # | 1 | # |
2 | # Copyright OpenEmbedded Contributors | ||
3 | # | ||
2 | # SPDX-License-Identifier: MIT | 4 | # SPDX-License-Identifier: MIT |
3 | # | 5 | # |
4 | 6 | ||
7 | import errno | ||
5 | import os | 8 | import os |
6 | import re | 9 | import re |
7 | import shutil | 10 | import shutil |
8 | import tempfile | 11 | import tempfile |
9 | import glob | 12 | import glob |
10 | import fnmatch | 13 | import fnmatch |
14 | import unittest | ||
15 | import json | ||
11 | 16 | ||
12 | import oeqa.utils.ftools as ftools | ||
13 | from oeqa.selftest.case import OESelftestTestCase | 17 | from oeqa.selftest.case import OESelftestTestCase |
14 | from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer | 18 | from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer |
15 | from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer | 19 | from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer |
20 | from oeqa.core.decorator import OETestTag | ||
16 | 21 | ||
17 | oldmetapath = None | 22 | oldmetapath = None |
18 | 23 | ||
@@ -24,6 +29,9 @@ def setUpModule(): | |||
24 | corecopydir = os.path.join(templayerdir, 'core-copy') | 29 | corecopydir = os.path.join(templayerdir, 'core-copy') |
25 | bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf') | 30 | bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf') |
26 | edited_layers = [] | 31 | edited_layers = [] |
32 | # make sure user doesn't have a local workspace | ||
33 | result = runCmd('bitbake-layers show-layers') | ||
34 | assert "workspacelayer" not in result.output, "Devtool test suite cannot be run with a local workspace directory" | ||
27 | 35 | ||
28 | # We need to take a copy of the meta layer so we can modify it and not | 36 | # We need to take a copy of the meta layer so we can modify it and not |
29 | # have any races against other tests that might be running in parallel | 37 | # have any races against other tests that might be running in parallel |
@@ -38,10 +46,17 @@ def setUpModule(): | |||
38 | canonical_layerpath = os.path.realpath(canonical_layerpath) + '/' | 46 | canonical_layerpath = os.path.realpath(canonical_layerpath) + '/' |
39 | edited_layers.append(layerpath) | 47 | edited_layers.append(layerpath) |
40 | oldmetapath = os.path.realpath(layerpath) | 48 | oldmetapath = os.path.realpath(layerpath) |
49 | |||
50 | # when downloading poky from tar.gz some tests will be skipped (BUG 12389) | ||
51 | try: | ||
52 | runCmd('git rev-parse --is-inside-work-tree', cwd=canonical_layerpath) | ||
53 | except: | ||
54 | raise unittest.SkipTest("devtool tests require folder to be a git repo") | ||
55 | |||
41 | result = runCmd('git rev-parse --show-toplevel', cwd=canonical_layerpath) | 56 | result = runCmd('git rev-parse --show-toplevel', cwd=canonical_layerpath) |
42 | oldreporoot = result.output.rstrip() | 57 | oldreporoot = result.output.rstrip() |
43 | newmetapath = os.path.join(corecopydir, os.path.relpath(oldmetapath, oldreporoot)) | 58 | newmetapath = os.path.join(corecopydir, os.path.relpath(oldmetapath, oldreporoot)) |
44 | runCmd('git clone %s %s' % (oldreporoot, corecopydir), cwd=templayerdir) | 59 | runCmd('git clone file://%s %s' % (oldreporoot, corecopydir), cwd=templayerdir) |
45 | # Now we need to copy any modified files | 60 | # Now we need to copy any modified files |
46 | # You might ask "why not just copy the entire tree instead of | 61 | # You might ask "why not just copy the entire tree instead of |
47 | # cloning and doing this?" - well, the problem with that is | 62 | # cloning and doing this?" - well, the problem with that is |
@@ -80,32 +95,15 @@ def tearDownModule(): | |||
80 | bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) | 95 | bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb) |
81 | shutil.rmtree(templayerdir) | 96 | shutil.rmtree(templayerdir) |
82 | 97 | ||
83 | class DevtoolBase(OESelftestTestCase): | 98 | class DevtoolTestCase(OESelftestTestCase): |
84 | |||
85 | @classmethod | ||
86 | def setUpClass(cls): | ||
87 | super(DevtoolBase, cls).setUpClass() | ||
88 | bb_vars = get_bb_vars(['TOPDIR', 'SSTATE_DIR']) | ||
89 | cls.original_sstate = bb_vars['SSTATE_DIR'] | ||
90 | cls.devtool_sstate = os.path.join(bb_vars['TOPDIR'], 'sstate_devtool') | ||
91 | cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate | ||
92 | cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n' | ||
93 | % cls.original_sstate) | ||
94 | |||
95 | @classmethod | ||
96 | def tearDownClass(cls): | ||
97 | cls.logger.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate) | ||
98 | runCmd('rm -rf %s' % cls.devtool_sstate) | ||
99 | super(DevtoolBase, cls).tearDownClass() | ||
100 | 99 | ||
101 | def setUp(self): | 100 | def setUp(self): |
102 | """Test case setup function""" | 101 | """Test case setup function""" |
103 | super(DevtoolBase, self).setUp() | 102 | super(DevtoolTestCase, self).setUp() |
104 | self.workspacedir = os.path.join(self.builddir, 'workspace') | 103 | self.workspacedir = os.path.join(self.builddir, 'workspace') |
105 | self.assertTrue(not os.path.exists(self.workspacedir), | 104 | self.assertTrue(not os.path.exists(self.workspacedir), |
106 | 'This test cannot be run with a workspace directory ' | 105 | 'This test cannot be run with a workspace directory ' |
107 | 'under the build directory') | 106 | 'under the build directory') |
108 | self.append_config(self.sstate_conf) | ||
109 | 107 | ||
110 | def _check_src_repo(self, repo_dir): | 108 | def _check_src_repo(self, repo_dir): |
111 | """Check srctree git repository""" | 109 | """Check srctree git repository""" |
@@ -235,6 +233,103 @@ class DevtoolBase(OESelftestTestCase): | |||
235 | filelist.append(' '.join(splitline)) | 233 | filelist.append(' '.join(splitline)) |
236 | return filelist | 234 | return filelist |
237 | 235 | ||
236 | def _check_diff(self, diffoutput, addlines, removelines): | ||
237 | """Check output from 'git diff' matches expectation""" | ||
238 | remaining_addlines = addlines[:] | ||
239 | remaining_removelines = removelines[:] | ||
240 | for line in diffoutput.splitlines(): | ||
241 | if line.startswith('+++') or line.startswith('---'): | ||
242 | continue | ||
243 | elif line.startswith('+'): | ||
244 | matched = False | ||
245 | for item in addlines: | ||
246 | if re.match(item, line[1:].strip()): | ||
247 | matched = True | ||
248 | remaining_addlines.remove(item) | ||
249 | break | ||
250 | self.assertTrue(matched, 'Unexpected diff add line: %s' % line) | ||
251 | elif line.startswith('-'): | ||
252 | matched = False | ||
253 | for item in removelines: | ||
254 | if re.match(item, line[1:].strip()): | ||
255 | matched = True | ||
256 | remaining_removelines.remove(item) | ||
257 | break | ||
258 | self.assertTrue(matched, 'Unexpected diff remove line: %s' % line) | ||
259 | if remaining_addlines: | ||
260 | self.fail('Expected added lines not found: %s' % remaining_addlines) | ||
261 | if remaining_removelines: | ||
262 | self.fail('Expected removed lines not found: %s' % remaining_removelines) | ||
263 | |||
264 | def _check_runqemu_prerequisites(self): | ||
265 | """Check runqemu is available | ||
266 | |||
267 | Whilst some tests would seemingly be better placed as a runtime test, | ||
268 | unfortunately the runtime tests run under bitbake and you can't run | ||
269 | devtool within bitbake (since devtool needs to run bitbake itself). | ||
270 | Additionally we are testing build-time functionality as well, so | ||
271 | really this has to be done as an oe-selftest test. | ||
272 | """ | ||
273 | machine = get_bb_var('MACHINE') | ||
274 | if not machine.startswith('qemu'): | ||
275 | self.skipTest('This test only works with qemu machines') | ||
276 | if not os.path.exists('/etc/runqemu-nosudo'): | ||
277 | self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') | ||
278 | result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ip tuntap show', ignore_status=True) | ||
279 | if result.status != 0: | ||
280 | result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ifconfig -a', ignore_status=True) | ||
281 | if result.status != 0: | ||
282 | self.skipTest('Failed to determine if tap devices exist with ifconfig or ip: %s' % result.output) | ||
283 | for line in result.output.splitlines(): | ||
284 | if line.startswith('tap'): | ||
285 | break | ||
286 | else: | ||
287 | self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') | ||
288 | |||
289 | def _test_devtool_add_git_url(self, git_url, version, pn, resulting_src_uri, srcrev=None): | ||
290 | self.track_for_cleanup(self.workspacedir) | ||
291 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
292 | command = 'devtool add --version %s %s %s' % (version, pn, git_url) | ||
293 | if srcrev : | ||
294 | command += ' --srcrev %s' %srcrev | ||
295 | result = runCmd(command) | ||
296 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') | ||
297 | # Check the recipe name is correct | ||
298 | recipefile = get_bb_var('FILE', pn) | ||
299 | self.assertIn('%s_git.bb' % pn, recipefile, 'Recipe file incorrectly named') | ||
300 | self.assertIn(recipefile, result.output) | ||
301 | # Test devtool status | ||
302 | result = runCmd('devtool status') | ||
303 | self.assertIn(pn, result.output) | ||
304 | self.assertIn(recipefile, result.output) | ||
305 | checkvars = {} | ||
306 | checkvars['SRC_URI'] = resulting_src_uri | ||
307 | self._test_recipe_contents(recipefile, checkvars, []) | ||
308 | |||
309 | class DevtoolBase(DevtoolTestCase): | ||
310 | |||
311 | @classmethod | ||
312 | def setUpClass(cls): | ||
313 | super(DevtoolBase, cls).setUpClass() | ||
314 | bb_vars = get_bb_vars(['TOPDIR', 'SSTATE_DIR']) | ||
315 | cls.original_sstate = bb_vars['SSTATE_DIR'] | ||
316 | cls.devtool_sstate = os.path.join(bb_vars['TOPDIR'], 'sstate_devtool') | ||
317 | cls.sstate_conf = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate | ||
318 | cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n' | ||
319 | % cls.original_sstate) | ||
320 | cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687"\n') | ||
321 | |||
322 | @classmethod | ||
323 | def tearDownClass(cls): | ||
324 | cls.logger.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate) | ||
325 | runCmd('rm -rf %s' % cls.devtool_sstate) | ||
326 | super(DevtoolBase, cls).tearDownClass() | ||
327 | |||
328 | def setUp(self): | ||
329 | """Test case setup function""" | ||
330 | super(DevtoolBase, self).setUp() | ||
331 | self.append_config(self.sstate_conf) | ||
332 | |||
238 | 333 | ||
239 | class DevtoolTests(DevtoolBase): | 334 | class DevtoolTests(DevtoolBase): |
240 | 335 | ||
@@ -304,6 +399,38 @@ class DevtoolAddTests(DevtoolBase): | |||
304 | bindir = bindir[1:] | 399 | bindir = bindir[1:] |
305 | self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D') | 400 | self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D') |
306 | 401 | ||
402 | def test_devtool_add_binary(self): | ||
403 | # Create a binary package containing a known test file | ||
404 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
405 | self.track_for_cleanup(tempdir) | ||
406 | pn = 'tst-bin' | ||
407 | pv = '1.0' | ||
408 | test_file_dir = "var/lib/%s/" % pn | ||
409 | test_file_name = "test_file" | ||
410 | test_file_content = "TEST CONTENT" | ||
411 | test_file_package_root = os.path.join(tempdir, pn) | ||
412 | test_file_dir_full = os.path.join(test_file_package_root, test_file_dir) | ||
413 | bb.utils.mkdirhier(test_file_dir_full) | ||
414 | with open(os.path.join(test_file_dir_full, test_file_name), "w") as f: | ||
415 | f.write(test_file_content) | ||
416 | bin_package_path = os.path.join(tempdir, "%s.tar.gz" % pn) | ||
417 | runCmd("tar czf %s -C %s ." % (bin_package_path, test_file_package_root)) | ||
418 | |||
419 | # Test devtool add -b on the binary package | ||
420 | self.track_for_cleanup(self.workspacedir) | ||
421 | self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn) | ||
422 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
423 | result = runCmd('devtool add -b %s %s' % (pn, bin_package_path)) | ||
424 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') | ||
425 | |||
426 | # Build the resulting recipe | ||
427 | result = runCmd('devtool build %s' % pn) | ||
428 | installdir = get_bb_var('D', pn) | ||
429 | self.assertTrue(installdir, 'Could not query installdir variable') | ||
430 | |||
431 | # Check that a known file from the binary package has indeed been installed | ||
432 | self.assertTrue(os.path.isfile(os.path.join(installdir, test_file_dir, test_file_name)), '%s not found in D' % test_file_name) | ||
433 | |||
307 | def test_devtool_add_git_local(self): | 434 | def test_devtool_add_git_local(self): |
308 | # We need dbus built so that DEPENDS recognition works | 435 | # We need dbus built so that DEPENDS recognition works |
309 | bitbake('dbus') | 436 | bitbake('dbus') |
@@ -336,15 +463,32 @@ class DevtoolAddTests(DevtoolBase): | |||
336 | self.assertIn(srcdir, result.output) | 463 | self.assertIn(srcdir, result.output) |
337 | self.assertIn(recipefile, result.output) | 464 | self.assertIn(recipefile, result.output) |
338 | checkvars = {} | 465 | checkvars = {} |
339 | checkvars['LICENSE'] = 'GPLv2' | 466 | checkvars['LICENSE'] = 'GPL-2.0-only' |
340 | checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263' | 467 | checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263' |
341 | checkvars['S'] = '${WORKDIR}/git' | 468 | checkvars['S'] = '${WORKDIR}/git' |
342 | checkvars['PV'] = '0.1+git${SRCPV}' | 469 | checkvars['PV'] = '0.1+git' |
343 | checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https' | 470 | checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master' |
344 | checkvars['SRCREV'] = srcrev | 471 | checkvars['SRCREV'] = srcrev |
345 | checkvars['DEPENDS'] = set(['dbus']) | 472 | checkvars['DEPENDS'] = set(['dbus']) |
346 | self._test_recipe_contents(recipefile, checkvars, []) | 473 | self._test_recipe_contents(recipefile, checkvars, []) |
347 | 474 | ||
475 | def test_devtool_add_git_style1(self): | ||
476 | version = 'v3.1.0' | ||
477 | pn = 'mbedtls' | ||
478 | # this will trigger reformat_git_uri with branch parameter in url | ||
479 | git_url = "'git://git@github.com/ARMmbed/mbedtls.git;branch=mbedtls-2.28;protocol=https'" | ||
480 | resulting_src_uri = "git://git@github.com/ARMmbed/mbedtls.git;branch=mbedtls-2.28;protocol=https" | ||
481 | self._test_devtool_add_git_url(git_url, version, pn, resulting_src_uri) | ||
482 | |||
483 | def test_devtool_add_git_style2(self): | ||
484 | version = 'v3.1.0' | ||
485 | srcrev = 'v3.1.0' | ||
486 | pn = 'mbedtls' | ||
487 | # this will trigger reformat_git_uri with branch parameter in url | ||
488 | git_url = "'git://git@github.com/ARMmbed/mbedtls.git;protocol=https'" | ||
489 | resulting_src_uri = "git://git@github.com/ARMmbed/mbedtls.git;protocol=https;branch=master" | ||
490 | self._test_devtool_add_git_url(git_url, version, pn, resulting_src_uri, srcrev) | ||
491 | |||
348 | def test_devtool_add_library(self): | 492 | def test_devtool_add_library(self): |
349 | # Fetch source | 493 | # Fetch source |
350 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | 494 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') |
@@ -373,7 +517,7 @@ class DevtoolAddTests(DevtoolBase): | |||
373 | recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version) | 517 | recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version) |
374 | result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- \'-DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules\'' % recipefile) | 518 | result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- \'-DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules\'' % recipefile) |
375 | with open(recipefile, 'a') as f: | 519 | with open(recipefile, 'a') as f: |
376 | f.write('\nFILES_${PN}-dev += "${datadir}/cmake/Modules"\n') | 520 | f.write('\nFILES:${PN}-dev += "${datadir}/cmake/Modules"\n') |
377 | # We don't have the ability to pick up this dependency automatically yet... | 521 | # We don't have the ability to pick up this dependency automatically yet... |
378 | f.write('\nDEPENDS += "libusb1"\n') | 522 | f.write('\nDEPENDS += "libusb1"\n') |
379 | f.write('\nTESTLIBOUTPUT = "${COMPONENTS_DIR}/${TUNE_PKGARCH}/${PN}/${libdir}"\n') | 523 | f.write('\nTESTLIBOUTPUT = "${COMPONENTS_DIR}/${TUNE_PKGARCH}/${PN}/${libdir}"\n') |
@@ -405,7 +549,7 @@ class DevtoolAddTests(DevtoolBase): | |||
405 | self.track_for_cleanup(self.workspacedir) | 549 | self.track_for_cleanup(self.workspacedir) |
406 | self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe) | 550 | self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe) |
407 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | 551 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') |
408 | result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url)) | 552 | result = runCmd('devtool add --no-pypi %s %s -f %s' % (testrecipe, srcdir, url)) |
409 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output) | 553 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output) |
410 | self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory') | 554 | self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory') |
411 | self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created') | 555 | self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created') |
@@ -424,7 +568,7 @@ class DevtoolAddTests(DevtoolBase): | |||
424 | result = runCmd('devtool reset -n %s' % testrecipe) | 568 | result = runCmd('devtool reset -n %s' % testrecipe) |
425 | shutil.rmtree(srcdir) | 569 | shutil.rmtree(srcdir) |
426 | fakever = '1.9' | 570 | fakever = '1.9' |
427 | result = runCmd('devtool add %s %s -f %s -V %s' % (testrecipe, srcdir, url, fakever)) | 571 | result = runCmd('devtool add --no-pypi %s %s -f %s -V %s' % (testrecipe, srcdir, url, fakever)) |
428 | self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory') | 572 | self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory') |
429 | # Test devtool status | 573 | # Test devtool status |
430 | result = runCmd('devtool status') | 574 | result = runCmd('devtool status') |
@@ -442,6 +586,7 @@ class DevtoolAddTests(DevtoolBase): | |||
442 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | 586 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') |
443 | self.track_for_cleanup(tempdir) | 587 | self.track_for_cleanup(tempdir) |
444 | url = 'gitsm://git.yoctoproject.org/mraa' | 588 | url = 'gitsm://git.yoctoproject.org/mraa' |
589 | url_branch = '%s;branch=master' % url | ||
445 | checkrev = 'ae127b19a50aa54255e4330ccfdd9a5d058e581d' | 590 | checkrev = 'ae127b19a50aa54255e4330ccfdd9a5d058e581d' |
446 | testrecipe = 'mraa' | 591 | testrecipe = 'mraa' |
447 | srcdir = os.path.join(tempdir, testrecipe) | 592 | srcdir = os.path.join(tempdir, testrecipe) |
@@ -461,8 +606,8 @@ class DevtoolAddTests(DevtoolBase): | |||
461 | self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') | 606 | self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') |
462 | checkvars = {} | 607 | checkvars = {} |
463 | checkvars['S'] = '${WORKDIR}/git' | 608 | checkvars['S'] = '${WORKDIR}/git' |
464 | checkvars['PV'] = '1.0+git${SRCPV}' | 609 | checkvars['PV'] = '1.0+git' |
465 | checkvars['SRC_URI'] = url | 610 | checkvars['SRC_URI'] = url_branch |
466 | checkvars['SRCREV'] = '${AUTOREV}' | 611 | checkvars['SRCREV'] = '${AUTOREV}' |
467 | self._test_recipe_contents(recipefile, checkvars, []) | 612 | self._test_recipe_contents(recipefile, checkvars, []) |
468 | # Try with revision and version specified | 613 | # Try with revision and version specified |
@@ -480,8 +625,8 @@ class DevtoolAddTests(DevtoolBase): | |||
480 | self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') | 625 | self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') |
481 | checkvars = {} | 626 | checkvars = {} |
482 | checkvars['S'] = '${WORKDIR}/git' | 627 | checkvars['S'] = '${WORKDIR}/git' |
483 | checkvars['PV'] = '1.5+git${SRCPV}' | 628 | checkvars['PV'] = '1.5+git' |
484 | checkvars['SRC_URI'] = url | 629 | checkvars['SRC_URI'] = url_branch |
485 | checkvars['SRCREV'] = checkrev | 630 | checkvars['SRCREV'] = checkrev |
486 | self._test_recipe_contents(recipefile, checkvars, []) | 631 | self._test_recipe_contents(recipefile, checkvars, []) |
487 | 632 | ||
@@ -504,7 +649,7 @@ class DevtoolAddTests(DevtoolBase): | |||
504 | result = runCmd('devtool status') | 649 | result = runCmd('devtool status') |
505 | self.assertIn(testrecipe, result.output) | 650 | self.assertIn(testrecipe, result.output) |
506 | self.assertIn(srcdir, result.output) | 651 | self.assertIn(srcdir, result.output) |
507 | # Check recipe | 652 | # Check recipedevtool add |
508 | recipefile = get_bb_var('FILE', testrecipe) | 653 | recipefile = get_bb_var('FILE', testrecipe) |
509 | self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named') | 654 | self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named') |
510 | checkvars = {} | 655 | checkvars = {} |
@@ -536,6 +681,19 @@ class DevtoolAddTests(DevtoolBase): | |||
536 | # Test devtool build | 681 | # Test devtool build |
537 | result = runCmd('devtool build %s' % pn) | 682 | result = runCmd('devtool build %s' % pn) |
538 | 683 | ||
684 | def test_devtool_add_python_egg_requires(self): | ||
685 | # Fetch source | ||
686 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
687 | self.track_for_cleanup(tempdir) | ||
688 | testver = '0.14.0' | ||
689 | url = 'https://files.pythonhosted.org/packages/e9/9e/25d59f5043cf763833b2581c8027fa92342c4cf8ee523b498ecdf460c16d/uvicorn-%s.tar.gz' % testver | ||
690 | testrecipe = 'python3-uvicorn' | ||
691 | srcdir = os.path.join(tempdir, testrecipe) | ||
692 | # Test devtool add | ||
693 | self.track_for_cleanup(self.workspacedir) | ||
694 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
695 | result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url)) | ||
696 | |||
539 | class DevtoolModifyTests(DevtoolBase): | 697 | class DevtoolModifyTests(DevtoolBase): |
540 | 698 | ||
541 | def test_devtool_modify(self): | 699 | def test_devtool_modify(self): |
@@ -649,7 +807,7 @@ class DevtoolModifyTests(DevtoolBase): | |||
649 | self.track_for_cleanup(self.workspacedir) | 807 | self.track_for_cleanup(self.workspacedir) |
650 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | 808 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') |
651 | 809 | ||
652 | testrecipes = 'perf kernel-devsrc package-index core-image-minimal meta-toolchain packagegroup-core-sdk meta-ide-support'.split() | 810 | testrecipes = 'perf kernel-devsrc package-index core-image-minimal meta-toolchain packagegroup-core-sdk'.split() |
653 | # Find actual name of gcc-source since it now includes the version - crude, but good enough for this purpose | 811 | # Find actual name of gcc-source since it now includes the version - crude, but good enough for this purpose |
654 | result = runCmd('bitbake-layers show-recipes gcc-source*') | 812 | result = runCmd('bitbake-layers show-recipes gcc-source*') |
655 | for line in result.output.splitlines(): | 813 | for line in result.output.splitlines(): |
@@ -697,6 +855,7 @@ class DevtoolModifyTests(DevtoolBase): | |||
697 | 855 | ||
698 | self.assertTrue(bbclassextended, 'None of these recipes are BBCLASSEXTENDed to native - need to adjust testrecipes list: %s' % ', '.join(testrecipes)) | 856 | self.assertTrue(bbclassextended, 'None of these recipes are BBCLASSEXTENDed to native - need to adjust testrecipes list: %s' % ', '.join(testrecipes)) |
699 | self.assertTrue(inheritnative, 'None of these recipes do "inherit native" - need to adjust testrecipes list: %s' % ', '.join(testrecipes)) | 857 | self.assertTrue(inheritnative, 'None of these recipes do "inherit native" - need to adjust testrecipes list: %s' % ', '.join(testrecipes)) |
858 | |||
700 | def test_devtool_modify_localfiles_only(self): | 859 | def test_devtool_modify_localfiles_only(self): |
701 | # Check preconditions | 860 | # Check preconditions |
702 | testrecipe = 'base-files' | 861 | testrecipe = 'base-files' |
@@ -763,6 +922,122 @@ class DevtoolModifyTests(DevtoolBase): | |||
763 | # Try building | 922 | # Try building |
764 | bitbake(testrecipe) | 923 | bitbake(testrecipe) |
765 | 924 | ||
925 | def test_devtool_modify_git_no_extract(self): | ||
926 | # Check preconditions | ||
927 | testrecipe = 'psplash' | ||
928 | src_uri = get_bb_var('SRC_URI', testrecipe) | ||
929 | self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe) | ||
930 | # Clean up anything in the workdir/sysroot/sstate cache | ||
931 | bitbake('%s -c cleansstate' % testrecipe) | ||
932 | # Try modifying a recipe | ||
933 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
934 | self.track_for_cleanup(tempdir) | ||
935 | self.track_for_cleanup(self.workspacedir) | ||
936 | self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) | ||
937 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
938 | result = runCmd('git clone https://git.yoctoproject.org/psplash %s && devtool modify -n %s %s' % (tempdir, testrecipe, tempdir)) | ||
939 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output) | ||
940 | matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'psplash_*.bbappend')) | ||
941 | self.assertTrue(matches, 'bbappend not created') | ||
942 | # Test devtool status | ||
943 | result = runCmd('devtool status') | ||
944 | self.assertIn(testrecipe, result.output) | ||
945 | self.assertIn(tempdir, result.output) | ||
946 | |||
947 | def test_devtool_modify_git_crates_subpath(self): | ||
948 | # This tests two things in devtool context: | ||
949 | # - that we support local git dependencies for cargo based recipe | ||
950 | # - that we support patches in SRC_URI when git url contains subpath parameter | ||
951 | |||
952 | # Check preconditions: | ||
953 | # recipe inherits cargo | ||
954 | # git:// uri with a subpath as the main package | ||
955 | # some crate:// in SRC_URI | ||
956 | # others git:// in SRC_URI | ||
957 | # cointains a patch | ||
958 | testrecipe = 'hello-rs' | ||
959 | bb_vars = get_bb_vars(['SRC_URI', 'FILE', 'WORKDIR', 'CARGO_HOME'], testrecipe) | ||
960 | recipefile = bb_vars['FILE'] | ||
961 | workdir = bb_vars['WORKDIR'] | ||
962 | cargo_home = bb_vars['CARGO_HOME'] | ||
963 | src_uri = bb_vars['SRC_URI'].split() | ||
964 | self.assertTrue(src_uri[0].startswith('git://'), | ||
965 | 'This test expects the %s recipe to have a git repo has its main uri' % testrecipe) | ||
966 | self.assertIn(';subpath=', src_uri[0], | ||
967 | 'This test expects the %s recipe to have a git uri with subpath' % testrecipe) | ||
968 | self.assertTrue(any([uri.startswith('crate://') for uri in src_uri]), | ||
969 | 'This test expects the %s recipe to have some crates in its src uris' % testrecipe) | ||
970 | self.assertGreaterEqual(sum(map(lambda x:x.startswith('git://'), src_uri)), 2, | ||
971 | 'This test expects the %s recipe to have several git:// uris' % testrecipe) | ||
972 | self.assertTrue(any([uri.startswith('file://') and '.patch' in uri for uri in src_uri]), | ||
973 | 'This test expects the %s recipe to have a patch in its src uris' % testrecipe) | ||
974 | |||
975 | self._test_recipe_contents(recipefile, {}, ['ptest-cargo']) | ||
976 | |||
977 | # Clean up anything in the workdir/sysroot/sstate cache | ||
978 | bitbake('%s -c cleansstate' % testrecipe) | ||
979 | # Try modifying a recipe | ||
980 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
981 | self.track_for_cleanup(tempdir) | ||
982 | self.track_for_cleanup(self.workspacedir) | ||
983 | self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) | ||
984 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
985 | result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) | ||
986 | self.assertExists(os.path.join(tempdir, 'Cargo.toml'), 'Extracted source could not be found') | ||
987 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output) | ||
988 | matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe)) | ||
989 | self.assertTrue(matches, 'bbappend not created') | ||
990 | # Test devtool status | ||
991 | result = runCmd('devtool status') | ||
992 | self.assertIn(testrecipe, result.output) | ||
993 | self.assertIn(tempdir, result.output) | ||
994 | # Check git repo | ||
995 | self._check_src_repo(tempdir) | ||
996 | # Check that the patch is correctly applied. | ||
997 | # The last commit message in the tree must contain the following note: | ||
998 | # Notes (devtool): | ||
999 | # original patch: <patchname> | ||
1000 | # .. | ||
1001 | patchname = None | ||
1002 | for uri in src_uri: | ||
1003 | if uri.startswith('file://') and '.patch' in uri: | ||
1004 | patchname = uri.replace("file://", "").partition('.patch')[0] + '.patch' | ||
1005 | self.assertIsNotNone(patchname) | ||
1006 | result = runCmd('git -C %s log -1' % tempdir) | ||
1007 | self.assertIn("Notes (devtool):\n original patch: %s" % patchname, result.output) | ||
1008 | |||
1009 | # Configure the recipe to check that the git dependencies are correctly patched in cargo config | ||
1010 | bitbake('-c configure %s' % testrecipe) | ||
1011 | |||
1012 | cargo_config_path = os.path.join(cargo_home, 'config') | ||
1013 | with open(cargo_config_path, "r") as f: | ||
1014 | cargo_config_contents = [line.strip('\n') for line in f.readlines()] | ||
1015 | |||
1016 | # Get back git dependencies of the recipe (ignoring the main one) | ||
1017 | # and check that they are all correctly patched to be fetched locally | ||
1018 | git_deps = [uri for uri in src_uri if uri.startswith("git://")][1:] | ||
1019 | for git_dep in git_deps: | ||
1020 | raw_url, _, raw_parms = git_dep.partition(";") | ||
1021 | parms = {} | ||
1022 | for parm in raw_parms.split(";"): | ||
1023 | name_parm, _, value_parm = parm.partition('=') | ||
1024 | parms[name_parm]=value_parm | ||
1025 | self.assertIn('protocol', parms, 'git dependencies uri should contain the "protocol" parameter') | ||
1026 | self.assertIn('name', parms, 'git dependencies uri should contain the "name" parameter') | ||
1027 | self.assertIn('destsuffix', parms, 'git dependencies uri should contain the "destsuffix" parameter') | ||
1028 | self.assertIn('type', parms, 'git dependencies uri should contain the "type" parameter') | ||
1029 | self.assertEqual(parms['type'], 'git-dependency', 'git dependencies uri should have "type=git-dependency"') | ||
1030 | raw_url = raw_url.replace("git://", '%s://' % parms['protocol']) | ||
1031 | patch_line = '[patch."%s"]' % raw_url | ||
1032 | path_patched = os.path.join(workdir, parms['destsuffix']) | ||
1033 | path_override_line = '%s = { path = "%s" }' % (parms['name'], path_patched) | ||
1034 | # Would have been better to use tomllib to read this file :/ | ||
1035 | self.assertIn(patch_line, cargo_config_contents) | ||
1036 | self.assertIn(path_override_line, cargo_config_contents) | ||
1037 | |||
1038 | # Try to package the recipe | ||
1039 | bitbake('-c package_qa %s' % testrecipe) | ||
1040 | |||
766 | def test_devtool_modify_localfiles(self): | 1041 | def test_devtool_modify_localfiles(self): |
767 | # Check preconditions | 1042 | # Check preconditions |
768 | testrecipe = 'lighttpd' | 1043 | testrecipe = 'lighttpd' |
@@ -828,12 +1103,43 @@ class DevtoolModifyTests(DevtoolBase): | |||
828 | runCmd('git -C %s checkout %s' % (tempdir, branch)) | 1103 | runCmd('git -C %s checkout %s' % (tempdir, branch)) |
829 | with open(source, "rt") as f: | 1104 | with open(source, "rt") as f: |
830 | content = f.read() | 1105 | content = f.read() |
831 | self.assertEquals(content, expected) | 1106 | self.assertEqual(content, expected) |
832 | check('devtool', 'This is a test for something\n') | 1107 | if self.td["MACHINE"] == "qemux86": |
1108 | check('devtool', 'This is a test for qemux86\n') | ||
1109 | elif self.td["MACHINE"] == "qemuarm": | ||
1110 | check('devtool', 'This is a test for qemuarm\n') | ||
1111 | else: | ||
1112 | check('devtool', 'This is a test for something\n') | ||
833 | check('devtool-no-overrides', 'This is a test for something\n') | 1113 | check('devtool-no-overrides', 'This is a test for something\n') |
834 | check('devtool-override-qemuarm', 'This is a test for qemuarm\n') | 1114 | check('devtool-override-qemuarm', 'This is a test for qemuarm\n') |
835 | check('devtool-override-qemux86', 'This is a test for qemux86\n') | 1115 | check('devtool-override-qemux86', 'This is a test for qemux86\n') |
836 | 1116 | ||
1117 | def test_devtool_modify_multiple_sources(self): | ||
1118 | # This test check that recipes fetching several sources can be used with devtool modify/build | ||
1119 | # Check preconditions | ||
1120 | testrecipe = 'bzip2' | ||
1121 | src_uri = get_bb_var('SRC_URI', testrecipe) | ||
1122 | src1 = 'https://' in src_uri | ||
1123 | src2 = 'git://' in src_uri | ||
1124 | self.assertTrue(src1 and src2, 'This test expects the %s recipe to fetch both a git source and a tarball and it seems that it no longer does' % testrecipe) | ||
1125 | # Clean up anything in the workdir/sysroot/sstate cache | ||
1126 | bitbake('%s -c cleansstate' % testrecipe) | ||
1127 | # Try modifying a recipe | ||
1128 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
1129 | self.track_for_cleanup(tempdir) | ||
1130 | self.track_for_cleanup(self.workspacedir) | ||
1131 | self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) | ||
1132 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
1133 | result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) | ||
1134 | self.assertEqual(result.status, 0, "Could not modify recipe %s. Output: %s" % (testrecipe, result.output)) | ||
1135 | # Test devtool status | ||
1136 | result = runCmd('devtool status') | ||
1137 | self.assertIn(testrecipe, result.output) | ||
1138 | self.assertIn(tempdir, result.output) | ||
1139 | # Try building | ||
1140 | result = bitbake(testrecipe) | ||
1141 | self.assertEqual(result.status, 0, "Bitbake failed, exit code %s, output %s" % (result.status, result.output)) | ||
1142 | |||
837 | class DevtoolUpdateTests(DevtoolBase): | 1143 | class DevtoolUpdateTests(DevtoolBase): |
838 | 1144 | ||
839 | def test_devtool_update_recipe(self): | 1145 | def test_devtool_update_recipe(self): |
@@ -863,14 +1169,15 @@ class DevtoolUpdateTests(DevtoolBase): | |||
863 | result = runCmd('git commit -m "Add a new file"', cwd=tempdir) | 1169 | result = runCmd('git commit -m "Add a new file"', cwd=tempdir) |
864 | self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) | 1170 | self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) |
865 | result = runCmd('devtool update-recipe %s' % testrecipe) | 1171 | result = runCmd('devtool update-recipe %s' % testrecipe) |
1172 | result = runCmd('git add minicom', cwd=os.path.dirname(recipefile)) | ||
866 | expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), | 1173 | expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), |
867 | ('??', '.*/0001-Change-the-README.patch$'), | 1174 | ('A ', '.*/0001-Change-the-README.patch$'), |
868 | ('??', '.*/0002-Add-a-new-file.patch$')] | 1175 | ('A ', '.*/0002-Add-a-new-file.patch$')] |
869 | self._check_repo_status(os.path.dirname(recipefile), expected_status) | 1176 | self._check_repo_status(os.path.dirname(recipefile), expected_status) |
870 | 1177 | ||
871 | def test_devtool_update_recipe_git(self): | 1178 | def test_devtool_update_recipe_git(self): |
872 | # Check preconditions | 1179 | # Check preconditions |
873 | testrecipe = 'mtd-utils' | 1180 | testrecipe = 'mtd-utils-selftest' |
874 | bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe) | 1181 | bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe) |
875 | recipefile = bb_vars['FILE'] | 1182 | recipefile = bb_vars['FILE'] |
876 | src_uri = bb_vars['SRC_URI'] | 1183 | src_uri = bb_vars['SRC_URI'] |
@@ -904,28 +1211,12 @@ class DevtoolUpdateTests(DevtoolBase): | |||
904 | self._check_repo_status(os.path.dirname(recipefile), expected_status) | 1211 | self._check_repo_status(os.path.dirname(recipefile), expected_status) |
905 | 1212 | ||
906 | result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile)) | 1213 | result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile)) |
907 | addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git"'] | 1214 | addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git;branch=master"'] |
908 | srcurilines = src_uri.split() | 1215 | srcurilines = src_uri.split() |
909 | srcurilines[0] = 'SRC_URI = "' + srcurilines[0] | 1216 | srcurilines[0] = 'SRC_URI = "' + srcurilines[0] |
910 | srcurilines.append('"') | 1217 | srcurilines.append('"') |
911 | removelines = ['SRCREV = ".*"'] + srcurilines | 1218 | removelines = ['SRCREV = ".*"'] + srcurilines |
912 | for line in result.output.splitlines(): | 1219 | self._check_diff(result.output, addlines, removelines) |
913 | if line.startswith('+++') or line.startswith('---'): | ||
914 | continue | ||
915 | elif line.startswith('+'): | ||
916 | matched = False | ||
917 | for item in addlines: | ||
918 | if re.match(item, line[1:].strip()): | ||
919 | matched = True | ||
920 | break | ||
921 | self.assertTrue(matched, 'Unexpected diff add line: %s' % line) | ||
922 | elif line.startswith('-'): | ||
923 | matched = False | ||
924 | for item in removelines: | ||
925 | if re.match(item, line[1:].strip()): | ||
926 | matched = True | ||
927 | break | ||
928 | self.assertTrue(matched, 'Unexpected diff remove line: %s' % line) | ||
929 | # Now try with auto mode | 1220 | # Now try with auto mode |
930 | runCmd('cd %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, os.path.basename(recipefile))) | 1221 | runCmd('cd %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, os.path.basename(recipefile))) |
931 | result = runCmd('devtool update-recipe %s' % testrecipe) | 1222 | result = runCmd('devtool update-recipe %s' % testrecipe) |
@@ -975,7 +1266,7 @@ class DevtoolUpdateTests(DevtoolBase): | |||
975 | self.assertExists(patchfile, 'Patch file not created') | 1266 | self.assertExists(patchfile, 'Patch file not created') |
976 | 1267 | ||
977 | # Check bbappend contents | 1268 | # Check bbappend contents |
978 | expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', | 1269 | expectedlines = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n', |
979 | '\n', | 1270 | '\n', |
980 | 'SRC_URI += "file://0001-Add-our-custom-version.patch"\n', | 1271 | 'SRC_URI += "file://0001-Add-our-custom-version.patch"\n', |
981 | '\n'] | 1272 | '\n'] |
@@ -990,7 +1281,7 @@ class DevtoolUpdateTests(DevtoolBase): | |||
990 | result = runCmd('git reset HEAD^', cwd=tempsrcdir) | 1281 | result = runCmd('git reset HEAD^', cwd=tempsrcdir) |
991 | result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) | 1282 | result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) |
992 | self.assertNotExists(patchfile, 'Patch file not deleted') | 1283 | self.assertNotExists(patchfile, 'Patch file not deleted') |
993 | expectedlines2 = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', | 1284 | expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n', |
994 | '\n'] | 1285 | '\n'] |
995 | with open(bbappendfile, 'r') as f: | 1286 | with open(bbappendfile, 'r') as f: |
996 | self.assertEqual(expectedlines2, f.readlines()) | 1287 | self.assertEqual(expectedlines2, f.readlines()) |
@@ -1007,10 +1298,11 @@ class DevtoolUpdateTests(DevtoolBase): | |||
1007 | 1298 | ||
1008 | def test_devtool_update_recipe_append_git(self): | 1299 | def test_devtool_update_recipe_append_git(self): |
1009 | # Check preconditions | 1300 | # Check preconditions |
1010 | testrecipe = 'mtd-utils' | 1301 | testrecipe = 'mtd-utils-selftest' |
1011 | bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe) | 1302 | bb_vars = get_bb_vars(['FILE', 'SRC_URI', 'LAYERSERIES_CORENAMES'], testrecipe) |
1012 | recipefile = bb_vars['FILE'] | 1303 | recipefile = bb_vars['FILE'] |
1013 | src_uri = bb_vars['SRC_URI'] | 1304 | src_uri = bb_vars['SRC_URI'] |
1305 | corenames = bb_vars['LAYERSERIES_CORENAMES'] | ||
1014 | self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe) | 1306 | self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe) |
1015 | for entry in src_uri.split(): | 1307 | for entry in src_uri.split(): |
1016 | if entry.startswith('git://'): | 1308 | if entry.startswith('git://'): |
@@ -1041,7 +1333,7 @@ class DevtoolUpdateTests(DevtoolBase): | |||
1041 | f.write('BBFILE_PATTERN_oeselftesttemplayer = "^${LAYERDIR}/"\n') | 1333 | f.write('BBFILE_PATTERN_oeselftesttemplayer = "^${LAYERDIR}/"\n') |
1042 | f.write('BBFILE_PRIORITY_oeselftesttemplayer = "999"\n') | 1334 | f.write('BBFILE_PRIORITY_oeselftesttemplayer = "999"\n') |
1043 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_oeselftesttemplayer = "1"\n') | 1335 | f.write('BBFILE_PATTERN_IGNORE_EMPTY_oeselftesttemplayer = "1"\n') |
1044 | f.write('LAYERSERIES_COMPAT_oeselftesttemplayer = "${LAYERSERIES_COMPAT_core}"\n') | 1336 | f.write('LAYERSERIES_COMPAT_oeselftesttemplayer = "%s"\n' % corenames) |
1045 | self.add_command_to_tearDown('bitbake-layers remove-layer %s || true' % templayerdir) | 1337 | self.add_command_to_tearDown('bitbake-layers remove-layer %s || true' % templayerdir) |
1046 | result = runCmd('bitbake-layers add-layer %s' % templayerdir, cwd=self.builddir) | 1338 | result = runCmd('bitbake-layers add-layer %s' % templayerdir, cwd=self.builddir) |
1047 | # Create the bbappend | 1339 | # Create the bbappend |
@@ -1117,14 +1409,30 @@ class DevtoolUpdateTests(DevtoolBase): | |||
1117 | runCmd('echo "Bar" > new-file', cwd=tempdir) | 1409 | runCmd('echo "Bar" > new-file', cwd=tempdir) |
1118 | runCmd('git add new-file', cwd=tempdir) | 1410 | runCmd('git add new-file', cwd=tempdir) |
1119 | runCmd('git commit -m "Add new file"', cwd=tempdir) | 1411 | runCmd('git commit -m "Add new file"', cwd=tempdir) |
1120 | self.add_command_to_tearDown('cd %s; git clean -fd .; git checkout .' % | ||
1121 | os.path.dirname(recipefile)) | ||
1122 | runCmd('devtool update-recipe %s' % testrecipe) | 1412 | runCmd('devtool update-recipe %s' % testrecipe) |
1123 | expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), | 1413 | expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), |
1124 | (' M', '.*/makedevs/makedevs.c$'), | 1414 | (' M', '.*/makedevs/makedevs.c$'), |
1125 | ('??', '.*/makedevs/new-local$'), | 1415 | ('??', '.*/makedevs/new-local$'), |
1126 | ('??', '.*/makedevs/0001-Add-new-file.patch$')] | 1416 | ('??', '.*/makedevs/0001-Add-new-file.patch$')] |
1127 | self._check_repo_status(os.path.dirname(recipefile), expected_status) | 1417 | self._check_repo_status(os.path.dirname(recipefile), expected_status) |
1418 | # Now try to update recipe in another layer, so first, clean it | ||
1419 | runCmd('cd %s; git clean -fd .; git checkout .' % os.path.dirname(recipefile)) | ||
1420 | # Create a temporary layer and add it to bblayers.conf | ||
1421 | self._create_temp_layer(templayerdir, True, 'templayer') | ||
1422 | # Update recipe in templayer | ||
1423 | result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) | ||
1424 | self.assertNotIn('WARNING:', result.output) | ||
1425 | # Check recipe is still clean | ||
1426 | self._check_repo_status(os.path.dirname(recipefile), []) | ||
1427 | splitpath = os.path.dirname(recipefile).split(os.sep) | ||
1428 | appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1]) | ||
1429 | bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir) | ||
1430 | patchfile = os.path.join(appenddir, testrecipe, '0001-Add-new-file.patch') | ||
1431 | new_local_file = os.path.join(appenddir, testrecipe, 'new_local') | ||
1432 | local_file = os.path.join(appenddir, testrecipe, 'makedevs.c') | ||
1433 | self.assertExists(patchfile, 'Patch file 0001-Add-new-file.patch not created') | ||
1434 | self.assertExists(local_file, 'File makedevs.c not created') | ||
1435 | self.assertExists(patchfile, 'File new_local not created') | ||
1128 | 1436 | ||
1129 | def test_devtool_update_recipe_local_files_2(self): | 1437 | def test_devtool_update_recipe_local_files_2(self): |
1130 | """Check local source files support when oe-local-files is in Git""" | 1438 | """Check local source files support when oe-local-files is in Git""" |
@@ -1259,7 +1567,7 @@ class DevtoolUpdateTests(DevtoolBase): | |||
1259 | # Modify one file | 1567 | # Modify one file |
1260 | srctree = os.path.join(self.workspacedir, 'sources', testrecipe) | 1568 | srctree = os.path.join(self.workspacedir, 'sources', testrecipe) |
1261 | runCmd('echo "Another line" >> README', cwd=srctree) | 1569 | runCmd('echo "Another line" >> README', cwd=srctree) |
1262 | runCmd('git commit -a --amend --no-edit', cwd=srctree) | 1570 | runCmd('git commit -a --amend --no-edit --no-verify', cwd=srctree) |
1263 | self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) | 1571 | self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) |
1264 | result = runCmd('devtool update-recipe %s' % testrecipe) | 1572 | result = runCmd('devtool update-recipe %s' % testrecipe) |
1265 | expected_status = [(' M', '.*/%s/readme.patch.gz$' % testrecipe)] | 1573 | expected_status = [(' M', '.*/%s/readme.patch.gz$' % testrecipe)] |
@@ -1295,6 +1603,121 @@ class DevtoolUpdateTests(DevtoolBase): | |||
1295 | expected_status = [] | 1603 | expected_status = [] |
1296 | self._check_repo_status(os.path.dirname(recipefile), expected_status) | 1604 | self._check_repo_status(os.path.dirname(recipefile), expected_status) |
1297 | 1605 | ||
1606 | def test_devtool_finish_modify_git_subdir(self): | ||
1607 | # Check preconditions | ||
1608 | testrecipe = 'dos2unix' | ||
1609 | self.append_config('ERROR_QA:remove:pn-dos2unix = "patch-status"\n') | ||
1610 | bb_vars = get_bb_vars(['SRC_URI', 'S', 'WORKDIR', 'FILE'], testrecipe) | ||
1611 | self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe) | ||
1612 | workdir_git = '%s/git/' % bb_vars['WORKDIR'] | ||
1613 | if not bb_vars['S'].startswith(workdir_git): | ||
1614 | self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe) | ||
1615 | subdir = bb_vars['S'].split(workdir_git, 1)[1] | ||
1616 | # Clean up anything in the workdir/sysroot/sstate cache | ||
1617 | bitbake('%s -c cleansstate' % testrecipe) | ||
1618 | # Try modifying a recipe | ||
1619 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
1620 | self.track_for_cleanup(tempdir) | ||
1621 | self.track_for_cleanup(self.workspacedir) | ||
1622 | self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) | ||
1623 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
1624 | result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) | ||
1625 | testsrcfile = os.path.join(tempdir, subdir, 'dos2unix.c') | ||
1626 | self.assertExists(testsrcfile, 'Extracted source could not be found') | ||
1627 | self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output) | ||
1628 | self.assertNotExists(os.path.join(tempdir, subdir, '.git'), 'Subdirectory has been initialised as a git repo') | ||
1629 | # Check git repo | ||
1630 | self._check_src_repo(tempdir) | ||
1631 | # Modify file | ||
1632 | runCmd("sed -i '1s:^:/* Add a comment */\\n:' %s" % testsrcfile) | ||
1633 | result = runCmd('git commit -a -m "Add a comment"', cwd=tempdir) | ||
1634 | # Now try updating original recipe | ||
1635 | recipefile = bb_vars['FILE'] | ||
1636 | recipedir = os.path.dirname(recipefile) | ||
1637 | self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (recipedir, testrecipe)) | ||
1638 | result = runCmd('devtool update-recipe %s' % testrecipe) | ||
1639 | expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)), | ||
1640 | ('??', '.*/%s/%s/$' % (testrecipe, testrecipe))] | ||
1641 | self._check_repo_status(os.path.dirname(recipefile), expected_status) | ||
1642 | result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile)) | ||
1643 | removelines = ['SRC_URI = "git://.*"'] | ||
1644 | addlines = [ | ||
1645 | 'SRC_URI = "git://.* \\\\', | ||
1646 | 'file://0001-Add-a-comment.patch;patchdir=.. \\\\', | ||
1647 | '"' | ||
1648 | ] | ||
1649 | self._check_diff(result.output, addlines, removelines) | ||
1650 | # Put things back so we can run devtool finish on a different layer | ||
1651 | runCmd('cd %s; rm -f %s/*.patch; git checkout .' % (recipedir, testrecipe)) | ||
1652 | # Run devtool finish | ||
1653 | res = re.search('recipes-.*', recipedir) | ||
1654 | self.assertTrue(res, 'Unable to find recipe subdirectory') | ||
1655 | recipesubdir = res[0] | ||
1656 | self.add_command_to_tearDown('rm -rf %s' % os.path.join(self.testlayer_path, recipesubdir)) | ||
1657 | result = runCmd('devtool finish %s meta-selftest' % testrecipe) | ||
1658 | # Check bbappend file contents | ||
1659 | appendfn = os.path.join(self.testlayer_path, recipesubdir, '%s_%%.bbappend' % testrecipe) | ||
1660 | with open(appendfn, 'r') as f: | ||
1661 | appendlines = f.readlines() | ||
1662 | expected_appendlines = [ | ||
1663 | 'FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n', | ||
1664 | '\n', | ||
1665 | 'SRC_URI += "file://0001-Add-a-comment.patch;patchdir=.."\n', | ||
1666 | '\n' | ||
1667 | ] | ||
1668 | self.assertEqual(appendlines, expected_appendlines) | ||
1669 | self.assertExists(os.path.join(os.path.dirname(appendfn), testrecipe, '0001-Add-a-comment.patch')) | ||
1670 | # Try building | ||
1671 | bitbake('%s -c patch' % testrecipe) | ||
1672 | |||
1673 | def test_devtool_git_submodules(self): | ||
1674 | # This tests if we can add a patch in a git submodule and extract it properly using devtool finish | ||
1675 | # Check preconditions | ||
1676 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | ||
1677 | self.track_for_cleanup(self.workspacedir) | ||
1678 | recipe = 'vulkan-samples' | ||
1679 | src_uri = get_bb_var('SRC_URI', recipe) | ||
1680 | self.assertIn('gitsm://', src_uri, 'This test expects the %s recipe to be a git recipe with submodules' % recipe) | ||
1681 | oldrecipefile = get_bb_var('FILE', recipe) | ||
1682 | recipedir = os.path.dirname(oldrecipefile) | ||
1683 | result = runCmd('git status --porcelain .', cwd=recipedir) | ||
1684 | if result.output.strip(): | ||
1685 | self.fail('Recipe directory for %s contains uncommitted changes' % recipe) | ||
1686 | self.assertIn('/meta/', recipedir) | ||
1687 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
1688 | self.track_for_cleanup(tempdir) | ||
1689 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
1690 | result = runCmd('devtool modify %s %s' % (recipe, tempdir)) | ||
1691 | self.assertExists(os.path.join(tempdir, 'CMakeLists.txt'), 'Extracted source could not be found') | ||
1692 | # Test devtool status | ||
1693 | result = runCmd('devtool status') | ||
1694 | self.assertIn(recipe, result.output) | ||
1695 | self.assertIn(tempdir, result.output) | ||
1696 | # Modify a source file in a submodule, (grab the first one) | ||
1697 | result = runCmd('git submodule --quiet foreach \'echo $sm_path\'', cwd=tempdir) | ||
1698 | submodule = result.output.splitlines()[0] | ||
1699 | submodule_path = os.path.join(tempdir, submodule) | ||
1700 | runCmd('echo "#This is a first comment" >> testfile', cwd=submodule_path) | ||
1701 | result = runCmd('git status --porcelain . ', cwd=submodule_path) | ||
1702 | self.assertIn("testfile", result.output) | ||
1703 | runCmd('git add testfile; git commit -m "Adding a new file"', cwd=submodule_path) | ||
1704 | |||
1705 | # Try finish to the original layer | ||
1706 | self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir)) | ||
1707 | runCmd('devtool finish -f %s meta' % recipe) | ||
1708 | result = runCmd('devtool status') | ||
1709 | self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') | ||
1710 | self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') | ||
1711 | expected_status = [(' M', '.*/%s$' % os.path.basename(oldrecipefile)), | ||
1712 | ('??', '.*/.*-Adding-a-new-file.patch$')] | ||
1713 | self._check_repo_status(recipedir, expected_status) | ||
1714 | # Make sure the patch is added to the recipe with the correct "patchdir" option | ||
1715 | result = runCmd('git diff .', cwd=recipedir) | ||
1716 | addlines = [ | ||
1717 | 'file://0001-Adding-a-new-file.patch;patchdir=%s \\\\' % submodule | ||
1718 | ] | ||
1719 | self._check_diff(result.output, addlines, []) | ||
1720 | |||
1298 | class DevtoolExtractTests(DevtoolBase): | 1721 | class DevtoolExtractTests(DevtoolBase): |
1299 | 1722 | ||
1300 | def test_devtool_extract(self): | 1723 | def test_devtool_extract(self): |
@@ -1343,33 +1766,15 @@ class DevtoolExtractTests(DevtoolBase): | |||
1343 | matches2 = glob.glob(stampprefix2 + '*') | 1766 | matches2 = glob.glob(stampprefix2 + '*') |
1344 | self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) | 1767 | self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) |
1345 | 1768 | ||
1769 | @OETestTag("runqemu") | ||
1346 | def test_devtool_deploy_target(self): | 1770 | def test_devtool_deploy_target(self): |
1347 | # NOTE: Whilst this test would seemingly be better placed as a runtime test, | 1771 | self._check_runqemu_prerequisites() |
1348 | # unfortunately the runtime tests run under bitbake and you can't run | ||
1349 | # devtool within bitbake (since devtool needs to run bitbake itself). | ||
1350 | # Additionally we are testing build-time functionality as well, so | ||
1351 | # really this has to be done as an oe-selftest test. | ||
1352 | # | ||
1353 | # Check preconditions | ||
1354 | machine = get_bb_var('MACHINE') | ||
1355 | if not machine.startswith('qemu'): | ||
1356 | self.skipTest('This test only works with qemu machines') | ||
1357 | if not os.path.exists('/etc/runqemu-nosudo'): | ||
1358 | self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') | ||
1359 | result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ip tuntap show', ignore_status=True) | ||
1360 | if result.status != 0: | ||
1361 | result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ifconfig -a', ignore_status=True) | ||
1362 | if result.status != 0: | ||
1363 | self.skipTest('Failed to determine if tap devices exist with ifconfig or ip: %s' % result.output) | ||
1364 | for line in result.output.splitlines(): | ||
1365 | if line.startswith('tap'): | ||
1366 | break | ||
1367 | else: | ||
1368 | self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') | ||
1369 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | 1772 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') |
1370 | # Definitions | 1773 | # Definitions |
1371 | testrecipe = 'mdadm' | 1774 | testrecipe = 'mdadm' |
1372 | testfile = '/sbin/mdadm' | 1775 | testfile = '/sbin/mdadm' |
1776 | if "usrmerge" in get_bb_var('DISTRO_FEATURES'): | ||
1777 | testfile = '/usr/sbin/mdadm' | ||
1373 | testimage = 'oe-selftest-image' | 1778 | testimage = 'oe-selftest-image' |
1374 | testcommand = '/sbin/mdadm --help' | 1779 | testcommand = '/sbin/mdadm --help' |
1375 | # Build an image to run | 1780 | # Build an image to run |
@@ -1463,6 +1868,14 @@ class DevtoolExtractTests(DevtoolBase): | |||
1463 | 1868 | ||
1464 | class DevtoolUpgradeTests(DevtoolBase): | 1869 | class DevtoolUpgradeTests(DevtoolBase): |
1465 | 1870 | ||
1871 | def setUp(self): | ||
1872 | super().setUp() | ||
1873 | try: | ||
1874 | runCmd("git config --global user.name") | ||
1875 | runCmd("git config --global user.email") | ||
1876 | except: | ||
1877 | self.skip("Git user.name and user.email must be set") | ||
1878 | |||
1466 | def test_devtool_upgrade(self): | 1879 | def test_devtool_upgrade(self): |
1467 | # Check preconditions | 1880 | # Check preconditions |
1468 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | 1881 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') |
@@ -1543,6 +1956,54 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1543 | self.assertNotIn(recipe, result.output) | 1956 | self.assertNotIn(recipe, result.output) |
1544 | self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') | 1957 | self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') |
1545 | 1958 | ||
1959 | def test_devtool_upgrade_drop_md5sum(self): | ||
1960 | # Check preconditions | ||
1961 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | ||
1962 | self.track_for_cleanup(self.workspacedir) | ||
1963 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
1964 | # For the moment, we are using a real recipe. | ||
1965 | recipe = 'devtool-upgrade-test3' | ||
1966 | version = '1.6.0' | ||
1967 | oldrecipefile = get_bb_var('FILE', recipe) | ||
1968 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
1969 | self.track_for_cleanup(tempdir) | ||
1970 | # Check upgrade. Code does not check if new PV is older or newer that current PV, so, it may be that | ||
1971 | # we are downgrading instead of upgrading. | ||
1972 | result = runCmd('devtool upgrade %s %s -V %s' % (recipe, tempdir, version)) | ||
1973 | # Check new recipe file is present | ||
1974 | newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, '%s_%s.bb' % (recipe, version)) | ||
1975 | self.assertExists(newrecipefile, 'Recipe file should exist after upgrade') | ||
1976 | # Check recipe got changed as expected | ||
1977 | with open(oldrecipefile + '.upgraded', 'r') as f: | ||
1978 | desiredlines = f.readlines() | ||
1979 | with open(newrecipefile, 'r') as f: | ||
1980 | newlines = f.readlines() | ||
1981 | self.assertEqual(desiredlines, newlines) | ||
1982 | |||
1983 | def test_devtool_upgrade_all_checksums(self): | ||
1984 | # Check preconditions | ||
1985 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | ||
1986 | self.track_for_cleanup(self.workspacedir) | ||
1987 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
1988 | # For the moment, we are using a real recipe. | ||
1989 | recipe = 'devtool-upgrade-test4' | ||
1990 | version = '1.6.0' | ||
1991 | oldrecipefile = get_bb_var('FILE', recipe) | ||
1992 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
1993 | self.track_for_cleanup(tempdir) | ||
1994 | # Check upgrade. Code does not check if new PV is older or newer that current PV, so, it may be that | ||
1995 | # we are downgrading instead of upgrading. | ||
1996 | result = runCmd('devtool upgrade %s %s -V %s' % (recipe, tempdir, version)) | ||
1997 | # Check new recipe file is present | ||
1998 | newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, '%s_%s.bb' % (recipe, version)) | ||
1999 | self.assertExists(newrecipefile, 'Recipe file should exist after upgrade') | ||
2000 | # Check recipe got changed as expected | ||
2001 | with open(oldrecipefile + '.upgraded', 'r') as f: | ||
2002 | desiredlines = f.readlines() | ||
2003 | with open(newrecipefile, 'r') as f: | ||
2004 | newlines = f.readlines() | ||
2005 | self.assertEqual(desiredlines, newlines) | ||
2006 | |||
1546 | def test_devtool_layer_plugins(self): | 2007 | def test_devtool_layer_plugins(self): |
1547 | """Test that devtool can use plugins from other layers. | 2008 | """Test that devtool can use plugins from other layers. |
1548 | 2009 | ||
@@ -1561,7 +2022,15 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1561 | for p in paths: | 2022 | for p in paths: |
1562 | dstdir = os.path.join(dstdir, p) | 2023 | dstdir = os.path.join(dstdir, p) |
1563 | if not os.path.exists(dstdir): | 2024 | if not os.path.exists(dstdir): |
1564 | os.makedirs(dstdir) | 2025 | try: |
2026 | os.makedirs(dstdir) | ||
2027 | except PermissionError: | ||
2028 | return False | ||
2029 | except OSError as e: | ||
2030 | if e.errno == errno.EROFS: | ||
2031 | return False | ||
2032 | else: | ||
2033 | raise e | ||
1565 | if p == "lib": | 2034 | if p == "lib": |
1566 | # Can race with other tests | 2035 | # Can race with other tests |
1567 | self.add_command_to_tearDown('rmdir --ignore-fail-on-non-empty %s' % dstdir) | 2036 | self.add_command_to_tearDown('rmdir --ignore-fail-on-non-empty %s' % dstdir) |
@@ -1569,8 +2038,12 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1569 | self.track_for_cleanup(dstdir) | 2038 | self.track_for_cleanup(dstdir) |
1570 | dstfile = os.path.join(dstdir, os.path.basename(srcfile)) | 2039 | dstfile = os.path.join(dstdir, os.path.basename(srcfile)) |
1571 | if srcfile != dstfile: | 2040 | if srcfile != dstfile: |
1572 | shutil.copy(srcfile, dstfile) | 2041 | try: |
2042 | shutil.copy(srcfile, dstfile) | ||
2043 | except PermissionError: | ||
2044 | return False | ||
1573 | self.track_for_cleanup(dstfile) | 2045 | self.track_for_cleanup(dstfile) |
2046 | return True | ||
1574 | 2047 | ||
1575 | def test_devtool_load_plugin(self): | 2048 | def test_devtool_load_plugin(self): |
1576 | """Test that devtool loads only the first found plugin in BBPATH.""" | 2049 | """Test that devtool loads only the first found plugin in BBPATH.""" |
@@ -1588,15 +2061,17 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1588 | plugincontent = fh.readlines() | 2061 | plugincontent = fh.readlines() |
1589 | try: | 2062 | try: |
1590 | self.assertIn('meta-selftest', srcfile, 'wrong bbpath plugin found') | 2063 | self.assertIn('meta-selftest', srcfile, 'wrong bbpath plugin found') |
1591 | for path in searchpath: | 2064 | searchpath = [ |
1592 | self._copy_file_with_cleanup(srcfile, path, 'lib', 'devtool') | 2065 | path for path in searchpath |
2066 | if self._copy_file_with_cleanup(srcfile, path, 'lib', 'devtool') | ||
2067 | ] | ||
1593 | result = runCmd("devtool --quiet count") | 2068 | result = runCmd("devtool --quiet count") |
1594 | self.assertEqual(result.output, '1') | 2069 | self.assertEqual(result.output, '1') |
1595 | result = runCmd("devtool --quiet multiloaded") | 2070 | result = runCmd("devtool --quiet multiloaded") |
1596 | self.assertEqual(result.output, "no") | 2071 | self.assertEqual(result.output, "no") |
1597 | for path in searchpath: | 2072 | for path in searchpath: |
1598 | result = runCmd("devtool --quiet bbdir") | 2073 | result = runCmd("devtool --quiet bbdir") |
1599 | self.assertEqual(result.output, path) | 2074 | self.assertEqual(os.path.realpath(result.output), os.path.realpath(path)) |
1600 | os.unlink(os.path.join(result.output, 'lib', 'devtool', 'bbpath.py')) | 2075 | os.unlink(os.path.join(result.output, 'lib', 'devtool', 'bbpath.py')) |
1601 | finally: | 2076 | finally: |
1602 | with open(srcfile, 'w') as fh: | 2077 | with open(srcfile, 'w') as fh: |
@@ -1777,6 +2252,52 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1777 | if files: | 2252 | if files: |
1778 | self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files)) | 2253 | self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files)) |
1779 | 2254 | ||
2255 | def test_devtool_finish_update_patch(self): | ||
2256 | # This test uses a modified version of the sysdig recipe from meta-oe. | ||
2257 | # - The patches have been renamed. | ||
2258 | # - The dependencies are commented out since the recipe is not being | ||
2259 | # built. | ||
2260 | # | ||
2261 | # The sysdig recipe is interesting in that it fetches two different Git | ||
2262 | # repositories, and there are patches for both. This leads to that | ||
2263 | # devtool will create ignore commits as it uses Git submodules to keep | ||
2264 | # track of the second repository. | ||
2265 | # | ||
2266 | # This test will verify that the ignored commits actually are ignored | ||
2267 | # when a commit in between is modified. It will also verify that the | ||
2268 | # updated patch keeps its original name. | ||
2269 | |||
2270 | # Check preconditions | ||
2271 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | ||
2272 | # Try modifying a recipe | ||
2273 | self.track_for_cleanup(self.workspacedir) | ||
2274 | recipe = 'sysdig-selftest' | ||
2275 | recipefile = get_bb_var('FILE', recipe) | ||
2276 | recipedir = os.path.dirname(recipefile) | ||
2277 | result = runCmd('git status --porcelain .', cwd=recipedir) | ||
2278 | if result.output.strip(): | ||
2279 | self.fail('Recipe directory for %s contains uncommitted changes' % recipe) | ||
2280 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
2281 | self.track_for_cleanup(tempdir) | ||
2282 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
2283 | result = runCmd('devtool modify %s %s' % (recipe, tempdir)) | ||
2284 | self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (recipedir, recipe, recipe, os.path.basename(recipefile))) | ||
2285 | self.assertExists(os.path.join(tempdir, 'CMakeLists.txt'), 'Extracted source could not be found') | ||
2286 | # Make a change to one of the existing commits | ||
2287 | result = runCmd('echo "# A comment " >> CMakeLists.txt', cwd=tempdir) | ||
2288 | result = runCmd('git status --porcelain', cwd=tempdir) | ||
2289 | self.assertIn('M CMakeLists.txt', result.output) | ||
2290 | result = runCmd('git commit --fixup HEAD^ CMakeLists.txt', cwd=tempdir) | ||
2291 | result = runCmd('git show -s --format=%s', cwd=tempdir) | ||
2292 | self.assertIn('fixup! cmake: Pass PROBE_NAME via CFLAGS', result.output) | ||
2293 | result = runCmd('GIT_SEQUENCE_EDITOR=true git rebase -i --autosquash devtool-base', cwd=tempdir) | ||
2294 | result = runCmd('devtool finish %s meta-selftest' % recipe) | ||
2295 | result = runCmd('devtool status') | ||
2296 | self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') | ||
2297 | self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') | ||
2298 | expected_status = [(' M', '.*/0099-cmake-Pass-PROBE_NAME-via-CFLAGS.patch$')] | ||
2299 | self._check_repo_status(recipedir, expected_status) | ||
2300 | |||
1780 | def test_devtool_rename(self): | 2301 | def test_devtool_rename(self): |
1781 | # Check preconditions | 2302 | # Check preconditions |
1782 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') | 2303 | self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') |
@@ -1813,7 +2334,6 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1813 | self._test_recipe_contents(newrecipefile, checkvars, []) | 2334 | self._test_recipe_contents(newrecipefile, checkvars, []) |
1814 | # Try again - change just name this time | 2335 | # Try again - change just name this time |
1815 | result = runCmd('devtool reset -n %s' % newrecipename) | 2336 | result = runCmd('devtool reset -n %s' % newrecipename) |
1816 | shutil.rmtree(newsrctree) | ||
1817 | add_recipe() | 2337 | add_recipe() |
1818 | newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, recipever)) | 2338 | newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, recipever)) |
1819 | result = runCmd('devtool rename %s %s' % (recipename, newrecipename)) | 2339 | result = runCmd('devtool rename %s %s' % (recipename, newrecipename)) |
@@ -1826,7 +2346,6 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1826 | self._test_recipe_contents(newrecipefile, checkvars, []) | 2346 | self._test_recipe_contents(newrecipefile, checkvars, []) |
1827 | # Try again - change just version this time | 2347 | # Try again - change just version this time |
1828 | result = runCmd('devtool reset -n %s' % newrecipename) | 2348 | result = runCmd('devtool reset -n %s' % newrecipename) |
1829 | shutil.rmtree(newsrctree) | ||
1830 | add_recipe() | 2349 | add_recipe() |
1831 | newrecipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, newrecipever)) | 2350 | newrecipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, newrecipever)) |
1832 | result = runCmd('devtool rename %s -V %s' % (recipename, newrecipever)) | 2351 | result = runCmd('devtool rename %s -V %s' % (recipename, newrecipever)) |
@@ -1858,8 +2377,9 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1858 | Expected: devtool modify is able to checkout the source of the kernel | 2377 | Expected: devtool modify is able to checkout the source of the kernel |
1859 | and modification to the source and configurations are reflected | 2378 | and modification to the source and configurations are reflected |
1860 | when building the kernel. | 2379 | when building the kernel. |
1861 | """ | 2380 | """ |
1862 | kernel_provider = get_bb_var('PREFERRED_PROVIDER_virtual/kernel') | 2381 | kernel_provider = self.td['PREFERRED_PROVIDER_virtual/kernel'] |
2382 | |||
1863 | # Clean up the environment | 2383 | # Clean up the environment |
1864 | bitbake('%s -c clean' % kernel_provider) | 2384 | bitbake('%s -c clean' % kernel_provider) |
1865 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | 2385 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') |
@@ -1886,33 +2406,545 @@ class DevtoolUpgradeTests(DevtoolBase): | |||
1886 | self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') | 2406 | self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') |
1887 | #Step 4.2 | 2407 | #Step 4.2 |
1888 | configfile = os.path.join(tempdir,'.config') | 2408 | configfile = os.path.join(tempdir,'.config') |
1889 | diff = runCmd('diff %s %s' % (tmpconfig, configfile)) | 2409 | runCmd('diff %s %s' % (tmpconfig, configfile)) |
1890 | self.assertEqual(0,diff.status,'Kernel .config file is not the same using bitbake and devtool') | 2410 | |
1891 | #Step 4.3 | 2411 | #Step 4.3 |
1892 | #NOTE: virtual/kernel is mapped to kernel_provider | 2412 | #NOTE: virtual/kernel is mapped to kernel_provider |
1893 | result = runCmd('devtool build %s' % kernel_provider) | 2413 | runCmd('devtool build %s' % kernel_provider) |
1894 | self.assertEqual(0,result.status,'Cannot build kernel using `devtool build`') | ||
1895 | kernelfile = os.path.join(get_bb_var('KBUILD_OUTPUT', kernel_provider), 'vmlinux') | 2414 | kernelfile = os.path.join(get_bb_var('KBUILD_OUTPUT', kernel_provider), 'vmlinux') |
1896 | self.assertExists(kernelfile, 'Kernel was not build correctly') | 2415 | self.assertExists(kernelfile, 'Kernel was not build correctly') |
1897 | 2416 | ||
1898 | #Modify the kernel source | 2417 | #Modify the kernel source |
1899 | modfile = os.path.join(tempdir,'arch/x86/boot/header.S') | 2418 | modfile = os.path.join(tempdir, 'init/version.c') |
1900 | modstring = "Use a boot loader. Devtool testing." | 2419 | # Moved to uts.h in 6.1 onwards |
1901 | modapplied = runCmd("sed -i 's/Use a boot loader./%s/' %s" % (modstring, modfile)) | 2420 | modfile2 = os.path.join(tempdir, 'include/linux/uts.h') |
1902 | self.assertEqual(0,modapplied.status,'Modification to %s on kernel source failed' % modfile) | 2421 | runCmd("sed -i 's/Linux/LiNuX/g' %s %s" % (modfile, modfile2)) |
2422 | |||
1903 | #Modify the configuration | 2423 | #Modify the configuration |
1904 | codeconfigfile = os.path.join(tempdir,'.config.new') | 2424 | codeconfigfile = os.path.join(tempdir, '.config.new') |
1905 | modconfopt = "CONFIG_SG_POOL=n" | 2425 | modconfopt = "CONFIG_SG_POOL=n" |
1906 | modconf = runCmd("sed -i 's/CONFIG_SG_POOL=y/%s/' %s" % (modconfopt, codeconfigfile)) | 2426 | runCmd("sed -i 's/CONFIG_SG_POOL=y/%s/' %s" % (modconfopt, codeconfigfile)) |
1907 | self.assertEqual(0,modconf.status,'Modification to %s failed' % codeconfigfile) | 2427 | |
1908 | #Build again kernel with devtool | 2428 | #Build again kernel with devtool |
1909 | rebuild = runCmd('devtool build %s' % kernel_provider) | 2429 | runCmd('devtool build %s' % kernel_provider) |
1910 | self.assertEqual(0,rebuild.status,'Fail to build kernel after modification of source and config') | 2430 | |
1911 | #Step 4.4 | 2431 | #Step 4.4 |
1912 | bzimagename = 'bzImage-' + get_bb_var('KERNEL_VERSION_NAME', kernel_provider) | 2432 | runCmd("grep '%s' %s" % ('LiNuX', kernelfile)) |
1913 | bzimagefile = os.path.join(get_bb_var('D', kernel_provider),'boot', bzimagename) | 2433 | |
1914 | checkmodcode = runCmd("grep '%s' %s" % (modstring, bzimagefile)) | ||
1915 | self.assertEqual(0,checkmodcode.status,'Modification on kernel source failed') | ||
1916 | #Step 4.5 | 2434 | #Step 4.5 |
1917 | checkmodconfg = runCmd("grep %s %s" % (modconfopt, codeconfigfile)) | 2435 | runCmd("grep %s %s" % (modconfopt, codeconfigfile)) |
1918 | self.assertEqual(0,checkmodconfg.status,'Modification to configuration file failed') | 2436 | |
2437 | |||
2438 | class DevtoolIdeSdkTests(DevtoolBase): | ||
2439 | def _write_bb_config(self, recipe_names): | ||
2440 | """Helper to write the bitbake local.conf file""" | ||
2441 | conf_lines = [ | ||
2442 | 'IMAGE_CLASSES += "image-combined-dbg"', | ||
2443 | 'IMAGE_GEN_DEBUGFS = "1"', | ||
2444 | 'IMAGE_INSTALL:append = " gdbserver %s"' % ' '.join( | ||
2445 | [r + '-ptest' for r in recipe_names]) | ||
2446 | ] | ||
2447 | self.write_config("\n".join(conf_lines)) | ||
2448 | |||
2449 | def _check_workspace(self): | ||
2450 | """Check if a workspace directory is available and setup the cleanup""" | ||
2451 | self.assertTrue(not os.path.exists(self.workspacedir), | ||
2452 | 'This test cannot be run with a workspace directory under the build directory') | ||
2453 | self.track_for_cleanup(self.workspacedir) | ||
2454 | self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') | ||
2455 | |||
2456 | def _workspace_scripts_dir(self, recipe_name): | ||
2457 | return os.path.realpath(os.path.join(self.builddir, 'workspace', 'ide-sdk', recipe_name, 'scripts')) | ||
2458 | |||
2459 | def _sources_scripts_dir(self, src_dir): | ||
2460 | return os.path.realpath(os.path.join(src_dir, 'oe-scripts')) | ||
2461 | |||
2462 | def _workspace_gdbinit_dir(self, recipe_name): | ||
2463 | return os.path.realpath(os.path.join(self.builddir, 'workspace', 'ide-sdk', recipe_name, 'scripts', 'gdbinit')) | ||
2464 | |||
2465 | def _sources_gdbinit_dir(self, src_dir): | ||
2466 | return os.path.realpath(os.path.join(src_dir, 'oe-gdbinit')) | ||
2467 | |||
2468 | def _devtool_ide_sdk_recipe(self, recipe_name, build_file, testimage): | ||
2469 | """Setup a recipe for working with devtool ide-sdk | ||
2470 | |||
2471 | Basically devtool modify -x followed by some tests | ||
2472 | """ | ||
2473 | tempdir = tempfile.mkdtemp(prefix='devtoolqa') | ||
2474 | self.track_for_cleanup(tempdir) | ||
2475 | self.add_command_to_tearDown('bitbake -c clean %s' % recipe_name) | ||
2476 | |||
2477 | result = runCmd('devtool modify %s -x %s' % (recipe_name, tempdir)) | ||
2478 | self.assertExists(os.path.join(tempdir, build_file), | ||
2479 | 'Extracted source could not be found') | ||
2480 | self.assertExists(os.path.join(self.workspacedir, 'conf', | ||
2481 | 'layer.conf'), 'Workspace directory not created') | ||
2482 | matches = glob.glob(os.path.join(self.workspacedir, | ||
2483 | 'appends', recipe_name + '.bbappend')) | ||
2484 | self.assertTrue(matches, 'bbappend not created %s' % result.output) | ||
2485 | |||
2486 | # Test devtool status | ||
2487 | result = runCmd('devtool status') | ||
2488 | self.assertIn(recipe_name, result.output) | ||
2489 | self.assertIn(tempdir, result.output) | ||
2490 | self._check_src_repo(tempdir) | ||
2491 | |||
2492 | # Usually devtool ide-sdk would initiate the build of the SDK. | ||
2493 | # But there is a circular dependency with starting Qemu and passing the IP of runqemu to devtool ide-sdk. | ||
2494 | if testimage: | ||
2495 | bitbake("%s qemu-native qemu-helper-native" % testimage) | ||
2496 | deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') | ||
2497 | self.add_command_to_tearDown('bitbake -c clean %s' % testimage) | ||
2498 | self.add_command_to_tearDown( | ||
2499 | 'rm -f %s/%s*' % (deploy_dir_image, testimage)) | ||
2500 | |||
2501 | return tempdir | ||
2502 | |||
2503 | def _get_recipe_ids(self, recipe_name): | ||
2504 | """IDs needed to write recipe specific config entries into IDE config files""" | ||
2505 | package_arch = get_bb_var('PACKAGE_ARCH', recipe_name) | ||
2506 | recipe_id = recipe_name + "-" + package_arch | ||
2507 | recipe_id_pretty = recipe_name + ": " + package_arch | ||
2508 | return (recipe_id, recipe_id_pretty) | ||
2509 | |||
2510 | def _verify_install_script_code(self, tempdir, recipe_name): | ||
2511 | """Verify the scripts referred by the tasks.json file are fine. | ||
2512 | |||
2513 | This function does not depend on Qemu. Therefore it verifies the scripts | ||
2514 | exists and the delete step works as expected. But it does not try to | ||
2515 | deploy to Qemu. | ||
2516 | """ | ||
2517 | recipe_id, recipe_id_pretty = self._get_recipe_ids(recipe_name) | ||
2518 | with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as tasks_j: | ||
2519 | tasks_d = json.load(tasks_j) | ||
2520 | tasks = tasks_d["tasks"] | ||
2521 | task_install = next( | ||
2522 | (task for task in tasks if task["label"] == "install && deploy-target %s" % recipe_id_pretty), None) | ||
2523 | self.assertIsNot(task_install, None) | ||
2524 | # execute only the bb_run_do_install script since the deploy would require e.g. Qemu running. | ||
2525 | i_and_d_script = "install_and_deploy_" + recipe_id | ||
2526 | i_and_d_script_path = os.path.join( | ||
2527 | self._workspace_scripts_dir(recipe_name), i_and_d_script) | ||
2528 | self.assertExists(i_and_d_script_path) | ||
2529 | del_script = "delete_package_dirs_" + recipe_id | ||
2530 | del_script_path = os.path.join( | ||
2531 | self._workspace_scripts_dir(recipe_name), del_script) | ||
2532 | self.assertExists(del_script_path) | ||
2533 | runCmd(del_script_path, cwd=tempdir) | ||
2534 | |||
2535 | def _devtool_ide_sdk_qemu(self, tempdir, qemu, recipe_name, example_exe): | ||
2536 | """Verify deployment and execution in Qemu system work for one recipe. | ||
2537 | |||
2538 | This function checks the entire SDK workflow: changing the code, recompiling | ||
2539 | it and deploying it back to Qemu, and checking that the changes have been | ||
2540 | incorporated into the provided binaries. It also runs the tests of the recipe. | ||
2541 | """ | ||
2542 | recipe_id, _ = self._get_recipe_ids(recipe_name) | ||
2543 | i_and_d_script = "install_and_deploy_" + recipe_id | ||
2544 | install_deploy_cmd = os.path.join( | ||
2545 | self._workspace_scripts_dir(recipe_name), i_and_d_script) | ||
2546 | self.assertExists(install_deploy_cmd, | ||
2547 | '%s script not found' % install_deploy_cmd) | ||
2548 | runCmd(install_deploy_cmd) | ||
2549 | |||
2550 | MAGIC_STRING_ORIG = "Magic: 123456789" | ||
2551 | MAGIC_STRING_NEW = "Magic: 987654321" | ||
2552 | ptest_cmd = "ptest-runner " + recipe_name | ||
2553 | |||
2554 | # validate that SSH is working | ||
2555 | status, _ = qemu.run("uname") | ||
2556 | self.assertEqual( | ||
2557 | status, 0, msg="Failed to connect to the SSH server on Qemu") | ||
2558 | |||
2559 | # Verify the unmodified example prints the magic string | ||
2560 | status, output = qemu.run(example_exe) | ||
2561 | self.assertEqual(status, 0, msg="%s failed: %s" % | ||
2562 | (example_exe, output)) | ||
2563 | self.assertIn(MAGIC_STRING_ORIG, output) | ||
2564 | |||
2565 | # Verify the unmodified ptests work | ||
2566 | status, output = qemu.run(ptest_cmd) | ||
2567 | self.assertEqual(status, 0, msg="%s failed: %s" % (ptest_cmd, output)) | ||
2568 | self.assertIn("PASS: cpp-example-lib", output) | ||
2569 | |||
2570 | # Verify remote debugging works | ||
2571 | self._gdb_cross_debugging( | ||
2572 | qemu, recipe_name, example_exe, MAGIC_STRING_ORIG) | ||
2573 | |||
2574 | # Replace the Magic String in the code, compile and deploy to Qemu | ||
2575 | cpp_example_lib_hpp = os.path.join(tempdir, 'cpp-example-lib.hpp') | ||
2576 | with open(cpp_example_lib_hpp, 'r') as file: | ||
2577 | cpp_code = file.read() | ||
2578 | cpp_code = cpp_code.replace(MAGIC_STRING_ORIG, MAGIC_STRING_NEW) | ||
2579 | with open(cpp_example_lib_hpp, 'w') as file: | ||
2580 | file.write(cpp_code) | ||
2581 | runCmd(install_deploy_cmd, cwd=tempdir) | ||
2582 | |||
2583 | # Verify the modified example prints the modified magic string | ||
2584 | status, output = qemu.run(example_exe) | ||
2585 | self.assertEqual(status, 0, msg="%s failed: %s" % | ||
2586 | (example_exe, output)) | ||
2587 | self.assertNotIn(MAGIC_STRING_ORIG, output) | ||
2588 | self.assertIn(MAGIC_STRING_NEW, output) | ||
2589 | |||
2590 | # Verify the modified example ptests work | ||
2591 | status, output = qemu.run(ptest_cmd) | ||
2592 | self.assertEqual(status, 0, msg="%s failed: %s" % (ptest_cmd, output)) | ||
2593 | self.assertIn("PASS: cpp-example-lib", output) | ||
2594 | |||
2595 | # Verify remote debugging works wit the modified magic string | ||
2596 | self._gdb_cross_debugging( | ||
2597 | qemu, recipe_name, example_exe, MAGIC_STRING_NEW) | ||
2598 | |||
2599 | def _gdb_cross(self): | ||
2600 | """Verify gdb-cross is provided by devtool ide-sdk""" | ||
2601 | target_arch = self.td["TARGET_ARCH"] | ||
2602 | target_sys = self.td["TARGET_SYS"] | ||
2603 | gdb_recipe = "gdb-cross-" + target_arch | ||
2604 | gdb_binary = target_sys + "-gdb" | ||
2605 | |||
2606 | native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", gdb_recipe) | ||
2607 | r = runCmd("%s --version" % gdb_binary, | ||
2608 | native_sysroot=native_sysroot, target_sys=target_sys) | ||
2609 | self.assertEqual(r.status, 0) | ||
2610 | self.assertIn("GNU gdb", r.output) | ||
2611 | |||
2612 | def _gdb_cross_debugging(self, qemu, recipe_name, example_exe, magic_string): | ||
2613 | """Verify gdb-cross is working | ||
2614 | |||
2615 | Test remote debugging: | ||
2616 | break main | ||
2617 | run | ||
2618 | continue | ||
2619 | break CppExample::print_json() | ||
2620 | continue | ||
2621 | print CppExample::test_string.compare("cpp-example-lib Magic: 123456789") | ||
2622 | $1 = 0 | ||
2623 | print CppExample::test_string.compare("cpp-example-lib Magic: 123456789aaa") | ||
2624 | $2 = -3 | ||
2625 | list cpp-example-lib.hpp:13,13 | ||
2626 | 13 inline static const std::string test_string = "cpp-example-lib Magic: 123456789"; | ||
2627 | continue | ||
2628 | """ | ||
2629 | sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' | ||
2630 | gdbserver_script = os.path.join(self._workspace_scripts_dir( | ||
2631 | recipe_name), 'gdbserver_1234_usr-bin-' + example_exe + '_m') | ||
2632 | gdb_script = os.path.join(self._workspace_scripts_dir( | ||
2633 | recipe_name), 'gdb_1234_usr-bin-' + example_exe) | ||
2634 | |||
2635 | # Start a gdbserver | ||
2636 | r = runCmd(gdbserver_script) | ||
2637 | self.assertEqual(r.status, 0) | ||
2638 | |||
2639 | # Check there is a gdbserver running | ||
2640 | r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps')) | ||
2641 | self.assertEqual(r.status, 0) | ||
2642 | self.assertIn("gdbserver ", r.output) | ||
2643 | |||
2644 | # Check the pid file is correct | ||
2645 | test_cmd = "cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \ | ||
2646 | example_exe + "/pid)/cmdline" | ||
2647 | r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd)) | ||
2648 | self.assertEqual(r.status, 0) | ||
2649 | self.assertIn("gdbserver", r.output) | ||
2650 | |||
2651 | # Test remote debugging works | ||
2652 | gdb_batch_cmd = " --batch -ex 'break main' -ex 'run'" | ||
2653 | gdb_batch_cmd += " -ex 'break CppExample::print_json()' -ex 'continue'" | ||
2654 | gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %s\")'" % magic_string | ||
2655 | gdb_batch_cmd += " -ex 'print CppExample::test_string.compare(\"cpp-example-lib %saaa\")'" % magic_string | ||
2656 | gdb_batch_cmd += " -ex 'list cpp-example-lib.hpp:13,13'" | ||
2657 | gdb_batch_cmd += " -ex 'continue'" | ||
2658 | r = runCmd(gdb_script + gdb_batch_cmd) | ||
2659 | self.logger.debug("%s %s returned: %s", gdb_script, | ||
2660 | gdb_batch_cmd, r.output) | ||
2661 | self.assertEqual(r.status, 0) | ||
2662 | self.assertIn("Breakpoint 1, main", r.output) | ||
2663 | self.assertIn("$1 = 0", r.output) # test.string.compare equal | ||
2664 | self.assertIn("$2 = -3", r.output) # test.string.compare longer | ||
2665 | self.assertIn( | ||
2666 | 'inline static const std::string test_string = "cpp-example-lib %s";' % magic_string, r.output) | ||
2667 | self.assertIn("exited normally", r.output) | ||
2668 | |||
2669 | # Stop the gdbserver | ||
2670 | r = runCmd(gdbserver_script + ' stop') | ||
2671 | self.assertEqual(r.status, 0) | ||
2672 | |||
2673 | # Check there is no gdbserver running | ||
2674 | r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps')) | ||
2675 | self.assertEqual(r.status, 0) | ||
2676 | self.assertNotIn("gdbserver ", r.output) | ||
2677 | |||
2678 | def _verify_cmake_preset(self, tempdir): | ||
2679 | """Verify the generated cmake preset works as expected | ||
2680 | |||
2681 | Check if compiling works | ||
2682 | Check if unit tests can be executed in qemu (not qemu-system) | ||
2683 | """ | ||
2684 | with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j: | ||
2685 | cmake_preset_d = json.load(cmake_preset_j) | ||
2686 | config_presets = cmake_preset_d["configurePresets"] | ||
2687 | self.assertEqual(len(config_presets), 1) | ||
2688 | cmake_exe = config_presets[0]["cmakeExecutable"] | ||
2689 | preset_name = config_presets[0]["name"] | ||
2690 | |||
2691 | # Verify the wrapper for cmake native is available | ||
2692 | self.assertExists(cmake_exe) | ||
2693 | |||
2694 | # Verify the cmake preset generated by devtool ide-sdk is available | ||
2695 | result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir) | ||
2696 | self.assertIn(preset_name, result.output) | ||
2697 | |||
2698 | # Verify cmake re-uses the o files compiled by bitbake | ||
2699 | result = runCmd('%s --build --preset %s' % | ||
2700 | (cmake_exe, preset_name), cwd=tempdir) | ||
2701 | self.assertIn("ninja: no work to do.", result.output) | ||
2702 | |||
2703 | # Verify the unit tests work (in Qemu user mode) | ||
2704 | result = runCmd('%s --build --preset %s --target test' % | ||
2705 | (cmake_exe, preset_name), cwd=tempdir) | ||
2706 | self.assertIn("100% tests passed", result.output) | ||
2707 | |||
2708 | # Verify re-building and testing works again | ||
2709 | result = runCmd('%s --build --preset %s --target clean' % | ||
2710 | (cmake_exe, preset_name), cwd=tempdir) | ||
2711 | self.assertIn("Cleaning", result.output) | ||
2712 | result = runCmd('%s --build --preset %s' % | ||
2713 | (cmake_exe, preset_name), cwd=tempdir) | ||
2714 | self.assertIn("Building", result.output) | ||
2715 | self.assertIn("Linking", result.output) | ||
2716 | result = runCmd('%s --build --preset %s --target test' % | ||
2717 | (cmake_exe, preset_name), cwd=tempdir) | ||
2718 | self.assertIn("Running tests...", result.output) | ||
2719 | self.assertIn("100% tests passed", result.output) | ||
2720 | |||
2721 | @OETestTag("runqemu") | ||
2722 | def test_devtool_ide_sdk_none_qemu(self): | ||
2723 | """Start qemu-system and run tests for multiple recipes. ide=none is used.""" | ||
2724 | recipe_names = ["cmake-example", "meson-example"] | ||
2725 | testimage = "oe-selftest-image" | ||
2726 | |||
2727 | self._check_workspace() | ||
2728 | self._write_bb_config(recipe_names) | ||
2729 | self._check_runqemu_prerequisites() | ||
2730 | |||
2731 | # Verify deployment to Qemu (system mode) works | ||
2732 | bitbake(testimage) | ||
2733 | with runqemu(testimage, runqemuparams="nographic") as qemu: | ||
2734 | # cmake-example recipe | ||
2735 | recipe_name = "cmake-example" | ||
2736 | example_exe = "cmake-example" | ||
2737 | build_file = "CMakeLists.txt" | ||
2738 | tempdir = self._devtool_ide_sdk_recipe( | ||
2739 | recipe_name, build_file, testimage) | ||
2740 | bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( | ||
2741 | recipe_name, testimage, qemu.ip) | ||
2742 | runCmd(bitbake_sdk_cmd) | ||
2743 | self._gdb_cross() | ||
2744 | self._verify_cmake_preset(tempdir) | ||
2745 | self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) | ||
2746 | # Verify the oe-scripts sym-link is valid | ||
2747 | self.assertEqual(self._workspace_scripts_dir( | ||
2748 | recipe_name), self._sources_scripts_dir(tempdir)) | ||
2749 | |||
2750 | # meson-example recipe | ||
2751 | recipe_name = "meson-example" | ||
2752 | example_exe = "mesonex" | ||
2753 | build_file = "meson.build" | ||
2754 | tempdir = self._devtool_ide_sdk_recipe( | ||
2755 | recipe_name, build_file, testimage) | ||
2756 | bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@%s -c --ide=none' % ( | ||
2757 | recipe_name, testimage, qemu.ip) | ||
2758 | runCmd(bitbake_sdk_cmd) | ||
2759 | self._gdb_cross() | ||
2760 | self._devtool_ide_sdk_qemu(tempdir, qemu, recipe_name, example_exe) | ||
2761 | # Verify the oe-scripts sym-link is valid | ||
2762 | self.assertEqual(self._workspace_scripts_dir( | ||
2763 | recipe_name), self._sources_scripts_dir(tempdir)) | ||
2764 | |||
2765 | def test_devtool_ide_sdk_code_cmake(self): | ||
2766 | """Verify a cmake recipe works with ide=code mode""" | ||
2767 | recipe_name = "cmake-example" | ||
2768 | build_file = "CMakeLists.txt" | ||
2769 | testimage = "oe-selftest-image" | ||
2770 | |||
2771 | self._check_workspace() | ||
2772 | self._write_bb_config([recipe_name]) | ||
2773 | tempdir = self._devtool_ide_sdk_recipe( | ||
2774 | recipe_name, build_file, testimage) | ||
2775 | bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@192.168.17.17 -c --ide=code' % ( | ||
2776 | recipe_name, testimage) | ||
2777 | runCmd(bitbake_sdk_cmd) | ||
2778 | self._verify_cmake_preset(tempdir) | ||
2779 | self._verify_install_script_code(tempdir, recipe_name) | ||
2780 | self._gdb_cross() | ||
2781 | |||
2782 | def test_devtool_ide_sdk_code_meson(self): | ||
2783 | """Verify a meson recipe works with ide=code mode""" | ||
2784 | recipe_name = "meson-example" | ||
2785 | build_file = "meson.build" | ||
2786 | testimage = "oe-selftest-image" | ||
2787 | |||
2788 | self._check_workspace() | ||
2789 | self._write_bb_config([recipe_name]) | ||
2790 | tempdir = self._devtool_ide_sdk_recipe( | ||
2791 | recipe_name, build_file, testimage) | ||
2792 | bitbake_sdk_cmd = 'devtool ide-sdk %s %s -t root@192.168.17.17 -c --ide=code' % ( | ||
2793 | recipe_name, testimage) | ||
2794 | runCmd(bitbake_sdk_cmd) | ||
2795 | |||
2796 | with open(os.path.join(tempdir, '.vscode', 'settings.json')) as settings_j: | ||
2797 | settings_d = json.load(settings_j) | ||
2798 | meson_exe = settings_d["mesonbuild.mesonPath"] | ||
2799 | meson_build_folder = settings_d["mesonbuild.buildFolder"] | ||
2800 | |||
2801 | # Verify the wrapper for meson native is available | ||
2802 | self.assertExists(meson_exe) | ||
2803 | |||
2804 | # Verify meson re-uses the o files compiled by bitbake | ||
2805 | result = runCmd('%s compile -C %s' % | ||
2806 | (meson_exe, meson_build_folder), cwd=tempdir) | ||
2807 | self.assertIn("ninja: no work to do.", result.output) | ||
2808 | |||
2809 | # Verify the unit tests work (in Qemu) | ||
2810 | runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir) | ||
2811 | |||
2812 | # Verify re-building and testing works again | ||
2813 | result = runCmd('%s compile -C %s --clean' % | ||
2814 | (meson_exe, meson_build_folder), cwd=tempdir) | ||
2815 | self.assertIn("Cleaning...", result.output) | ||
2816 | result = runCmd('%s compile -C %s' % | ||
2817 | (meson_exe, meson_build_folder), cwd=tempdir) | ||
2818 | self.assertIn("Linking target", result.output) | ||
2819 | runCmd('%s test -C %s' % (meson_exe, meson_build_folder), cwd=tempdir) | ||
2820 | |||
2821 | self._verify_install_script_code(tempdir, recipe_name) | ||
2822 | self._gdb_cross() | ||
2823 | |||
2824 | def test_devtool_ide_sdk_shared_sysroots(self): | ||
2825 | """Verify the shared sysroot SDK""" | ||
2826 | |||
2827 | # Handle the workspace (which is not needed by this test case) | ||
2828 | self._check_workspace() | ||
2829 | |||
2830 | result_init = runCmd( | ||
2831 | 'devtool ide-sdk -m shared oe-selftest-image cmake-example meson-example --ide=code') | ||
2832 | bb_vars = get_bb_vars( | ||
2833 | ['REAL_MULTIMACH_TARGET_SYS', 'DEPLOY_DIR_IMAGE', 'COREBASE'], "meta-ide-support") | ||
2834 | environment_script = 'environment-setup-%s' % bb_vars['REAL_MULTIMACH_TARGET_SYS'] | ||
2835 | deploydir = bb_vars['DEPLOY_DIR_IMAGE'] | ||
2836 | environment_script_path = os.path.join(deploydir, environment_script) | ||
2837 | cpp_example_src = os.path.join( | ||
2838 | bb_vars['COREBASE'], 'meta-selftest', 'recipes-test', 'cpp', 'files') | ||
2839 | |||
2840 | # Verify the cross environment script is available | ||
2841 | self.assertExists(environment_script_path) | ||
2842 | |||
2843 | def runCmdEnv(cmd, cwd): | ||
2844 | cmd = '/bin/sh -c ". %s > /dev/null && %s"' % ( | ||
2845 | environment_script_path, cmd) | ||
2846 | return runCmd(cmd, cwd) | ||
2847 | |||
2848 | # Verify building the C++ example works with CMake | ||
2849 | tempdir_cmake = tempfile.mkdtemp(prefix='devtoolqa') | ||
2850 | self.track_for_cleanup(tempdir_cmake) | ||
2851 | |||
2852 | result_cmake = runCmdEnv("which cmake", cwd=tempdir_cmake) | ||
2853 | cmake_native = os.path.normpath(result_cmake.output.strip()) | ||
2854 | self.assertExists(cmake_native) | ||
2855 | |||
2856 | runCmdEnv('cmake %s' % cpp_example_src, cwd=tempdir_cmake) | ||
2857 | runCmdEnv('cmake --build %s' % tempdir_cmake, cwd=tempdir_cmake) | ||
2858 | |||
2859 | # Verify the printed note really referres to a cmake executable | ||
2860 | cmake_native_code = "" | ||
2861 | for line in result_init.output.splitlines(): | ||
2862 | m = re.search(r'"cmake.cmakePath": "(.*)"', line) | ||
2863 | if m: | ||
2864 | cmake_native_code = m.group(1) | ||
2865 | break | ||
2866 | self.assertExists(cmake_native_code) | ||
2867 | self.assertEqual(cmake_native, cmake_native_code) | ||
2868 | |||
2869 | # Verify building the C++ example works with Meson | ||
2870 | tempdir_meson = tempfile.mkdtemp(prefix='devtoolqa') | ||
2871 | self.track_for_cleanup(tempdir_meson) | ||
2872 | |||
2873 | result_cmake = runCmdEnv("which meson", cwd=tempdir_meson) | ||
2874 | meson_native = os.path.normpath(result_cmake.output.strip()) | ||
2875 | self.assertExists(meson_native) | ||
2876 | |||
2877 | runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src) | ||
2878 | runCmdEnv('meson compile', cwd=tempdir_meson) | ||
2879 | |||
2880 | def test_devtool_ide_sdk_plugins(self): | ||
2881 | """Test that devtool ide-sdk can use plugins from other layers.""" | ||
2882 | |||
2883 | # We need a workspace layer and a modified recipe (but no image) | ||
2884 | modified_recipe_name = "meson-example" | ||
2885 | modified_build_file = "meson.build" | ||
2886 | testimage = "oe-selftest-image" | ||
2887 | shared_recipe_name = "cmake-example" | ||
2888 | |||
2889 | self._check_workspace() | ||
2890 | self._write_bb_config([modified_recipe_name]) | ||
2891 | tempdir = self._devtool_ide_sdk_recipe( | ||
2892 | modified_recipe_name, modified_build_file, None) | ||
2893 | |||
2894 | IDE_RE = re.compile(r'.*--ide \{(.*)\}.*') | ||
2895 | |||
2896 | def get_ides_from_help(help_str): | ||
2897 | m = IDE_RE.search(help_str) | ||
2898 | return m.group(1).split(',') | ||
2899 | |||
2900 | # verify the default plugins are available but the foo plugin is not | ||
2901 | result = runCmd('devtool ide-sdk -h') | ||
2902 | found_ides = get_ides_from_help(result.output) | ||
2903 | self.assertIn('code', found_ides) | ||
2904 | self.assertIn('none', found_ides) | ||
2905 | self.assertNotIn('foo', found_ides) | ||
2906 | |||
2907 | shared_config_file = os.path.join(tempdir, 'shared-config.txt') | ||
2908 | shared_config_str = 'Dummy shared IDE config' | ||
2909 | modified_config_file = os.path.join(tempdir, 'modified-config.txt') | ||
2910 | modified_config_str = 'Dummy modified IDE config' | ||
2911 | |||
2912 | # Generate a foo plugin in the workspace layer | ||
2913 | plugin_dir = os.path.join( | ||
2914 | self.workspacedir, 'lib', 'devtool', 'ide_plugins') | ||
2915 | os.makedirs(plugin_dir) | ||
2916 | plugin_code = 'from devtool.ide_plugins import IdeBase\n\n' | ||
2917 | plugin_code += 'class IdeFoo(IdeBase):\n' | ||
2918 | plugin_code += ' def setup_shared_sysroots(self, shared_env):\n' | ||
2919 | plugin_code += ' with open("%s", "w") as config_file:\n' % shared_config_file | ||
2920 | plugin_code += ' config_file.write("%s")\n\n' % shared_config_str | ||
2921 | plugin_code += ' def setup_modified_recipe(self, args, image_recipe, modified_recipe):\n' | ||
2922 | plugin_code += ' with open("%s", "w") as config_file:\n' % modified_config_file | ||
2923 | plugin_code += ' config_file.write("%s")\n\n' % modified_config_str | ||
2924 | plugin_code += 'def register_ide_plugin(ide_plugins):\n' | ||
2925 | plugin_code += ' ide_plugins["foo"] = IdeFoo\n' | ||
2926 | |||
2927 | plugin_py = os.path.join(plugin_dir, 'ide_foo.py') | ||
2928 | with open(plugin_py, 'w') as plugin_file: | ||
2929 | plugin_file.write(plugin_code) | ||
2930 | |||
2931 | # Verify the foo plugin is available as well | ||
2932 | result = runCmd('devtool ide-sdk -h') | ||
2933 | found_ides = get_ides_from_help(result.output) | ||
2934 | self.assertIn('code', found_ides) | ||
2935 | self.assertIn('none', found_ides) | ||
2936 | self.assertIn('foo', found_ides) | ||
2937 | |||
2938 | # Verify the foo plugin generates a shared config | ||
2939 | result = runCmd( | ||
2940 | 'devtool ide-sdk -m shared --skip-bitbake --ide foo %s' % shared_recipe_name) | ||
2941 | with open(shared_config_file) as shared_config: | ||
2942 | shared_config_new = shared_config.read() | ||
2943 | self.assertEqual(shared_config_str, shared_config_new) | ||
2944 | |||
2945 | # Verify the foo plugin generates a modified config | ||
2946 | result = runCmd('devtool ide-sdk --skip-bitbake --ide foo %s %s' % | ||
2947 | (modified_recipe_name, testimage)) | ||
2948 | with open(modified_config_file) as modified_config: | ||
2949 | modified_config_new = modified_config.read() | ||
2950 | self.assertEqual(modified_config_str, modified_config_new) | ||