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