1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
9from oeqa.core.decorator import OETestTag
10import os
11import tempfile
12import oe.lsb
13from oeqa.core.decorator.data import skipIfNotQemu
14
15class TestExport(OESelftestTestCase):
16
17    @OETestTag("runqemu")
18    def test_testexport_basic(self):
19        """
20        Summary: Check basic testexport functionality with only ping test enabled.
21        Expected: 1. testexport directory must be created.
22                  2. runexported.py must run without any error/exception.
23                  3. ping test must succeed.
24        Product: oe-core
25        Author: Mariano Lopez <mariano.lopez@intel.com>
26        """
27
28        features = 'IMAGE_CLASSES += "testexport"\n'
29        # These aren't the actual IP addresses but testexport class needs something defined
30        features += 'TEST_SERVER_IP = "192.168.7.1"\n'
31        features += 'TEST_TARGET_IP = "192.168.7.1"\n'
32        features += 'TEST_SUITES = "ping"\n'
33        self.write_config(features)
34
35        # Build tesexport for core-image-minimal
36        bitbake('core-image-minimal')
37        bitbake('-c testexport core-image-minimal')
38
39        testexport_dir = get_bb_var('TEST_EXPORT_DIR', 'core-image-minimal')
40
41        # Verify if TEST_EXPORT_DIR was created
42        isdir = os.path.isdir(testexport_dir)
43        self.assertEqual(True, isdir, 'Failed to create testexport dir: %s' % testexport_dir)
44
45        with runqemu('core-image-minimal') as qemu:
46            # Attempt to run runexported.py to perform ping test
47            test_path = os.path.join(testexport_dir, "oe-test")
48            data_file = os.path.join(testexport_dir, 'data', 'testdata.json')
49            manifest = os.path.join(testexport_dir, 'data', 'manifest')
50            cmd = ("%s runtime --test-data-file %s --packages-manifest %s "
51                   "--target-ip %s --server-ip %s --quiet"
52                  % (test_path, data_file, manifest, qemu.ip, qemu.server_ip))
53            result = runCmd(cmd)
54            # Verify ping test was succesful
55            self.assertEqual(0, result.status, 'oe-test runtime returned a non 0 status')
56
57    def test_testexport_sdk(self):
58        """
59        Summary: Check sdk functionality for testexport.
60        Expected: 1. testexport directory must be created.
61                  2. SDK tarball must exists.
62                  3. Uncompressing of tarball must succeed.
63                  4. Check if the SDK directory is added to PATH.
64                  5. Run tar from the SDK directory.
65        Product: oe-core
66        Author: Mariano Lopez <mariano.lopez@intel.com>
67        """
68
69        features = 'IMAGE_CLASSES += "testexport"\n'
70        # These aren't the actual IP addresses but testexport class needs something defined
71        features += 'TEST_SERVER_IP = "192.168.7.1"\n'
72        features += 'TEST_TARGET_IP = "192.168.7.1"\n'
73        features += 'TEST_SUITES = "ping"\n'
74        features += 'TEST_EXPORT_SDK_ENABLED = "1"\n'
75        features += 'TEST_EXPORT_SDK_PACKAGES = "nativesdk-tar"\n'
76        self.write_config(features)
77
78        # Build tesexport for core-image-minimal
79        bitbake('core-image-minimal')
80        bitbake('-c testexport core-image-minimal')
81
82        needed_vars = ['TEST_EXPORT_DIR', 'TEST_EXPORT_SDK_DIR', 'TEST_EXPORT_SDK_NAME']
83        bb_vars = get_bb_vars(needed_vars, 'core-image-minimal')
84        testexport_dir = bb_vars['TEST_EXPORT_DIR']
85        sdk_dir = bb_vars['TEST_EXPORT_SDK_DIR']
86        sdk_name = bb_vars['TEST_EXPORT_SDK_NAME']
87
88        # Check for SDK
89        tarball_name = "%s.sh" % sdk_name
90        tarball_path = os.path.join(testexport_dir, sdk_dir, tarball_name)
91        msg = "Couldn't find SDK tarball: %s" % tarball_path
92        self.assertEqual(os.path.isfile(tarball_path), True, msg)
93
94        with tempfile.TemporaryDirectory() as tmpdirname:
95            # Extract SDK and run tar from SDK
96            result = runCmd("%s -y -d %s" % (tarball_path, tmpdirname))
97            self.assertEqual(0, result.status, "Couldn't extract SDK")
98
99            env_script = result.output.split()[-1]
100            result = runCmd(". %s; which tar" % env_script, shell=True)
101            self.assertEqual(0, result.status, "Couldn't setup SDK environment")
102            is_sdk_tar = True if tmpdirname in result.output else False
103            self.assertTrue(is_sdk_tar, "Couldn't setup SDK environment")
104
105            tar_sdk = result.output
106            result = runCmd("%s --version" % tar_sdk)
107            self.assertEqual(0, result.status, "Couldn't run tar from SDK")
108
109
110@OETestTag("runqemu")
111class TestImage(OESelftestTestCase):
112
113    def test_testimage_install(self):
114        """
115        Summary: Check install packages functionality for testimage/testexport.
116        Expected: 1. Import tests from a directory other than meta.
117                  2. Check install/uninstall of socat.
118        Product: oe-core
119        Author: Mariano Lopez <mariano.lopez@intel.com>
120        """
121        if get_bb_var('DISTRO') == 'poky-tiny':
122            self.skipTest('core-image-full-cmdline not buildable for poky-tiny')
123
124        features = 'IMAGE_CLASSES += "testimage"\n'
125        features += 'IMAGE_INSTALL:append = " libssl"\n'
126        features += 'TEST_SUITES = "ping ssh selftest"\n'
127        self.write_config(features)
128
129        bitbake('core-image-full-cmdline socat')
130        bitbake('-c testimage core-image-full-cmdline')
131
132    def test_testimage_dnf(self):
133        """
134        Summary: Check package feeds functionality for dnf
135        Expected: 1. Check that remote package feeds can be accessed
136        Product: oe-core
137        Author: Alexander Kanavin <alex.kanavin@gmail.com>
138        """
139        if get_bb_var('DISTRO') == 'poky-tiny':
140            self.skipTest('core-image-full-cmdline not buildable for poky-tiny')
141
142        features = 'IMAGE_CLASSES += "testimage"\n'
143        features += 'TEST_SUITES = "ping ssh dnf_runtime dnf.DnfBasicTest.test_dnf_help"\n'
144        # We don't yet know what the server ip and port will be - they will be patched
145        # in at the start of the on-image test
146        features += 'PACKAGE_FEED_URIS = "http://bogus_ip:bogus_port"\n'
147        features += 'EXTRA_IMAGE_FEATURES += "package-management"\n'
148        features += 'PACKAGE_CLASSES = "package_rpm"\n'
149
150        bitbake('gnupg-native -c addto_recipe_sysroot')
151
152        # Enable package feed signing
153        self.gpg_home = tempfile.mkdtemp(prefix="oeqa-feed-sign-")
154        self.track_for_cleanup(self.gpg_home)
155        signing_key_dir = os.path.join(self.testlayer_path, 'files', 'signing')
156        runCmd('gpgconf --list-dirs --homedir %s; gpg -v --batch --homedir %s --import %s' % (self.gpg_home, self.gpg_home, os.path.join(signing_key_dir, 'key.secret')), native_sysroot=get_bb_var("RECIPE_SYSROOT_NATIVE", "gnupg-native"), shell=True)
157        features += 'INHERIT += "sign_package_feed"\n'
158        features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n'
159        features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase')
160        features += 'GPG_PATH = "%s"\n' % self.gpg_home
161        features += 'PSEUDO_IGNORE_PATHS .= ",%s"\n' % self.gpg_home
162        self.write_config(features)
163
164        bitbake('core-image-full-cmdline socat')
165        bitbake('-c testimage core-image-full-cmdline')
166
167    def test_testimage_apt(self):
168        """
169        Summary: Check package feeds functionality for apt
170        Expected: 1. Check that remote package feeds can be accessed
171        Product: oe-core
172        Author: Ferry Toth <fntoth@gmail.com>
173        """
174        if get_bb_var('DISTRO') == 'poky-tiny':
175            self.skipTest('core-image-full-cmdline not buildable for poky-tiny')
176
177        features = 'IMAGE_CLASSES += "testimage"\n'
178        features += 'TEST_SUITES = "ping ssh apt.AptRepoTest.test_apt_install_from_repo"\n'
179        # We don't yet know what the server ip and port will be - they will be patched
180        # in at the start of the on-image test
181        features += 'PACKAGE_FEED_URIS = "http://bogus_ip:bogus_port"\n'
182        features += 'EXTRA_IMAGE_FEATURES += "package-management"\n'
183        features += 'PACKAGE_CLASSES = "package_deb"\n'
184        # We need  gnupg on the target to install keys
185        features += 'IMAGE_INSTALL:append:pn-core-image-full-cmdline = " gnupg"\n'
186
187        bitbake('gnupg-native -c addto_recipe_sysroot')
188
189        # Enable package feed signing
190        self.gpg_home = tempfile.mkdtemp(prefix="oeqa-feed-sign-")
191        self.track_for_cleanup(self.gpg_home)
192        signing_key_dir = os.path.join(self.testlayer_path, 'files', 'signing')
193        runCmd('gpgconf --list-dirs --homedir %s; gpg -v --batch --homedir %s --import %s' % (self.gpg_home, self.gpg_home, os.path.join(signing_key_dir, 'key.secret')), native_sysroot=get_bb_var("RECIPE_SYSROOT_NATIVE", "gnupg-native"), shell=True)
194        features += 'INHERIT += "sign_package_feed"\n'
195        features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n'
196        features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase')
197        features += 'GPG_PATH = "%s"\n' % self.gpg_home
198        features += 'PSEUDO_IGNORE_PATHS .= ",%s"\n' % self.gpg_home
199        self.write_config(features)
200
201        # Build core-image-sato and testimage
202        bitbake('core-image-full-cmdline socat')
203        bitbake('-c testimage core-image-full-cmdline')
204
205    def test_testimage_virgl_gtk_sdl(self):
206        """
207        Summary: Check host-assisted accelerate OpenGL functionality in qemu with gtk and SDL frontends
208        Expected: 1. Check that virgl kernel driver is loaded and 3d acceleration is enabled
209                  2. Check that kmscube demo runs without crashing.
210        Product: oe-core
211        Author: Alexander Kanavin <alex.kanavin@gmail.com>
212        """
213        if "DISPLAY" not in os.environ:
214            self.skipTest("virgl gtk test must be run inside a X session")
215        distro = oe.lsb.distro_identifier()
216        if distro and distro == 'debian-8':
217            self.skipTest('virgl isn\'t working with Debian 8')
218        if distro and distro == 'debian-9':
219            self.skipTest('virgl isn\'t working with Debian 9')
220        if distro and distro == 'centos-7':
221            self.skipTest('virgl isn\'t working with Centos 7')
222        if distro and distro == 'opensuseleap-15.0':
223            self.skipTest('virgl isn\'t working with Opensuse 15.0')
224
225        qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native')
226        qemu_distrofeatures = get_bb_var('DISTRO_FEATURES', 'qemu-system-native')
227        features = 'IMAGE_CLASSES += "testimage"\n'
228        if 'gtk+' not in qemu_packageconfig:
229            features += 'PACKAGECONFIG:append:pn-qemu-system-native = " gtk+"\n'
230        if 'sdl' not in qemu_packageconfig:
231            features += 'PACKAGECONFIG:append:pn-qemu-system-native = " sdl"\n'
232        if 'opengl' not in qemu_distrofeatures:
233            features += 'DISTRO_FEATURES:append = " opengl"\n'
234        features += 'TEST_SUITES = "ping ssh virgl"\n'
235        features += 'IMAGE_FEATURES:append = " ssh-server-dropbear"\n'
236        features += 'IMAGE_INSTALL:append = " kmscube"\n'
237        features_gtk = features + 'TEST_RUNQEMUPARAMS = "gtk gl"\n'
238        self.write_config(features_gtk)
239        bitbake('core-image-minimal')
240        bitbake('-c testimage core-image-minimal')
241        features_sdl = features + 'TEST_RUNQEMUPARAMS = "sdl gl"\n'
242        self.write_config(features_sdl)
243        bitbake('core-image-minimal')
244        bitbake('-c testimage core-image-minimal')
245
246    def test_testimage_virgl_headless(self):
247        """
248        Summary: Check host-assisted accelerate OpenGL functionality in qemu with egl-headless frontend
249        Expected: 1. Check that virgl kernel driver is loaded and 3d acceleration is enabled
250                  2. Check that kmscube demo runs without crashing.
251        Product: oe-core
252        Author: Alexander Kanavin <alex.kanavin@gmail.com>
253        """
254        import subprocess, os
255
256        distro = oe.lsb.distro_identifier()
257        if distro and (distro in ['debian-9', 'debian-10', 'centos-7', 'centos-8', 'ubuntu-16.04', 'ubuntu-18.04'] or distro.startswith('almalinux')):
258            self.skipTest('virgl headless cannot be tested with %s' %(distro))
259
260        render_hint = """If /dev/dri/renderD* is absent due to lack of suitable GPU, 'modprobe vgem' will create one suitable for mesa llvmpipe software renderer."""
261        try:
262            content = os.listdir("/dev/dri")
263            if len([i for i in content if i.startswith('render')]) == 0:
264                self.fail("No render nodes found in /dev/dri: %s. %s" %(content, render_hint))
265        except FileNotFoundError:
266            self.fail("/dev/dri directory does not exist; no render nodes available on this machine. %s" %(render_hint))
267        try:
268            dripath = subprocess.check_output("pkg-config --variable=dridriverdir dri", shell=True)
269        except subprocess.CalledProcessError as e:
270            self.fail("Could not determine the path to dri drivers on the host via pkg-config.\nPlease install Mesa development files (particularly, dri.pc) on the host machine.")
271        qemu_distrofeatures = get_bb_var('DISTRO_FEATURES', 'qemu-system-native')
272        features = 'IMAGE_CLASSES += "testimage"\n'
273        if 'opengl' not in qemu_distrofeatures:
274            features += 'DISTRO_FEATURES:append = " opengl"\n'
275        features += 'TEST_SUITES = "ping ssh virgl"\n'
276        features += 'IMAGE_FEATURES:append = " ssh-server-dropbear"\n'
277        features += 'IMAGE_INSTALL:append = " kmscube"\n'
278        features += 'TEST_RUNQEMUPARAMS = "egl-headless"\n'
279        self.write_config(features)
280        bitbake('core-image-minimal')
281        bitbake('-c testimage core-image-minimal')
282
283@OETestTag("runqemu")
284class Postinst(OESelftestTestCase):
285
286    def init_manager_loop(self, init_manager):
287        import oe.path
288
289        vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal")
290        rootfs = vars["IMAGE_ROOTFS"]
291        self.assertIsNotNone(rootfs)
292        sysconfdir = vars["sysconfdir"]
293        self.assertIsNotNone(sysconfdir)
294        # Need to use oe.path here as sysconfdir starts with /
295        hosttestdir = oe.path.join(rootfs, sysconfdir, "postinst-test")
296        targettestdir = os.path.join(sysconfdir, "postinst-test")
297
298        for classes in ("package_rpm", "package_deb", "package_ipk"):
299            with self.subTest(init_manager=init_manager, package_class=classes):
300                features = 'CORE_IMAGE_EXTRA_INSTALL = "postinst-delayed-b"\n'
301                features += 'IMAGE_FEATURES += "package-management empty-root-password"\n'
302                features += 'PACKAGE_CLASSES = "%s"\n' % classes
303                if init_manager == "systemd":
304                    features += 'DISTRO_FEATURES:append = " systemd"\n'
305                    features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n'
306                    features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n'
307                    features += 'VIRTUAL-RUNTIME_initscripts = ""\n'
308                self.write_config(features)
309
310                bitbake('core-image-minimal')
311
312                self.assertTrue(os.path.isfile(os.path.join(hosttestdir, "rootfs")),
313                                "rootfs state file was not created")
314
315                with runqemu('core-image-minimal') as qemu:
316                    # Make the test echo a string and search for that as
317                    # run_serial()'s status code is useless.'
318                    for filename in ("rootfs", "delayed-a", "delayed-b"):
319                        status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename))
320                        self.assertIn("found", output, "%s was not present on boot" % filename)
321
322
323
324    @skipIfNotQemu()
325    def test_postinst_rootfs_and_boot_sysvinit(self):
326        """
327        Summary:        The purpose of this test case is to verify Post-installation
328                        scripts are called when rootfs is created and also test
329                        that script can be delayed to run at first boot.
330        Dependencies:   NA
331        Steps:          1. Add proper configuration to local.conf file
332                        2. Build a "core-image-minimal" image
333                        3. Verify that file created by postinst_rootfs recipe is
334                           present on rootfs dir.
335                        4. Boot the image created on qemu and verify that the file
336                           created by postinst_boot recipe is present on image.
337        Expected:       The files are successfully created during rootfs and boot
338                        time for 3 different package managers: rpm,ipk,deb and
339                        for initialization managers: sysvinit.
340
341        """
342        self.init_manager_loop("sysvinit")
343
344
345    @skipIfNotQemu()
346    def test_postinst_rootfs_and_boot_systemd(self):
347        """
348        Summary:        The purpose of this test case is to verify Post-installation
349                        scripts are called when rootfs is created and also test
350                        that script can be delayed to run at first boot.
351        Dependencies:   NA
352        Steps:          1. Add proper configuration to local.conf file
353                        2. Build a "core-image-minimal" image
354                        3. Verify that file created by postinst_rootfs recipe is
355                           present on rootfs dir.
356                        4. Boot the image created on qemu and verify that the file
357                           created by postinst_boot recipe is present on image.
358        Expected:       The files are successfully created during rootfs and boot
359                        time for 3 different package managers: rpm,ipk,deb and
360                        for initialization managers: systemd.
361
362        """
363
364        self.init_manager_loop("systemd")
365
366
367    def test_failing_postinst(self):
368        """
369        Summary:        The purpose of this test case is to verify that post-installation
370                        scripts that contain errors are properly reported.
371        Expected:       The scriptlet failure is properly reported.
372                        The file that is created after the error in the scriptlet is not present.
373        Product: oe-core
374        Author: Alexander Kanavin <alex.kanavin@gmail.com>
375        """
376
377        import oe.path
378
379        vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal")
380        rootfs = vars["IMAGE_ROOTFS"]
381        self.assertIsNotNone(rootfs)
382        sysconfdir = vars["sysconfdir"]
383        self.assertIsNotNone(sysconfdir)
384        # Need to use oe.path here as sysconfdir starts with /
385        hosttestdir = oe.path.join(rootfs, sysconfdir, "postinst-test")
386
387        for classes in ("package_rpm", "package_deb", "package_ipk"):
388            with self.subTest(package_class=classes):
389                features = 'CORE_IMAGE_EXTRA_INSTALL = "postinst-rootfs-failing"\n'
390                features += 'PACKAGE_CLASSES = "%s"\n' % classes
391                self.write_config(features)
392                bb_result = bitbake('core-image-minimal', ignore_status=True)
393                self.assertGreaterEqual(bb_result.output.find("Postinstall scriptlets of ['postinst-rootfs-failing'] have failed."), 0,
394                    "Warning about a failed scriptlet not found in bitbake output: %s" %(bb_result.output))
395
396                self.assertTrue(os.path.isfile(os.path.join(hosttestdir, "rootfs-before-failure")),
397                                    "rootfs-before-failure file was not created")
398                self.assertFalse(os.path.isfile(os.path.join(hosttestdir, "rootfs-after-failure")),
399                                    "rootfs-after-failure file was created")
400
401@OETestTag("runqemu")
402class SystemTap(OESelftestTestCase):
403        """
404        Summary:        The purpose of this test case is to verify native crosstap
405                        works while talking to a target.
406        Expected:       The script should successfully connect to the qemu machine
407                        and run some systemtap examples on a qemu machine.
408        """
409
410        @classmethod
411        def setUpClass(cls):
412            super(SystemTap, cls).setUpClass()
413            cls.image = "core-image-minimal"
414
415        def default_config(self):
416            return """
417# These aren't the actual IP addresses but testexport class needs something defined
418TEST_SERVER_IP = "192.168.7.1"
419TEST_TARGET_IP = "192.168.7.2"
420
421EXTRA_IMAGE_FEATURES += "tools-profile dbg-pkgs"
422IMAGE_FEATURES:append = " ssh-server-dropbear"
423
424# enables kernel debug symbols
425KERNEL_EXTRA_FEATURES:append = " features/debug/debug-kernel.scc"
426KERNEL_EXTRA_FEATURES:append = " features/systemtap/systemtap.scc"
427
428# add systemtap run-time into target image if it is not there yet
429IMAGE_INSTALL:append = " systemtap-runtime"
430"""
431
432        def test_crosstap_helloworld(self):
433            self.write_config(self.default_config())
434            bitbake('systemtap-native')
435            systemtap_examples = os.path.join(get_bb_var("WORKDIR","systemtap-native"), "usr/share/systemtap/examples")
436            bitbake(self.image)
437
438            with runqemu(self.image) as qemu:
439                cmd = "crosstap -r root@192.168.7.2 -s %s/general/helloworld.stp " % systemtap_examples
440                result = runCmd(cmd)
441                self.assertEqual(0, result.status, 'crosstap helloworld returned a non 0 status:%s' % result.output)
442
443        def test_crosstap_pstree(self):
444            self.write_config(self.default_config())
445
446            bitbake('systemtap-native')
447            systemtap_examples = os.path.join(get_bb_var("WORKDIR","systemtap-native"), "usr/share/systemtap/examples")
448            bitbake(self.image)
449
450            with runqemu(self.image) as qemu:
451                cmd = "crosstap -r root@192.168.7.2 -s %s/process/pstree.stp" % systemtap_examples
452                result = runCmd(cmd)
453                self.assertEqual(0, result.status, 'crosstap pstree returned a non 0 status:%s' % result.output)
454
455        def test_crosstap_syscalls_by_proc(self):
456            self.write_config(self.default_config())
457
458            bitbake('systemtap-native')
459            systemtap_examples = os.path.join(get_bb_var("WORKDIR","systemtap-native"), "usr/share/systemtap/examples")
460            bitbake(self.image)
461
462            with runqemu(self.image) as qemu:
463                cmd = "crosstap -r root@192.168.7.2 -s %s/process/ syscalls_by_proc.stp" % systemtap_examples
464                result = runCmd(cmd)
465                self.assertEqual(0, result.status, 'crosstap  syscalls_by_proc returned a non 0 status:%s' % result.output)
466
467        def test_crosstap_syscalls_by_pid(self):
468            self.write_config(self.default_config())
469
470            bitbake('systemtap-native')
471            systemtap_examples = os.path.join(get_bb_var("WORKDIR","systemtap-native"), "usr/share/systemtap/examples")
472            bitbake(self.image)
473
474            with runqemu(self.image) as qemu:
475                cmd = "crosstap -r root@192.168.7.2 -s %s/process/ syscalls_by_pid.stp" % systemtap_examples
476                result = runCmd(cmd)
477                self.assertEqual(0, result.status, 'crosstap  syscalls_by_pid returned a non 0 status:%s' % result.output)
478