1#
2# Copyright (c) 2015, Intel Corporation.
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# AUTHORS
7# Ed Bartosh <ed.bartosh@linux.intel.com>
8
9"""Test cases for wic."""
10
11import os
12import sys
13import unittest
14import hashlib
15
16from glob import glob
17from shutil import rmtree, copy
18from tempfile import NamedTemporaryFile
19
20from oeqa.selftest.case import OESelftestTestCase
21from oeqa.core.decorator import OETestTag
22from oeqa.core.decorator.data import skipIfNotArch
23from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
24
25
26def extract_files(debugfs_output):
27    """
28    extract file names from the output of debugfs -R 'ls -p',
29    which looks like this:
30
31     /2/040755/0/0/.//\n
32     /2/040755/0/0/..//\n
33     /11/040700/0/0/lost+found^M//\n
34     /12/040755/1002/1002/run//\n
35     /13/040755/1002/1002/sys//\n
36     /14/040755/1002/1002/bin//\n
37     /80/040755/1002/1002/var//\n
38     /92/040755/1002/1002/tmp//\n
39    """
40    # NOTE the occasional ^M in file names
41    return [line.split('/')[5].strip() for line in \
42            debugfs_output.strip().split('/\n')]
43
44def files_own_by_root(debugfs_output):
45    for line in debugfs_output.strip().split('/\n'):
46        if line.split('/')[3:5] != ['0', '0']:
47            print(debugfs_output)
48            return False
49    return True
50
51class WicTestCase(OESelftestTestCase):
52    """Wic test class."""
53
54    image_is_ready = False
55    wicenv_cache = {}
56
57    def setUpLocal(self):
58        """This code is executed before each test method."""
59        self.resultdir = os.path.join(self.builddir, "wic-tmp")
60        super(WicTestCase, self).setUpLocal()
61
62        # Do this here instead of in setUpClass as the base setUp does some
63        # clean up which can result in the native tools built earlier in
64        # setUpClass being unavailable.
65        if not WicTestCase.image_is_ready:
66            if self.td['USE_NLS'] != 'yes':
67                self.skipTest('wic-tools needs USE_NLS=yes')
68
69            bitbake('wic-tools core-image-minimal core-image-minimal-mtdutils')
70            WicTestCase.image_is_ready = True
71        rmtree(self.resultdir, ignore_errors=True)
72
73    def tearDownLocal(self):
74        """Remove resultdir as it may contain images."""
75        rmtree(self.resultdir, ignore_errors=True)
76        super(WicTestCase, self).tearDownLocal()
77
78    def _get_image_env_path(self, image):
79        """Generate and obtain the path to <image>.env"""
80        if image not in WicTestCase.wicenv_cache:
81            bitbake('%s -c do_rootfs_wicenv' % image)
82            stdir = get_bb_var('STAGING_DIR', image)
83            machine = self.td["MACHINE"]
84            WicTestCase.wicenv_cache[image] = os.path.join(stdir, machine, 'imgdata')
85        return WicTestCase.wicenv_cache[image]
86
87class CLITests(OESelftestTestCase):
88    def test_version(self):
89        """Test wic --version"""
90        runCmd('wic --version')
91
92    def test_help(self):
93        """Test wic --help and wic -h"""
94        runCmd('wic --help')
95        runCmd('wic -h')
96
97    def test_createhelp(self):
98        """Test wic create --help"""
99        runCmd('wic create --help')
100
101    def test_listhelp(self):
102        """Test wic list --help"""
103        runCmd('wic list --help')
104
105    def test_help_create(self):
106        """Test wic help create"""
107        runCmd('wic help create')
108
109    def test_help_list(self):
110        """Test wic help list"""
111        runCmd('wic help list')
112
113    def test_help_overview(self):
114        """Test wic help overview"""
115        runCmd('wic help overview')
116
117    def test_help_plugins(self):
118        """Test wic help plugins"""
119        runCmd('wic help plugins')
120
121    def test_help_kickstart(self):
122        """Test wic help kickstart"""
123        runCmd('wic help kickstart')
124
125    def test_list_images(self):
126        """Test wic list images"""
127        runCmd('wic list images')
128
129    def test_list_source_plugins(self):
130        """Test wic list source-plugins"""
131        runCmd('wic list source-plugins')
132
133    def test_listed_images_help(self):
134        """Test wic listed images help"""
135        output = runCmd('wic list images').output
136        imagelist = [line.split()[0] for line in output.splitlines()]
137        for image in imagelist:
138            runCmd('wic list %s help' % image)
139
140    def test_unsupported_subcommand(self):
141        """Test unsupported subcommand"""
142        self.assertNotEqual(0, runCmd('wic unsupported', ignore_status=True).status)
143
144    def test_no_command(self):
145        """Test wic without command"""
146        self.assertEqual(1, runCmd('wic', ignore_status=True).status)
147
148class Wic(WicTestCase):
149    def test_build_image_name(self):
150        """Test wic create wictestdisk --image-name=core-image-minimal"""
151        cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir
152        runCmd(cmd)
153        self.assertEqual(1, len(glob(os.path.join (self.resultdir, "wictestdisk-*.direct"))))
154
155    @skipIfNotArch(['i586', 'i686', 'x86_64'])
156    def test_gpt_image(self):
157        """Test creation of core-image-minimal with gpt table and UUID boot"""
158        cmd = "wic create directdisk-gpt --image-name core-image-minimal -o %s" % self.resultdir
159        runCmd(cmd)
160        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-*.direct"))))
161
162    @skipIfNotArch(['i586', 'i686', 'x86_64'])
163    def test_iso_image(self):
164        """Test creation of hybrid iso image with legacy and EFI boot"""
165        config = 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\
166                 'MACHINE_FEATURES:append = " efi"\n'\
167                 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
168        self.append_config(config)
169        bitbake('core-image-minimal core-image-minimal-initramfs')
170        self.remove_config(config)
171        cmd = "wic create mkhybridiso --image-name core-image-minimal -o %s" % self.resultdir
172        runCmd(cmd)
173        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "HYBRID_ISO_IMG-*.direct"))))
174        self.assertEqual(1, len(glob(os.path.join (self.resultdir, "HYBRID_ISO_IMG-*.iso"))))
175
176    @skipIfNotArch(['i586', 'i686', 'x86_64'])
177    def test_qemux86_directdisk(self):
178        """Test creation of qemux-86-directdisk image"""
179        cmd = "wic create qemux86-directdisk -e core-image-minimal -o %s" % self.resultdir
180        runCmd(cmd)
181        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "qemux86-directdisk-*direct"))))
182
183    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
184    def test_mkefidisk(self):
185        """Test creation of mkefidisk image"""
186        cmd = "wic create mkefidisk -e core-image-minimal -o %s" % self.resultdir
187        runCmd(cmd)
188        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "mkefidisk-*direct"))))
189
190    @skipIfNotArch(['i586', 'i686', 'x86_64'])
191    def test_bootloader_config(self):
192        """Test creation of directdisk-bootloader-config image"""
193        config = 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
194        self.append_config(config)
195        bitbake('core-image-minimal')
196        self.remove_config(config)
197        cmd = "wic create directdisk-bootloader-config -e core-image-minimal -o %s" % self.resultdir
198        runCmd(cmd)
199        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-bootloader-config-*direct"))))
200
201    @skipIfNotArch(['i586', 'i686', 'x86_64', 'aarch64'])
202    def test_systemd_bootdisk(self):
203        """Test creation of systemd-bootdisk image"""
204        config = 'MACHINE_FEATURES:append = " efi"\n'
205        self.append_config(config)
206        bitbake('core-image-minimal')
207        self.remove_config(config)
208        cmd = "wic create systemd-bootdisk -e core-image-minimal -o %s" % self.resultdir
209        runCmd(cmd)
210        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "systemd-bootdisk-*direct"))))
211
212    def test_efi_bootpart(self):
213        """Test creation of efi-bootpart image"""
214        cmd = "wic create mkefidisk -e core-image-minimal -o %s" % self.resultdir
215        kimgtype = get_bb_var('KERNEL_IMAGETYPE', 'core-image-minimal')
216        self.append_config('IMAGE_EFI_BOOT_FILES = "%s;kernel"\n' % kimgtype)
217        runCmd(cmd)
218        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
219        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
220        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
221        self.assertIn("kernel",result.output)
222
223    def test_sdimage_bootpart(self):
224        """Test creation of sdimage-bootpart image"""
225        cmd = "wic create sdimage-bootpart -e core-image-minimal -o %s" % self.resultdir
226        kimgtype = get_bb_var('KERNEL_IMAGETYPE', 'core-image-minimal')
227        self.write_config('IMAGE_BOOT_FILES = "%s"\n' % kimgtype)
228        runCmd(cmd)
229        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "sdimage-bootpart-*direct"))))
230
231    # TODO this doesn't have to be x86-specific
232    @skipIfNotArch(['i586', 'i686', 'x86_64'])
233    def test_default_output_dir(self):
234        """Test default output location"""
235        for fname in glob("directdisk-*.direct"):
236            os.remove(fname)
237        config = 'DEPENDS:pn-core-image-minimal += "syslinux"\n'
238        self.append_config(config)
239        bitbake('core-image-minimal')
240        self.remove_config(config)
241        cmd = "wic create directdisk -e core-image-minimal"
242        runCmd(cmd)
243        self.assertEqual(1, len(glob("directdisk-*.direct")))
244
245    @skipIfNotArch(['i586', 'i686', 'x86_64'])
246    def test_build_artifacts(self):
247        """Test wic create directdisk providing all artifacts."""
248        bb_vars = get_bb_vars(['STAGING_DATADIR', 'RECIPE_SYSROOT_NATIVE'],
249                              'wic-tools')
250        bb_vars.update(get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_ROOTFS'],
251                                   'core-image-minimal'))
252        bbvars = {key.lower(): value for key, value in bb_vars.items()}
253        bbvars['resultdir'] = self.resultdir
254        runCmd("wic create directdisk "
255                        "-b %(staging_datadir)s "
256                        "-k %(deploy_dir_image)s "
257                        "-n %(recipe_sysroot_native)s "
258                        "-r %(image_rootfs)s "
259                        "-o %(resultdir)s" % bbvars)
260        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-*.direct"))))
261
262    def test_compress_gzip(self):
263        """Test compressing an image with gzip"""
264        runCmd("wic create wictestdisk "
265                                   "--image-name core-image-minimal "
266                                   "-c gzip -o %s" % self.resultdir)
267        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.gz"))))
268
269    def test_compress_bzip2(self):
270        """Test compressing an image with bzip2"""
271        runCmd("wic create wictestdisk "
272                                   "--image-name=core-image-minimal "
273                                   "-c bzip2 -o %s" % self.resultdir)
274        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.bz2"))))
275
276    def test_compress_xz(self):
277        """Test compressing an image with xz"""
278        runCmd("wic create wictestdisk "
279                                   "--image-name=core-image-minimal "
280                                   "--compress-with=xz -o %s" % self.resultdir)
281        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct.xz"))))
282
283    def test_wrong_compressor(self):
284        """Test how wic breaks if wrong compressor is provided"""
285        self.assertEqual(2, runCmd("wic create wictestdisk "
286                                   "--image-name=core-image-minimal "
287                                   "-c wrong -o %s" % self.resultdir,
288                                   ignore_status=True).status)
289
290    def test_debug_short(self):
291        """Test -D option"""
292        runCmd("wic create wictestdisk "
293                                   "--image-name=core-image-minimal "
294                                   "-D -o %s" % self.resultdir)
295        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
296        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "tmp.wic*"))))
297
298    def test_debug_long(self):
299        """Test --debug option"""
300        runCmd("wic create wictestdisk "
301                                   "--image-name=core-image-minimal "
302                                   "--debug -o %s" % self.resultdir)
303        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
304        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "tmp.wic*"))))
305
306    def test_skip_build_check_short(self):
307        """Test -s option"""
308        runCmd("wic create wictestdisk "
309                                   "--image-name=core-image-minimal "
310                                   "-s -o %s" % self.resultdir)
311        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
312
313    def test_skip_build_check_long(self):
314        """Test --skip-build-check option"""
315        runCmd("wic create wictestdisk "
316                                   "--image-name=core-image-minimal "
317                                   "--skip-build-check "
318                                   "--outdir %s" % self.resultdir)
319        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
320
321    def test_build_rootfs_short(self):
322        """Test -f option"""
323        runCmd("wic create wictestdisk "
324                                   "--image-name=core-image-minimal "
325                                   "-f -o %s" % self.resultdir)
326        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
327
328    def test_build_rootfs_long(self):
329        """Test --build-rootfs option"""
330        runCmd("wic create wictestdisk "
331                                   "--image-name=core-image-minimal "
332                                   "--build-rootfs "
333                                   "--outdir %s" % self.resultdir)
334        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))))
335
336    # TODO this doesn't have to be x86-specific
337    @skipIfNotArch(['i586', 'i686', 'x86_64'])
338    def test_rootfs_indirect_recipes(self):
339        """Test usage of rootfs plugin with rootfs recipes"""
340        runCmd("wic create directdisk-multi-rootfs "
341                        "--image-name=core-image-minimal "
342                        "--rootfs rootfs1=core-image-minimal "
343                        "--rootfs rootfs2=core-image-minimal "
344                        "--outdir %s" % self.resultdir)
345        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "directdisk-multi-rootfs*.direct"))))
346
347    # TODO this doesn't have to be x86-specific
348    @skipIfNotArch(['i586', 'i686', 'x86_64'])
349    def test_rootfs_artifacts(self):
350        """Test usage of rootfs plugin with rootfs paths"""
351        bb_vars = get_bb_vars(['STAGING_DATADIR', 'RECIPE_SYSROOT_NATIVE'],
352                              'wic-tools')
353        bb_vars.update(get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_ROOTFS'],
354                                   'core-image-minimal'))
355        bbvars = {key.lower(): value for key, value in bb_vars.items()}
356        bbvars['wks'] = "directdisk-multi-rootfs"
357        bbvars['resultdir'] = self.resultdir
358        runCmd("wic create %(wks)s "
359                        "--bootimg-dir=%(staging_datadir)s "
360                        "--kernel-dir=%(deploy_dir_image)s "
361                        "--native-sysroot=%(recipe_sysroot_native)s "
362                        "--rootfs-dir rootfs1=%(image_rootfs)s "
363                        "--rootfs-dir rootfs2=%(image_rootfs)s "
364                        "--outdir %(resultdir)s" % bbvars)
365        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "%(wks)s-*.direct" % bbvars))))
366
367    def test_exclude_path(self):
368        """Test --exclude-path wks option."""
369
370        oldpath = os.environ['PATH']
371        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
372
373        try:
374            wks_file = 'temp.wks'
375            with open(wks_file, 'w') as wks:
376                rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal')
377                wks.write("""
378part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path usr
379part /usr --source rootfs --ondisk mmcblk0 --fstype=ext4 --rootfs-dir %s/usr
380part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --rootfs-dir %s/usr"""
381                          % (rootfs_dir, rootfs_dir))
382            runCmd("wic create %s -e core-image-minimal -o %s" \
383                                       % (wks_file, self.resultdir))
384
385            os.remove(wks_file)
386            wicout = glob(os.path.join(self.resultdir, "%s-*direct" % 'temp'))
387            self.assertEqual(1, len(wicout))
388
389            wicimg = wicout[0]
390
391            # verify partition size with wic
392            res = runCmd("parted -m %s unit b p 2>/dev/null" % wicimg)
393
394            # parse parted output which looks like this:
395            # BYT;\n
396            # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
397            # 1:0.00MiB:200MiB:200MiB:ext4::;\n
398            partlns = res.output.splitlines()[2:]
399
400            self.assertEqual(3, len(partlns))
401
402            for part in [1, 2, 3]:
403                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
404                partln = partlns[part-1].split(":")
405                self.assertEqual(7, len(partln))
406                start = int(partln[1].rstrip("B")) / 512
407                length = int(partln[3].rstrip("B")) / 512
408                runCmd("dd if=%s of=%s skip=%d count=%d" %
409                                           (wicimg, part_file, start, length))
410
411            # Test partition 1, should contain the normal root directories, except
412            # /usr.
413            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \
414                             os.path.join(self.resultdir, "selftest_img.part1"))
415            files = extract_files(res.output)
416            self.assertIn("etc", files)
417            self.assertNotIn("usr", files)
418
419            # Partition 2, should contain common directories for /usr, not root
420            # directories.
421            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \
422                             os.path.join(self.resultdir, "selftest_img.part2"))
423            files = extract_files(res.output)
424            self.assertNotIn("etc", files)
425            self.assertNotIn("usr", files)
426            self.assertIn("share", files)
427
428            # Partition 3, should contain the same as partition 2, including the bin
429            # directory, but not the files inside it.
430            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \
431                             os.path.join(self.resultdir, "selftest_img.part3"))
432            files = extract_files(res.output)
433            self.assertNotIn("etc", files)
434            self.assertNotIn("usr", files)
435            self.assertIn("share", files)
436            self.assertIn("bin", files)
437            res = runCmd("debugfs -R 'ls -p bin' %s 2>/dev/null" % \
438                             os.path.join(self.resultdir, "selftest_img.part3"))
439            files = extract_files(res.output)
440            self.assertIn(".", files)
441            self.assertIn("..", files)
442            self.assertEqual(2, len(files))
443
444            for part in [1, 2, 3]:
445                part_file = os.path.join(self.resultdir, "selftest_img.part%d" % part)
446                os.remove(part_file)
447
448        finally:
449            os.environ['PATH'] = oldpath
450
451    def test_include_path(self):
452        """Test --include-path wks option."""
453
454        oldpath = os.environ['PATH']
455        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
456
457        try:
458            include_path = os.path.join(self.resultdir, 'test-include')
459            os.makedirs(include_path)
460            with open(os.path.join(include_path, 'test-file'), 'w') as t:
461                t.write("test\n")
462            wks_file = os.path.join(include_path, 'temp.wks')
463            with open(wks_file, 'w') as wks:
464                rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal')
465                wks.write("""
466part /part1 --source rootfs --ondisk mmcblk0 --fstype=ext4
467part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s"""
468                          % (include_path))
469            runCmd("wic create %s -e core-image-minimal -o %s" \
470                                       % (wks_file, self.resultdir))
471
472            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
473            part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0]
474
475            # Test partition 1, should not contain 'test-file'
476            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1))
477            files = extract_files(res.output)
478            self.assertNotIn('test-file', files)
479            self.assertEqual(True, files_own_by_root(res.output))
480
481            # Test partition 2, should contain 'test-file'
482            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part2))
483            files = extract_files(res.output)
484            self.assertIn('test-file', files)
485            self.assertEqual(True, files_own_by_root(res.output))
486
487        finally:
488            os.environ['PATH'] = oldpath
489
490    def test_include_path_embeded(self):
491        """Test --include-path wks option."""
492
493        oldpath = os.environ['PATH']
494        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
495
496        try:
497            include_path = os.path.join(self.resultdir, 'test-include')
498            os.makedirs(include_path)
499            with open(os.path.join(include_path, 'test-file'), 'w') as t:
500                t.write("test\n")
501            wks_file = os.path.join(include_path, 'temp.wks')
502            with open(wks_file, 'w') as wks:
503                wks.write("""
504part / --source rootfs  --fstype=ext4 --include-path %s --include-path core-image-minimal-mtdutils export/"""
505                          % (include_path))
506            runCmd("wic create %s -e core-image-minimal -o %s" \
507                                       % (wks_file, self.resultdir))
508
509            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
510
511            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1))
512            files = extract_files(res.output)
513            self.assertIn('test-file', files)
514            self.assertEqual(True, files_own_by_root(res.output))
515
516            res = runCmd("debugfs -R 'ls -p /export/etc/' %s 2>/dev/null" % (part1))
517            files = extract_files(res.output)
518            self.assertIn('passwd', files)
519            self.assertEqual(True, files_own_by_root(res.output))
520
521        finally:
522            os.environ['PATH'] = oldpath
523
524    def test_include_path_errors(self):
525        """Test --include-path wks option error handling."""
526        wks_file = 'temp.wks'
527
528        # Absolute argument.
529        with open(wks_file, 'w') as wks:
530            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils /export")
531        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
532                                      % (wks_file, self.resultdir), ignore_status=True).status)
533        os.remove(wks_file)
534
535        # Argument pointing to parent directory.
536        with open(wks_file, 'w') as wks:
537            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils ././..")
538        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
539                                      % (wks_file, self.resultdir), ignore_status=True).status)
540        os.remove(wks_file)
541
542        # 3 Argument pointing to parent directory.
543        with open(wks_file, 'w') as wks:
544            wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils export/ dummy")
545        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
546                                      % (wks_file, self.resultdir), ignore_status=True).status)
547        os.remove(wks_file)
548
549    def test_exclude_path_errors(self):
550        """Test --exclude-path wks option error handling."""
551        wks_file = 'temp.wks'
552
553        # Absolute argument.
554        with open(wks_file, 'w') as wks:
555            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path /usr")
556        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
557                                      % (wks_file, self.resultdir), ignore_status=True).status)
558        os.remove(wks_file)
559
560        # Argument pointing to parent directory.
561        with open(wks_file, 'w') as wks:
562            wks.write("part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path ././..")
563        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
564                                      % (wks_file, self.resultdir), ignore_status=True).status)
565        os.remove(wks_file)
566
567    def test_permissions(self):
568        """Test permissions are respected"""
569
570        # prepare wicenv and rootfs
571        bitbake('core-image-minimal core-image-minimal-mtdutils -c do_rootfs_wicenv')
572
573        oldpath = os.environ['PATH']
574        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
575
576        t_normal = """
577part / --source rootfs --fstype=ext4
578"""
579        t_exclude = """
580part / --source rootfs --fstype=ext4 --exclude-path=home
581"""
582        t_multi = """
583part / --source rootfs --ondisk sda --fstype=ext4
584part /export --source rootfs --rootfs=core-image-minimal-mtdutils --fstype=ext4
585"""
586        t_change = """
587part / --source rootfs --ondisk sda --fstype=ext4 --exclude-path=etc/   
588part /etc --source rootfs --fstype=ext4 --change-directory=etc
589"""
590        tests = [t_normal, t_exclude, t_multi, t_change]
591
592        try:
593            for test in tests:
594                include_path = os.path.join(self.resultdir, 'test-include')
595                os.makedirs(include_path)
596                wks_file = os.path.join(include_path, 'temp.wks')
597                with open(wks_file, 'w') as wks:
598                    wks.write(test)
599                runCmd("wic create %s -e core-image-minimal -o %s" \
600                                       % (wks_file, self.resultdir))
601
602                for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')):
603                    res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part))
604                    self.assertEqual(True, files_own_by_root(res.output))
605
606                config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "%s"\n' % wks_file
607                self.append_config(config)
608                bitbake('core-image-minimal')
609                tmpdir = os.path.join(get_bb_var('WORKDIR', 'core-image-minimal'),'build-wic')
610
611                # check each partition for permission
612                for part in glob(os.path.join(tmpdir, 'temp-*.direct.p*')):
613                    res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part))
614                    self.assertTrue(files_own_by_root(res.output)
615                        ,msg='Files permission incorrect using wks set "%s"' % test)
616
617                # clean config and result directory for next cases
618                self.remove_config(config)
619                rmtree(self.resultdir, ignore_errors=True)
620
621        finally:
622            os.environ['PATH'] = oldpath
623
624    def test_change_directory(self):
625        """Test --change-directory wks option."""
626
627        oldpath = os.environ['PATH']
628        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
629
630        try:
631            include_path = os.path.join(self.resultdir, 'test-include')
632            os.makedirs(include_path)
633            wks_file = os.path.join(include_path, 'temp.wks')
634            with open(wks_file, 'w') as wks:
635                wks.write("part /etc --source rootfs --fstype=ext4 --change-directory=etc")
636            runCmd("wic create %s -e core-image-minimal -o %s" \
637                                       % (wks_file, self.resultdir))
638
639            part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0]
640
641            res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1))
642            files = extract_files(res.output)
643            self.assertIn('passwd', files)
644
645        finally:
646            os.environ['PATH'] = oldpath
647
648    def test_change_directory_errors(self):
649        """Test --change-directory wks option error handling."""
650        wks_file = 'temp.wks'
651
652        # Absolute argument.
653        with open(wks_file, 'w') as wks:
654            wks.write("part / --source rootfs --fstype=ext4 --change-directory /usr")
655        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
656                                      % (wks_file, self.resultdir), ignore_status=True).status)
657        os.remove(wks_file)
658
659        # Argument pointing to parent directory.
660        with open(wks_file, 'w') as wks:
661            wks.write("part / --source rootfs --fstype=ext4 --change-directory ././..")
662        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
663                                      % (wks_file, self.resultdir), ignore_status=True).status)
664        os.remove(wks_file)
665
666    def test_no_fstab_update(self):
667        """Test --no-fstab-update wks option."""
668
669        oldpath = os.environ['PATH']
670        os.environ['PATH'] = get_bb_var("PATH", "wic-tools")
671
672        # Get stock fstab from base-files recipe
673        bitbake('base-files -c do_install')
674        bf_fstab = os.path.join(get_bb_var('D', 'base-files'), 'etc', 'fstab')
675        self.assertEqual(True, os.path.exists(bf_fstab))
676        bf_fstab_md5sum = runCmd('md5sum %s 2>/dev/null' % bf_fstab).output.split(" ")[0]
677
678        try:
679            no_fstab_update_path = os.path.join(self.resultdir, 'test-no-fstab-update')
680            os.makedirs(no_fstab_update_path)
681            wks_file = os.path.join(no_fstab_update_path, 'temp.wks')
682            with open(wks_file, 'w') as wks:
683                wks.writelines(['part / --source rootfs --fstype=ext4 --label rootfs\n',
684                                'part /mnt/p2 --source rootfs --rootfs-dir=core-image-minimal ',
685                                '--fstype=ext4 --label p2 --no-fstab-update\n'])
686            runCmd("wic create %s -e core-image-minimal -o %s" \
687                                       % (wks_file, self.resultdir))
688
689            part_fstab_md5sum = []
690            for i in range(1, 3):
691                part = glob(os.path.join(self.resultdir, 'temp-*.direct.p') + str(i))[0]
692                part_fstab = runCmd("debugfs -R 'cat etc/fstab' %s 2>/dev/null" % (part))
693                part_fstab_md5sum.append(hashlib.md5((part_fstab.output + "\n\n").encode('utf-8')).hexdigest())
694
695            # '/etc/fstab' in partition 2 should contain the same stock fstab file
696            # as the one installed by the base-file recipe.
697            self.assertEqual(bf_fstab_md5sum, part_fstab_md5sum[1])
698
699            # '/etc/fstab' in partition 1 should contain an updated fstab file.
700            self.assertNotEqual(bf_fstab_md5sum, part_fstab_md5sum[0])
701
702        finally:
703            os.environ['PATH'] = oldpath
704
705    def test_no_fstab_update_errors(self):
706        """Test --no-fstab-update wks option error handling."""
707        wks_file = 'temp.wks'
708
709        # Absolute argument.
710        with open(wks_file, 'w') as wks:
711            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update /etc")
712        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
713                                      % (wks_file, self.resultdir), ignore_status=True).status)
714        os.remove(wks_file)
715
716        # Argument pointing to parent directory.
717        with open(wks_file, 'w') as wks:
718            wks.write("part / --source rootfs --fstype=ext4 --no-fstab-update ././..")
719        self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \
720                                      % (wks_file, self.resultdir), ignore_status=True).status)
721        os.remove(wks_file)
722
723    def test_extra_space(self):
724        """Test --extra-space wks option."""
725        extraspace = 1024**3
726        runCmd("wic create wictestdisk "
727                                   "--image-name core-image-minimal "
728                                   "--extra-space %i -o %s" % (extraspace ,self.resultdir))
729        wicout = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
730        self.assertEqual(1, len(wicout))
731        size = os.path.getsize(wicout[0])
732        self.assertTrue(size > extraspace)
733
734class Wic2(WicTestCase):
735
736    def test_bmap_short(self):
737        """Test generation of .bmap file -m option"""
738        cmd = "wic create wictestdisk -e core-image-minimal -m -o %s" % self.resultdir
739        runCmd(cmd)
740        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
741        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
742
743    def test_bmap_long(self):
744        """Test generation of .bmap file --bmap option"""
745        cmd = "wic create wictestdisk -e core-image-minimal --bmap -o %s" % self.resultdir
746        runCmd(cmd)
747        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
748        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct.bmap"))))
749
750    def test_image_env(self):
751        """Test generation of <image>.env files."""
752        image = 'core-image-minimal'
753        imgdatadir = self._get_image_env_path(image)
754
755        bb_vars = get_bb_vars(['IMAGE_BASENAME', 'WICVARS'], image)
756        basename = bb_vars['IMAGE_BASENAME']
757        self.assertEqual(basename, image)
758        path = os.path.join(imgdatadir, basename) + '.env'
759        self.assertTrue(os.path.isfile(path))
760
761        wicvars = set(bb_vars['WICVARS'].split())
762        # filter out optional variables
763        wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES',
764                                      'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE',
765                                      'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME',
766                                      'APPEND', 'IMAGE_EFI_BOOT_FILES'))
767        with open(path) as envfile:
768            content = dict(line.split("=", 1) for line in envfile)
769            # test if variables used by wic present in the .env file
770            for var in wicvars:
771                self.assertTrue(var in content, "%s is not in .env file" % var)
772                self.assertTrue(content[var])
773
774    def test_image_vars_dir_short(self):
775        """Test image vars directory selection -v option"""
776        image = 'core-image-minimal'
777        imgenvdir = self._get_image_env_path(image)
778        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
779
780        runCmd("wic create wictestdisk "
781                                   "--image-name=%s -v %s -n %s -o %s"
782                                   % (image, imgenvdir, native_sysroot,
783                                      self.resultdir))
784        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
785
786    def test_image_vars_dir_long(self):
787        """Test image vars directory selection --vars option"""
788        image = 'core-image-minimal'
789        imgenvdir = self._get_image_env_path(image)
790        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
791
792        runCmd("wic create wictestdisk "
793                                   "--image-name=%s "
794                                   "--vars %s "
795                                   "--native-sysroot %s "
796                                   "--outdir %s"
797                                   % (image, imgenvdir, native_sysroot,
798                                      self.resultdir))
799        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "wictestdisk-*direct"))))
800
801    # TODO this test could also work on aarch64
802    @skipIfNotArch(['i586', 'i686', 'x86_64'])
803    def test_wic_image_type(self):
804        """Test building wic images by bitbake"""
805        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
806                 'MACHINE_FEATURES:append = " efi"\n'
807        self.append_config(config)
808        bitbake('wic-image-minimal')
809        self.remove_config(config)
810
811        deploy_dir = get_bb_var('DEPLOY_DIR_IMAGE')
812        machine = self.td['MACHINE']
813        prefix = os.path.join(deploy_dir, 'wic-image-minimal-%s.' % machine)
814        # check if we have result image and manifests symlinks
815        # pointing to existing files
816        for suffix in ('wic', 'manifest'):
817            path = prefix + suffix
818            self.assertTrue(os.path.islink(path))
819            self.assertTrue(os.path.isfile(os.path.realpath(path)))
820
821    # TODO this should work on aarch64
822    @skipIfNotArch(['i586', 'i686', 'x86_64'])
823    @OETestTag("runqemu")
824    def test_qemu(self):
825        """Test wic-image-minimal under qemu"""
826        config = 'IMAGE_FSTYPES += "wic"\nWKS_FILE = "wic-image-minimal"\n'\
827                 'MACHINE_FEATURES:append = " efi"\n'
828        self.append_config(config)
829        bitbake('wic-image-minimal')
830        self.remove_config(config)
831
832        with runqemu('wic-image-minimal', ssh=False, runqemuparams='nographic') as qemu:
833            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' " \
834                  "-e '/dev/root /|/dev/sda2 /' -e '/dev/sda3 /media' -e '/dev/sda4 /mnt'"
835            status, output = qemu.run_serial(cmd)
836            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
837            self.assertEqual(output, '4')
838            cmd = "grep UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba /etc/fstab"
839            status, output = qemu.run_serial(cmd)
840            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
841            self.assertEqual(output, 'UUID=2c71ef06-a81d-4735-9d3a-379b69c6bdba\t/media\text4\tdefaults\t0\t0')
842
843    @skipIfNotArch(['i586', 'i686', 'x86_64'])
844    @OETestTag("runqemu")
845    def test_qemu_efi(self):
846        """Test core-image-minimal efi image under qemu"""
847        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "mkefidisk.wks"\n'
848        self.append_config(config)
849        bitbake('core-image-minimal ovmf')
850        self.remove_config(config)
851
852        with runqemu('core-image-minimal', ssh=False,
853                     runqemuparams='nographic ovmf', image_fstype='wic') as qemu:
854            cmd = "grep sda. /proc/partitions  |wc -l"
855            status, output = qemu.run_serial(cmd)
856            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
857            self.assertEqual(output, '3')
858
859    @staticmethod
860    def _make_fixed_size_wks(size):
861        """
862        Create a wks of an image with a single partition. Size of the partition is set
863        using --fixed-size flag. Returns a tuple: (path to wks file, wks image name)
864        """
865        with NamedTemporaryFile("w", suffix=".wks", delete=False) as tempf:
866            wkspath = tempf.name
867            tempf.write("part " \
868                     "--source rootfs --ondisk hda --align 4 --fixed-size %d "
869                     "--fstype=ext4\n" % size)
870
871        return wkspath
872
873    def _get_wic_partitions(self, wkspath, native_sysroot=None, ignore_status=False):
874        p = runCmd("wic create %s -e core-image-minimal -o %s" % (wkspath, self.resultdir),
875                   ignore_status=ignore_status)
876
877        if p.status:
878            return (p, None)
879
880        wksname = os.path.splitext(os.path.basename(wkspath))[0]
881
882        wicout = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
883
884        if not wicout:
885            return (p, None)
886
887        wicimg = wicout[0]
888
889        if not native_sysroot:
890            native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
891
892        # verify partition size with wic
893        res = runCmd("parted -m %s unit kib p 2>/dev/null" % wicimg,
894                     native_sysroot=native_sysroot)
895
896        # parse parted output which looks like this:
897        # BYT;\n
898        # /var/tmp/wic/build/tmpfwvjjkf_-201611101222-hda.direct:200MiB:file:512:512:msdos::;\n
899        # 1:0.00MiB:200MiB:200MiB:ext4::;\n
900        return (p, res.output.splitlines()[2:])
901
902    def test_fixed_size(self):
903        """
904        Test creation of a simple image with partition size controlled through
905        --fixed-size flag
906        """
907        wkspath = Wic2._make_fixed_size_wks(200)
908        _, partlns = self._get_wic_partitions(wkspath)
909        os.remove(wkspath)
910
911        self.assertEqual(partlns, [
912                        "1:4.00kiB:204804kiB:204800kiB:ext4::;",
913                        ])
914
915    def test_fixed_size_error(self):
916        """
917        Test creation of a simple image with partition size controlled through
918        --fixed-size flag. The size of partition is intentionally set to 1MiB
919        in order to trigger an error in wic.
920        """
921        wkspath = Wic2._make_fixed_size_wks(1)
922        p, _ = self._get_wic_partitions(wkspath, ignore_status=True)
923        os.remove(wkspath)
924
925        self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
926
927    def test_offset(self):
928        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
929
930        with NamedTemporaryFile("w", suffix=".wks") as tempf:
931            # Test that partitions are placed at the correct offsets, default KB
932            tempf.write("bootloader --ptable gpt\n" \
933                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 100M --fstype=ext4\n" \
934                        "part /bar                 --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n")
935            tempf.flush()
936
937            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
938            self.assertEqual(partlns, [
939                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
940                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
941                ])
942
943        with NamedTemporaryFile("w", suffix=".wks") as tempf:
944            # Test that partitions are placed at the correct offsets, same with explicit KB
945            tempf.write("bootloader --ptable gpt\n" \
946                        "part /    --source rootfs --ondisk hda --offset 32K     --fixed-size 100M --fstype=ext4\n" \
947                        "part /bar                 --ondisk hda --offset 102432K --fixed-size 100M --fstype=ext4\n")
948            tempf.flush()
949
950            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
951            self.assertEqual(partlns, [
952                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
953                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
954                ])
955
956        with NamedTemporaryFile("w", suffix=".wks") as tempf:
957            # Test that partitions are placed at the correct offsets using MB
958            tempf.write("bootloader --ptable gpt\n" \
959                        "part /    --source rootfs --ondisk hda --offset 32K  --fixed-size 100M --fstype=ext4\n" \
960                        "part /bar                 --ondisk hda --offset 101M --fixed-size 100M --fstype=ext4\n")
961            tempf.flush()
962
963            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
964            self.assertEqual(partlns, [
965                "1:32.0kiB:102432kiB:102400kiB:ext4:primary:;",
966                "2:103424kiB:205824kiB:102400kiB:ext4:primary:;",
967                ])
968
969        with NamedTemporaryFile("w", suffix=".wks") as tempf:
970            # Test that partitions can be placed on a 512 byte sector boundary
971            tempf.write("bootloader --ptable gpt\n" \
972                        "part /    --source rootfs --ondisk hda --offset 65s --fixed-size 99M --fstype=ext4\n" \
973                        "part /bar                 --ondisk hda --offset 102432 --fixed-size 100M --fstype=ext4\n")
974            tempf.flush()
975
976            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
977            self.assertEqual(partlns, [
978                "1:32.5kiB:101408kiB:101376kiB:ext4:primary:;",
979                "2:102432kiB:204832kiB:102400kiB:ext4:primary:;",
980                ])
981
982        with NamedTemporaryFile("w", suffix=".wks") as tempf:
983            # Test that a partition can be placed immediately after a MSDOS partition table
984            tempf.write("bootloader --ptable msdos\n" \
985                        "part /    --source rootfs --ondisk hda --offset 1s --fixed-size 100M --fstype=ext4\n")
986            tempf.flush()
987
988            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
989            self.assertEqual(partlns, [
990                "1:0.50kiB:102400kiB:102400kiB:ext4::;",
991                ])
992
993        with NamedTemporaryFile("w", suffix=".wks") as tempf:
994            # Test that image creation fails if the partitions would overlap
995            tempf.write("bootloader --ptable gpt\n" \
996                        "part /    --source rootfs --ondisk hda --offset 32     --fixed-size 100M --fstype=ext4\n" \
997                        "part /bar                 --ondisk hda --offset 102431 --fixed-size 100M --fstype=ext4\n")
998            tempf.flush()
999
1000            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1001            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1002
1003        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1004            # Test that partitions are not allowed to overlap with the booloader
1005            tempf.write("bootloader --ptable gpt\n" \
1006                        "part /    --source rootfs --ondisk hda --offset 8 --fixed-size 100M --fstype=ext4\n")
1007            tempf.flush()
1008
1009            p, _ = self._get_wic_partitions(tempf.name, ignore_status=True)
1010            self.assertNotEqual(p.status, 0, "wic exited successfully when an error was expected:\n%s" % p.output)
1011
1012    def test_extra_space(self):
1013        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
1014
1015        with NamedTemporaryFile("w", suffix=".wks") as tempf:
1016            tempf.write("bootloader --ptable gpt\n" \
1017                        "part /     --source rootfs --ondisk hda --extra-space 200M --fstype=ext4\n")
1018            tempf.flush()
1019
1020            _, partlns = self._get_wic_partitions(tempf.name, native_sysroot)
1021            self.assertEqual(len(partlns), 1)
1022            size = partlns[0].split(':')[3]
1023            self.assertRegex(size, r'^[0-9]+kiB$')
1024            size = int(size[:-3])
1025            self.assertGreaterEqual(size, 204800)
1026
1027    # TODO this test could also work on aarch64
1028    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1029    @OETestTag("runqemu")
1030    def test_rawcopy_plugin_qemu(self):
1031        """Test rawcopy plugin in qemu"""
1032        # build ext4 and then use it for a wic image
1033        config = 'IMAGE_FSTYPES = "ext4"\n'
1034        self.append_config(config)
1035        bitbake('core-image-minimal')
1036        self.remove_config(config)
1037
1038        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_rawcopy_plugin.wks.in"\n'
1039        self.append_config(config)
1040        bitbake('core-image-minimal-mtdutils')
1041        self.remove_config(config)
1042
1043        with runqemu('core-image-minimal-mtdutils', ssh=False,
1044                     runqemuparams='nographic', image_fstype='wic') as qemu:
1045            cmd = "grep sda. /proc/partitions  |wc -l"
1046            status, output = qemu.run_serial(cmd)
1047            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1048            self.assertEqual(output, '2')
1049
1050    def _rawcopy_plugin(self, fstype):
1051        """Test rawcopy plugin"""
1052        img = 'core-image-minimal'
1053        machine = self.td["MACHINE"]
1054        params = ',unpack' if fstype.endswith('.gz') else ''
1055        with NamedTemporaryFile("w", suffix=".wks") as wks:
1056            wks.write('part / --source rawcopy --sourceparams="file=%s-%s.%s%s"\n'\
1057                      % (img, machine, fstype, params))
1058            wks.flush()
1059            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1060            runCmd(cmd)
1061            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1062            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1063            self.assertEqual(1, len(out))
1064
1065    def test_rawcopy_plugin(self):
1066        self._rawcopy_plugin('ext4')
1067
1068    def test_rawcopy_plugin_unpack(self):
1069        fstype = 'ext4.gz'
1070        config = 'IMAGE_FSTYPES = "%s"\n' % fstype
1071        self.append_config(config)
1072        self.assertEqual(0, bitbake('core-image-minimal').status)
1073        self.remove_config(config)
1074        self._rawcopy_plugin(fstype)
1075
1076    def test_empty_plugin(self):
1077        """Test empty plugin"""
1078        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_empty_plugin.wks"\n'
1079        self.append_config(config)
1080        bitbake('core-image-minimal')
1081        self.remove_config(config)
1082        deploy_dir = get_bb_var('DEPLOY_DIR_IMAGE')
1083        machine = self.td['MACHINE']
1084
1085        image_path = os.path.join(deploy_dir, 'core-image-minimal-%s.wic' % machine)
1086        self.assertTrue(os.path.exists(image_path))
1087
1088        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1089
1090        # Fstype column from 'wic ls' should be empty for the second partition
1091        # as listed in test_empty_plugin.wks
1092        result = runCmd("wic ls %s -n %s | awk -F ' ' '{print $1 \" \" $5}' | grep '^2' | wc -w" % (image_path, sysroot))
1093        self.assertEqual('1', result.output)
1094
1095    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1096    @OETestTag("runqemu")
1097    def test_biosplusefi_plugin_qemu(self):
1098        """Test biosplusefi plugin in qemu"""
1099        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1100        self.append_config(config)
1101        bitbake('core-image-minimal')
1102        self.remove_config(config)
1103
1104        with runqemu('core-image-minimal', ssh=False,
1105                     runqemuparams='nographic', image_fstype='wic') as qemu:
1106            # Check that we have ONLY two /dev/sda* partitions (/boot and /)
1107            cmd = "grep sda. /proc/partitions | wc -l"
1108            status, output = qemu.run_serial(cmd)
1109            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1110            self.assertEqual(output, '2')
1111            # Check that /dev/sda1 is /boot and that either /dev/root OR /dev/sda2 is /
1112            cmd = "mount | grep '^/dev/' | cut -f1,3 -d ' ' | egrep -c -e '/dev/sda1 /boot' -e '/dev/root /|/dev/sda2 /'"
1113            status, output = qemu.run_serial(cmd)
1114            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1115            self.assertEqual(output, '2')
1116            # Check that /boot has EFI bootx64.efi (required for EFI)
1117            cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l"
1118            status, output = qemu.run_serial(cmd)
1119            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1120            self.assertEqual(output, '1')
1121            # Check that "BOOTABLE" flag is set on boot partition (required for PC-Bios)
1122            # Trailing "cat" seems to be required; otherwise run_serial() sends back echo of the input command
1123            cmd = "fdisk -l /dev/sda | grep /dev/sda1 | awk {print'$2'} | cat"
1124            status, output = qemu.run_serial(cmd)
1125            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1126            self.assertEqual(output, '*')
1127
1128    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1129    def test_biosplusefi_plugin(self):
1130        """Test biosplusefi plugin"""
1131        # Wic generation below may fail depending on the order of the unittests
1132        # This is because bootimg-pcbios (that bootimg-biosplusefi uses) generate its MBR inside STAGING_DATADIR directory
1133        #    which may or may not exists depending on what was built already
1134        # If an image hasn't been built yet, directory ${STAGING_DATADIR}/syslinux won't exists and _get_bootimg_dir()
1135        #   will raise with "Couldn't find correct bootimg_dir"
1136        # The easiest way to work-around this issue is to make sure we already built an image here, hence the bitbake call
1137        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_biosplusefi_plugin.wks"\nMACHINE_FEATURES:append = " efi"\n'
1138        self.append_config(config)
1139        bitbake('core-image-minimal')
1140        self.remove_config(config)
1141
1142        img = 'core-image-minimal'
1143        with NamedTemporaryFile("w", suffix=".wks") as wks:
1144            wks.writelines(['part /boot --active --source bootimg-biosplusefi --sourceparams="loader=grub-efi"\n',
1145                            'part / --source rootfs --fstype=ext4 --align 1024 --use-uuid\n'\
1146                            'bootloader --timeout=0 --append="console=ttyS0,115200n8"\n'])
1147            wks.flush()
1148            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1149            runCmd(cmd)
1150            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1151            out = glob(os.path.join(self.resultdir, "%s-*.direct" % wksname))
1152            self.assertEqual(1, len(out))
1153
1154    # TODO this test could also work on aarch64
1155    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1156    @OETestTag("runqemu")
1157    def test_efi_plugin_unified_kernel_image_qemu(self):
1158        """Test efi plugin's Unified Kernel Image feature in qemu"""
1159        config = 'IMAGE_FSTYPES = "wic"\n'\
1160                 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\
1161                 'WKS_FILE = "test_efi_plugin.wks"\n'\
1162                 'MACHINE_FEATURES:append = " efi"\n'
1163        self.append_config(config)
1164        bitbake('core-image-minimal core-image-minimal-initramfs ovmf')
1165        self.remove_config(config)
1166
1167        with runqemu('core-image-minimal', ssh=False,
1168                     runqemuparams='nographic ovmf', image_fstype='wic') as qemu:
1169            # Check that /boot has EFI bootx64.efi (required for EFI)
1170            cmd = "ls /boot/EFI/BOOT/bootx64.efi | wc -l"
1171            status, output = qemu.run_serial(cmd)
1172            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1173            self.assertEqual(output, '1')
1174            # Check that /boot has EFI/Linux/linux.efi (required for Unified Kernel Images auto detection)
1175            cmd = "ls /boot/EFI/Linux/linux.efi | wc -l"
1176            status, output = qemu.run_serial(cmd)
1177            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1178            self.assertEqual(output, '1')
1179            # Check that /boot doesn't have loader/entries/boot.conf (Unified Kernel Images are auto detected by the bootloader)
1180            cmd = "ls /boot/loader/entries/boot.conf 2&>/dev/null | wc -l"
1181            status, output = qemu.run_serial(cmd)
1182            self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1183            self.assertEqual(output, '0')
1184
1185    def test_fs_types(self):
1186        """Test filesystem types for empty and not empty partitions"""
1187        img = 'core-image-minimal'
1188        with NamedTemporaryFile("w", suffix=".wks") as wks:
1189            wks.writelines(['part ext2   --fstype ext2     --source rootfs\n',
1190                            'part btrfs  --fstype btrfs    --source rootfs --size 40M\n',
1191                            'part squash --fstype squashfs --source rootfs\n',
1192                            'part swap   --fstype swap --size 1M\n',
1193                            'part emptyvfat   --fstype vfat   --size 1M\n',
1194                            'part emptymsdos  --fstype msdos  --size 1M\n',
1195                            'part emptyext2   --fstype ext2   --size 1M\n',
1196                            'part emptybtrfs  --fstype btrfs  --size 150M\n'])
1197            wks.flush()
1198            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1199            runCmd(cmd)
1200            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1201            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1202            self.assertEqual(1, len(out))
1203
1204    def test_kickstart_parser(self):
1205        """Test wks parser options"""
1206        with NamedTemporaryFile("w", suffix=".wks") as wks:
1207            wks.writelines(['part / --fstype ext3 --source rootfs --system-id 0xFF '\
1208                            '--overhead-factor 1.2 --size 100k\n'])
1209            wks.flush()
1210            cmd = "wic create %s -e core-image-minimal -o %s" % (wks.name, self.resultdir)
1211            runCmd(cmd)
1212            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1213            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1214            self.assertEqual(1, len(out))
1215
1216    def test_image_bootpart_globbed(self):
1217        """Test globbed sources with image-bootpart plugin"""
1218        img = "core-image-minimal"
1219        cmd = "wic create sdimage-bootpart -e %s -o %s" % (img, self.resultdir)
1220        config = 'IMAGE_BOOT_FILES = "%s*"' % get_bb_var('KERNEL_IMAGETYPE', img)
1221        self.append_config(config)
1222        runCmd(cmd)
1223        self.remove_config(config)
1224        self.assertEqual(1, len(glob(os.path.join(self.resultdir, "sdimage-bootpart-*direct"))))
1225
1226    def test_sparse_copy(self):
1227        """Test sparse_copy with FIEMAP and SEEK_HOLE filemap APIs"""
1228        libpath = os.path.join(self.td['COREBASE'], 'scripts', 'lib', 'wic')
1229        sys.path.insert(0, libpath)
1230        from  filemap import FilemapFiemap, FilemapSeek, sparse_copy, ErrorNotSupp
1231        with NamedTemporaryFile("w", suffix=".wic-sparse") as sparse:
1232            src_name = sparse.name
1233            src_size = 1024 * 10
1234            sparse.truncate(src_size)
1235            # write one byte to the file
1236            with open(src_name, 'r+b') as sfile:
1237                sfile.seek(1024 * 4)
1238                sfile.write(b'\x00')
1239            dest = sparse.name + '.out'
1240            # copy src file to dest using different filemap APIs
1241            for api in (FilemapFiemap, FilemapSeek, None):
1242                if os.path.exists(dest):
1243                    os.unlink(dest)
1244                try:
1245                    sparse_copy(sparse.name, dest, api=api)
1246                except ErrorNotSupp:
1247                    continue # skip unsupported API
1248                dest_stat = os.stat(dest)
1249                self.assertEqual(dest_stat.st_size, src_size)
1250                # 8 blocks is 4K (physical sector size)
1251                self.assertEqual(dest_stat.st_blocks, 8)
1252            os.unlink(dest)
1253
1254    def test_mkfs_extraopts(self):
1255        """Test wks option --mkfs-extraopts for empty and not empty partitions"""
1256        img = 'core-image-minimal'
1257        with NamedTemporaryFile("w", suffix=".wks") as wks:
1258            wks.writelines(
1259                ['part ext2   --fstype ext2     --source rootfs --mkfs-extraopts "-D -F -i 8192"\n',
1260                 "part btrfs  --fstype btrfs    --source rootfs --size 40M --mkfs-extraopts='--quiet'\n",
1261                 'part squash --fstype squashfs --source rootfs --mkfs-extraopts "-no-sparse -b 4096"\n',
1262                 'part emptyvfat   --fstype vfat   --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1263                 'part emptymsdos  --fstype msdos  --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
1264                 'part emptyext2   --fstype ext2   --size 1M --mkfs-extraopts "-D -F -i 8192"\n',
1265                 'part emptybtrfs  --fstype btrfs  --size 100M --mkfs-extraopts "--mixed -K"\n'])
1266            wks.flush()
1267            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
1268            runCmd(cmd)
1269            wksname = os.path.splitext(os.path.basename(wks.name))[0]
1270            out = glob(os.path.join(self.resultdir, "%s-*direct" % wksname))
1271            self.assertEqual(1, len(out))
1272
1273    @skipIfNotArch(['i586', 'i686', 'x86_64'])
1274    @OETestTag("runqemu")
1275    def test_expand_mbr_image(self):
1276        """Test wic write --expand command for mbr image"""
1277        # build an image
1278        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "directdisk.wks"\n'
1279        self.append_config(config)
1280        bitbake('core-image-minimal')
1281
1282        # get path to the image
1283        deploy_dir = get_bb_var('DEPLOY_DIR_IMAGE')
1284        machine = self.td['MACHINE']
1285        image_path = os.path.join(deploy_dir, 'core-image-minimal-%s.wic' % machine)
1286
1287        self.remove_config(config)
1288
1289        try:
1290            # expand image to 1G
1291            new_image_path = None
1292            with NamedTemporaryFile(mode='wb', suffix='.wic.exp',
1293                                    dir=deploy_dir, delete=False) as sparse:
1294                sparse.truncate(1024 ** 3)
1295                new_image_path = sparse.name
1296
1297            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1298            cmd = "wic write -n %s --expand 1:0 %s %s" % (sysroot, image_path, new_image_path)
1299            runCmd(cmd)
1300
1301            # check if partitions are expanded
1302            orig = runCmd("wic ls %s -n %s" % (image_path, sysroot))
1303            exp = runCmd("wic ls %s -n %s" % (new_image_path, sysroot))
1304            orig_sizes = [int(line.split()[3]) for line in orig.output.split('\n')[1:]]
1305            exp_sizes = [int(line.split()[3]) for line in exp.output.split('\n')[1:]]
1306            self.assertEqual(orig_sizes[0], exp_sizes[0]) # first partition is not resized
1307            self.assertTrue(orig_sizes[1] < exp_sizes[1])
1308
1309            # Check if all free space is partitioned
1310            result = runCmd("%s/usr/sbin/sfdisk -F %s" % (sysroot, new_image_path))
1311            self.assertTrue("0 B, 0 bytes, 0 sectors" in result.output)
1312
1313            os.rename(image_path, image_path + '.bak')
1314            os.rename(new_image_path, image_path)
1315
1316            # Check if it boots in qemu
1317            with runqemu('core-image-minimal', ssh=False, runqemuparams='nographic') as qemu:
1318                cmd = "ls /etc/"
1319                status, output = qemu.run_serial('true')
1320                self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
1321        finally:
1322            if os.path.exists(new_image_path):
1323                os.unlink(new_image_path)
1324            if os.path.exists(image_path + '.bak'):
1325                os.rename(image_path + '.bak', image_path)
1326
1327class ModifyTests(WicTestCase):
1328    def test_wic_ls(self):
1329        """Test listing image content using 'wic ls'"""
1330        runCmd("wic create wictestdisk "
1331                                   "--image-name=core-image-minimal "
1332                                   "-D -o %s" % self.resultdir)
1333        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1334        self.assertEqual(1, len(images))
1335
1336        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1337
1338        # list partitions
1339        result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
1340        self.assertEqual(3, len(result.output.split('\n')))
1341
1342        # list directory content of the first partition
1343        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1344        self.assertEqual(6, len(result.output.split('\n')))
1345
1346    def test_wic_cp(self):
1347        """Test copy files and directories to the the wic image."""
1348        runCmd("wic create wictestdisk "
1349                                   "--image-name=core-image-minimal "
1350                                   "-D -o %s" % self.resultdir)
1351        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1352        self.assertEqual(1, len(images))
1353
1354        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1355
1356        # list directory content of the first partition
1357        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1358        self.assertEqual(6, len(result.output.split('\n')))
1359
1360        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1361            testfile.write("test")
1362
1363            # copy file to the partition
1364            runCmd("wic cp %s %s:1/ -n %s" % (testfile.name, images[0], sysroot))
1365
1366            # check if file is there
1367            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1368            self.assertEqual(7, len(result.output.split('\n')))
1369            self.assertTrue(os.path.basename(testfile.name) in result.output)
1370
1371            # prepare directory
1372            testdir = os.path.join(self.resultdir, 'wic-test-cp-dir')
1373            testsubdir = os.path.join(testdir, 'subdir')
1374            os.makedirs(os.path.join(testsubdir))
1375            copy(testfile.name, testdir)
1376
1377            # copy directory to the partition
1378            runCmd("wic cp %s %s:1/ -n %s" % (testdir, images[0], sysroot))
1379
1380            # check if directory is there
1381            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
1382            self.assertEqual(8, len(result.output.split('\n')))
1383            self.assertTrue(os.path.basename(testdir) in result.output)
1384
1385            # copy the file from the partition and check if it success
1386            dest = '%s-cp' % testfile.name
1387            runCmd("wic cp %s:1/%s %s -n %s" % (images[0],
1388                    os.path.basename(testfile.name), dest, sysroot))
1389            self.assertTrue(os.path.exists(dest))
1390
1391
1392    def test_wic_rm(self):
1393        """Test removing files and directories from the the wic image."""
1394        runCmd("wic create mkefidisk "
1395                                   "--image-name=core-image-minimal "
1396                                   "-D -o %s" % self.resultdir)
1397        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
1398        self.assertEqual(1, len(images))
1399
1400        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1401        # Not bulletproof but hopefully sufficient
1402        kerneltype = get_bb_var('KERNEL_IMAGETYPE', 'virtual/kernel')
1403
1404        # list directory content of the first partition
1405        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1406        self.assertIn('\n%s ' % kerneltype.upper(), result.output)
1407        self.assertIn('\nEFI          <DIR>     ', result.output)
1408
1409        # remove file. EFI partitions are case-insensitive so exercise that too
1410        runCmd("wic rm %s:1/%s -n %s" % (images[0], kerneltype.lower(), sysroot))
1411
1412        # remove directory
1413        runCmd("wic rm %s:1/efi -n %s" % (images[0], sysroot))
1414
1415        # check if they're removed
1416        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
1417        self.assertNotIn('\n%s        ' % kerneltype.upper(), result.output)
1418        self.assertNotIn('\nEFI          <DIR>     ', result.output)
1419
1420    def test_wic_ls_ext(self):
1421        """Test listing content of the ext partition using 'wic ls'"""
1422        runCmd("wic create wictestdisk "
1423                                   "--image-name=core-image-minimal "
1424                                   "-D -o %s" % self.resultdir)
1425        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1426        self.assertEqual(1, len(images))
1427
1428        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1429
1430        # list directory content of the second ext4 partition
1431        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1432        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(
1433                            set(line.split()[-1] for line in result.output.split('\n') if line)))
1434
1435    def test_wic_cp_ext(self):
1436        """Test copy files and directories to the ext partition."""
1437        runCmd("wic create wictestdisk "
1438                                   "--image-name=core-image-minimal "
1439                                   "-D -o %s" % self.resultdir)
1440        images = glob(os.path.join(self.resultdir, "wictestdisk-*.direct"))
1441        self.assertEqual(1, len(images))
1442
1443        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1444
1445        # list directory content of the ext4 partition
1446        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1447        dirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1448        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(dirs))
1449
1450        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
1451            testfile.write("test")
1452
1453            # copy file to the partition
1454            runCmd("wic cp %s %s:2/ -n %s" % (testfile.name, images[0], sysroot))
1455
1456            # check if file is there
1457            result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1458            newdirs = set(line.split()[-1] for line in result.output.split('\n') if line)
1459            self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)]))
1460
1461            # check if the file to copy is in the partition
1462            result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1463            self.assertTrue('fstab' in [line.split()[-1] for line in result.output.split('\n') if line])
1464
1465            # copy file from the partition, replace the temporary file content with it and
1466            # check for the file size to validate the copy
1467            runCmd("wic cp %s:2/etc/fstab %s -n %s" % (images[0], testfile.name, sysroot))
1468            self.assertTrue(os.stat(testfile.name).st_size > 0)
1469
1470
1471    def test_wic_rm_ext(self):
1472        """Test removing files from the ext partition."""
1473        runCmd("wic create mkefidisk "
1474                                   "--image-name=core-image-minimal "
1475                                   "-D -o %s" % self.resultdir)
1476        images = glob(os.path.join(self.resultdir, "mkefidisk-*.direct"))
1477        self.assertEqual(1, len(images))
1478
1479        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
1480
1481        # list directory content of the /etc directory on ext4 partition
1482        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1483        self.assertTrue('fstab' in [line.split()[-1] for line in result.output.split('\n') if line])
1484
1485        # remove file
1486        runCmd("wic rm %s:2/etc/fstab -n %s" % (images[0], sysroot))
1487
1488        # check if it's removed
1489        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
1490        self.assertTrue('fstab' not in [line.split()[-1] for line in result.output.split('\n') if line])
1491
1492        # remove non-empty directory
1493        runCmd("wic rm -r %s:2/etc/ -n %s" % (images[0], sysroot))
1494
1495        # check if it's removed
1496        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
1497        self.assertTrue('etc' not in [line.split()[-1] for line in result.output.split('\n') if line])
1498