xref: /OK3568_Linux_fs/yocto/poky/meta/lib/oeqa/selftest/cases/devtool.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1#
2# SPDX-License-Identifier: MIT
3#
4
5import os
6import re
7import shutil
8import tempfile
9import glob
10import fnmatch
11
12from oeqa.selftest.case import OESelftestTestCase
13from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer
14from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer
15from oeqa.core.decorator import OETestTag
16
17oldmetapath = None
18
19def setUpModule():
20    import bb.utils
21
22    global templayerdir
23    templayerdir = tempfile.mkdtemp(prefix='devtoolqa')
24    corecopydir = os.path.join(templayerdir, 'core-copy')
25    bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf')
26    edited_layers = []
27
28    # 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
30    # however things like COREBASE mean that you can't just copy meta, you
31    # need the whole repository.
32    def bblayers_edit_cb(layerpath, canonical_layerpath):
33        global oldmetapath
34        if not canonical_layerpath.endswith('/'):
35            # This helps us match exactly when we're using this path later
36            canonical_layerpath += '/'
37        if not edited_layers and canonical_layerpath.endswith('/meta/'):
38            canonical_layerpath = os.path.realpath(canonical_layerpath) + '/'
39            edited_layers.append(layerpath)
40            oldmetapath = os.path.realpath(layerpath)
41            result = runCmd('git rev-parse --show-toplevel', cwd=canonical_layerpath)
42            oldreporoot = result.output.rstrip()
43            newmetapath = os.path.join(corecopydir, os.path.relpath(oldmetapath, oldreporoot))
44            runCmd('git clone %s %s' % (oldreporoot, corecopydir), cwd=templayerdir)
45            # Now we need to copy any modified files
46            # You might ask "why not just copy the entire tree instead of
47            # cloning and doing this?" - well, the problem with that is
48            # TMPDIR or an equally large subdirectory might exist
49            # under COREBASE and we don't want to copy that, so we have
50            # to be selective.
51            result = runCmd('git status --porcelain', cwd=oldreporoot)
52            for line in result.output.splitlines():
53                if line.startswith(' M ') or line.startswith('?? '):
54                    relpth = line.split()[1]
55                    pth = os.path.join(oldreporoot, relpth)
56                    if pth.startswith(canonical_layerpath):
57                        if relpth.endswith('/'):
58                            destdir = os.path.join(corecopydir, relpth)
59                            # avoid race condition by not copying .pyc files YPBZ#13421,13803
60                            shutil.copytree(pth, destdir, ignore=shutil.ignore_patterns('*.pyc', '__pycache__'))
61                        else:
62                            destdir = os.path.join(corecopydir, os.path.dirname(relpth))
63                            bb.utils.mkdirhier(destdir)
64                            shutil.copy2(pth, destdir)
65            return newmetapath
66        else:
67            return layerpath
68    bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb)
69
70def tearDownModule():
71    if oldmetapath:
72        edited_layers = []
73        def bblayers_edit_cb(layerpath, canonical_layerpath):
74            if not edited_layers and canonical_layerpath.endswith('/meta'):
75                edited_layers.append(layerpath)
76                return oldmetapath
77            else:
78                return layerpath
79        bblayers_conf = os.path.join(os.environ['BUILDDIR'], 'conf', 'bblayers.conf')
80        bb.utils.edit_bblayers_conf(bblayers_conf, None, None, bblayers_edit_cb)
81    shutil.rmtree(templayerdir)
82
83class DevtoolTestCase(OESelftestTestCase):
84
85    def setUp(self):
86        """Test case setup function"""
87        super(DevtoolTestCase, self).setUp()
88        self.workspacedir = os.path.join(self.builddir, 'workspace')
89        self.assertTrue(not os.path.exists(self.workspacedir),
90                        'This test cannot be run with a workspace directory '
91                        'under the build directory')
92
93    def _check_src_repo(self, repo_dir):
94        """Check srctree git repository"""
95        self.assertTrue(os.path.isdir(os.path.join(repo_dir, '.git')),
96                        'git repository for external source tree not found')
97        result = runCmd('git status --porcelain', cwd=repo_dir)
98        self.assertEqual(result.output.strip(), "",
99                         'Created git repo is not clean')
100        result = runCmd('git symbolic-ref HEAD', cwd=repo_dir)
101        self.assertEqual(result.output.strip(), "refs/heads/devtool",
102                         'Wrong branch in git repo')
103
104    def _check_repo_status(self, repo_dir, expected_status):
105        """Check the worktree status of a repository"""
106        result = runCmd('git status . --porcelain',
107                        cwd=repo_dir)
108        for line in result.output.splitlines():
109            for ind, (f_status, fn_re) in enumerate(expected_status):
110                if re.match(fn_re, line[3:]):
111                    if f_status != line[:2]:
112                        self.fail('Unexpected status in line: %s' % line)
113                    expected_status.pop(ind)
114                    break
115            else:
116                self.fail('Unexpected modified file in line: %s' % line)
117        if expected_status:
118            self.fail('Missing file changes: %s' % expected_status)
119
120    def _test_recipe_contents(self, recipefile, checkvars, checkinherits):
121        with open(recipefile, 'r') as f:
122            invar = None
123            invalue = None
124            inherits = set()
125            for line in f:
126                var = None
127                if invar:
128                    value = line.strip().strip('"')
129                    if value.endswith('\\'):
130                        invalue += ' ' + value[:-1].strip()
131                        continue
132                    else:
133                        invalue += ' ' + value.strip()
134                        var = invar
135                        value = invalue
136                        invar = None
137                elif '=' in line:
138                    splitline = line.split('=', 1)
139                    var = splitline[0].rstrip()
140                    value = splitline[1].strip().strip('"')
141                    if value.endswith('\\'):
142                        invalue = value[:-1].strip()
143                        invar = var
144                        continue
145                elif line.startswith('inherit '):
146                    inherits.update(line.split()[1:])
147
148                if var and var in checkvars:
149                    needvalue = checkvars.pop(var)
150                    if needvalue is None:
151                        self.fail('Variable %s should not appear in recipe, but value is being set to "%s"' % (var, value))
152                    if isinstance(needvalue, set):
153                        if var == 'LICENSE':
154                            value = set(value.split(' & '))
155                        else:
156                            value = set(value.split())
157                    self.assertEqual(value, needvalue, 'values for %s do not match' % var)
158
159
160        missingvars = {}
161        for var, value in checkvars.items():
162            if value is not None:
163                missingvars[var] = value
164        self.assertEqual(missingvars, {}, 'Some expected variables not found in recipe: %s' % checkvars)
165
166        for inherit in checkinherits:
167            self.assertIn(inherit, inherits, 'Missing inherit of %s' % inherit)
168
169    def _check_bbappend(self, testrecipe, recipefile, appenddir):
170        result = runCmd('bitbake-layers show-appends', cwd=self.builddir)
171        resultlines = result.output.splitlines()
172        inrecipe = False
173        bbappends = []
174        bbappendfile = None
175        for line in resultlines:
176            if inrecipe:
177                if line.startswith(' '):
178                    bbappends.append(line.strip())
179                else:
180                    break
181            elif line == '%s:' % os.path.basename(recipefile):
182                inrecipe = True
183        self.assertLessEqual(len(bbappends), 2, '%s recipe is being bbappended by another layer - bbappends found:\n  %s' % (testrecipe, '\n  '.join(bbappends)))
184        for bbappend in bbappends:
185            if bbappend.startswith(appenddir):
186                bbappendfile = bbappend
187                break
188        else:
189            self.fail('bbappend for recipe %s does not seem to be created in test layer' % testrecipe)
190        return bbappendfile
191
192    def _create_temp_layer(self, templayerdir, addlayer, templayername, priority=999, recipepathspec='recipes-*/*'):
193        create_temp_layer(templayerdir, templayername, priority, recipepathspec)
194        if addlayer:
195            self.add_command_to_tearDown('bitbake-layers remove-layer %s || true' % templayerdir)
196            result = runCmd('bitbake-layers add-layer %s' % templayerdir, cwd=self.builddir)
197
198    def _process_ls_output(self, output):
199        """
200        Convert ls -l output to a format we can reasonably compare from one context
201        to another (e.g. from host to target)
202        """
203        filelist = []
204        for line in output.splitlines():
205            splitline = line.split()
206            if len(splitline) < 8:
207                self.fail('_process_ls_output: invalid output line: %s' % line)
208            # Remove trailing . on perms
209            splitline[0] = splitline[0].rstrip('.')
210            # Remove leading . on paths
211            splitline[-1] = splitline[-1].lstrip('.')
212            # Drop fields we don't want to compare
213            del splitline[7]
214            del splitline[6]
215            del splitline[5]
216            del splitline[4]
217            del splitline[1]
218            filelist.append(' '.join(splitline))
219        return filelist
220
221    def _check_diff(self, diffoutput, addlines, removelines):
222        """Check output from 'git diff' matches expectation"""
223        remaining_addlines = addlines[:]
224        remaining_removelines = removelines[:]
225        for line in diffoutput.splitlines():
226            if line.startswith('+++') or line.startswith('---'):
227                continue
228            elif line.startswith('+'):
229                matched = False
230                for item in addlines:
231                    if re.match(item, line[1:].strip()):
232                        matched = True
233                        remaining_addlines.remove(item)
234                        break
235                self.assertTrue(matched, 'Unexpected diff add line: %s' % line)
236            elif line.startswith('-'):
237                matched = False
238                for item in removelines:
239                    if re.match(item, line[1:].strip()):
240                        matched = True
241                        remaining_removelines.remove(item)
242                        break
243                self.assertTrue(matched, 'Unexpected diff remove line: %s' % line)
244        if remaining_addlines:
245            self.fail('Expected added lines not found: %s' % remaining_addlines)
246        if remaining_removelines:
247            self.fail('Expected removed lines not found: %s' % remaining_removelines)
248
249
250class DevtoolBase(DevtoolTestCase):
251
252    @classmethod
253    def setUpClass(cls):
254        super(DevtoolBase, cls).setUpClass()
255        bb_vars = get_bb_vars(['TOPDIR', 'SSTATE_DIR'])
256        cls.original_sstate = bb_vars['SSTATE_DIR']
257        cls.devtool_sstate = os.path.join(bb_vars['TOPDIR'], 'sstate_devtool')
258        cls.sstate_conf  = 'SSTATE_DIR = "%s"\n' % cls.devtool_sstate
259        cls.sstate_conf += ('SSTATE_MIRRORS += "file://.* file:///%s/PATH"\n'
260                            % cls.original_sstate)
261        cls.sstate_conf += ('BB_HASHSERVE_UPSTREAM = "hashserv.yocto.io:8687"\n')
262
263    @classmethod
264    def tearDownClass(cls):
265        cls.logger.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate)
266        runCmd('rm -rf %s' % cls.devtool_sstate)
267        super(DevtoolBase, cls).tearDownClass()
268
269    def setUp(self):
270        """Test case setup function"""
271        super(DevtoolBase, self).setUp()
272        self.append_config(self.sstate_conf)
273
274
275class DevtoolTests(DevtoolBase):
276
277    def test_create_workspace(self):
278        # Check preconditions
279        result = runCmd('bitbake-layers show-layers')
280        self.assertTrue('\nworkspace' not in result.output, 'This test cannot be run with a workspace layer in bblayers.conf')
281        # remove conf/devtool.conf to avoid it corrupting tests
282        devtoolconf = os.path.join(self.builddir, 'conf', 'devtool.conf')
283        self.track_for_cleanup(devtoolconf)
284        # Try creating a workspace layer with a specific path
285        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
286        self.track_for_cleanup(tempdir)
287        result = runCmd('devtool create-workspace %s' % tempdir)
288        self.assertTrue(os.path.isfile(os.path.join(tempdir, 'conf', 'layer.conf')), msg = "No workspace created. devtool output: %s " % result.output)
289        result = runCmd('bitbake-layers show-layers')
290        self.assertIn(tempdir, result.output)
291        # Try creating a workspace layer with the default path
292        self.track_for_cleanup(self.workspacedir)
293        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
294        result = runCmd('devtool create-workspace')
295        self.assertTrue(os.path.isfile(os.path.join(self.workspacedir, 'conf', 'layer.conf')), msg = "No workspace created. devtool output: %s " % result.output)
296        result = runCmd('bitbake-layers show-layers')
297        self.assertNotIn(tempdir, result.output)
298        self.assertIn(self.workspacedir, result.output)
299
300class DevtoolAddTests(DevtoolBase):
301
302    def test_devtool_add(self):
303        # Fetch source
304        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
305        self.track_for_cleanup(tempdir)
306        pn = 'pv'
307        pv = '1.5.3'
308        url = 'http://downloads.yoctoproject.org/mirror/sources/pv-1.5.3.tar.bz2'
309        result = runCmd('wget %s' % url, cwd=tempdir)
310        result = runCmd('tar xfv %s' % os.path.basename(url), cwd=tempdir)
311        srcdir = os.path.join(tempdir, '%s-%s' % (pn, pv))
312        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory')
313        # Test devtool add
314        self.track_for_cleanup(self.workspacedir)
315        self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn)
316        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
317        result = runCmd('devtool add %s %s' % (pn, srcdir))
318        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
319        # Test devtool status
320        result = runCmd('devtool status')
321        recipepath = '%s/recipes/%s/%s_%s.bb' % (self.workspacedir, pn, pn, pv)
322        self.assertIn(recipepath, result.output)
323        self.assertIn(srcdir, result.output)
324        # Test devtool find-recipe
325        result = runCmd('devtool -q find-recipe %s' % pn)
326        self.assertEqual(recipepath, result.output.strip())
327        # Test devtool edit-recipe
328        result = runCmd('VISUAL="echo 123" devtool -q edit-recipe %s' % pn)
329        self.assertEqual('123 %s' % recipepath, result.output.strip())
330        # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then)
331        bitbake('%s -c cleansstate' % pn)
332        # Test devtool build
333        result = runCmd('devtool build %s' % pn)
334        bb_vars = get_bb_vars(['D', 'bindir'], pn)
335        installdir = bb_vars['D']
336        self.assertTrue(installdir, 'Could not query installdir variable')
337        bindir = bb_vars['bindir']
338        self.assertTrue(bindir, 'Could not query bindir variable')
339        if bindir[0] == '/':
340            bindir = bindir[1:]
341        self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D')
342
343    def test_devtool_add_git_local(self):
344        # We need dbus built so that DEPENDS recognition works
345        bitbake('dbus')
346        # Fetch source from a remote URL, but do it outside of devtool
347        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
348        self.track_for_cleanup(tempdir)
349        pn = 'dbus-wait'
350        srcrev = '6cc6077a36fe2648a5f993fe7c16c9632f946517'
351        # We choose an https:// git URL here to check rewriting the URL works
352        url = 'https://git.yoctoproject.org/git/dbus-wait'
353        # Force fetching to "noname" subdir so we verify we're picking up the name from autoconf
354        # instead of the directory name
355        result = runCmd('git clone %s noname' % url, cwd=tempdir)
356        srcdir = os.path.join(tempdir, 'noname')
357        result = runCmd('git reset --hard %s' % srcrev, cwd=srcdir)
358        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure.ac')), 'Unable to find configure script in source directory')
359        # Test devtool add
360        self.track_for_cleanup(self.workspacedir)
361        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
362        # Don't specify a name since we should be able to auto-detect it
363        result = runCmd('devtool add %s' % srcdir)
364        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
365        # Check the recipe name is correct
366        recipefile = get_bb_var('FILE', pn)
367        self.assertIn('%s_git.bb' % pn, recipefile, 'Recipe file incorrectly named')
368        self.assertIn(recipefile, result.output)
369        # Test devtool status
370        result = runCmd('devtool status')
371        self.assertIn(pn, result.output)
372        self.assertIn(srcdir, result.output)
373        self.assertIn(recipefile, result.output)
374        checkvars = {}
375        checkvars['LICENSE'] = 'GPL-2.0-only'
376        checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263'
377        checkvars['S'] = '${WORKDIR}/git'
378        checkvars['PV'] = '0.1+git${SRCPV}'
379        checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master'
380        checkvars['SRCREV'] = srcrev
381        checkvars['DEPENDS'] = set(['dbus'])
382        self._test_recipe_contents(recipefile, checkvars, [])
383
384    def test_devtool_add_library(self):
385        # Fetch source
386        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
387        self.track_for_cleanup(tempdir)
388        version = '1.1'
389        url = 'https://www.intra2net.com/en/developer/libftdi/download/libftdi1-%s.tar.bz2' % version
390        result = runCmd('wget %s' % url, cwd=tempdir)
391        result = runCmd('tar xfv libftdi1-%s.tar.bz2' % version, cwd=tempdir)
392        srcdir = os.path.join(tempdir, 'libftdi1-%s' % version)
393        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'CMakeLists.txt')), 'Unable to find CMakeLists.txt in source directory')
394        # Test devtool add (and use -V so we test that too)
395        self.track_for_cleanup(self.workspacedir)
396        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
397        result = runCmd('devtool add libftdi %s -V %s' % (srcdir, version))
398        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
399        # Test devtool status
400        result = runCmd('devtool status')
401        self.assertIn('libftdi', result.output)
402        self.assertIn(srcdir, result.output)
403        # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then)
404        bitbake('libftdi -c cleansstate')
405        # libftdi's python/CMakeLists.txt is a bit broken, so let's just disable it
406        # There's also the matter of it installing cmake files to a path we don't
407        # normally cover, which triggers the installed-vs-shipped QA test we have
408        # within do_package
409        recipefile = '%s/recipes/libftdi/libftdi_%s.bb' % (self.workspacedir, version)
410        result = runCmd('recipetool setvar %s EXTRA_OECMAKE -- \'-DPYTHON_BINDINGS=OFF -DLIBFTDI_CMAKE_CONFIG_DIR=${datadir}/cmake/Modules\'' % recipefile)
411        with open(recipefile, 'a') as f:
412            f.write('\nFILES:${PN}-dev += "${datadir}/cmake/Modules"\n')
413            # We don't have the ability to pick up this dependency automatically yet...
414            f.write('\nDEPENDS += "libusb1"\n')
415            f.write('\nTESTLIBOUTPUT = "${COMPONENTS_DIR}/${TUNE_PKGARCH}/${PN}/${libdir}"\n')
416        # Test devtool build
417        result = runCmd('devtool build libftdi')
418        bb_vars = get_bb_vars(['TESTLIBOUTPUT', 'STAMP'], 'libftdi')
419        staging_libdir = bb_vars['TESTLIBOUTPUT']
420        self.assertTrue(staging_libdir, 'Could not query TESTLIBOUTPUT variable')
421        self.assertTrue(os.path.isfile(os.path.join(staging_libdir, 'libftdi1.so.2.1.0')), "libftdi binary not found in STAGING_LIBDIR. Output of devtool build libftdi %s" % result.output)
422        # Test devtool reset
423        stampprefix = bb_vars['STAMP']
424        result = runCmd('devtool reset libftdi')
425        result = runCmd('devtool status')
426        self.assertNotIn('libftdi', result.output)
427        self.assertTrue(stampprefix, 'Unable to get STAMP value for recipe libftdi')
428        matches = glob.glob(stampprefix + '*')
429        self.assertFalse(matches, 'Stamp files exist for recipe libftdi that should have been cleaned')
430        self.assertFalse(os.path.isfile(os.path.join(staging_libdir, 'libftdi1.so.2.1.0')), 'libftdi binary still found in STAGING_LIBDIR after cleaning')
431
432    def test_devtool_add_fetch(self):
433        # Fetch source
434        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
435        self.track_for_cleanup(tempdir)
436        testver = '0.23'
437        url = 'https://files.pythonhosted.org/packages/c0/41/bae1254e0396c0cc8cf1751cb7d9afc90a602353695af5952530482c963f/MarkupSafe-%s.tar.gz' % testver
438        testrecipe = 'python-markupsafe'
439        srcdir = os.path.join(tempdir, testrecipe)
440        # Test devtool add
441        self.track_for_cleanup(self.workspacedir)
442        self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe)
443        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
444        result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url))
445        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output)
446        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory')
447        self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created')
448        # Test devtool status
449        result = runCmd('devtool status')
450        self.assertIn(testrecipe, result.output)
451        self.assertIn(srcdir, result.output)
452        # Check recipe
453        recipefile = get_bb_var('FILE', testrecipe)
454        self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named')
455        checkvars = {}
456        checkvars['S'] = '${WORKDIR}/MarkupSafe-${PV}'
457        checkvars['SRC_URI'] = url.replace(testver, '${PV}')
458        self._test_recipe_contents(recipefile, checkvars, [])
459        # Try with version specified
460        result = runCmd('devtool reset -n %s' % testrecipe)
461        shutil.rmtree(srcdir)
462        fakever = '1.9'
463        result = runCmd('devtool add %s %s -f %s -V %s' % (testrecipe, srcdir, url, fakever))
464        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory')
465        # Test devtool status
466        result = runCmd('devtool status')
467        self.assertIn(testrecipe, result.output)
468        self.assertIn(srcdir, result.output)
469        # Check recipe
470        recipefile = get_bb_var('FILE', testrecipe)
471        self.assertIn('%s_%s.bb' % (testrecipe, fakever), recipefile, 'Recipe file incorrectly named')
472        checkvars = {}
473        checkvars['S'] = '${WORKDIR}/MarkupSafe-%s' % testver
474        checkvars['SRC_URI'] = url
475        self._test_recipe_contents(recipefile, checkvars, [])
476
477    def test_devtool_add_fetch_git(self):
478        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
479        self.track_for_cleanup(tempdir)
480        url = 'gitsm://git.yoctoproject.org/mraa'
481        url_branch = '%s;branch=master' % url
482        checkrev = 'ae127b19a50aa54255e4330ccfdd9a5d058e581d'
483        testrecipe = 'mraa'
484        srcdir = os.path.join(tempdir, testrecipe)
485        # Test devtool add
486        self.track_for_cleanup(self.workspacedir)
487        self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe)
488        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
489        result = runCmd('devtool add %s %s -a -f %s' % (testrecipe, srcdir, url))
490        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created: %s' % result.output)
491        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'imraa', 'imraa.c')), 'Unable to find imraa/imraa.c in source directory')
492        # Test devtool status
493        result = runCmd('devtool status')
494        self.assertIn(testrecipe, result.output)
495        self.assertIn(srcdir, result.output)
496        # Check recipe
497        recipefile = get_bb_var('FILE', testrecipe)
498        self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
499        checkvars = {}
500        checkvars['S'] = '${WORKDIR}/git'
501        checkvars['PV'] = '1.0+git${SRCPV}'
502        checkvars['SRC_URI'] = url_branch
503        checkvars['SRCREV'] = '${AUTOREV}'
504        self._test_recipe_contents(recipefile, checkvars, [])
505        # Try with revision and version specified
506        result = runCmd('devtool reset -n %s' % testrecipe)
507        shutil.rmtree(srcdir)
508        url_rev = '%s;rev=%s' % (url, checkrev)
509        result = runCmd('devtool add %s %s -f "%s" -V 1.5' % (testrecipe, srcdir, url_rev))
510        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'imraa', 'imraa.c')), 'Unable to find imraa/imraa.c in source directory')
511        # Test devtool status
512        result = runCmd('devtool status')
513        self.assertIn(testrecipe, result.output)
514        self.assertIn(srcdir, result.output)
515        # Check recipe
516        recipefile = get_bb_var('FILE', testrecipe)
517        self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named')
518        checkvars = {}
519        checkvars['S'] = '${WORKDIR}/git'
520        checkvars['PV'] = '1.5+git${SRCPV}'
521        checkvars['SRC_URI'] = url_branch
522        checkvars['SRCREV'] = checkrev
523        self._test_recipe_contents(recipefile, checkvars, [])
524
525    def test_devtool_add_fetch_simple(self):
526        # Fetch source from a remote URL, auto-detecting name
527        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
528        self.track_for_cleanup(tempdir)
529        testver = '1.6.0'
530        url = 'http://www.ivarch.com/programs/sources/pv-%s.tar.bz2' % testver
531        testrecipe = 'pv'
532        srcdir = os.path.join(self.workspacedir, 'sources', testrecipe)
533        # Test devtool add
534        self.track_for_cleanup(self.workspacedir)
535        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
536        result = runCmd('devtool add %s' % url)
537        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output)
538        self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory')
539        self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created')
540        # Test devtool status
541        result = runCmd('devtool status')
542        self.assertIn(testrecipe, result.output)
543        self.assertIn(srcdir, result.output)
544        # Check recipe
545        recipefile = get_bb_var('FILE', testrecipe)
546        self.assertIn('%s_%s.bb' % (testrecipe, testver), recipefile, 'Recipe file incorrectly named')
547        checkvars = {}
548        checkvars['S'] = None
549        checkvars['SRC_URI'] = url.replace(testver, '${PV}')
550        self._test_recipe_contents(recipefile, checkvars, [])
551
552    def test_devtool_add_npm(self):
553        collections = get_bb_var('BBFILE_COLLECTIONS').split()
554        if "openembedded-layer" not in collections:
555            self.skipTest("Test needs meta-oe for nodejs")
556
557        pn = 'savoirfairelinux-node-server-example'
558        pv = '1.0.0'
559        url = 'npm://registry.npmjs.org;package=@savoirfairelinux/node-server-example;version=' + pv
560        # Test devtool add
561        self.track_for_cleanup(self.workspacedir)
562        self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn)
563        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
564        result = runCmd('devtool add \'%s\'' % url)
565        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
566        self.assertExists(os.path.join(self.workspacedir, 'recipes', pn, '%s_%s.bb' % (pn, pv)), 'Recipe not created')
567        self.assertExists(os.path.join(self.workspacedir, 'recipes', pn, pn, 'npm-shrinkwrap.json'), 'Shrinkwrap not created')
568        # Test devtool status
569        result = runCmd('devtool status')
570        self.assertIn(pn, result.output)
571        # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then)
572        bitbake('%s -c cleansstate' % pn)
573        # Test devtool build
574        result = runCmd('devtool build %s' % pn)
575
576    def test_devtool_add_python_egg_requires(self):
577        # Fetch source
578        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
579        self.track_for_cleanup(tempdir)
580        testver = '0.14.0'
581        url = 'https://files.pythonhosted.org/packages/e9/9e/25d59f5043cf763833b2581c8027fa92342c4cf8ee523b498ecdf460c16d/uvicorn-%s.tar.gz' % testver
582        testrecipe = 'python3-uvicorn'
583        srcdir = os.path.join(tempdir, testrecipe)
584        # Test devtool add
585        self.track_for_cleanup(self.workspacedir)
586        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
587        result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url))
588
589class DevtoolModifyTests(DevtoolBase):
590
591    def test_devtool_modify(self):
592        import oe.path
593
594        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
595        self.track_for_cleanup(tempdir)
596        self.track_for_cleanup(self.workspacedir)
597        self.add_command_to_tearDown('bitbake -c clean mdadm')
598        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
599        result = runCmd('devtool modify mdadm -x %s' % tempdir)
600        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
601        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
602        matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'mdadm_*.bbappend'))
603        self.assertTrue(matches, 'bbappend not created %s' % result.output)
604
605        # Test devtool status
606        result = runCmd('devtool status')
607        self.assertIn('mdadm', result.output)
608        self.assertIn(tempdir, result.output)
609        self._check_src_repo(tempdir)
610
611        bitbake('mdadm -C unpack')
612
613        def check_line(checkfile, expected, message, present=True):
614            # Check for $expected, on a line on its own, in checkfile.
615            with open(checkfile, 'r') as f:
616                if present:
617                    self.assertIn(expected + '\n', f, message)
618                else:
619                    self.assertNotIn(expected + '\n', f, message)
620
621        modfile = os.path.join(tempdir, 'mdadm.8.in')
622        bb_vars = get_bb_vars(['PKGD', 'mandir'], 'mdadm')
623        pkgd = bb_vars['PKGD']
624        self.assertTrue(pkgd, 'Could not query PKGD variable')
625        mandir = bb_vars['mandir']
626        self.assertTrue(mandir, 'Could not query mandir variable')
627        manfile = oe.path.join(pkgd, mandir, 'man8', 'mdadm.8')
628
629        check_line(modfile, 'Linux Software RAID', 'Could not find initial string')
630        check_line(modfile, 'antique pin sardine', 'Unexpectedly found replacement string', present=False)
631
632        result = runCmd("sed -i 's!^Linux Software RAID$!antique pin sardine!' %s" % modfile)
633        check_line(modfile, 'antique pin sardine', 'mdadm.8.in file not modified (sed failed)')
634
635        bitbake('mdadm -c package')
636        check_line(manfile, 'antique pin sardine', 'man file not modified. man searched file path: %s' % manfile)
637
638        result = runCmd('git checkout -- %s' % modfile, cwd=tempdir)
639        check_line(modfile, 'Linux Software RAID', 'man .in file not restored (git failed)')
640
641        bitbake('mdadm -c package')
642        check_line(manfile, 'Linux Software RAID', 'man file not updated. man searched file path: %s' % manfile)
643
644        result = runCmd('devtool reset mdadm')
645        result = runCmd('devtool status')
646        self.assertNotIn('mdadm', result.output)
647
648    def test_devtool_buildclean(self):
649        def assertFile(path, *paths):
650            f = os.path.join(path, *paths)
651            self.assertExists(f)
652        def assertNoFile(path, *paths):
653            f = os.path.join(path, *paths)
654            self.assertNotExists(f)
655
656        # Clean up anything in the workdir/sysroot/sstate cache
657        bitbake('mdadm m4 -c cleansstate')
658        # Try modifying a recipe
659        tempdir_mdadm = tempfile.mkdtemp(prefix='devtoolqa')
660        tempdir_m4 = tempfile.mkdtemp(prefix='devtoolqa')
661        builddir_m4 = tempfile.mkdtemp(prefix='devtoolqa')
662        self.track_for_cleanup(tempdir_mdadm)
663        self.track_for_cleanup(tempdir_m4)
664        self.track_for_cleanup(builddir_m4)
665        self.track_for_cleanup(self.workspacedir)
666        self.add_command_to_tearDown('bitbake -c clean mdadm m4')
667        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
668        self.write_recipeinc('m4', 'EXTERNALSRC_BUILD = "%s"\ndo_clean() {\n\t:\n}\n' % builddir_m4)
669        try:
670            runCmd('devtool modify mdadm -x %s' % tempdir_mdadm)
671            runCmd('devtool modify m4 -x %s' % tempdir_m4)
672            assertNoFile(tempdir_mdadm, 'mdadm')
673            assertNoFile(builddir_m4, 'src/m4')
674            result = bitbake('m4 -e')
675            result = bitbake('mdadm m4 -c compile')
676            self.assertEqual(result.status, 0)
677            assertFile(tempdir_mdadm, 'mdadm')
678            assertFile(builddir_m4, 'src/m4')
679            # Check that buildclean task exists and does call make clean
680            bitbake('mdadm m4 -c buildclean')
681            assertNoFile(tempdir_mdadm, 'mdadm')
682            assertNoFile(builddir_m4, 'src/m4')
683            runCmd('echo "#Trigger rebuild" >> %s/Makefile' % tempdir_mdadm)
684            bitbake('mdadm m4 -c compile')
685            assertFile(tempdir_mdadm, 'mdadm')
686            assertFile(builddir_m4, 'src/m4')
687            bitbake('mdadm m4 -c clean')
688            # Check that buildclean task is run before clean for B == S
689            assertNoFile(tempdir_mdadm, 'mdadm')
690            # Check that buildclean task is not run before clean for B != S
691            assertFile(builddir_m4, 'src/m4')
692        finally:
693            self.delete_recipeinc('m4')
694
695    def test_devtool_modify_invalid(self):
696        # Try modifying some recipes
697        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
698        self.track_for_cleanup(tempdir)
699        self.track_for_cleanup(self.workspacedir)
700        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
701
702        testrecipes = 'perf kernel-devsrc package-index core-image-minimal meta-toolchain packagegroup-core-sdk'.split()
703        # Find actual name of gcc-source since it now includes the version - crude, but good enough for this purpose
704        result = runCmd('bitbake-layers show-recipes gcc-source*')
705        for line in result.output.splitlines():
706            # just match those lines that contain a real target
707            m = re.match('(?P<recipe>^[a-zA-Z0-9.-]+)(?P<colon>:$)', line)
708            if m:
709                testrecipes.append(m.group('recipe'))
710        for testrecipe in testrecipes:
711            # Check it's a valid recipe
712            bitbake('%s -e' % testrecipe)
713            # devtool extract should fail
714            result = runCmd('devtool extract %s %s' % (testrecipe, os.path.join(tempdir, testrecipe)), ignore_status=True)
715            self.assertNotEqual(result.status, 0, 'devtool extract on %s should have failed. devtool output: %s' % (testrecipe, result.output))
716            self.assertNotIn('Fetching ', result.output, 'devtool extract on %s should have errored out before trying to fetch' % testrecipe)
717            self.assertIn('ERROR: ', result.output, 'devtool extract on %s should have given an ERROR' % testrecipe)
718            # devtool modify should fail
719            result = runCmd('devtool modify %s -x %s' % (testrecipe, os.path.join(tempdir, testrecipe)), ignore_status=True)
720            self.assertNotEqual(result.status, 0, 'devtool modify on %s should have failed. devtool output: %s' %  (testrecipe, result.output))
721            self.assertIn('ERROR: ', result.output, 'devtool modify on %s should have given an ERROR' % testrecipe)
722
723    def test_devtool_modify_native(self):
724        # Check preconditions
725        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
726        # Try modifying some recipes
727        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
728        self.track_for_cleanup(tempdir)
729        self.track_for_cleanup(self.workspacedir)
730        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
731
732        bbclassextended = False
733        inheritnative = False
734        testrecipes = 'cdrtools-native mtools-native apt-native desktop-file-utils-native'.split()
735        for testrecipe in testrecipes:
736            checkextend = 'native' in (get_bb_var('BBCLASSEXTEND', testrecipe) or '').split()
737            if not bbclassextended:
738                bbclassextended = checkextend
739            if not inheritnative:
740                inheritnative = not checkextend
741            result = runCmd('devtool modify %s -x %s' % (testrecipe, os.path.join(tempdir, testrecipe)))
742            self.assertNotIn('ERROR: ', result.output, 'ERROR in devtool modify output: %s' % result.output)
743            result = runCmd('devtool build %s' % testrecipe)
744            self.assertNotIn('ERROR: ', result.output, 'ERROR in devtool build output: %s' % result.output)
745            result = runCmd('devtool reset %s' % testrecipe)
746            self.assertNotIn('ERROR: ', result.output, 'ERROR in devtool reset output: %s' % result.output)
747
748        self.assertTrue(bbclassextended, 'None of these recipes are BBCLASSEXTENDed to native - need to adjust testrecipes list: %s' % ', '.join(testrecipes))
749        self.assertTrue(inheritnative, 'None of these recipes do "inherit native" - need to adjust testrecipes list: %s' % ', '.join(testrecipes))
750
751    def test_devtool_modify_localfiles_only(self):
752        # Check preconditions
753        testrecipe = 'base-files'
754        src_uri = (get_bb_var('SRC_URI', testrecipe) or '').split()
755        foundlocalonly = False
756        correct_symlink = False
757        for item in src_uri:
758            if item.startswith('file://'):
759                if '.patch' not in item:
760                    foundlocalonly = True
761            else:
762                foundlocalonly = False
763                break
764        self.assertTrue(foundlocalonly, 'This test expects the %s recipe to fetch local files only and it seems that it no longer does' % testrecipe)
765        # Clean up anything in the workdir/sysroot/sstate cache
766        bitbake('%s -c cleansstate' % testrecipe)
767        # Try modifying a recipe
768        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
769        self.track_for_cleanup(tempdir)
770        self.track_for_cleanup(self.workspacedir)
771        self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
772        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
773        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
774        srcfile = os.path.join(tempdir, 'oe-local-files/share/dot.bashrc')
775        srclink = os.path.join(tempdir, 'share/dot.bashrc')
776        self.assertExists(srcfile, 'Extracted source could not be found')
777        if os.path.islink(srclink) and os.path.exists(srclink) and os.path.samefile(srcfile, srclink):
778            correct_symlink = True
779        self.assertTrue(correct_symlink, 'Source symlink to oe-local-files is broken')
780
781        matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe))
782        self.assertTrue(matches, 'bbappend not created')
783        # Test devtool status
784        result = runCmd('devtool status')
785        self.assertIn(testrecipe, result.output)
786        self.assertIn(tempdir, result.output)
787        # Try building
788        bitbake(testrecipe)
789
790    def test_devtool_modify_git(self):
791        # Check preconditions
792        testrecipe = 'psplash'
793        src_uri = get_bb_var('SRC_URI', testrecipe)
794        self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe)
795        # Clean up anything in the workdir/sysroot/sstate cache
796        bitbake('%s -c cleansstate' % testrecipe)
797        # Try modifying a recipe
798        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
799        self.track_for_cleanup(tempdir)
800        self.track_for_cleanup(self.workspacedir)
801        self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
802        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
803        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
804        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
805        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output)
806        matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'psplash_*.bbappend'))
807        self.assertTrue(matches, 'bbappend not created')
808        # Test devtool status
809        result = runCmd('devtool status')
810        self.assertIn(testrecipe, result.output)
811        self.assertIn(tempdir, result.output)
812        # Check git repo
813        self._check_src_repo(tempdir)
814        # Try building
815        bitbake(testrecipe)
816
817    def test_devtool_modify_localfiles(self):
818        # Check preconditions
819        testrecipe = 'lighttpd'
820        src_uri = (get_bb_var('SRC_URI', testrecipe) or '').split()
821        foundlocal = False
822        for item in src_uri:
823            if item.startswith('file://') and '.patch' not in item:
824                foundlocal = True
825                break
826        self.assertTrue(foundlocal, 'This test expects the %s recipe to fetch local files and it seems that it no longer does' % testrecipe)
827        # Clean up anything in the workdir/sysroot/sstate cache
828        bitbake('%s -c cleansstate' % testrecipe)
829        # Try modifying a recipe
830        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
831        self.track_for_cleanup(tempdir)
832        self.track_for_cleanup(self.workspacedir)
833        self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
834        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
835        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
836        self.assertExists(os.path.join(tempdir, 'configure.ac'), 'Extracted source could not be found')
837        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
838        matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe))
839        self.assertTrue(matches, 'bbappend not created')
840        # Test devtool status
841        result = runCmd('devtool status')
842        self.assertIn(testrecipe, result.output)
843        self.assertIn(tempdir, result.output)
844        # Try building
845        bitbake(testrecipe)
846
847    def test_devtool_modify_virtual(self):
848        # Try modifying a virtual recipe
849        virtrecipe = 'virtual/make'
850        realrecipe = 'make'
851        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
852        self.track_for_cleanup(tempdir)
853        self.track_for_cleanup(self.workspacedir)
854        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
855        result = runCmd('devtool modify %s -x %s' % (virtrecipe, tempdir))
856        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
857        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
858        matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % realrecipe))
859        self.assertTrue(matches, 'bbappend not created %s' % result.output)
860        # Test devtool status
861        result = runCmd('devtool status')
862        self.assertNotIn(virtrecipe, result.output)
863        self.assertIn(realrecipe, result.output)
864        # Check git repo
865        self._check_src_repo(tempdir)
866        # This is probably sufficient
867
868    def test_devtool_modify_overrides(self):
869        # Try modifying a recipe with patches in overrides
870        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
871        self.track_for_cleanup(tempdir)
872        self.track_for_cleanup(self.workspacedir)
873        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
874        result = runCmd('devtool modify devtool-patch-overrides -x %s' % (tempdir))
875
876        self._check_src_repo(tempdir)
877        source = os.path.join(tempdir, "source")
878        def check(branch, expected):
879            runCmd('git -C %s checkout %s' % (tempdir, branch))
880            with open(source, "rt") as f:
881                content = f.read()
882            self.assertEquals(content, expected)
883        check('devtool', 'This is a test for something\n')
884        check('devtool-no-overrides', 'This is a test for something\n')
885        check('devtool-override-qemuarm', 'This is a test for qemuarm\n')
886        check('devtool-override-qemux86', 'This is a test for qemux86\n')
887
888class DevtoolUpdateTests(DevtoolBase):
889
890    def test_devtool_update_recipe(self):
891        # Check preconditions
892        testrecipe = 'minicom'
893        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
894        recipefile = bb_vars['FILE']
895        src_uri = bb_vars['SRC_URI']
896        self.assertNotIn('git://', src_uri, 'This test expects the %s recipe to NOT be a git recipe' % testrecipe)
897        self._check_repo_status(os.path.dirname(recipefile), [])
898        # First, modify a recipe
899        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
900        self.track_for_cleanup(tempdir)
901        self.track_for_cleanup(self.workspacedir)
902        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
903        # (don't bother with cleaning the recipe on teardown, we won't be building it)
904        # We don't use -x here so that we test the behaviour of devtool modify without it
905        result = runCmd('devtool modify %s %s' % (testrecipe, tempdir))
906        # Check git repo
907        self._check_src_repo(tempdir)
908        # Add a couple of commits
909        # FIXME: this only tests adding, need to also test update and remove
910        result = runCmd('echo "Additional line" >> README', cwd=tempdir)
911        result = runCmd('git commit -a -m "Change the README"', cwd=tempdir)
912        result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir)
913        result = runCmd('git add devtool-new-file', cwd=tempdir)
914        result = runCmd('git commit -m "Add a new file"', cwd=tempdir)
915        self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
916        result = runCmd('devtool update-recipe %s' % testrecipe)
917        expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
918                           ('??', '.*/0001-Change-the-README.patch$'),
919                           ('??', '.*/0002-Add-a-new-file.patch$')]
920        self._check_repo_status(os.path.dirname(recipefile), expected_status)
921
922    def test_devtool_update_recipe_git(self):
923        # Check preconditions
924        testrecipe = 'mtd-utils'
925        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
926        recipefile = bb_vars['FILE']
927        src_uri = bb_vars['SRC_URI']
928        self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe)
929        patches = []
930        for entry in src_uri.split():
931            if entry.startswith('file://') and entry.endswith('.patch'):
932                patches.append(entry[7:].split(';')[0])
933        self.assertGreater(len(patches), 0, 'The %s recipe does not appear to contain any patches, so this test will not be effective' % testrecipe)
934        self._check_repo_status(os.path.dirname(recipefile), [])
935        # First, modify a recipe
936        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
937        self.track_for_cleanup(tempdir)
938        self.track_for_cleanup(self.workspacedir)
939        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
940        # (don't bother with cleaning the recipe on teardown, we won't be building it)
941        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
942        # Check git repo
943        self._check_src_repo(tempdir)
944        # Add a couple of commits
945        # FIXME: this only tests adding, need to also test update and remove
946        result = runCmd('echo "# Additional line" >> Makefile.am', cwd=tempdir)
947        result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempdir)
948        result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir)
949        result = runCmd('git add devtool-new-file', cwd=tempdir)
950        result = runCmd('git commit -m "Add a new file"', cwd=tempdir)
951        self.add_command_to_tearDown('cd %s; rm -rf %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
952        result = runCmd('devtool update-recipe -m srcrev %s' % testrecipe)
953        expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile))] + \
954                          [(' D', '.*/%s$' % patch) for patch in patches]
955        self._check_repo_status(os.path.dirname(recipefile), expected_status)
956
957        result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile))
958        addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git;branch=master"']
959        srcurilines = src_uri.split()
960        srcurilines[0] = 'SRC_URI = "' + srcurilines[0]
961        srcurilines.append('"')
962        removelines = ['SRCREV = ".*"'] + srcurilines
963        self._check_diff(result.output, addlines, removelines)
964        # Now try with auto mode
965        runCmd('cd %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, os.path.basename(recipefile)))
966        result = runCmd('devtool update-recipe %s' % testrecipe)
967        result = runCmd('git rev-parse --show-toplevel', cwd=os.path.dirname(recipefile))
968        topleveldir = result.output.strip()
969        relpatchpath = os.path.join(os.path.relpath(os.path.dirname(recipefile), topleveldir), testrecipe)
970        expected_status = [(' M', os.path.relpath(recipefile, topleveldir)),
971                           ('??', '%s/0001-Change-the-Makefile.patch' % relpatchpath),
972                           ('??', '%s/0002-Add-a-new-file.patch' % relpatchpath)]
973        self._check_repo_status(os.path.dirname(recipefile), expected_status)
974
975    def test_devtool_update_recipe_append(self):
976        # Check preconditions
977        testrecipe = 'mdadm'
978        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
979        recipefile = bb_vars['FILE']
980        src_uri = bb_vars['SRC_URI']
981        self.assertNotIn('git://', src_uri, 'This test expects the %s recipe to NOT be a git recipe' % testrecipe)
982        self._check_repo_status(os.path.dirname(recipefile), [])
983        # First, modify a recipe
984        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
985        tempsrcdir = os.path.join(tempdir, 'source')
986        templayerdir = os.path.join(tempdir, 'layer')
987        self.track_for_cleanup(tempdir)
988        self.track_for_cleanup(self.workspacedir)
989        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
990        # (don't bother with cleaning the recipe on teardown, we won't be building it)
991        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempsrcdir))
992        # Check git repo
993        self._check_src_repo(tempsrcdir)
994        # Add a commit
995        result = runCmd("sed 's!\\(#define VERSION\\W*\"[^\"]*\\)\"!\\1-custom\"!' -i ReadMe.c", cwd=tempsrcdir)
996        result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
997        self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe))
998        # Create a temporary layer and add it to bblayers.conf
999        self._create_temp_layer(templayerdir, True, 'selftestupdaterecipe')
1000        # Create the bbappend
1001        result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1002        self.assertNotIn('WARNING:', result.output)
1003        # Check recipe is still clean
1004        self._check_repo_status(os.path.dirname(recipefile), [])
1005        # Check bbappend was created
1006        splitpath = os.path.dirname(recipefile).split(os.sep)
1007        appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1])
1008        bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir)
1009        patchfile = os.path.join(appenddir, testrecipe, '0001-Add-our-custom-version.patch')
1010        self.assertExists(patchfile, 'Patch file not created')
1011
1012        # Check bbappend contents
1013        expectedlines = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n',
1014                         '\n',
1015                         'SRC_URI += "file://0001-Add-our-custom-version.patch"\n',
1016                         '\n']
1017        with open(bbappendfile, 'r') as f:
1018            self.assertEqual(expectedlines, f.readlines())
1019
1020        # Check we can run it again and bbappend isn't modified
1021        result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1022        with open(bbappendfile, 'r') as f:
1023            self.assertEqual(expectedlines, f.readlines())
1024        # Drop new commit and check patch gets deleted
1025        result = runCmd('git reset HEAD^', cwd=tempsrcdir)
1026        result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1027        self.assertNotExists(patchfile, 'Patch file not deleted')
1028        expectedlines2 = ['FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n',
1029                         '\n']
1030        with open(bbappendfile, 'r') as f:
1031            self.assertEqual(expectedlines2, f.readlines())
1032        # Put commit back and check we can run it if layer isn't in bblayers.conf
1033        os.remove(bbappendfile)
1034        result = runCmd('git commit -a -m "Add our custom version"', cwd=tempsrcdir)
1035        result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1036        result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
1037        self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output)
1038        self.assertExists(patchfile, 'Patch file not created (with disabled layer)')
1039        with open(bbappendfile, 'r') as f:
1040            self.assertEqual(expectedlines, f.readlines())
1041        # Deleting isn't expected to work under these circumstances
1042
1043    def test_devtool_update_recipe_append_git(self):
1044        # Check preconditions
1045        testrecipe = 'mtd-utils'
1046        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1047        recipefile = bb_vars['FILE']
1048        src_uri = bb_vars['SRC_URI']
1049        self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe)
1050        for entry in src_uri.split():
1051            if entry.startswith('git://'):
1052                git_uri = entry
1053                break
1054        self._check_repo_status(os.path.dirname(recipefile), [])
1055        # First, modify a recipe
1056        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1057        tempsrcdir = os.path.join(tempdir, 'source')
1058        templayerdir = os.path.join(tempdir, 'layer')
1059        self.track_for_cleanup(tempdir)
1060        self.track_for_cleanup(self.workspacedir)
1061        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1062        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1063        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempsrcdir))
1064        # Check git repo
1065        self._check_src_repo(tempsrcdir)
1066        # Add a commit
1067        result = runCmd('echo "# Additional line" >> Makefile.am', cwd=tempsrcdir)
1068        result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir)
1069        self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (os.path.dirname(recipefile), testrecipe))
1070        # Create a temporary layer
1071        os.makedirs(os.path.join(templayerdir, 'conf'))
1072        with open(os.path.join(templayerdir, 'conf', 'layer.conf'), 'w') as f:
1073            f.write('BBPATH .= ":${LAYERDIR}"\n')
1074            f.write('BBFILES += "${LAYERDIR}/recipes-*/*/*.bbappend"\n')
1075            f.write('BBFILE_COLLECTIONS += "oeselftesttemplayer"\n')
1076            f.write('BBFILE_PATTERN_oeselftesttemplayer = "^${LAYERDIR}/"\n')
1077            f.write('BBFILE_PRIORITY_oeselftesttemplayer = "999"\n')
1078            f.write('BBFILE_PATTERN_IGNORE_EMPTY_oeselftesttemplayer = "1"\n')
1079            f.write('LAYERSERIES_COMPAT_oeselftesttemplayer = "${LAYERSERIES_COMPAT_core}"\n')
1080        self.add_command_to_tearDown('bitbake-layers remove-layer %s || true' % templayerdir)
1081        result = runCmd('bitbake-layers add-layer %s' % templayerdir, cwd=self.builddir)
1082        # Create the bbappend
1083        result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1084        self.assertNotIn('WARNING:', result.output)
1085        # Check recipe is still clean
1086        self._check_repo_status(os.path.dirname(recipefile), [])
1087        # Check bbappend was created
1088        splitpath = os.path.dirname(recipefile).split(os.sep)
1089        appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1])
1090        bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir)
1091        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
1092
1093        # Check bbappend contents
1094        result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
1095        expectedlines = set(['SRCREV = "%s"\n' % result.output,
1096                             '\n',
1097                             'SRC_URI = "%s"\n' % git_uri,
1098                             '\n'])
1099        with open(bbappendfile, 'r') as f:
1100            self.assertEqual(expectedlines, set(f.readlines()))
1101
1102        # Check we can run it again and bbappend isn't modified
1103        result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1104        with open(bbappendfile, 'r') as f:
1105            self.assertEqual(expectedlines, set(f.readlines()))
1106        # Drop new commit and check SRCREV changes
1107        result = runCmd('git reset HEAD^', cwd=tempsrcdir)
1108        result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1109        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
1110        result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
1111        expectedlines = set(['SRCREV = "%s"\n' % result.output,
1112                             '\n',
1113                             'SRC_URI = "%s"\n' % git_uri,
1114                             '\n'])
1115        with open(bbappendfile, 'r') as f:
1116            self.assertEqual(expectedlines, set(f.readlines()))
1117        # Put commit back and check we can run it if layer isn't in bblayers.conf
1118        os.remove(bbappendfile)
1119        result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir)
1120        result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
1121        result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
1122        self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output)
1123        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
1124        result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
1125        expectedlines = set(['SRCREV = "%s"\n' % result.output,
1126                             '\n',
1127                             'SRC_URI = "%s"\n' % git_uri,
1128                             '\n'])
1129        with open(bbappendfile, 'r') as f:
1130            self.assertEqual(expectedlines, set(f.readlines()))
1131        # Deleting isn't expected to work under these circumstances
1132
1133    def test_devtool_update_recipe_local_files(self):
1134        """Check that local source files are copied over instead of patched"""
1135        testrecipe = 'makedevs'
1136        recipefile = get_bb_var('FILE', testrecipe)
1137        # Setup srctree for modifying the recipe
1138        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1139        self.track_for_cleanup(tempdir)
1140        self.track_for_cleanup(self.workspacedir)
1141        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1142        # (don't bother with cleaning the recipe on teardown, we won't be
1143        # building it)
1144        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1145        # Check git repo
1146        self._check_src_repo(tempdir)
1147        # Try building just to ensure we haven't broken that
1148        bitbake("%s" % testrecipe)
1149        # Edit / commit local source
1150        runCmd('echo "/* Foobar */" >> oe-local-files/makedevs.c', cwd=tempdir)
1151        runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir)
1152        runCmd('echo "Bar" > new-file', cwd=tempdir)
1153        runCmd('git add new-file', cwd=tempdir)
1154        runCmd('git commit -m "Add new file"', cwd=tempdir)
1155        self.add_command_to_tearDown('cd %s; git clean -fd .; git checkout .' %
1156                                     os.path.dirname(recipefile))
1157        runCmd('devtool update-recipe %s' % testrecipe)
1158        expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1159                           (' M', '.*/makedevs/makedevs.c$'),
1160                           ('??', '.*/makedevs/new-local$'),
1161                           ('??', '.*/makedevs/0001-Add-new-file.patch$')]
1162        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1163
1164    def test_devtool_update_recipe_local_files_2(self):
1165        """Check local source files support when oe-local-files is in Git"""
1166        testrecipe = 'devtool-test-local'
1167        recipefile = get_bb_var('FILE', testrecipe)
1168        recipedir = os.path.dirname(recipefile)
1169        result = runCmd('git status --porcelain .', cwd=recipedir)
1170        if result.output.strip():
1171            self.fail('Recipe directory for %s contains uncommitted changes' % testrecipe)
1172        # Setup srctree for modifying the recipe
1173        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1174        self.track_for_cleanup(tempdir)
1175        self.track_for_cleanup(self.workspacedir)
1176        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1177        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1178        # Check git repo
1179        self._check_src_repo(tempdir)
1180        # Add oe-local-files to Git
1181        runCmd('rm oe-local-files/.gitignore', cwd=tempdir)
1182        runCmd('git add oe-local-files', cwd=tempdir)
1183        runCmd('git commit -m "Add local sources"', cwd=tempdir)
1184        # Edit / commit local sources
1185        runCmd('echo "# Foobar" >> oe-local-files/file1', cwd=tempdir)
1186        runCmd('git commit -am "Edit existing file"', cwd=tempdir)
1187        runCmd('git rm oe-local-files/file2', cwd=tempdir)
1188        runCmd('git commit -m"Remove file"', cwd=tempdir)
1189        runCmd('echo "Foo" > oe-local-files/new-local', cwd=tempdir)
1190        runCmd('git add oe-local-files/new-local', cwd=tempdir)
1191        runCmd('git commit -m "Add new local file"', cwd=tempdir)
1192        runCmd('echo "Gar" > new-file', cwd=tempdir)
1193        runCmd('git add new-file', cwd=tempdir)
1194        runCmd('git commit -m "Add new file"', cwd=tempdir)
1195        self.add_command_to_tearDown('cd %s; git clean -fd .; git checkout .' %
1196                                     os.path.dirname(recipefile))
1197        # Checkout unmodified file to working copy -> devtool should still pick
1198        # the modified version from HEAD
1199        runCmd('git checkout HEAD^ -- oe-local-files/file1', cwd=tempdir)
1200        runCmd('devtool update-recipe %s' % testrecipe)
1201        expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1202                           (' M', '.*/file1$'),
1203                           (' D', '.*/file2$'),
1204                           ('??', '.*/new-local$'),
1205                           ('??', '.*/0001-Add-new-file.patch$')]
1206        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1207
1208    def test_devtool_update_recipe_with_gitignore(self):
1209        # First, modify the recipe
1210        testrecipe = 'devtool-test-ignored'
1211        bb_vars = get_bb_vars(['FILE'], testrecipe)
1212        recipefile = bb_vars['FILE']
1213        patchfile = os.path.join(os.path.dirname(recipefile), testrecipe, testrecipe + '.patch')
1214        newpatchfile = os.path.join(os.path.dirname(recipefile), testrecipe, testrecipe + '.patch.expected')
1215        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1216        self.track_for_cleanup(tempdir)
1217        self.track_for_cleanup(self.workspacedir)
1218        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1219        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1220        result = runCmd('devtool modify %s' % testrecipe)
1221        self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1222        result = runCmd('devtool finish --force-patch-refresh %s meta-selftest' % testrecipe)
1223        # Check recipe got changed as expected
1224        with open(newpatchfile, 'r') as f:
1225            desiredlines = f.readlines()
1226        with open(patchfile, 'r') as f:
1227            newlines = f.readlines()
1228        # Ignore the initial lines, because oe-selftest creates own meta-selftest repo
1229        # which changes the metadata subject which is added into the patch, but keep
1230        # .patch.expected as it is in case someone runs devtool finish --force-patch-refresh
1231        # devtool-test-ignored manually, then it should generate exactly the same .patch file
1232        self.assertEqual(desiredlines[5:], newlines[5:])
1233
1234    def test_devtool_update_recipe_long_filename(self):
1235        # First, modify the recipe
1236        testrecipe = 'devtool-test-long-filename'
1237        bb_vars = get_bb_vars(['FILE'], testrecipe)
1238        recipefile = bb_vars['FILE']
1239        patchfilename = '0001-I-ll-patch-you-only-if-devtool-lets-me-to-do-it-corr.patch'
1240        patchfile = os.path.join(os.path.dirname(recipefile), testrecipe, patchfilename)
1241        newpatchfile = os.path.join(os.path.dirname(recipefile), testrecipe, patchfilename + '.expected')
1242        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1243        self.track_for_cleanup(tempdir)
1244        self.track_for_cleanup(self.workspacedir)
1245        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1246        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1247        result = runCmd('devtool modify %s' % testrecipe)
1248        self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1249        result = runCmd('devtool finish --force-patch-refresh %s meta-selftest' % testrecipe)
1250        # Check recipe got changed as expected
1251        with open(newpatchfile, 'r') as f:
1252            desiredlines = f.readlines()
1253        with open(patchfile, 'r') as f:
1254            newlines = f.readlines()
1255        # Ignore the initial lines, because oe-selftest creates own meta-selftest repo
1256        # which changes the metadata subject which is added into the patch, but keep
1257        # .patch.expected as it is in case someone runs devtool finish --force-patch-refresh
1258        # devtool-test-ignored manually, then it should generate exactly the same .patch file
1259        self.assertEqual(desiredlines[5:], newlines[5:])
1260
1261    def test_devtool_update_recipe_local_files_3(self):
1262        # First, modify the recipe
1263        testrecipe = 'devtool-test-localonly'
1264        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1265        recipefile = bb_vars['FILE']
1266        src_uri = bb_vars['SRC_URI']
1267        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1268        self.track_for_cleanup(tempdir)
1269        self.track_for_cleanup(self.workspacedir)
1270        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1271        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1272        result = runCmd('devtool modify %s' % testrecipe)
1273        # Modify one file
1274        runCmd('echo "Another line" >> file2', cwd=os.path.join(self.workspacedir, 'sources', testrecipe, 'oe-local-files'))
1275        self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1276        result = runCmd('devtool update-recipe %s' % testrecipe)
1277        expected_status = [(' M', '.*/%s/file2$' % testrecipe)]
1278        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1279
1280    def test_devtool_update_recipe_local_patch_gz(self):
1281        # First, modify the recipe
1282        testrecipe = 'devtool-test-patch-gz'
1283        if get_bb_var('DISTRO') == 'poky-tiny':
1284            self.skipTest("The DISTRO 'poky-tiny' does not provide the dependencies needed by %s" % testrecipe)
1285        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1286        recipefile = bb_vars['FILE']
1287        src_uri = bb_vars['SRC_URI']
1288        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1289        self.track_for_cleanup(tempdir)
1290        self.track_for_cleanup(self.workspacedir)
1291        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1292        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1293        result = runCmd('devtool modify %s' % testrecipe)
1294        # Modify one file
1295        srctree = os.path.join(self.workspacedir, 'sources', testrecipe)
1296        runCmd('echo "Another line" >> README', cwd=srctree)
1297        runCmd('git commit -a --amend --no-edit', cwd=srctree)
1298        self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1299        result = runCmd('devtool update-recipe %s' % testrecipe)
1300        expected_status = [(' M', '.*/%s/readme.patch.gz$' % testrecipe)]
1301        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1302        patch_gz = os.path.join(os.path.dirname(recipefile), testrecipe, 'readme.patch.gz')
1303        result = runCmd('file %s' % patch_gz)
1304        if 'gzip compressed data' not in result.output:
1305            self.fail('New patch file is not gzipped - file reports:\n%s' % result.output)
1306
1307    def test_devtool_update_recipe_local_files_subdir(self):
1308        # Try devtool update-recipe on a recipe that has a file with subdir= set in
1309        # SRC_URI such that it overwrites a file that was in an archive that
1310        # was also in SRC_URI
1311        # First, modify the recipe
1312        testrecipe = 'devtool-test-subdir'
1313        bb_vars = get_bb_vars(['FILE', 'SRC_URI'], testrecipe)
1314        recipefile = bb_vars['FILE']
1315        src_uri = bb_vars['SRC_URI']
1316        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1317        self.track_for_cleanup(tempdir)
1318        self.track_for_cleanup(self.workspacedir)
1319        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1320        # (don't bother with cleaning the recipe on teardown, we won't be building it)
1321        result = runCmd('devtool modify %s' % testrecipe)
1322        testfile = os.path.join(self.workspacedir, 'sources', testrecipe, 'testfile')
1323        self.assertExists(testfile, 'Extracted source could not be found')
1324        with open(testfile, 'r') as f:
1325            contents = f.read().rstrip()
1326        self.assertEqual(contents, 'Modified version', 'File has apparently not been overwritten as it should have been')
1327        # Test devtool update-recipe without modifying any files
1328        self.add_command_to_tearDown('cd %s; rm %s/*; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile)))
1329        result = runCmd('devtool update-recipe %s' % testrecipe)
1330        expected_status = []
1331        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1332
1333    def test_devtool_finish_modify_git_subdir(self):
1334        # Check preconditions
1335        testrecipe = 'dos2unix'
1336        bb_vars = get_bb_vars(['SRC_URI', 'S', 'WORKDIR', 'FILE'], testrecipe)
1337        self.assertIn('git://', bb_vars['SRC_URI'], 'This test expects the %s recipe to be a git recipe' % testrecipe)
1338        workdir_git = '%s/git/' % bb_vars['WORKDIR']
1339        if not bb_vars['S'].startswith(workdir_git):
1340            self.fail('This test expects the %s recipe to be building from a subdirectory of the git repo' % testrecipe)
1341        subdir = bb_vars['S'].split(workdir_git, 1)[1]
1342        # Clean up anything in the workdir/sysroot/sstate cache
1343        bitbake('%s -c cleansstate' % testrecipe)
1344        # Try modifying a recipe
1345        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1346        self.track_for_cleanup(tempdir)
1347        self.track_for_cleanup(self.workspacedir)
1348        self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
1349        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1350        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1351        testsrcfile = os.path.join(tempdir, subdir, 'dos2unix.c')
1352        self.assertExists(testsrcfile, 'Extracted source could not be found')
1353        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output)
1354        self.assertNotExists(os.path.join(tempdir, subdir, '.git'), 'Subdirectory has been initialised as a git repo')
1355        # Check git repo
1356        self._check_src_repo(tempdir)
1357        # Modify file
1358        runCmd("sed -i '1s:^:/* Add a comment */\\n:' %s" % testsrcfile)
1359        result = runCmd('git commit -a -m "Add a comment"', cwd=tempdir)
1360        # Now try updating original recipe
1361        recipefile = bb_vars['FILE']
1362        recipedir = os.path.dirname(recipefile)
1363        self.add_command_to_tearDown('cd %s; rm -f %s/*.patch; git checkout .' % (recipedir, testrecipe))
1364        result = runCmd('devtool update-recipe %s' % testrecipe)
1365        expected_status = [(' M', '.*/%s$' % os.path.basename(recipefile)),
1366                           ('??', '.*/%s/%s/$' % (testrecipe, testrecipe))]
1367        self._check_repo_status(os.path.dirname(recipefile), expected_status)
1368        result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile))
1369        removelines = ['SRC_URI = "git://.*"']
1370        addlines = [
1371            'SRC_URI = "git://.* \\\\',
1372            'file://0001-Add-a-comment.patch;patchdir=.. \\\\',
1373            '"'
1374        ]
1375        self._check_diff(result.output, addlines, removelines)
1376        # Put things back so we can run devtool finish on a different layer
1377        runCmd('cd %s; rm -f %s/*.patch; git checkout .' % (recipedir, testrecipe))
1378        # Run devtool finish
1379        res = re.search('recipes-.*', recipedir)
1380        self.assertTrue(res, 'Unable to find recipe subdirectory')
1381        recipesubdir = res[0]
1382        self.add_command_to_tearDown('rm -rf %s' % os.path.join(self.testlayer_path, recipesubdir))
1383        result = runCmd('devtool finish %s meta-selftest' % testrecipe)
1384        # Check bbappend file contents
1385        appendfn = os.path.join(self.testlayer_path, recipesubdir, '%s_%%.bbappend' % testrecipe)
1386        with open(appendfn, 'r') as f:
1387            appendlines = f.readlines()
1388        expected_appendlines = [
1389            'FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"\n',
1390            '\n',
1391            'SRC_URI += "file://0001-Add-a-comment.patch;patchdir=.."\n',
1392            '\n'
1393        ]
1394        self.assertEqual(appendlines, expected_appendlines)
1395        self.assertExists(os.path.join(os.path.dirname(appendfn), testrecipe, '0001-Add-a-comment.patch'))
1396        # Try building
1397        bitbake('%s -c patch' % testrecipe)
1398
1399
1400class DevtoolExtractTests(DevtoolBase):
1401
1402    def test_devtool_extract(self):
1403        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1404        # Try devtool extract
1405        self.track_for_cleanup(tempdir)
1406        self.track_for_cleanup(self.workspacedir)
1407        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1408        result = runCmd('devtool extract matchbox-terminal %s' % tempdir)
1409        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
1410        self._check_src_repo(tempdir)
1411
1412    def test_devtool_extract_virtual(self):
1413        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1414        # Try devtool extract
1415        self.track_for_cleanup(tempdir)
1416        self.track_for_cleanup(self.workspacedir)
1417        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1418        result = runCmd('devtool extract virtual/make %s' % tempdir)
1419        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
1420        self._check_src_repo(tempdir)
1421
1422    def test_devtool_reset_all(self):
1423        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1424        self.track_for_cleanup(tempdir)
1425        self.track_for_cleanup(self.workspacedir)
1426        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1427        testrecipe1 = 'mdadm'
1428        testrecipe2 = 'cronie'
1429        result = runCmd('devtool modify -x %s %s' % (testrecipe1, os.path.join(tempdir, testrecipe1)))
1430        result = runCmd('devtool modify -x %s %s' % (testrecipe2, os.path.join(tempdir, testrecipe2)))
1431        result = runCmd('devtool build %s' % testrecipe1)
1432        result = runCmd('devtool build %s' % testrecipe2)
1433        stampprefix1 = get_bb_var('STAMP', testrecipe1)
1434        self.assertTrue(stampprefix1, 'Unable to get STAMP value for recipe %s' % testrecipe1)
1435        stampprefix2 = get_bb_var('STAMP', testrecipe2)
1436        self.assertTrue(stampprefix2, 'Unable to get STAMP value for recipe %s' % testrecipe2)
1437        result = runCmd('devtool reset -a')
1438        self.assertIn(testrecipe1, result.output)
1439        self.assertIn(testrecipe2, result.output)
1440        result = runCmd('devtool status')
1441        self.assertNotIn(testrecipe1, result.output)
1442        self.assertNotIn(testrecipe2, result.output)
1443        matches1 = glob.glob(stampprefix1 + '*')
1444        self.assertFalse(matches1, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe1)
1445        matches2 = glob.glob(stampprefix2 + '*')
1446        self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2)
1447
1448    @OETestTag("runqemu")
1449    def test_devtool_deploy_target(self):
1450        # NOTE: Whilst this test would seemingly be better placed as a runtime test,
1451        # unfortunately the runtime tests run under bitbake and you can't run
1452        # devtool within bitbake (since devtool needs to run bitbake itself).
1453        # Additionally we are testing build-time functionality as well, so
1454        # really this has to be done as an oe-selftest test.
1455        #
1456        # Check preconditions
1457        machine = get_bb_var('MACHINE')
1458        if not machine.startswith('qemu'):
1459            self.skipTest('This test only works with qemu machines')
1460        if not os.path.exists('/etc/runqemu-nosudo'):
1461            self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test')
1462        result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ip tuntap show', ignore_status=True)
1463        if result.status != 0:
1464            result = runCmd('PATH="$PATH:/sbin:/usr/sbin" ifconfig -a', ignore_status=True)
1465            if result.status != 0:
1466                self.skipTest('Failed to determine if tap devices exist with ifconfig or ip: %s' % result.output)
1467        for line in result.output.splitlines():
1468            if line.startswith('tap'):
1469                break
1470        else:
1471            self.skipTest('No tap devices found - you must set up tap devices with scripts/runqemu-gen-tapdevs before running this test')
1472        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1473        # Definitions
1474        testrecipe = 'mdadm'
1475        testfile = '/sbin/mdadm'
1476        testimage = 'oe-selftest-image'
1477        testcommand = '/sbin/mdadm --help'
1478        # Build an image to run
1479        bitbake("%s qemu-native qemu-helper-native" % testimage)
1480        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
1481        self.add_command_to_tearDown('bitbake -c clean %s' % testimage)
1482        self.add_command_to_tearDown('rm -f %s/%s*' % (deploy_dir_image, testimage))
1483        # Clean recipe so the first deploy will fail
1484        bitbake("%s -c clean" % testrecipe)
1485        # Try devtool modify
1486        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1487        self.track_for_cleanup(tempdir)
1488        self.track_for_cleanup(self.workspacedir)
1489        self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
1490        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1491        result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
1492        # Test that deploy-target at this point fails (properly)
1493        result = runCmd('devtool deploy-target -n %s root@localhost' % testrecipe, ignore_status=True)
1494        self.assertNotEqual(result.output, 0, 'devtool deploy-target should have failed, output: %s' % result.output)
1495        self.assertNotIn(result.output, 'Traceback', 'devtool deploy-target should have failed with a proper error not a traceback, output: %s' % result.output)
1496        result = runCmd('devtool build %s' % testrecipe)
1497        # First try a dry-run of deploy-target
1498        result = runCmd('devtool deploy-target -n %s root@localhost' % testrecipe)
1499        self.assertIn('  %s' % testfile, result.output)
1500        # Boot the image
1501        with runqemu(testimage) as qemu:
1502            # Now really test deploy-target
1503            result = runCmd('devtool deploy-target -c %s root@%s' % (testrecipe, qemu.ip))
1504            # Run a test command to see if it was installed properly
1505            sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
1506            result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
1507            # Check if it deployed all of the files with the right ownership/perms
1508            # First look on the host - need to do this under pseudo to get the correct ownership/perms
1509            bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD'], testrecipe)
1510            installdir = bb_vars['D']
1511            fakerootenv = bb_vars['FAKEROOTENV']
1512            fakerootcmd = bb_vars['FAKEROOTCMD']
1513            result = runCmd('%s %s find . -type f -exec ls -l {} \\;' % (fakerootenv, fakerootcmd), cwd=installdir)
1514            filelist1 = self._process_ls_output(result.output)
1515
1516            # Now look on the target
1517            tempdir2 = tempfile.mkdtemp(prefix='devtoolqa')
1518            self.track_for_cleanup(tempdir2)
1519            tmpfilelist = os.path.join(tempdir2, 'files.txt')
1520            with open(tmpfilelist, 'w') as f:
1521                for line in filelist1:
1522                    splitline = line.split()
1523                    f.write(splitline[-1] + '\n')
1524            result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip))
1525            filelist2 = self._process_ls_output(result.output)
1526            filelist1.sort(key=lambda item: item.split()[-1])
1527            filelist2.sort(key=lambda item: item.split()[-1])
1528            self.assertEqual(filelist1, filelist2)
1529            # Test undeploy-target
1530            result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip))
1531            result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
1532            self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
1533
1534    def test_devtool_build_image(self):
1535        """Test devtool build-image plugin"""
1536        # Check preconditions
1537        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1538        image = 'core-image-minimal'
1539        self.track_for_cleanup(self.workspacedir)
1540        self.add_command_to_tearDown('bitbake -c clean %s' % image)
1541        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1542        bitbake('%s -c clean' % image)
1543        # Add target and native recipes to workspace
1544        recipes = ['mdadm', 'parted-native']
1545        for recipe in recipes:
1546            tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1547            self.track_for_cleanup(tempdir)
1548            self.add_command_to_tearDown('bitbake -c clean %s' % recipe)
1549            runCmd('devtool modify %s -x %s' % (recipe, tempdir))
1550        # Try to build image
1551        result = runCmd('devtool build-image %s' % image)
1552        self.assertNotEqual(result, 0, 'devtool build-image failed')
1553        # Check if image contains expected packages
1554        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
1555        image_link_name = get_bb_var('IMAGE_LINK_NAME', image)
1556        reqpkgs = [item for item in recipes if not item.endswith('-native')]
1557        with open(os.path.join(deploy_dir_image, image_link_name + '.manifest'), 'r') as f:
1558            for line in f:
1559                splitval = line.split()
1560                if splitval:
1561                    pkg = splitval[0]
1562                    if pkg in reqpkgs:
1563                        reqpkgs.remove(pkg)
1564        if reqpkgs:
1565            self.fail('The following packages were not present in the image as expected: %s' % ', '.join(reqpkgs))
1566
1567class DevtoolUpgradeTests(DevtoolBase):
1568
1569    def setUp(self):
1570        super().setUp()
1571        try:
1572            runCmd("git config --global user.name")
1573            runCmd("git config --global user.email")
1574        except:
1575            self.skip("Git user.name and user.email must be set")
1576
1577    def test_devtool_upgrade(self):
1578        # Check preconditions
1579        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1580        self.track_for_cleanup(self.workspacedir)
1581        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1582        # Check parameters
1583        result = runCmd('devtool upgrade -h')
1584        for param in 'recipename srctree --version -V --branch -b --keep-temp --no-patch'.split():
1585            self.assertIn(param, result.output)
1586        # For the moment, we are using a real recipe.
1587        recipe = 'devtool-upgrade-test1'
1588        version = '1.6.0'
1589        oldrecipefile = get_bb_var('FILE', recipe)
1590        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1591        self.track_for_cleanup(tempdir)
1592        # Check that recipe is not already under devtool control
1593        result = runCmd('devtool status')
1594        self.assertNotIn(recipe, result.output)
1595        # Check upgrade. Code does not check if new PV is older or newer that current PV, so, it may be that
1596        # we are downgrading instead of upgrading.
1597        result = runCmd('devtool upgrade %s %s -V %s' % (recipe, tempdir, version))
1598        # Check if srctree at least is populated
1599        self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, version))
1600        # Check new recipe subdirectory is present
1601        self.assertExists(os.path.join(self.workspacedir, 'recipes', recipe, '%s-%s' % (recipe, version)), 'Recipe folder should exist')
1602        # Check new recipe file is present
1603        newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, '%s_%s.bb' % (recipe, version))
1604        self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
1605        # Check devtool status and make sure recipe is present
1606        result = runCmd('devtool status')
1607        self.assertIn(recipe, result.output)
1608        self.assertIn(tempdir, result.output)
1609        # Check recipe got changed as expected
1610        with open(oldrecipefile + '.upgraded', 'r') as f:
1611            desiredlines = f.readlines()
1612        with open(newrecipefile, 'r') as f:
1613            newlines = f.readlines()
1614        self.assertEqual(desiredlines, newlines)
1615        # Check devtool reset recipe
1616        result = runCmd('devtool reset %s -n' % recipe)
1617        result = runCmd('devtool status')
1618        self.assertNotIn(recipe, result.output)
1619        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
1620
1621    def test_devtool_upgrade_git(self):
1622        # Check preconditions
1623        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1624        self.track_for_cleanup(self.workspacedir)
1625        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1626        recipe = 'devtool-upgrade-test2'
1627        commit = '6cc6077a36fe2648a5f993fe7c16c9632f946517'
1628        oldrecipefile = get_bb_var('FILE', recipe)
1629        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1630        self.track_for_cleanup(tempdir)
1631        # Check that recipe is not already under devtool control
1632        result = runCmd('devtool status')
1633        self.assertNotIn(recipe, result.output)
1634        # Check upgrade
1635        result = runCmd('devtool upgrade %s %s -S %s' % (recipe, tempdir, commit))
1636        # Check if srctree at least is populated
1637        self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, commit))
1638        # Check new recipe file is present
1639        newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldrecipefile))
1640        self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
1641        # Check devtool status and make sure recipe is present
1642        result = runCmd('devtool status')
1643        self.assertIn(recipe, result.output)
1644        self.assertIn(tempdir, result.output)
1645        # Check recipe got changed as expected
1646        with open(oldrecipefile + '.upgraded', 'r') as f:
1647            desiredlines = f.readlines()
1648        with open(newrecipefile, 'r') as f:
1649            newlines = f.readlines()
1650        self.assertEqual(desiredlines, newlines)
1651        # Check devtool reset recipe
1652        result = runCmd('devtool reset %s -n' % recipe)
1653        result = runCmd('devtool status')
1654        self.assertNotIn(recipe, result.output)
1655        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
1656
1657    def test_devtool_layer_plugins(self):
1658        """Test that devtool can use plugins from other layers.
1659
1660        This test executes the selftest-reverse command from meta-selftest."""
1661
1662        self.track_for_cleanup(self.workspacedir)
1663        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1664
1665        s = "Microsoft Made No Profit From Anyone's Zunes Yo"
1666        result = runCmd("devtool --quiet selftest-reverse \"%s\"" % s)
1667        self.assertEqual(result.output, s[::-1])
1668
1669    def _copy_file_with_cleanup(self, srcfile, basedstdir, *paths):
1670        dstdir = basedstdir
1671        self.assertExists(dstdir)
1672        for p in paths:
1673            dstdir = os.path.join(dstdir, p)
1674            if not os.path.exists(dstdir):
1675                os.makedirs(dstdir)
1676                if p == "lib":
1677                    # Can race with other tests
1678                    self.add_command_to_tearDown('rmdir --ignore-fail-on-non-empty %s' % dstdir)
1679                else:
1680                    self.track_for_cleanup(dstdir)
1681        dstfile = os.path.join(dstdir, os.path.basename(srcfile))
1682        if srcfile != dstfile:
1683            shutil.copy(srcfile, dstfile)
1684            self.track_for_cleanup(dstfile)
1685
1686    def test_devtool_load_plugin(self):
1687        """Test that devtool loads only the first found plugin in BBPATH."""
1688
1689        self.track_for_cleanup(self.workspacedir)
1690        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1691
1692        devtool = runCmd("which devtool")
1693        fromname = runCmd("devtool --quiet pluginfile")
1694        srcfile = fromname.output
1695        bbpath = get_bb_var('BBPATH')
1696        searchpath = bbpath.split(':') + [os.path.dirname(devtool.output)]
1697        plugincontent = []
1698        with open(srcfile) as fh:
1699            plugincontent = fh.readlines()
1700        try:
1701            self.assertIn('meta-selftest', srcfile, 'wrong bbpath plugin found')
1702            for path in searchpath:
1703                self._copy_file_with_cleanup(srcfile, path, 'lib', 'devtool')
1704            result = runCmd("devtool --quiet count")
1705            self.assertEqual(result.output, '1')
1706            result = runCmd("devtool --quiet multiloaded")
1707            self.assertEqual(result.output, "no")
1708            for path in searchpath:
1709                result = runCmd("devtool --quiet bbdir")
1710                self.assertEqual(result.output, path)
1711                os.unlink(os.path.join(result.output, 'lib', 'devtool', 'bbpath.py'))
1712        finally:
1713            with open(srcfile, 'w') as fh:
1714                fh.writelines(plugincontent)
1715
1716    def _setup_test_devtool_finish_upgrade(self):
1717        # Check preconditions
1718        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1719        self.track_for_cleanup(self.workspacedir)
1720        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1721        # Use a "real" recipe from meta-selftest
1722        recipe = 'devtool-upgrade-test1'
1723        oldversion = '1.5.3'
1724        newversion = '1.6.0'
1725        oldrecipefile = get_bb_var('FILE', recipe)
1726        recipedir = os.path.dirname(oldrecipefile)
1727        result = runCmd('git status --porcelain .', cwd=recipedir)
1728        if result.output.strip():
1729            self.fail('Recipe directory for %s contains uncommitted changes' % recipe)
1730        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1731        self.track_for_cleanup(tempdir)
1732        # Check that recipe is not already under devtool control
1733        result = runCmd('devtool status')
1734        self.assertNotIn(recipe, result.output)
1735        # Do the upgrade
1736        result = runCmd('devtool upgrade %s %s -V %s' % (recipe, tempdir, newversion))
1737        # Check devtool status and make sure recipe is present
1738        result = runCmd('devtool status')
1739        self.assertIn(recipe, result.output)
1740        self.assertIn(tempdir, result.output)
1741        # Make a change to the source
1742        result = runCmd('sed -i \'/^#include "pv.h"/a \\/* Here is a new comment *\\/\' src/pv/number.c', cwd=tempdir)
1743        result = runCmd('git status --porcelain', cwd=tempdir)
1744        self.assertIn('M src/pv/number.c', result.output)
1745        result = runCmd('git commit src/pv/number.c -m "Add a comment to the code"', cwd=tempdir)
1746        # Check if patch is there
1747        recipedir = os.path.dirname(oldrecipefile)
1748        olddir = os.path.join(recipedir, recipe + '-' + oldversion)
1749        patchfn = '0001-Add-a-note-line-to-the-quick-reference.patch'
1750        backportedpatchfn = 'backported.patch'
1751        self.assertExists(os.path.join(olddir, patchfn), 'Original patch file does not exist')
1752        self.assertExists(os.path.join(olddir, backportedpatchfn), 'Backported patch file does not exist')
1753        return recipe, oldrecipefile, recipedir, olddir, newversion, patchfn, backportedpatchfn
1754
1755    def test_devtool_finish_upgrade_origlayer(self):
1756        recipe, oldrecipefile, recipedir, olddir, newversion, patchfn, backportedpatchfn = self._setup_test_devtool_finish_upgrade()
1757        # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
1758        self.assertIn('/meta-selftest/', recipedir)
1759        # Try finish to the original layer
1760        self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir))
1761        result = runCmd('devtool finish %s meta-selftest' % recipe)
1762        result = runCmd('devtool status')
1763        self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
1764        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
1765        self.assertNotExists(oldrecipefile, 'Old recipe file should have been deleted but wasn\'t')
1766        self.assertNotExists(os.path.join(olddir, patchfn), 'Old patch file should have been deleted but wasn\'t')
1767        self.assertNotExists(os.path.join(olddir, backportedpatchfn), 'Old backported patch file should have been deleted but wasn\'t')
1768        newrecipefile = os.path.join(recipedir, '%s_%s.bb' % (recipe, newversion))
1769        newdir = os.path.join(recipedir, recipe + '-' + newversion)
1770        self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t')
1771        self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t')
1772        self.assertNotExists(os.path.join(newdir, backportedpatchfn), 'Backported patch file should not have been copied into new directory but was')
1773        self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t')
1774        with open(newrecipefile, 'r') as f:
1775            newcontent = f.read()
1776        self.assertNotIn(backportedpatchfn, newcontent, "Backported patch should have been removed from the recipe but wasn't")
1777        self.assertIn(patchfn, newcontent, "Old patch should have not been removed from the recipe but was")
1778        self.assertIn("0002-Add-a-comment-to-the-code.patch", newcontent, "New patch should have been added to the recipe but wasn't")
1779        self.assertIn("http://www.ivarch.com/programs/sources/pv-${PV}.tar.gz", newcontent, "New recipe no longer has upstream source in SRC_URI")
1780
1781
1782    def test_devtool_finish_upgrade_otherlayer(self):
1783        recipe, oldrecipefile, recipedir, olddir, newversion, patchfn, backportedpatchfn = self._setup_test_devtool_finish_upgrade()
1784        # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
1785        self.assertIn('/meta-selftest/', recipedir)
1786        # Try finish to a different layer - should create a bbappend
1787        # This cleanup isn't strictly necessary but do it anyway just in case it goes wrong and writes to here
1788        self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir))
1789        oe_core_dir = os.path.join(get_bb_var('COREBASE'), 'meta')
1790        newrecipedir = os.path.join(oe_core_dir, 'recipes-test', 'devtool')
1791        newrecipefile = os.path.join(newrecipedir, '%s_%s.bb' % (recipe, newversion))
1792        self.track_for_cleanup(newrecipedir)
1793        result = runCmd('devtool finish %s oe-core' % recipe)
1794        result = runCmd('devtool status')
1795        self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
1796        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
1797        self.assertExists(oldrecipefile, 'Old recipe file should not have been deleted')
1798        self.assertExists(os.path.join(olddir, patchfn), 'Old patch file should not have been deleted')
1799        self.assertExists(os.path.join(olddir, backportedpatchfn), 'Old backported patch file should not have been deleted')
1800        newdir = os.path.join(newrecipedir, recipe + '-' + newversion)
1801        self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t')
1802        self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t')
1803        self.assertNotExists(os.path.join(newdir, backportedpatchfn), 'Backported patch file should not have been copied into new directory but was')
1804        self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t')
1805        with open(newrecipefile, 'r') as f:
1806            newcontent = f.read()
1807        self.assertNotIn(backportedpatchfn, newcontent, "Backported patch should have been removed from the recipe but wasn't")
1808        self.assertIn(patchfn, newcontent, "Old patch should have not been removed from the recipe but was")
1809        self.assertIn("0002-Add-a-comment-to-the-code.patch", newcontent, "New patch should have been added to the recipe but wasn't")
1810        self.assertIn("http://www.ivarch.com/programs/sources/pv-${PV}.tar.gz", newcontent, "New recipe no longer has upstream source in SRC_URI")
1811
1812    def _setup_test_devtool_finish_modify(self):
1813        # Check preconditions
1814        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1815        # Try modifying a recipe
1816        self.track_for_cleanup(self.workspacedir)
1817        recipe = 'mdadm'
1818        oldrecipefile = get_bb_var('FILE', recipe)
1819        recipedir = os.path.dirname(oldrecipefile)
1820        result = runCmd('git status --porcelain .', cwd=recipedir)
1821        if result.output.strip():
1822            self.fail('Recipe directory for %s contains uncommitted changes' % recipe)
1823        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1824        self.track_for_cleanup(tempdir)
1825        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1826        result = runCmd('devtool modify %s %s' % (recipe, tempdir))
1827        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
1828        # Test devtool status
1829        result = runCmd('devtool status')
1830        self.assertIn(recipe, result.output)
1831        self.assertIn(tempdir, result.output)
1832        # Make a change to the source
1833        result = runCmd('sed -i \'/^#include "mdadm.h"/a \\/* Here is a new comment *\\/\' maps.c', cwd=tempdir)
1834        result = runCmd('git status --porcelain', cwd=tempdir)
1835        self.assertIn('M maps.c', result.output)
1836        result = runCmd('git commit maps.c -m "Add a comment to the code"', cwd=tempdir)
1837        for entry in os.listdir(recipedir):
1838            filesdir = os.path.join(recipedir, entry)
1839            if os.path.isdir(filesdir):
1840                break
1841        else:
1842            self.fail('Unable to find recipe files directory for %s' % recipe)
1843        return recipe, oldrecipefile, recipedir, filesdir
1844
1845    def test_devtool_finish_modify_origlayer(self):
1846        recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify()
1847        # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
1848        self.assertIn('/meta/', recipedir)
1849        # Try finish to the original layer
1850        self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir))
1851        result = runCmd('devtool finish %s meta' % recipe)
1852        result = runCmd('devtool status')
1853        self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
1854        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
1855        expected_status = [(' M', '.*/%s$' % os.path.basename(oldrecipefile)),
1856                           ('??', '.*/.*-Add-a-comment-to-the-code.patch$')]
1857        self._check_repo_status(recipedir, expected_status)
1858
1859    def test_devtool_finish_modify_otherlayer(self):
1860        recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify()
1861        # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
1862        self.assertIn('/meta/', recipedir)
1863        relpth = os.path.relpath(recipedir, os.path.join(get_bb_var('COREBASE'), 'meta'))
1864        appenddir = os.path.join(get_test_layer(), relpth)
1865        self.track_for_cleanup(appenddir)
1866        # Try finish to the original layer
1867        self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir))
1868        result = runCmd('devtool finish %s meta-selftest' % recipe)
1869        result = runCmd('devtool status')
1870        self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
1871        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
1872        result = runCmd('git status --porcelain .', cwd=recipedir)
1873        if result.output.strip():
1874            self.fail('Recipe directory for %s contains the following unexpected changes after finish:\n%s' % (recipe, result.output.strip()))
1875        recipefn = os.path.splitext(os.path.basename(oldrecipefile))[0]
1876        recipefn = recipefn.split('_')[0] + '_%'
1877        appendfile = os.path.join(appenddir, recipefn + '.bbappend')
1878        self.assertExists(appendfile, 'bbappend %s should have been created but wasn\'t' % appendfile)
1879        newdir = os.path.join(appenddir, recipe)
1880        files = os.listdir(newdir)
1881        foundpatch = None
1882        for fn in files:
1883            if fnmatch.fnmatch(fn, '*-Add-a-comment-to-the-code.patch'):
1884                foundpatch = fn
1885        if not foundpatch:
1886            self.fail('No patch file created next to bbappend')
1887        files.remove(foundpatch)
1888        if files:
1889            self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files))
1890
1891    def test_devtool_rename(self):
1892        # Check preconditions
1893        self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
1894        self.track_for_cleanup(self.workspacedir)
1895        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1896
1897        # First run devtool add
1898        # We already have this recipe in OE-Core, but that doesn't matter
1899        recipename = 'i2c-tools'
1900        recipever = '3.1.2'
1901        recipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, recipever))
1902        url = 'http://downloads.yoctoproject.org/mirror/sources/i2c-tools-%s.tar.bz2' % recipever
1903        def add_recipe():
1904            result = runCmd('devtool add %s' % url)
1905            self.assertExists(recipefile, 'Expected recipe file not created')
1906            self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory not created')
1907            checkvars = {}
1908            checkvars['S'] = None
1909            checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
1910            self._test_recipe_contents(recipefile, checkvars, [])
1911        add_recipe()
1912        # Now rename it - change both name and version
1913        newrecipename = 'mynewrecipe'
1914        newrecipever = '456'
1915        newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, newrecipever))
1916        result = runCmd('devtool rename %s %s -V %s' % (recipename, newrecipename, newrecipever))
1917        self.assertExists(newrecipefile, 'Recipe file not renamed')
1918        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
1919        newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename)
1920        self.assertExists(newsrctree, 'Source directory not renamed')
1921        checkvars = {}
1922        checkvars['S'] = '${WORKDIR}/%s-%s' % (recipename, recipever)
1923        checkvars['SRC_URI'] = url
1924        self._test_recipe_contents(newrecipefile, checkvars, [])
1925        # Try again - change just name this time
1926        result = runCmd('devtool reset -n %s' % newrecipename)
1927        shutil.rmtree(newsrctree)
1928        add_recipe()
1929        newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, recipever))
1930        result = runCmd('devtool rename %s %s' % (recipename, newrecipename))
1931        self.assertExists(newrecipefile, 'Recipe file not renamed')
1932        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
1933        self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed')
1934        checkvars = {}
1935        checkvars['S'] = '${WORKDIR}/%s-${PV}' % recipename
1936        checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
1937        self._test_recipe_contents(newrecipefile, checkvars, [])
1938        # Try again - change just version this time
1939        result = runCmd('devtool reset -n %s' % newrecipename)
1940        shutil.rmtree(newsrctree)
1941        add_recipe()
1942        newrecipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, newrecipever))
1943        result = runCmd('devtool rename %s -V %s' % (recipename, newrecipever))
1944        self.assertExists(newrecipefile, 'Recipe file not renamed')
1945        self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists')
1946        checkvars = {}
1947        checkvars['S'] = '${WORKDIR}/${BPN}-%s' % recipever
1948        checkvars['SRC_URI'] = url
1949        self._test_recipe_contents(newrecipefile, checkvars, [])
1950
1951    def test_devtool_virtual_kernel_modify(self):
1952        """
1953        Summary:        The purpose of this test case is to verify that
1954                        devtool modify works correctly when building
1955                        the kernel.
1956        Dependencies:   NA
1957        Steps:          1. Build kernel with bitbake.
1958                        2. Save the config file generated.
1959                        3. Clean the environment.
1960                        4. Use `devtool modify virtual/kernel` to validate following:
1961                           4.1 The source is checked out correctly.
1962                           4.2 The resulting configuration is the same as
1963                               what was get on step 2.
1964                           4.3 The Kernel can be build correctly.
1965                           4.4 Changes made on the source are reflected on the
1966                               subsequent builds.
1967                           4.5 Changes on the configuration are reflected on the
1968                               subsequent builds
1969         Expected:       devtool modify is able to checkout the source of the kernel
1970                         and modification to the source and configurations are reflected
1971                         when building the kernel.
1972        """
1973        kernel_provider = self.td['PREFERRED_PROVIDER_virtual/kernel']
1974
1975        # Clean up the environment
1976        bitbake('%s -c clean' % kernel_provider)
1977        tempdir = tempfile.mkdtemp(prefix='devtoolqa')
1978        tempdir_cfg = tempfile.mkdtemp(prefix='config_qa')
1979        self.track_for_cleanup(tempdir)
1980        self.track_for_cleanup(tempdir_cfg)
1981        self.track_for_cleanup(self.workspacedir)
1982        self.add_command_to_tearDown('bitbake -c clean %s' % kernel_provider)
1983        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
1984        #Step 1
1985        #Here is just generated the config file instead of all the kernel to optimize the
1986        #time of executing this test case.
1987        bitbake('%s -c configure' % kernel_provider)
1988        bbconfig = os.path.join(get_bb_var('B', kernel_provider),'.config')
1989        #Step 2
1990        runCmd('cp %s %s' % (bbconfig, tempdir_cfg))
1991        self.assertExists(os.path.join(tempdir_cfg, '.config'), 'Could not copy .config file from kernel')
1992
1993        tmpconfig = os.path.join(tempdir_cfg, '.config')
1994        #Step 3
1995        bitbake('%s -c clean' % kernel_provider)
1996        #Step 4.1
1997        runCmd('devtool modify virtual/kernel -x %s' % tempdir)
1998        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
1999        #Step 4.2
2000        configfile = os.path.join(tempdir,'.config')
2001        runCmd('diff %s %s' % (tmpconfig, configfile))
2002
2003        #Step 4.3
2004        #NOTE: virtual/kernel is mapped to kernel_provider
2005        runCmd('devtool build %s' % kernel_provider)
2006        kernelfile = os.path.join(get_bb_var('KBUILD_OUTPUT', kernel_provider), 'vmlinux')
2007        self.assertExists(kernelfile, 'Kernel was not build correctly')
2008
2009        #Modify the kernel source
2010        modfile = os.path.join(tempdir, 'init/version.c')
2011        runCmd("sed -i 's/Linux/LiNuX/g' %s" % (modfile))
2012
2013        #Modify the configuration
2014        codeconfigfile = os.path.join(tempdir, '.config.new')
2015        modconfopt = "CONFIG_SG_POOL=n"
2016        runCmd("sed -i 's/CONFIG_SG_POOL=y/%s/' %s" % (modconfopt, codeconfigfile))
2017
2018        #Build again kernel with devtool
2019        runCmd('devtool build %s' % kernel_provider)
2020
2021        #Step 4.4
2022        runCmd("grep '%s' %s" % ('LiNuX', kernelfile))
2023
2024        #Step 4.5
2025        runCmd("grep %s %s" % (modconfopt, codeconfigfile))
2026