summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa')
-rw-r--r--meta/lib/oeqa/core/case.py9
-rw-r--r--meta/lib/oeqa/core/decorator/oetimeout.py5
-rw-r--r--meta/lib/oeqa/core/target/ssh.py4
-rw-r--r--meta/lib/oeqa/core/tests/cases/timeout.py13
-rwxr-xr-xmeta/lib/oeqa/core/tests/test_decorators.py6
-rw-r--r--meta/lib/oeqa/manual/eclipse-plugin.json6
-rw-r--r--meta/lib/oeqa/manual/toaster-managed-mode.json2
-rw-r--r--meta/lib/oeqa/runtime/cases/date.py13
-rw-r--r--meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py36
-rw-r--r--meta/lib/oeqa/runtime/cases/ksample.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/ltp.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/pam.py3
-rw-r--r--meta/lib/oeqa/runtime/cases/parselogs.py21
-rw-r--r--meta/lib/oeqa/runtime/cases/ping.py20
-rw-r--r--meta/lib/oeqa/runtime/cases/rpm.py32
-rw-r--r--meta/lib/oeqa/runtime/cases/rtc.py40
-rw-r--r--meta/lib/oeqa/runtime/cases/scp.py2
-rw-r--r--meta/lib/oeqa/runtime/cases/suspend.py33
-rw-r--r--meta/lib/oeqa/runtime/cases/terminal.py21
-rw-r--r--meta/lib/oeqa/runtime/cases/usb_hid.py22
-rw-r--r--meta/lib/oeqa/runtime/context.py33
-rw-r--r--meta/lib/oeqa/sdk/cases/buildepoxy.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/archiver.py16
-rw-r--r--meta/lib/oeqa/selftest/cases/bblayers.py5
-rw-r--r--meta/lib/oeqa/selftest/cases/bbtests.py13
-rw-r--r--meta/lib/oeqa/selftest/cases/buildoptions.py6
-rw-r--r--meta/lib/oeqa/selftest/cases/cve_check.py186
-rw-r--r--meta/lib/oeqa/selftest/cases/devtool.py19
-rw-r--r--meta/lib/oeqa/selftest/cases/diffoscope/A/file.txt1
-rw-r--r--meta/lib/oeqa/selftest/cases/diffoscope/B/file.txt1
-rw-r--r--meta/lib/oeqa/selftest/cases/distrodata.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/glibc.py8
-rw-r--r--meta/lib/oeqa/selftest/cases/gotoolchain.py6
-rw-r--r--meta/lib/oeqa/selftest/cases/imagefeatures.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/oelib/utils.py3
-rw-r--r--meta/lib/oeqa/selftest/cases/oescripts.py3
-rw-r--r--meta/lib/oeqa/selftest/cases/prservice.py2
-rw-r--r--meta/lib/oeqa/selftest/cases/recipetool.py6
-rw-r--r--meta/lib/oeqa/selftest/cases/reproducible.py125
-rw-r--r--meta/lib/oeqa/selftest/cases/runcmd.py4
-rw-r--r--meta/lib/oeqa/selftest/cases/runqemu.py9
-rw-r--r--meta/lib/oeqa/selftest/cases/runtime_test.py43
-rw-r--r--meta/lib/oeqa/selftest/cases/sstatetests.py14
-rw-r--r--meta/lib/oeqa/selftest/cases/tinfoil.py28
-rw-r--r--meta/lib/oeqa/selftest/cases/wic.py18
-rw-r--r--meta/lib/oeqa/utils/buildproject.py3
-rw-r--r--meta/lib/oeqa/utils/commands.py3
-rw-r--r--meta/lib/oeqa/utils/metadata.py6
-rw-r--r--meta/lib/oeqa/utils/nfs.py4
-rw-r--r--meta/lib/oeqa/utils/qemurunner.py41
-rw-r--r--meta/lib/oeqa/utils/targetbuild.py4
51 files changed, 727 insertions, 181 deletions
diff --git a/meta/lib/oeqa/core/case.py b/meta/lib/oeqa/core/case.py
index aae451fef2..bc4446a938 100644
--- a/meta/lib/oeqa/core/case.py
+++ b/meta/lib/oeqa/core/case.py
@@ -43,8 +43,13 @@ class OETestCase(unittest.TestCase):
43 clss.tearDownClassMethod() 43 clss.tearDownClassMethod()
44 44
45 def _oeSetUp(self): 45 def _oeSetUp(self):
46 for d in self.decorators: 46 try:
47 d.setUpDecorator() 47 for d in self.decorators:
48 d.setUpDecorator()
49 except:
50 for d in self.decorators:
51 d.tearDownDecorator()
52 raise
48 self.setUpMethod() 53 self.setUpMethod()
49 54
50 def _oeTearDown(self): 55 def _oeTearDown(self):
diff --git a/meta/lib/oeqa/core/decorator/oetimeout.py b/meta/lib/oeqa/core/decorator/oetimeout.py
index df90d1c798..5e6873ad48 100644
--- a/meta/lib/oeqa/core/decorator/oetimeout.py
+++ b/meta/lib/oeqa/core/decorator/oetimeout.py
@@ -24,5 +24,6 @@ class OETimeout(OETestDecorator):
24 24
25 def tearDownDecorator(self): 25 def tearDownDecorator(self):
26 signal.alarm(0) 26 signal.alarm(0)
27 signal.signal(signal.SIGALRM, self.alarmSignal) 27 if hasattr(self, 'alarmSignal'):
28 self.logger.debug("Removed SIGALRM handler") 28 signal.signal(signal.SIGALRM, self.alarmSignal)
29 self.logger.debug("Removed SIGALRM handler")
diff --git a/meta/lib/oeqa/core/target/ssh.py b/meta/lib/oeqa/core/target/ssh.py
index aefb576805..832b6216f6 100644
--- a/meta/lib/oeqa/core/target/ssh.py
+++ b/meta/lib/oeqa/core/target/ssh.py
@@ -34,6 +34,7 @@ class OESSHTarget(OETarget):
34 self.timeout = timeout 34 self.timeout = timeout
35 self.user = user 35 self.user = user
36 ssh_options = [ 36 ssh_options = [
37 '-o', 'HostKeyAlgorithms=+ssh-rsa',
37 '-o', 'UserKnownHostsFile=/dev/null', 38 '-o', 'UserKnownHostsFile=/dev/null',
38 '-o', 'StrictHostKeyChecking=no', 39 '-o', 'StrictHostKeyChecking=no',
39 '-o', 'LogLevel=ERROR' 40 '-o', 'LogLevel=ERROR'
@@ -225,6 +226,9 @@ def SSHCall(command, logger, timeout=None, **opts):
225 endtime = time.time() + timeout 226 endtime = time.time() + timeout
226 except InterruptedError: 227 except InterruptedError:
227 continue 228 continue
229 except BlockingIOError:
230 logger.debug('BlockingIOError')
231 continue
228 232
229 # process hasn't returned yet 233 # process hasn't returned yet
230 if not eof: 234 if not eof:
diff --git a/meta/lib/oeqa/core/tests/cases/timeout.py b/meta/lib/oeqa/core/tests/cases/timeout.py
index 5dfecc7b7c..69cf969a67 100644
--- a/meta/lib/oeqa/core/tests/cases/timeout.py
+++ b/meta/lib/oeqa/core/tests/cases/timeout.py
@@ -8,6 +8,7 @@ from time import sleep
8 8
9from oeqa.core.case import OETestCase 9from oeqa.core.case import OETestCase
10from oeqa.core.decorator.oetimeout import OETimeout 10from oeqa.core.decorator.oetimeout import OETimeout
11from oeqa.core.decorator.depends import OETestDepends
11 12
12class TimeoutTest(OETestCase): 13class TimeoutTest(OETestCase):
13 14
@@ -19,3 +20,15 @@ class TimeoutTest(OETestCase):
19 def testTimeoutFail(self): 20 def testTimeoutFail(self):
20 sleep(2) 21 sleep(2)
21 self.assertTrue(True, msg='How is this possible?') 22 self.assertTrue(True, msg='How is this possible?')
23
24
25 def testTimeoutSkip(self):
26 self.skipTest("This test needs to be skipped, so that testTimeoutDepends()'s OETestDepends kicks in")
27
28 @OETestDepends(["timeout.TimeoutTest.testTimeoutSkip"])
29 @OETimeout(3)
30 def testTimeoutDepends(self):
31 self.assertTrue(False, msg='How is this possible?')
32
33 def testTimeoutUnrelated(self):
34 sleep(6)
diff --git a/meta/lib/oeqa/core/tests/test_decorators.py b/meta/lib/oeqa/core/tests/test_decorators.py
index b798bf7d33..5095f39948 100755
--- a/meta/lib/oeqa/core/tests/test_decorators.py
+++ b/meta/lib/oeqa/core/tests/test_decorators.py
@@ -133,5 +133,11 @@ class TestTimeoutDecorator(TestBase):
133 msg = "OETestTimeout didn't restore SIGALRM" 133 msg = "OETestTimeout didn't restore SIGALRM"
134 self.assertIs(alarm_signal, signal.getsignal(signal.SIGALRM), msg=msg) 134 self.assertIs(alarm_signal, signal.getsignal(signal.SIGALRM), msg=msg)
135 135
136 def test_timeout_cancel(self):
137 tests = ['timeout.TimeoutTest.testTimeoutSkip', 'timeout.TimeoutTest.testTimeoutDepends', 'timeout.TimeoutTest.testTimeoutUnrelated']
138 msg = 'Unrelated test failed to complete'
139 tc = self._testLoader(modules=self.modules, tests=tests)
140 self.assertTrue(tc.runTests().wasSuccessful(), msg=msg)
141
136if __name__ == '__main__': 142if __name__ == '__main__':
137 unittest.main() 143 unittest.main()
diff --git a/meta/lib/oeqa/manual/eclipse-plugin.json b/meta/lib/oeqa/manual/eclipse-plugin.json
index d77d0e673b..6c110d0656 100644
--- a/meta/lib/oeqa/manual/eclipse-plugin.json
+++ b/meta/lib/oeqa/manual/eclipse-plugin.json
@@ -44,7 +44,7 @@
44 "expected_results": "" 44 "expected_results": ""
45 }, 45 },
46 "2": { 46 "2": {
47 "action": "wget autobuilder.yoctoproject.org/pub/releases//machines/qemu/qemux86/qemu (ex:core-image-sato-sdk-qemux86-date-rootfs-tar-bz2) \nsource /opt/poky/version/environment-setup-i585-poky-linux \n\nExtract qemu with runqemu-extract-sdk /home/user/file(ex.core-image-sato-sdk-qemux86.bz2) \n/home/user/qemux86-sato-sdk \n\n", 47 "action": "wget https://downloads.yoctoproject.org/releases/yocto/yocto-$VERSION/machines/qemu/qemux86/ (ex:core-image-sato-sdk-qemux86-date-rootfs-tar-bz2) \nsource /opt/poky/version/environment-setup-i585-poky-linux \n\nExtract qemu with runqemu-extract-sdk /home/user/file(ex.core-image-sato-sdk-qemux86.bz2) \n/home/user/qemux86-sato-sdk \n\n",
48 "expected_results": " Qemu can be lauched normally." 48 "expected_results": " Qemu can be lauched normally."
49 }, 49 },
50 "3": { 50 "3": {
@@ -60,7 +60,7 @@
60 "expected_results": "" 60 "expected_results": ""
61 }, 61 },
62 "6": { 62 "6": {
63 "action": "(d) QEMU: \nSelect this option if you will be using the QEMU emulator. Specify the Kernel matching the QEMU architecture you are using. \n wget autobuilder.yoctoproject.org/pub/releases//machines/qemu/qemux86/bzImage-qemux86.bin \n e.g: /home/$USER/yocto/adt-installer/download_image/bzImage-qemux86.bin \n\n", 63 "action": "(d) QEMU: \nSelect this option if you will be using the QEMU emulator. Specify the Kernel matching the QEMU architecture you are using. \n wget https://downloads.yoctoproject.org/releases/yocto/yocto-$VERSION/machines/qemu/qemux86/bzImage-qemux86.bin \n e.g: /home/$USER/yocto/adt-installer/download_image/bzImage-qemux86.bin \n\n",
64 "expected_results": "" 64 "expected_results": ""
65 }, 65 },
66 "7": { 66 "7": {
@@ -247,7 +247,7 @@
247 "execution": { 247 "execution": {
248 "1": { 248 "1": {
249 "action": "Clone eclipse-poky source. \n \n - git clone git://git.yoctoproject.org/eclipse-poky \n\n", 249 "action": "Clone eclipse-poky source. \n \n - git clone git://git.yoctoproject.org/eclipse-poky \n\n",
250 "expected_results": "Eclipse plugin is successfully installed \n\nDocumentation is there. For example if you have release yocto-2.0.1 you will found on http://autobuilder.yoctoproject.org/pub/releases/yocto-2.0.1/eclipse-plugin/mars/ archive with documentation like org.yocto.doc-development-$date.zip \n \n" 250 "expected_results": "Eclipse plugin is successfully installed \n\nDocumentation is there. For example if you have release yocto-2.0.1 you will found on https://downloads.yoctoproject.org/releases/yocto/yocto-2.0.1/eclipse-plugin/mars/ archive with documentation like org.yocto.doc-development-$date.zip \n \n"
251 }, 251 },
252 "2": { 252 "2": {
253 "action": "Checkout correct tag. \n\n - git checkout <eclipse-version>/<yocto-version> \n\n", 253 "action": "Checkout correct tag. \n\n - git checkout <eclipse-version>/<yocto-version> \n\n",
diff --git a/meta/lib/oeqa/manual/toaster-managed-mode.json b/meta/lib/oeqa/manual/toaster-managed-mode.json
index 12374c7c64..9566d9d10e 100644
--- a/meta/lib/oeqa/manual/toaster-managed-mode.json
+++ b/meta/lib/oeqa/manual/toaster-managed-mode.json
@@ -136,7 +136,7 @@
136 "expected_results": "" 136 "expected_results": ""
137 }, 137 },
138 "3": { 138 "3": {
139 "action": "Check that default values are as follows: \n\tDISTRO - poky \n\tIMAGE_FSTYPES - ext3 jffs2 tar.bz2 \n\tIMAGE_INSTALL_append - \"Not set\" \n\tPACKAGE_CLASES - package_rpm \n SSTATE_DIR - /homeDirectory/poky/sstate-cache \n\n", 139 "action": "Check that default values are as follows: \n\tDISTRO - poky \n\tIMAGE_FSTYPES - ext3 jffs2 tar.bz2 \n\tIMAGE_INSTALL_append - \"Not set\" \n\tPACKAGE_CLASSES - package_rpm \n SSTATE_DIR - /homeDirectory/poky/sstate-cache \n\n",
140 "expected_results": "" 140 "expected_results": ""
141 }, 141 },
142 "4": { 142 "4": {
diff --git a/meta/lib/oeqa/runtime/cases/date.py b/meta/lib/oeqa/runtime/cases/date.py
index fdd2a6ae58..bd6537400e 100644
--- a/meta/lib/oeqa/runtime/cases/date.py
+++ b/meta/lib/oeqa/runtime/cases/date.py
@@ -13,12 +13,12 @@ class DateTest(OERuntimeTestCase):
13 def setUp(self): 13 def setUp(self):
14 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd': 14 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd':
15 self.logger.debug('Stopping systemd-timesyncd daemon') 15 self.logger.debug('Stopping systemd-timesyncd daemon')
16 self.target.run('systemctl disable --now systemd-timesyncd') 16 self.target.run('systemctl disable --now --runtime systemd-timesyncd')
17 17
18 def tearDown(self): 18 def tearDown(self):
19 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd': 19 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd':
20 self.logger.debug('Starting systemd-timesyncd daemon') 20 self.logger.debug('Starting systemd-timesyncd daemon')
21 self.target.run('systemctl enable --now systemd-timesyncd') 21 self.target.run('systemctl enable --now --runtime systemd-timesyncd')
22 22
23 @OETestDepends(['ssh.SSHTest.test_ssh']) 23 @OETestDepends(['ssh.SSHTest.test_ssh'])
24 @OEHasPackage(['coreutils', 'busybox']) 24 @OEHasPackage(['coreutils', 'busybox'])
@@ -28,14 +28,13 @@ class DateTest(OERuntimeTestCase):
28 self.assertEqual(status, 0, msg=msg) 28 self.assertEqual(status, 0, msg=msg)
29 oldDate = output 29 oldDate = output
30 30
31 sampleDate = '"2016-08-09 10:00:00"' 31 sampleTimestamp = 1488800000
32 (status, output) = self.target.run("date -s %s" % sampleDate) 32 (status, output) = self.target.run("date -s @%d" % sampleTimestamp)
33 self.assertEqual(status, 0, msg='Date set failed, output: %s' % output) 33 self.assertEqual(status, 0, msg='Date set failed, output: %s' % output)
34 34
35 (status, output) = self.target.run("date -R") 35 (status, output) = self.target.run('date +"%s"')
36 p = re.match('Tue, 09 Aug 2016 10:00:.. \+0000', output)
37 msg = 'The date was not set correctly, output: %s' % output 36 msg = 'The date was not set correctly, output: %s' % output
38 self.assertTrue(p, msg=msg) 37 self.assertTrue(int(output) - sampleTimestamp < 300, msg=msg)
39 38
40 (status, output) = self.target.run('date -s "%s"' % oldDate) 39 (status, output) = self.target.run('date -s "%s"' % oldDate)
41 msg = 'Failed to reset date, output: %s' % output 40 msg = 'Failed to reset date, output: %s' % output
diff --git a/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py b/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py
new file mode 100644
index 0000000000..e010612838
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/ethernet_ip_connman.py
@@ -0,0 +1,36 @@
1from oeqa.runtime.case import OERuntimeTestCase
2from oeqa.core.decorator.depends import OETestDepends
3from oeqa.core.decorator.data import skipIfQemu
4
5class Ethernet_Test(OERuntimeTestCase):
6
7 def set_ip(self, x):
8 x = x.split(".")
9 sample_host_address = '150'
10 x[3] = sample_host_address
11 x = '.'.join(x)
12 return x
13
14 @skipIfQemu('qemuall', 'Test only runs on real hardware')
15 @OETestDepends(['ssh.SSHTest.test_ssh'])
16 def test_set_virtual_ip(self):
17 (status, output) = self.target.run("ifconfig eth0 | grep 'inet ' | awk '{print $2}'")
18 self.assertEqual(status, 0, msg='Failed to get ip address. Make sure you have an ethernet connection on your device, output: %s' % output)
19 original_ip = output
20 virtual_ip = self.set_ip(original_ip)
21
22 (status, output) = self.target.run("ifconfig eth0:1 %s netmask 255.255.255.0 && sleep 2 && ping -c 5 %s && ifconfig eth0:1 down" % (virtual_ip,virtual_ip))
23 self.assertEqual(status, 0, msg='Failed to create virtual ip address, output: %s' % output)
24
25 @OETestDepends(['ethernet_ip_connman.Ethernet_Test.test_set_virtual_ip'])
26 def test_get_ip_from_dhcp(self):
27 (status, output) = self.target.run("connmanctl services | grep -E '*AO Wired|*AR Wired' | awk '{print $3}'")
28 self.assertEqual(status, 0, msg='No wired interfaces are detected, output: %s' % output)
29 wired_interfaces = output
30
31 (status, output) = self.target.run("ip route | grep default | awk '{print $3}'")
32 self.assertEqual(status, 0, msg='Failed to retrieve the default gateway, output: %s' % output)
33 default_gateway = output
34
35 (status, output) = self.target.run("connmanctl config %s --ipv4 dhcp && sleep 2 && ping -c 5 %s" % (wired_interfaces,default_gateway))
36 self.assertEqual(status, 0, msg='Failed to get dynamic IP address via DHCP in connmand, output: %s' % output) \ No newline at end of file
diff --git a/meta/lib/oeqa/runtime/cases/ksample.py b/meta/lib/oeqa/runtime/cases/ksample.py
index a9a1620ebd..9883aa9aa8 100644
--- a/meta/lib/oeqa/runtime/cases/ksample.py
+++ b/meta/lib/oeqa/runtime/cases/ksample.py
@@ -10,7 +10,7 @@ from oeqa.core.decorator.depends import OETestDepends
10from oeqa.core.decorator.data import skipIfNotFeature 10from oeqa.core.decorator.data import skipIfNotFeature
11 11
12# need some kernel fragments 12# need some kernel fragments
13# echo "KERNEL_FEATURES_append += \" features\/kernel\-sample\/kernel\-sample.scc\"" >> local.conf 13# echo "KERNEL_FEATURES_append = \" features\/kernel\-sample\/kernel\-sample.scc\"" >> local.conf
14class KSample(OERuntimeTestCase): 14class KSample(OERuntimeTestCase):
15 def cmd_and_check(self, cmd='', match_string=''): 15 def cmd_and_check(self, cmd='', match_string=''):
16 status, output = self.target.run(cmd) 16 status, output = self.target.run(cmd)
diff --git a/meta/lib/oeqa/runtime/cases/ltp.py b/meta/lib/oeqa/runtime/cases/ltp.py
index a66d5d13d7..879f2a673c 100644
--- a/meta/lib/oeqa/runtime/cases/ltp.py
+++ b/meta/lib/oeqa/runtime/cases/ltp.py
@@ -67,7 +67,7 @@ class LtpTest(LtpTestBase):
67 def runltp(self, ltp_group): 67 def runltp(self, ltp_group):
68 cmd = '/opt/ltp/runltp -f %s -p -q -r /opt/ltp -l /opt/ltp/results/%s -I 1 -d /opt/ltp' % (ltp_group, ltp_group) 68 cmd = '/opt/ltp/runltp -f %s -p -q -r /opt/ltp -l /opt/ltp/results/%s -I 1 -d /opt/ltp' % (ltp_group, ltp_group)
69 starttime = time.time() 69 starttime = time.time()
70 (status, output) = self.target.run(cmd) 70 (status, output) = self.target.run(cmd, timeout=1200)
71 endtime = time.time() 71 endtime = time.time()
72 72
73 with open(os.path.join(self.ltptest_log_dir, "%s-raw.log" % ltp_group), 'w') as f: 73 with open(os.path.join(self.ltptest_log_dir, "%s-raw.log" % ltp_group), 'w') as f:
diff --git a/meta/lib/oeqa/runtime/cases/pam.py b/meta/lib/oeqa/runtime/cases/pam.py
index 271a1943e3..a482ded945 100644
--- a/meta/lib/oeqa/runtime/cases/pam.py
+++ b/meta/lib/oeqa/runtime/cases/pam.py
@@ -8,11 +8,14 @@
8from oeqa.runtime.case import OERuntimeTestCase 8from oeqa.runtime.case import OERuntimeTestCase
9from oeqa.core.decorator.depends import OETestDepends 9from oeqa.core.decorator.depends import OETestDepends
10from oeqa.core.decorator.data import skipIfNotFeature 10from oeqa.core.decorator.data import skipIfNotFeature
11from oeqa.runtime.decorator.package import OEHasPackage
11 12
12class PamBasicTest(OERuntimeTestCase): 13class PamBasicTest(OERuntimeTestCase):
13 14
14 @skipIfNotFeature('pam', 'Test requires pam to be in DISTRO_FEATURES') 15 @skipIfNotFeature('pam', 'Test requires pam to be in DISTRO_FEATURES')
15 @OETestDepends(['ssh.SSHTest.test_ssh']) 16 @OETestDepends(['ssh.SSHTest.test_ssh'])
17 @OEHasPackage(['shadow'])
18 @OEHasPackage(['shadow-base'])
16 def test_pam(self): 19 def test_pam(self):
17 status, output = self.target.run('login --help') 20 status, output = self.target.run('login --help')
18 msg = ('login command does not work as expected. ' 21 msg = ('login command does not work as expected. '
diff --git a/meta/lib/oeqa/runtime/cases/parselogs.py b/meta/lib/oeqa/runtime/cases/parselogs.py
index a1791b5cca..1cac59725d 100644
--- a/meta/lib/oeqa/runtime/cases/parselogs.py
+++ b/meta/lib/oeqa/runtime/cases/parselogs.py
@@ -32,7 +32,7 @@ common_errors = [
32 "Failed to load module \"fbdev\"", 32 "Failed to load module \"fbdev\"",
33 "Failed to load module fbdev", 33 "Failed to load module fbdev",
34 "Failed to load module glx", 34 "Failed to load module glx",
35 "[drm] Cannot find any crtc or sizes - going 1024x768", 35 "[drm] Cannot find any crtc or sizes",
36 "_OSC failed (AE_NOT_FOUND); disabling ASPM", 36 "_OSC failed (AE_NOT_FOUND); disabling ASPM",
37 "Open ACPI failed (/var/run/acpid.socket) (No such file or directory)", 37 "Open ACPI failed (/var/run/acpid.socket) (No such file or directory)",
38 "NX (Execute Disable) protection cannot be enabled: non-PAE kernel!", 38 "NX (Execute Disable) protection cannot be enabled: non-PAE kernel!",
@@ -61,6 +61,8 @@ common_errors = [
61 "[rdrand]: Initialization Failed", 61 "[rdrand]: Initialization Failed",
62 "[pulseaudio] authkey.c: Failed to open cookie file", 62 "[pulseaudio] authkey.c: Failed to open cookie file",
63 "[pulseaudio] authkey.c: Failed to load authentication key", 63 "[pulseaudio] authkey.c: Failed to load authentication key",
64 "was skipped because of a failed condition check",
65 "was skipped because all trigger condition checks failed",
64 ] 66 ]
65 67
66video_related = [ 68video_related = [
@@ -88,6 +90,9 @@ qemux86_common = [
88 'tsc: HPET/PMTIMER calibration failed', 90 'tsc: HPET/PMTIMER calibration failed',
89 "modeset(0): Failed to initialize the DRI2 extension", 91 "modeset(0): Failed to initialize the DRI2 extension",
90 "glamor initialization failed", 92 "glamor initialization failed",
93 "blk_update_request: I/O error, dev fd0, sector 0 op 0x0:(READ)",
94 "floppy: error",
95 'failed to IDENTIFY (I/O error, err_mask=0x4)',
91] + common_errors 96] + common_errors
92 97
93ignore_errors = { 98ignore_errors = {
@@ -293,7 +298,7 @@ class ParseLogsTest(OERuntimeTestCase):
293 grepcmd = 'grep ' 298 grepcmd = 'grep '
294 grepcmd += '-Ei "' 299 grepcmd += '-Ei "'
295 for error in errors: 300 for error in errors:
296 grepcmd += '\<' + error + '\>' + '|' 301 grepcmd += r'\<' + error + r'\>' + '|'
297 grepcmd = grepcmd[:-1] 302 grepcmd = grepcmd[:-1]
298 grepcmd += '" ' + str(log) + " | grep -Eiv \'" 303 grepcmd += '" ' + str(log) + " | grep -Eiv \'"
299 304
@@ -304,13 +309,13 @@ class ParseLogsTest(OERuntimeTestCase):
304 errorlist = ignore_errors['default'] 309 errorlist = ignore_errors['default']
305 310
306 for ignore_error in errorlist: 311 for ignore_error in errorlist:
307 ignore_error = ignore_error.replace('(', '\(') 312 ignore_error = ignore_error.replace('(', r'\(')
308 ignore_error = ignore_error.replace(')', '\)') 313 ignore_error = ignore_error.replace(')', r'\)')
309 ignore_error = ignore_error.replace("'", '.') 314 ignore_error = ignore_error.replace("'", '.')
310 ignore_error = ignore_error.replace('?', '\?') 315 ignore_error = ignore_error.replace('?', r'\?')
311 ignore_error = ignore_error.replace('[', '\[') 316 ignore_error = ignore_error.replace('[', r'\[')
312 ignore_error = ignore_error.replace(']', '\]') 317 ignore_error = ignore_error.replace(']', r'\]')
313 ignore_error = ignore_error.replace('*', '\*') 318 ignore_error = ignore_error.replace('*', r'\*')
314 ignore_error = ignore_error.replace('0-9', '[0-9]') 319 ignore_error = ignore_error.replace('0-9', '[0-9]')
315 grepcmd += ignore_error + '|' 320 grepcmd += ignore_error + '|'
316 grepcmd = grepcmd[:-1] 321 grepcmd = grepcmd[:-1]
diff --git a/meta/lib/oeqa/runtime/cases/ping.py b/meta/lib/oeqa/runtime/cases/ping.py
index f6603f75ec..498f80d0a5 100644
--- a/meta/lib/oeqa/runtime/cases/ping.py
+++ b/meta/lib/oeqa/runtime/cases/ping.py
@@ -6,6 +6,7 @@ from subprocess import Popen, PIPE
6 6
7from oeqa.runtime.case import OERuntimeTestCase 7from oeqa.runtime.case import OERuntimeTestCase
8from oeqa.core.decorator.oetimeout import OETimeout 8from oeqa.core.decorator.oetimeout import OETimeout
9from oeqa.core.exception import OEQATimeoutError
9 10
10class PingTest(OERuntimeTestCase): 11class PingTest(OERuntimeTestCase):
11 12
@@ -13,14 +14,17 @@ class PingTest(OERuntimeTestCase):
13 def test_ping(self): 14 def test_ping(self):
14 output = '' 15 output = ''
15 count = 0 16 count = 0
16 while count < 5: 17 try:
17 cmd = 'ping -c 1 %s' % self.target.ip 18 while count < 5:
18 proc = Popen(cmd, shell=True, stdout=PIPE) 19 cmd = 'ping -c 1 %s' % self.target.ip
19 output += proc.communicate()[0].decode('utf-8') 20 proc = Popen(cmd, shell=True, stdout=PIPE)
20 if proc.poll() == 0: 21 output += proc.communicate()[0].decode('utf-8')
21 count += 1 22 if proc.poll() == 0:
22 else: 23 count += 1
23 count = 0 24 else:
25 count = 0
26 except OEQATimeoutError:
27 self.fail("Ping timeout error for address %s, count %s, output: %s" % (self.target.ip, count, output))
24 msg = ('Expected 5 consecutive, got %d.\n' 28 msg = ('Expected 5 consecutive, got %d.\n'
25 'ping output is:\n%s' % (count,output)) 29 'ping output is:\n%s' % (count,output))
26 self.assertEqual(count, 5, msg = msg) 30 self.assertEqual(count, 5, msg = msg)
diff --git a/meta/lib/oeqa/runtime/cases/rpm.py b/meta/lib/oeqa/runtime/cases/rpm.py
index 8e18b426f8..203fcc8505 100644
--- a/meta/lib/oeqa/runtime/cases/rpm.py
+++ b/meta/lib/oeqa/runtime/cases/rpm.py
@@ -49,21 +49,20 @@ class RpmBasicTest(OERuntimeTestCase):
49 msg = 'status: %s. Cannot run rpm -qa: %s' % (status, output) 49 msg = 'status: %s. Cannot run rpm -qa: %s' % (status, output)
50 self.assertEqual(status, 0, msg=msg) 50 self.assertEqual(status, 0, msg=msg)
51 51
52 def check_no_process_for_user(u): 52 def wait_for_no_process_for_user(u, timeout = 120):
53 _, output = self.target.run(self.tc.target_cmds['ps']) 53 timeout_at = time.time() + timeout
54 if u + ' ' in output: 54 while time.time() < timeout_at:
55 return False 55 _, output = self.target.run(self.tc.target_cmds['ps'])
56 else: 56 if u + ' ' not in output:
57 return True 57 return
58 time.sleep(1)
59 user_pss = [ps for ps in output.split("\n") if u + ' ' in ps]
60 msg = "User %s has processes still running: %s" % (u, "\n".join(user_pss))
61 self.fail(msg=msg)
58 62
59 def unset_up_test_user(u): 63 def unset_up_test_user(u):
60 # ensure no test1 process in running 64 # ensure no test1 process in running
61 timeout = time.time() + 30 65 wait_for_no_process_for_user(u)
62 while time.time() < timeout:
63 if check_no_process_for_user(u):
64 break
65 else:
66 time.sleep(1)
67 status, output = self.target.run('userdel -r %s' % u) 66 status, output = self.target.run('userdel -r %s' % u)
68 msg = 'Failed to erase user: %s' % output 67 msg = 'Failed to erase user: %s' % output
69 self.assertTrue(status == 0, msg=msg) 68 self.assertTrue(status == 0, msg=msg)
@@ -141,13 +140,4 @@ class RpmInstallRemoveTest(OERuntimeTestCase):
141 140
142 self.tc.target.run('rm -f %s' % self.dst) 141 self.tc.target.run('rm -f %s' % self.dst)
143 142
144 # if using systemd this should ensure all entries are flushed to /var
145 status, output = self.target.run("journalctl --sync")
146 # Get the amount of entries in the log file
147 status, output = self.target.run(check_log_cmd)
148 msg = 'Failed to get the final size of the log file.'
149 self.assertEqual(0, status, msg=msg)
150 143
151 # Check that there's enough of them
152 self.assertGreaterEqual(int(output), 80,
153 'Cound not find sufficient amount of rpm entries in /var/log/messages, found {} entries'.format(output))
diff --git a/meta/lib/oeqa/runtime/cases/rtc.py b/meta/lib/oeqa/runtime/cases/rtc.py
new file mode 100644
index 0000000000..39f4d29f23
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/rtc.py
@@ -0,0 +1,40 @@
1from oeqa.runtime.case import OERuntimeTestCase
2from oeqa.core.decorator.depends import OETestDepends
3from oeqa.core.decorator.data import skipIfFeature
4from oeqa.runtime.decorator.package import OEHasPackage
5
6import re
7
8class RTCTest(OERuntimeTestCase):
9
10 def setUp(self):
11 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd':
12 self.logger.debug('Stopping systemd-timesyncd daemon')
13 self.target.run('systemctl disable --now --runtime systemd-timesyncd')
14
15 def tearDown(self):
16 if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd':
17 self.logger.debug('Starting systemd-timesyncd daemon')
18 self.target.run('systemctl enable --now --runtime systemd-timesyncd')
19
20 @skipIfFeature('read-only-rootfs',
21 'Test does not work with read-only-rootfs in IMAGE_FEATURES')
22 @OETestDepends(['ssh.SSHTest.test_ssh'])
23 @OEHasPackage(['coreutils', 'busybox'])
24 def test_rtc(self):
25 (status, output) = self.target.run('hwclock -r')
26 self.assertEqual(status, 0, msg='Failed to get RTC time, output: %s' % output)
27
28 (status, current_datetime) = self.target.run('date +"%m%d%H%M%Y"')
29 self.assertEqual(status, 0, msg='Failed to get system current date & time, output: %s' % current_datetime)
30
31 example_datetime = '062309452008'
32 (status, output) = self.target.run('date %s ; hwclock -w ; hwclock -r' % example_datetime)
33 check_hwclock = re.search('2008-06-23 09:45:..', output)
34 self.assertTrue(check_hwclock, msg='The RTC time was not set correctly, output: %s' % output)
35
36 (status, output) = self.target.run('date %s' % current_datetime)
37 self.assertEqual(status, 0, msg='Failed to reset system date & time, output: %s' % output)
38
39 (status, output) = self.target.run('hwclock -w')
40 self.assertEqual(status, 0, msg='Failed to reset RTC time, output: %s' % output)
diff --git a/meta/lib/oeqa/runtime/cases/scp.py b/meta/lib/oeqa/runtime/cases/scp.py
index 3a5f292152..f2bbc947d6 100644
--- a/meta/lib/oeqa/runtime/cases/scp.py
+++ b/meta/lib/oeqa/runtime/cases/scp.py
@@ -23,7 +23,7 @@ class ScpTest(OERuntimeTestCase):
23 os.remove(cls.tmp_path) 23 os.remove(cls.tmp_path)
24 24
25 @OETestDepends(['ssh.SSHTest.test_ssh']) 25 @OETestDepends(['ssh.SSHTest.test_ssh'])
26 @OEHasPackage(['openssh-scp', 'dropbear']) 26 @OEHasPackage(['openssh-scp'])
27 def test_scp_file(self): 27 def test_scp_file(self):
28 dst = '/tmp/test_scp_file' 28 dst = '/tmp/test_scp_file'
29 29
diff --git a/meta/lib/oeqa/runtime/cases/suspend.py b/meta/lib/oeqa/runtime/cases/suspend.py
new file mode 100644
index 0000000000..67b6f7e56f
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/suspend.py
@@ -0,0 +1,33 @@
1from oeqa.runtime.case import OERuntimeTestCase
2from oeqa.core.decorator.depends import OETestDepends
3from oeqa.core.decorator.data import skipIfQemu
4import threading
5import time
6
7class Suspend_Test(OERuntimeTestCase):
8
9 def test_date(self):
10 (status, output) = self.target.run('date')
11 self.assertEqual(status, 0, msg = 'Failed to run date command, output : %s' % output)
12
13 def test_ping(self):
14 t_thread = threading.Thread(target=self.target.run, args=("ping 8.8.8.8",))
15 t_thread.start()
16 time.sleep(2)
17
18 status, output = self.target.run('pidof ping')
19 self.target.run('kill -9 %s' % output)
20 self.assertEqual(status, 0, msg = 'Not able to find process that runs ping, output : %s' % output)
21
22 def set_suspend(self):
23 (status, output) = self.target.run('sudo rtcwake -m mem -s 10')
24 self.assertEqual(status, 0, msg = 'Failed to suspends your system to RAM, output : %s' % output)
25
26 @skipIfQemu('qemuall', 'Test only runs on real hardware')
27 @OETestDepends(['ssh.SSHTest.test_ssh'])
28 def test_suspend(self):
29 self.test_date()
30 self.test_ping()
31 self.set_suspend()
32 self.test_date()
33 self.test_ping()
diff --git a/meta/lib/oeqa/runtime/cases/terminal.py b/meta/lib/oeqa/runtime/cases/terminal.py
new file mode 100644
index 0000000000..8fcca99f47
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/terminal.py
@@ -0,0 +1,21 @@
1from oeqa.runtime.case import OERuntimeTestCase
2from oeqa.core.decorator.depends import OETestDepends
3from oeqa.runtime.decorator.package import OEHasPackage
4
5import threading
6import time
7
8class TerminalTest(OERuntimeTestCase):
9
10 @OEHasPackage(['matchbox-terminal'])
11 @OETestDepends(['ssh.SSHTest.test_ssh'])
12 def test_terminal_running(self):
13 t_thread = threading.Thread(target=self.target.run, args=("export DISPLAY=:0 && matchbox-terminal -e 'sh -c \"uname -a && exec sh\"'",))
14 t_thread.start()
15 time.sleep(2)
16
17 status, output = self.target.run('pidof matchbox-terminal')
18 number_of_terminal = len(output.split())
19 self.assertEqual(number_of_terminal, 1, msg='There should be only one terminal being launched. Number of terminal launched : %s' % number_of_terminal)
20 self.target.run('kill -9 %s' % output)
21 self.assertEqual(status, 0, msg='Not able to find process that runs terminal.')
diff --git a/meta/lib/oeqa/runtime/cases/usb_hid.py b/meta/lib/oeqa/runtime/cases/usb_hid.py
new file mode 100644
index 0000000000..3c292cf661
--- /dev/null
+++ b/meta/lib/oeqa/runtime/cases/usb_hid.py
@@ -0,0 +1,22 @@
1from oeqa.runtime.case import OERuntimeTestCase
2from oeqa.core.decorator.depends import OETestDepends
3from oeqa.core.decorator.data import skipIfQemu
4from oeqa.runtime.decorator.package import OEHasPackage
5
6class USB_HID_Test(OERuntimeTestCase):
7
8 def keyboard_mouse_simulation(self):
9 (status, output) = self.target.run('export DISPLAY=:0 && xdotool key F2 && xdotool mousemove 100 100')
10 return self.assertEqual(status, 0, msg = 'Failed to simulate keyboard/mouse input event, output : %s' % output)
11
12 def set_suspend(self):
13 (status, output) = self.target.run('sudo rtcwake -m mem -s 10')
14 return self.assertEqual(status, 0, msg = 'Failed to suspends your system to RAM, output : %s' % output)
15
16 @OEHasPackage(['xdotool'])
17 @skipIfQemu('qemuall', 'Test only runs on real hardware')
18 @OETestDepends(['ssh.SSHTest.test_ssh'])
19 def test_USB_Hid_input(self):
20 self.keyboard_mouse_simulation()
21 self.set_suspend()
22 self.keyboard_mouse_simulation()
diff --git a/meta/lib/oeqa/runtime/context.py b/meta/lib/oeqa/runtime/context.py
index 3826f27642..8a0dbd0736 100644
--- a/meta/lib/oeqa/runtime/context.py
+++ b/meta/lib/oeqa/runtime/context.py
@@ -5,6 +5,7 @@
5# 5#
6 6
7import os 7import os
8import sys
8 9
9from oeqa.core.context import OETestContext, OETestContextExecutor 10from oeqa.core.context import OETestContext, OETestContextExecutor
10from oeqa.core.target.ssh import OESSHTarget 11from oeqa.core.target.ssh import OESSHTarget
@@ -66,11 +67,11 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
66 % self.default_target_type) 67 % self.default_target_type)
67 runtime_group.add_argument('--target-ip', action='store', 68 runtime_group.add_argument('--target-ip', action='store',
68 default=self.default_target_ip, 69 default=self.default_target_ip,
69 help="IP address of device under test, default: %s" \ 70 help="IP address and optionally ssh port (default 22) of device under test, for example '192.168.0.7:22'. Default: %s" \
70 % self.default_target_ip) 71 % self.default_target_ip)
71 runtime_group.add_argument('--server-ip', action='store', 72 runtime_group.add_argument('--server-ip', action='store',
72 default=self.default_target_ip, 73 default=self.default_target_ip,
73 help="IP address of device under test, default: %s" \ 74 help="IP address of the test host from test target machine, default: %s" \
74 % self.default_server_ip) 75 % self.default_server_ip)
75 76
76 runtime_group.add_argument('--host-dumper-dir', action='store', 77 runtime_group.add_argument('--host-dumper-dir', action='store',
@@ -119,8 +120,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
119 # XXX: Don't base your targets on this code it will be refactored 120 # XXX: Don't base your targets on this code it will be refactored
120 # in the near future. 121 # in the near future.
121 # Custom target module loading 122 # Custom target module loading
122 target_modules_path = kwargs.get('target_modules_path', '') 123 controller = OERuntimeTestContextExecutor.getControllerModule(target_type)
123 controller = OERuntimeTestContextExecutor.getControllerModule(target_type, target_modules_path)
124 target = controller(logger, target_ip, server_ip, **kwargs) 124 target = controller(logger, target_ip, server_ip, **kwargs)
125 125
126 return target 126 return target
@@ -130,15 +130,15 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
130 # AttributeError raised if not found. 130 # AttributeError raised if not found.
131 # ImportError raised if a provided module can not be imported. 131 # ImportError raised if a provided module can not be imported.
132 @staticmethod 132 @staticmethod
133 def getControllerModule(target, target_modules_path): 133 def getControllerModule(target):
134 controllerslist = OERuntimeTestContextExecutor._getControllerModulenames(target_modules_path) 134 controllerslist = OERuntimeTestContextExecutor._getControllerModulenames()
135 controller = OERuntimeTestContextExecutor._loadControllerFromName(target, controllerslist) 135 controller = OERuntimeTestContextExecutor._loadControllerFromName(target, controllerslist)
136 return controller 136 return controller
137 137
138 # Return a list of all python modules in lib/oeqa/controllers for each 138 # Return a list of all python modules in lib/oeqa/controllers for each
139 # layer in bbpath 139 # layer in bbpath
140 @staticmethod 140 @staticmethod
141 def _getControllerModulenames(target_modules_path): 141 def _getControllerModulenames():
142 142
143 controllerslist = [] 143 controllerslist = []
144 144
@@ -153,9 +153,8 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
153 else: 153 else:
154 raise RuntimeError("Duplicate controller module found for %s. Layers should create unique controller module names" % module) 154 raise RuntimeError("Duplicate controller module found for %s. Layers should create unique controller module names" % module)
155 155
156 extpath = target_modules_path.split(':') 156 for p in sys.path:
157 for p in extpath: 157 controllerpath = os.path.join(p, 'oeqa', 'controllers')
158 controllerpath = os.path.join(p, 'lib', 'oeqa', 'controllers')
159 if os.path.exists(controllerpath): 158 if os.path.exists(controllerpath):
160 add_controller_list(controllerpath) 159 add_controller_list(controllerpath)
161 return controllerslist 160 return controllerslist
@@ -175,16 +174,12 @@ class OERuntimeTestContextExecutor(OETestContextExecutor):
175 # Search for and return a controller or None from given module name 174 # Search for and return a controller or None from given module name
176 @staticmethod 175 @staticmethod
177 def _loadControllerFromModule(target, modulename): 176 def _loadControllerFromModule(target, modulename):
178 obj = None
179 # import module, allowing it to raise import exception
180 module = __import__(modulename, globals(), locals(), [target])
181 # look for target class in the module, catching any exceptions as it
182 # is valid that a module may not have the target class.
183 try: 177 try:
184 obj = getattr(module, target) 178 import importlib
185 except: 179 module = importlib.import_module(modulename)
186 obj = None 180 return getattr(module, target)
187 return obj 181 except AttributeError:
182 return None
188 183
189 @staticmethod 184 @staticmethod
190 def readPackagesManifest(manifest): 185 def readPackagesManifest(manifest):
diff --git a/meta/lib/oeqa/sdk/cases/buildepoxy.py b/meta/lib/oeqa/sdk/cases/buildepoxy.py
index 385f8ccca8..f69f720cd6 100644
--- a/meta/lib/oeqa/sdk/cases/buildepoxy.py
+++ b/meta/lib/oeqa/sdk/cases/buildepoxy.py
@@ -17,7 +17,7 @@ class EpoxyTest(OESDKTestCase):
17 """ 17 """
18 def setUp(self): 18 def setUp(self):
19 if not (self.tc.hasHostPackage("nativesdk-meson")): 19 if not (self.tc.hasHostPackage("nativesdk-meson")):
20 raise unittest.SkipTest("GalculatorTest class: SDK doesn't contain Meson") 20 raise unittest.SkipTest("EpoxyTest class: SDK doesn't contain Meson")
21 21
22 def test_epoxy(self): 22 def test_epoxy(self):
23 with tempfile.TemporaryDirectory(prefix="epoxy", dir=self.tc.sdk_dir) as testdir: 23 with tempfile.TemporaryDirectory(prefix="epoxy", dir=self.tc.sdk_dir) as testdir:
diff --git a/meta/lib/oeqa/selftest/cases/archiver.py b/meta/lib/oeqa/selftest/cases/archiver.py
index bc5447d2a3..6a5c8ec71e 100644
--- a/meta/lib/oeqa/selftest/cases/archiver.py
+++ b/meta/lib/oeqa/selftest/cases/archiver.py
@@ -35,11 +35,11 @@ class Archiver(OESelftestTestCase):
35 src_path = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['TARGET_SYS']) 35 src_path = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['TARGET_SYS'])
36 36
37 # Check that include_recipe was included 37 # Check that include_recipe was included
38 included_present = len(glob.glob(src_path + '/%s-*' % include_recipe)) 38 included_present = len(glob.glob(src_path + '/%s-*/*' % include_recipe))
39 self.assertTrue(included_present, 'Recipe %s was not included.' % include_recipe) 39 self.assertTrue(included_present, 'Recipe %s was not included.' % include_recipe)
40 40
41 # Check that exclude_recipe was excluded 41 # Check that exclude_recipe was excluded
42 excluded_present = len(glob.glob(src_path + '/%s-*' % exclude_recipe)) 42 excluded_present = len(glob.glob(src_path + '/%s-*/*' % exclude_recipe))
43 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % exclude_recipe) 43 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % exclude_recipe)
44 44
45 def test_archiver_filters_by_type(self): 45 def test_archiver_filters_by_type(self):
@@ -67,11 +67,11 @@ class Archiver(OESelftestTestCase):
67 src_path_native = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['BUILD_SYS']) 67 src_path_native = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['BUILD_SYS'])
68 68
69 # Check that target_recipe was included 69 # Check that target_recipe was included
70 included_present = len(glob.glob(src_path_target + '/%s-*' % target_recipe)) 70 included_present = len(glob.glob(src_path_target + '/%s-*/*' % target_recipe))
71 self.assertTrue(included_present, 'Recipe %s was not included.' % target_recipe) 71 self.assertTrue(included_present, 'Recipe %s was not included.' % target_recipe)
72 72
73 # Check that native_recipe was excluded 73 # Check that native_recipe was excluded
74 excluded_present = len(glob.glob(src_path_native + '/%s-*' % native_recipe)) 74 excluded_present = len(glob.glob(src_path_native + '/%s-*/*' % native_recipe))
75 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipe) 75 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipe)
76 76
77 def test_archiver_filters_by_type_and_name(self): 77 def test_archiver_filters_by_type_and_name(self):
@@ -104,17 +104,17 @@ class Archiver(OESelftestTestCase):
104 src_path_native = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['BUILD_SYS']) 104 src_path_native = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['BUILD_SYS'])
105 105
106 # Check that target_recipe[0] and native_recipes[1] were included 106 # Check that target_recipe[0] and native_recipes[1] were included
107 included_present = len(glob.glob(src_path_target + '/%s-*' % target_recipes[0])) 107 included_present = len(glob.glob(src_path_target + '/%s-*/*' % target_recipes[0]))
108 self.assertTrue(included_present, 'Recipe %s was not included.' % target_recipes[0]) 108 self.assertTrue(included_present, 'Recipe %s was not included.' % target_recipes[0])
109 109
110 included_present = len(glob.glob(src_path_native + '/%s-*' % native_recipes[1])) 110 included_present = len(glob.glob(src_path_native + '/%s-*/*' % native_recipes[1]))
111 self.assertTrue(included_present, 'Recipe %s was not included.' % native_recipes[1]) 111 self.assertTrue(included_present, 'Recipe %s was not included.' % native_recipes[1])
112 112
113 # Check that native_recipes[0] and target_recipes[1] were excluded 113 # Check that native_recipes[0] and target_recipes[1] were excluded
114 excluded_present = len(glob.glob(src_path_native + '/%s-*' % native_recipes[0])) 114 excluded_present = len(glob.glob(src_path_native + '/%s-*/*' % native_recipes[0]))
115 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipes[0]) 115 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipes[0])
116 116
117 excluded_present = len(glob.glob(src_path_target + '/%s-*' % target_recipes[1])) 117 excluded_present = len(glob.glob(src_path_target + '/%s-*/*' % target_recipes[1]))
118 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % target_recipes[1]) 118 self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % target_recipes[1])
119 119
120 120
diff --git a/meta/lib/oeqa/selftest/cases/bblayers.py b/meta/lib/oeqa/selftest/cases/bblayers.py
index f131d9856c..7d74833f61 100644
--- a/meta/lib/oeqa/selftest/cases/bblayers.py
+++ b/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -12,6 +12,11 @@ from oeqa.selftest.case import OESelftestTestCase
12 12
13class BitbakeLayers(OESelftestTestCase): 13class BitbakeLayers(OESelftestTestCase):
14 14
15 def test_bitbakelayers_layerindexshowdepends(self):
16 result = runCmd('bitbake-layers layerindex-show-depends meta-poky')
17 find_in_contents = re.search("openembedded-core", result.output)
18 self.assertTrue(find_in_contents, msg = "openembedded-core should have been listed at this step. bitbake-layers layerindex-show-depends meta-poky output: %s" % result.output)
19
15 def test_bitbakelayers_showcrossdepends(self): 20 def test_bitbakelayers_showcrossdepends(self):
16 result = runCmd('bitbake-layers show-cross-depends') 21 result = runCmd('bitbake-layers show-cross-depends')
17 self.assertIn('aspell', result.output) 22 self.assertIn('aspell', result.output)
diff --git a/meta/lib/oeqa/selftest/cases/bbtests.py b/meta/lib/oeqa/selftest/cases/bbtests.py
index dc423ec439..0b88316950 100644
--- a/meta/lib/oeqa/selftest/cases/bbtests.py
+++ b/meta/lib/oeqa/selftest/cases/bbtests.py
@@ -148,9 +148,6 @@ INHERIT_remove = \"report-error\"
148 self.delete_recipeinc('man-db') 148 self.delete_recipeinc('man-db')
149 self.assertEqual(result.status, 1, msg="Command succeded when it should have failed. bitbake output: %s" % result.output) 149 self.assertEqual(result.status, 1, msg="Command succeded when it should have failed. bitbake output: %s" % result.output)
150 self.assertIn('Fetcher failure: Unable to find file file://invalid anywhere. The paths that were searched were:', result.output) 150 self.assertIn('Fetcher failure: Unable to find file file://invalid anywhere. The paths that were searched were:', result.output)
151 line = self.getline(result, 'Fetcher failure for URL: \'file://invalid\'. Unable to fetch URL from any source.')
152 self.assertTrue(line and line.startswith("ERROR:"), msg = "\"invalid\" file \
153doesn't exist, yet fetcher didn't report any error. bitbake output: %s" % result.output)
154 151
155 def test_rename_downloaded_file(self): 152 def test_rename_downloaded_file(self):
156 # TODO unique dldir instead of using cleanall 153 # TODO unique dldir instead of using cleanall
@@ -160,7 +157,7 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\"
160""") 157""")
161 self.track_for_cleanup(os.path.join(self.builddir, "download-selftest")) 158 self.track_for_cleanup(os.path.join(self.builddir, "download-selftest"))
162 159
163 data = 'SRC_URI = "${GNU_MIRROR}/aspell/aspell-${PV}.tar.gz;downloadfilename=test-aspell.tar.gz"' 160 data = 'SRC_URI = "https://downloads.yoctoproject.org/mirror/sources/aspell-${PV}.tar.gz;downloadfilename=test-aspell.tar.gz"'
164 self.write_recipeinc('aspell', data) 161 self.write_recipeinc('aspell', data)
165 result = bitbake('-f -c fetch aspell', ignore_status=True) 162 result = bitbake('-f -c fetch aspell', ignore_status=True)
166 self.delete_recipeinc('aspell') 163 self.delete_recipeinc('aspell')
@@ -188,6 +185,10 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\"
188 self.assertTrue(find, "No version returned for searched recipe. bitbake output: %s" % result.output) 185 self.assertTrue(find, "No version returned for searched recipe. bitbake output: %s" % result.output)
189 186
190 def test_prefile(self): 187 def test_prefile(self):
188 # Test when the prefile does not exist
189 result = runCmd('bitbake -r conf/prefile.conf', ignore_status=True)
190 self.assertEqual(1, result.status, "bitbake didn't error and should have when a specified prefile didn't exist: %s" % result.output)
191 # Test when the prefile exists
191 preconf = os.path.join(self.builddir, 'conf/prefile.conf') 192 preconf = os.path.join(self.builddir, 'conf/prefile.conf')
192 self.track_for_cleanup(preconf) 193 self.track_for_cleanup(preconf)
193 ftools.write_file(preconf ,"TEST_PREFILE=\"prefile\"") 194 ftools.write_file(preconf ,"TEST_PREFILE=\"prefile\"")
@@ -198,6 +199,10 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\"
198 self.assertIn('localconf', result.output) 199 self.assertIn('localconf', result.output)
199 200
200 def test_postfile(self): 201 def test_postfile(self):
202 # Test when the postfile does not exist
203 result = runCmd('bitbake -R conf/postfile.conf', ignore_status=True)
204 self.assertEqual(1, result.status, "bitbake didn't error and should have when a specified postfile didn't exist: %s" % result.output)
205 # Test when the postfile exists
201 postconf = os.path.join(self.builddir, 'conf/postfile.conf') 206 postconf = os.path.join(self.builddir, 'conf/postfile.conf')
202 self.track_for_cleanup(postconf) 207 self.track_for_cleanup(postconf)
203 ftools.write_file(postconf , "TEST_POSTFILE=\"postfile\"") 208 ftools.write_file(postconf , "TEST_POSTFILE=\"postfile\"")
diff --git a/meta/lib/oeqa/selftest/cases/buildoptions.py b/meta/lib/oeqa/selftest/cases/buildoptions.py
index e91f0bd18f..b1b9ea7e55 100644
--- a/meta/lib/oeqa/selftest/cases/buildoptions.py
+++ b/meta/lib/oeqa/selftest/cases/buildoptions.py
@@ -57,15 +57,15 @@ class ImageOptionsTests(OESelftestTestCase):
57class DiskMonTest(OESelftestTestCase): 57class DiskMonTest(OESelftestTestCase):
58 58
59 def test_stoptask_behavior(self): 59 def test_stoptask_behavior(self):
60 self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},100000G,100K"') 60 self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},100000G,100K"\nBB_HEARTBEAT_EVENT = "1"')
61 res = bitbake("delay -c delay", ignore_status = True) 61 res = bitbake("delay -c delay", ignore_status = True)
62 self.assertTrue('ERROR: No new tasks can be executed since the disk space monitor action is "STOPTASKS"!' in res.output, msg = "Tasks should have stopped. Disk monitor is set to STOPTASK: %s" % res.output) 62 self.assertTrue('ERROR: No new tasks can be executed since the disk space monitor action is "STOPTASKS"!' in res.output, msg = "Tasks should have stopped. Disk monitor is set to STOPTASK: %s" % res.output)
63 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output)) 63 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output))
64 self.write_config('BB_DISKMON_DIRS = "ABORT,${TMPDIR},100000G,100K"') 64 self.write_config('BB_DISKMON_DIRS = "ABORT,${TMPDIR},100000G,100K"\nBB_HEARTBEAT_EVENT = "1"')
65 res = bitbake("delay -c delay", ignore_status = True) 65 res = bitbake("delay -c delay", ignore_status = True)
66 self.assertTrue('ERROR: Immediately abort since the disk space monitor action is "ABORT"!' in res.output, "Tasks should have been aborted immediatelly. Disk monitor is set to ABORT: %s" % res.output) 66 self.assertTrue('ERROR: Immediately abort since the disk space monitor action is "ABORT"!' in res.output, "Tasks should have been aborted immediatelly. Disk monitor is set to ABORT: %s" % res.output)
67 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output)) 67 self.assertEqual(res.status, 1, msg = "bitbake reported exit code %s. It should have been 1. Bitbake output: %s" % (str(res.status), res.output))
68 self.write_config('BB_DISKMON_DIRS = "WARN,${TMPDIR},100000G,100K"') 68 self.write_config('BB_DISKMON_DIRS = "WARN,${TMPDIR},100000G,100K"\nBB_HEARTBEAT_EVENT = "1"')
69 res = bitbake("delay -c delay") 69 res = bitbake("delay -c delay")
70 self.assertTrue('WARNING: The free space' in res.output, msg = "A warning should have been displayed for disk monitor is set to WARN: %s" %res.output) 70 self.assertTrue('WARNING: The free space' in res.output, msg = "A warning should have been displayed for disk monitor is set to WARN: %s" %res.output)
71 71
diff --git a/meta/lib/oeqa/selftest/cases/cve_check.py b/meta/lib/oeqa/selftest/cases/cve_check.py
index 3f343a2841..22ffeffd29 100644
--- a/meta/lib/oeqa/selftest/cases/cve_check.py
+++ b/meta/lib/oeqa/selftest/cases/cve_check.py
@@ -1,9 +1,13 @@
1from oe.cve_check import Version 1import json
2import os
2from oeqa.selftest.case import OESelftestTestCase 3from oeqa.selftest.case import OESelftestTestCase
4from oeqa.utils.commands import bitbake, get_bb_vars
3 5
4class CVECheck(OESelftestTestCase): 6class CVECheck(OESelftestTestCase):
5 7
6 def test_version_compare(self): 8 def test_version_compare(self):
9 from oe.cve_check import Version
10
7 result = Version("100") > Version("99") 11 result = Version("100") > Version("99")
8 self.assertTrue( result, msg="Failed to compare version '100' > '99'") 12 self.assertTrue( result, msg="Failed to compare version '100' > '99'")
9 result = Version("2.3.1") > Version("2.2.3") 13 result = Version("2.3.1") > Version("2.2.3")
@@ -34,3 +38,183 @@ class CVECheck(OESelftestTestCase):
34 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' < '1.0r'") 38 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' < '1.0r'")
35 result = Version("1.0b","alphabetical") > Version("1.0","alphabetical") 39 result = Version("1.0b","alphabetical") > Version("1.0","alphabetical")
36 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' > '1.0'") 40 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' > '1.0'")
41
42 # consider the trailing "p" and "patch" as patched released when comparing
43 result = Version("1.0","patch") < Version("1.0p1","patch")
44 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0' < '1.0p1'")
45 result = Version("1.0p2","patch") > Version("1.0p1","patch")
46 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0p2' > '1.0p1'")
47 result = Version("1.0_patch2","patch") < Version("1.0_patch3","patch")
48 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0_patch2' < '1.0_patch3'")
49
50
51 def test_convert_cve_version(self):
52 from oe.cve_check import convert_cve_version
53
54 # Default format
55 self.assertEqual(convert_cve_version("8.3"), "8.3")
56 self.assertEqual(convert_cve_version(""), "")
57
58 # OpenSSL format version
59 self.assertEqual(convert_cve_version("1.1.1t"), "1.1.1t")
60
61 # OpenSSH format
62 self.assertEqual(convert_cve_version("8.3_p1"), "8.3p1")
63 self.assertEqual(convert_cve_version("8.3_p22"), "8.3p22")
64
65 # Linux kernel format
66 self.assertEqual(convert_cve_version("6.2_rc8"), "6.2-rc8")
67 self.assertEqual(convert_cve_version("6.2_rc31"), "6.2-rc31")
68
69
70 def test_recipe_report_json(self):
71 config = """
72INHERIT += "cve-check"
73CVE_CHECK_FORMAT_JSON = "1"
74"""
75 self.write_config(config)
76
77 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
78 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
79 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "m4-native_cve.json")
80
81 try:
82 os.remove(summary_json)
83 os.remove(recipe_json)
84 except FileNotFoundError:
85 pass
86
87 bitbake("m4-native -c cve_check")
88
89 def check_m4_json(filename):
90 with open(filename) as f:
91 report = json.load(f)
92 self.assertEqual(report["version"], "1")
93 self.assertEqual(len(report["package"]), 1)
94 package = report["package"][0]
95 self.assertEqual(package["name"], "m4-native")
96 found_cves = { issue["id"]: issue["status"] for issue in package["issue"]}
97 self.assertIn("CVE-2008-1687", found_cves)
98 self.assertEqual(found_cves["CVE-2008-1687"], "Patched")
99
100 self.assertExists(summary_json)
101 check_m4_json(summary_json)
102 self.assertExists(recipe_json)
103 check_m4_json(recipe_json)
104
105
106 def test_image_json(self):
107 config = """
108INHERIT += "cve-check"
109CVE_CHECK_FORMAT_JSON = "1"
110"""
111 self.write_config(config)
112
113 vars = get_bb_vars(["CVE_CHECK_DIR", "CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
114 report_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
115 print(report_json)
116 try:
117 os.remove(report_json)
118 except FileNotFoundError:
119 pass
120
121 bitbake("core-image-minimal-initramfs")
122 self.assertExists(report_json)
123
124 # Check that the summary report lists at least one package
125 with open(report_json) as f:
126 report = json.load(f)
127 self.assertEqual(report["version"], "1")
128 self.assertGreater(len(report["package"]), 1)
129
130 # Check that a random recipe wrote a recipe report to deploy/cve/
131 recipename = report["package"][0]["name"]
132 recipe_report = os.path.join(vars["CVE_CHECK_DIR"], recipename + "_cve.json")
133 self.assertExists(recipe_report)
134 with open(recipe_report) as f:
135 report = json.load(f)
136 self.assertEqual(report["version"], "1")
137 self.assertEqual(len(report["package"]), 1)
138 self.assertEqual(report["package"][0]["name"], recipename)
139
140
141 def test_recipe_report_json_unpatched(self):
142 config = """
143INHERIT += "cve-check"
144CVE_CHECK_FORMAT_JSON = "1"
145CVE_CHECK_REPORT_PATCHED = "0"
146"""
147 self.write_config(config)
148
149 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
150 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
151 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "m4-native_cve.json")
152
153 try:
154 os.remove(summary_json)
155 os.remove(recipe_json)
156 except FileNotFoundError:
157 pass
158
159 bitbake("m4-native -c cve_check")
160
161 def check_m4_json(filename):
162 with open(filename) as f:
163 report = json.load(f)
164 self.assertEqual(report["version"], "1")
165 self.assertEqual(len(report["package"]), 1)
166 package = report["package"][0]
167 self.assertEqual(package["name"], "m4-native")
168 #m4 had only Patched CVEs, so the issues array will be empty
169 self.assertEqual(package["issue"], [])
170
171 self.assertExists(summary_json)
172 check_m4_json(summary_json)
173 self.assertExists(recipe_json)
174 check_m4_json(recipe_json)
175
176
177 def test_recipe_report_json_ignored(self):
178 config = """
179INHERIT += "cve-check"
180CVE_CHECK_FORMAT_JSON = "1"
181CVE_CHECK_REPORT_PATCHED = "1"
182"""
183 self.write_config(config)
184
185 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
186 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"])
187 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "logrotate_cve.json")
188
189 try:
190 os.remove(summary_json)
191 os.remove(recipe_json)
192 except FileNotFoundError:
193 pass
194
195 bitbake("logrotate -c cve_check")
196
197 def check_m4_json(filename):
198 with open(filename) as f:
199 report = json.load(f)
200 self.assertEqual(report["version"], "1")
201 self.assertEqual(len(report["package"]), 1)
202 package = report["package"][0]
203 self.assertEqual(package["name"], "logrotate")
204 found_cves = { issue["id"]: issue["status"] for issue in package["issue"]}
205 # m4 CVE should not be in logrotate
206 self.assertNotIn("CVE-2008-1687", found_cves)
207 # logrotate has both Patched and Ignored CVEs
208 self.assertIn("CVE-2011-1098", found_cves)
209 self.assertEqual(found_cves["CVE-2011-1098"], "Patched")
210 self.assertIn("CVE-2011-1548", found_cves)
211 self.assertEqual(found_cves["CVE-2011-1548"], "Ignored")
212 self.assertIn("CVE-2011-1549", found_cves)
213 self.assertEqual(found_cves["CVE-2011-1549"], "Ignored")
214 self.assertIn("CVE-2011-1550", found_cves)
215 self.assertEqual(found_cves["CVE-2011-1550"], "Ignored")
216
217 self.assertExists(summary_json)
218 check_m4_json(summary_json)
219 self.assertExists(recipe_json)
220 check_m4_json(recipe_json)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 0985434238..9efe342a0d 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -8,6 +8,7 @@ import shutil
8import tempfile 8import tempfile
9import glob 9import glob
10import fnmatch 10import fnmatch
11import unittest
11 12
12import oeqa.utils.ftools as ftools 13import oeqa.utils.ftools as ftools
13from oeqa.selftest.case import OESelftestTestCase 14from oeqa.selftest.case import OESelftestTestCase
@@ -38,6 +39,13 @@ def setUpModule():
38 canonical_layerpath = os.path.realpath(canonical_layerpath) + '/' 39 canonical_layerpath = os.path.realpath(canonical_layerpath) + '/'
39 edited_layers.append(layerpath) 40 edited_layers.append(layerpath)
40 oldmetapath = os.path.realpath(layerpath) 41 oldmetapath = os.path.realpath(layerpath)
42
43 # when downloading poky from tar.gz some tests will be skipped (BUG 12389)
44 try:
45 runCmd('git rev-parse --is-inside-work-tree', cwd=canonical_layerpath)
46 except:
47 raise unittest.SkipTest("devtool tests require folder to be a git repo")
48
41 result = runCmd('git rev-parse --show-toplevel', cwd=canonical_layerpath) 49 result = runCmd('git rev-parse --show-toplevel', cwd=canonical_layerpath)
42 oldreporoot = result.output.rstrip() 50 oldreporoot = result.output.rstrip()
43 newmetapath = os.path.join(corecopydir, os.path.relpath(oldmetapath, oldreporoot)) 51 newmetapath = os.path.join(corecopydir, os.path.relpath(oldmetapath, oldreporoot))
@@ -340,7 +348,7 @@ class DevtoolAddTests(DevtoolBase):
340 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263' 348 checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=b234ee4d69f5fce4486a80fdaf4a4263'
341 checkvars['S'] = '${WORKDIR}/git' 349 checkvars['S'] = '${WORKDIR}/git'
342 checkvars['PV'] = '0.1+git${SRCPV}' 350 checkvars['PV'] = '0.1+git${SRCPV}'
343 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https' 351 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https;branch=master'
344 checkvars['SRCREV'] = srcrev 352 checkvars['SRCREV'] = srcrev
345 checkvars['DEPENDS'] = set(['dbus']) 353 checkvars['DEPENDS'] = set(['dbus'])
346 self._test_recipe_contents(recipefile, checkvars, []) 354 self._test_recipe_contents(recipefile, checkvars, [])
@@ -442,6 +450,7 @@ class DevtoolAddTests(DevtoolBase):
442 tempdir = tempfile.mkdtemp(prefix='devtoolqa') 450 tempdir = tempfile.mkdtemp(prefix='devtoolqa')
443 self.track_for_cleanup(tempdir) 451 self.track_for_cleanup(tempdir)
444 url = 'gitsm://git.yoctoproject.org/mraa' 452 url = 'gitsm://git.yoctoproject.org/mraa'
453 url_branch = '%s;branch=master' % url
445 checkrev = 'ae127b19a50aa54255e4330ccfdd9a5d058e581d' 454 checkrev = 'ae127b19a50aa54255e4330ccfdd9a5d058e581d'
446 testrecipe = 'mraa' 455 testrecipe = 'mraa'
447 srcdir = os.path.join(tempdir, testrecipe) 456 srcdir = os.path.join(tempdir, testrecipe)
@@ -462,7 +471,7 @@ class DevtoolAddTests(DevtoolBase):
462 checkvars = {} 471 checkvars = {}
463 checkvars['S'] = '${WORKDIR}/git' 472 checkvars['S'] = '${WORKDIR}/git'
464 checkvars['PV'] = '1.0+git${SRCPV}' 473 checkvars['PV'] = '1.0+git${SRCPV}'
465 checkvars['SRC_URI'] = url 474 checkvars['SRC_URI'] = url_branch
466 checkvars['SRCREV'] = '${AUTOREV}' 475 checkvars['SRCREV'] = '${AUTOREV}'
467 self._test_recipe_contents(recipefile, checkvars, []) 476 self._test_recipe_contents(recipefile, checkvars, [])
468 # Try with revision and version specified 477 # Try with revision and version specified
@@ -481,7 +490,7 @@ class DevtoolAddTests(DevtoolBase):
481 checkvars = {} 490 checkvars = {}
482 checkvars['S'] = '${WORKDIR}/git' 491 checkvars['S'] = '${WORKDIR}/git'
483 checkvars['PV'] = '1.5+git${SRCPV}' 492 checkvars['PV'] = '1.5+git${SRCPV}'
484 checkvars['SRC_URI'] = url 493 checkvars['SRC_URI'] = url_branch
485 checkvars['SRCREV'] = checkrev 494 checkvars['SRCREV'] = checkrev
486 self._test_recipe_contents(recipefile, checkvars, []) 495 self._test_recipe_contents(recipefile, checkvars, [])
487 496
@@ -880,7 +889,7 @@ class DevtoolUpdateTests(DevtoolBase):
880 self._check_repo_status(os.path.dirname(recipefile), expected_status) 889 self._check_repo_status(os.path.dirname(recipefile), expected_status)
881 890
882 result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile)) 891 result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile))
883 addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git"'] 892 addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git;branch=master"']
884 srcurilines = src_uri.split() 893 srcurilines = src_uri.split()
885 srcurilines[0] = 'SRC_URI = "' + srcurilines[0] 894 srcurilines[0] = 'SRC_URI = "' + srcurilines[0]
886 srcurilines.append('"') 895 srcurilines.append('"')
@@ -1322,7 +1331,7 @@ class DevtoolExtractTests(DevtoolBase):
1322 # Now really test deploy-target 1331 # Now really test deploy-target
1323 result = runCmd('devtool deploy-target -c %s root@%s' % (testrecipe, qemu.ip)) 1332 result = runCmd('devtool deploy-target -c %s root@%s' % (testrecipe, qemu.ip))
1324 # Run a test command to see if it was installed properly 1333 # Run a test command to see if it was installed properly
1325 sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' 1334 sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa'
1326 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand)) 1335 result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
1327 # Check if it deployed all of the files with the right ownership/perms 1336 # Check if it deployed all of the files with the right ownership/perms
1328 # First look on the host - need to do this under pseudo to get the correct ownership/perms 1337 # First look on the host - need to do this under pseudo to get the correct ownership/perms
diff --git a/meta/lib/oeqa/selftest/cases/diffoscope/A/file.txt b/meta/lib/oeqa/selftest/cases/diffoscope/A/file.txt
new file mode 100644
index 0000000000..f70f10e4db
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/diffoscope/A/file.txt
@@ -0,0 +1 @@
A
diff --git a/meta/lib/oeqa/selftest/cases/diffoscope/B/file.txt b/meta/lib/oeqa/selftest/cases/diffoscope/B/file.txt
new file mode 100644
index 0000000000..223b7836fb
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/diffoscope/B/file.txt
@@ -0,0 +1 @@
B
diff --git a/meta/lib/oeqa/selftest/cases/distrodata.py b/meta/lib/oeqa/selftest/cases/distrodata.py
index e1cfc3b621..8e5e24db3d 100644
--- a/meta/lib/oeqa/selftest/cases/distrodata.py
+++ b/meta/lib/oeqa/selftest/cases/distrodata.py
@@ -63,7 +63,7 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re
63 return True 63 return True
64 return False 64 return False
65 65
66 feature = 'require conf/distro/include/maintainers.inc\nLICENSE_FLAGS_WHITELIST += " commercial"\nPARSE_ALL_RECIPES = "1"\n' 66 feature = 'require conf/distro/include/maintainers.inc\nLICENSE_FLAGS_WHITELIST += " commercial"\nPARSE_ALL_RECIPES = "1"\nPACKAGE_CLASSES = "package_ipk package_deb package_rpm"\n'
67 self.write_config(feature) 67 self.write_config(feature)
68 68
69 with bb.tinfoil.Tinfoil() as tinfoil: 69 with bb.tinfoil.Tinfoil() as tinfoil:
diff --git a/meta/lib/oeqa/selftest/cases/glibc.py b/meta/lib/oeqa/selftest/cases/glibc.py
index c687f6ef93..c1f6e4c1fb 100644
--- a/meta/lib/oeqa/selftest/cases/glibc.py
+++ b/meta/lib/oeqa/selftest/cases/glibc.py
@@ -33,7 +33,7 @@ class GlibcSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
33 33
34 ptestsuite = "glibc-user" if ssh is None else "glibc" 34 ptestsuite = "glibc-user" if ssh is None else "glibc"
35 self.ptest_section(ptestsuite) 35 self.ptest_section(ptestsuite)
36 with open(os.path.join(builddir, "tests.sum"), "r") as f: 36 with open(os.path.join(builddir, "tests.sum"), "r", errors='replace') as f:
37 for test, result in parse_values(f): 37 for test, result in parse_values(f):
38 self.ptest_result(ptestsuite, test, result) 38 self.ptest_result(ptestsuite, test, result)
39 39
@@ -41,7 +41,7 @@ class GlibcSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
41 with contextlib.ExitStack() as s: 41 with contextlib.ExitStack() as s:
42 # use the base work dir, as the nfs mount, since the recipe directory may not exist 42 # use the base work dir, as the nfs mount, since the recipe directory may not exist
43 tmpdir = get_bb_var("BASE_WORKDIR") 43 tmpdir = get_bb_var("BASE_WORKDIR")
44 nfsport, mountport = s.enter_context(unfs_server(tmpdir)) 44 nfsport, mountport = s.enter_context(unfs_server(tmpdir, udp = False))
45 45
46 # build core-image-minimal with required packages 46 # build core-image-minimal with required packages
47 default_installed_packages = [ 47 default_installed_packages = [
@@ -61,7 +61,7 @@ class GlibcSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
61 bitbake("core-image-minimal") 61 bitbake("core-image-minimal")
62 62
63 # start runqemu 63 # start runqemu
64 qemu = s.enter_context(runqemu("core-image-minimal", runqemuparams = "nographic")) 64 qemu = s.enter_context(runqemu("core-image-minimal", runqemuparams = "nographic", qemuparams = "-m 1024"))
65 65
66 # validate that SSH is working 66 # validate that SSH is working
67 status, _ = qemu.run("uname") 67 status, _ = qemu.run("uname")
@@ -70,7 +70,7 @@ class GlibcSelfTestBase(OESelftestTestCase, OEPTestResultTestCase):
70 # setup nfs mount 70 # setup nfs mount
71 if qemu.run("mkdir -p \"{0}\"".format(tmpdir))[0] != 0: 71 if qemu.run("mkdir -p \"{0}\"".format(tmpdir))[0] != 0:
72 raise Exception("Failed to setup NFS mount directory on target") 72 raise Exception("Failed to setup NFS mount directory on target")
73 mountcmd = "mount -o noac,nfsvers=3,port={0},udp,mountport={1} \"{2}:{3}\" \"{3}\"".format(nfsport, mountport, qemu.server_ip, tmpdir) 73 mountcmd = "mount -o noac,nfsvers=3,port={0},mountport={1} \"{2}:{3}\" \"{3}\"".format(nfsport, mountport, qemu.server_ip, tmpdir)
74 status, output = qemu.run(mountcmd) 74 status, output = qemu.run(mountcmd)
75 if status != 0: 75 if status != 0:
76 raise Exception("Failed to setup NFS mount on target ({})".format(repr(output))) 76 raise Exception("Failed to setup NFS mount on target ({})".format(repr(output)))
diff --git a/meta/lib/oeqa/selftest/cases/gotoolchain.py b/meta/lib/oeqa/selftest/cases/gotoolchain.py
index 3119520f0d..59f80aad28 100644
--- a/meta/lib/oeqa/selftest/cases/gotoolchain.py
+++ b/meta/lib/oeqa/selftest/cases/gotoolchain.py
@@ -43,6 +43,12 @@ class oeGoToolchainSelfTest(OESelftestTestCase):
43 43
44 @classmethod 44 @classmethod
45 def tearDownClass(cls): 45 def tearDownClass(cls):
46 # Go creates file which are readonly
47 for dirpath, dirnames, filenames in os.walk(cls.tmpdir_SDKQA):
48 for filename in filenames + dirnames:
49 f = os.path.join(dirpath, filename)
50 if not os.path.islink(f):
51 os.chmod(f, 0o775)
46 shutil.rmtree(cls.tmpdir_SDKQA, ignore_errors=True) 52 shutil.rmtree(cls.tmpdir_SDKQA, ignore_errors=True)
47 super(oeGoToolchainSelfTest, cls).tearDownClass() 53 super(oeGoToolchainSelfTest, cls).tearDownClass()
48 54
diff --git a/meta/lib/oeqa/selftest/cases/imagefeatures.py b/meta/lib/oeqa/selftest/cases/imagefeatures.py
index 2b9c4998f7..535d80cb86 100644
--- a/meta/lib/oeqa/selftest/cases/imagefeatures.py
+++ b/meta/lib/oeqa/selftest/cases/imagefeatures.py
@@ -240,7 +240,7 @@ USERADD_GID_TABLES += "files/static-group"
240 def test_no_busybox_base_utils(self): 240 def test_no_busybox_base_utils(self):
241 config = """ 241 config = """
242# Enable x11 242# Enable x11
243DISTRO_FEATURES_append += "x11" 243DISTRO_FEATURES_append = " x11"
244 244
245# Switch to systemd 245# Switch to systemd
246DISTRO_FEATURES += "systemd" 246DISTRO_FEATURES += "systemd"
diff --git a/meta/lib/oeqa/selftest/cases/oelib/utils.py b/meta/lib/oeqa/selftest/cases/oelib/utils.py
index a7214beb4c..bbf67bf9c9 100644
--- a/meta/lib/oeqa/selftest/cases/oelib/utils.py
+++ b/meta/lib/oeqa/selftest/cases/oelib/utils.py
@@ -64,7 +64,7 @@ class TestMultiprocessLaunch(TestCase):
64 import bb 64 import bb
65 65
66 def testfunction(item, d): 66 def testfunction(item, d):
67 if item == "2" or item == "1": 67 if item == "2":
68 raise KeyError("Invalid number %s" % item) 68 raise KeyError("Invalid number %s" % item)
69 return "Found %s" % item 69 return "Found %s" % item
70 70
@@ -99,5 +99,4 @@ class TestMultiprocessLaunch(TestCase):
99 # Assert the function prints exceptions 99 # Assert the function prints exceptions
100 with captured_output() as (out, err): 100 with captured_output() as (out, err):
101 self.assertRaises(bb.BBHandledException, multiprocess_launch, testfunction, ["1", "2", "3", "4", "5", "6"], d, extraargs=(d,)) 101 self.assertRaises(bb.BBHandledException, multiprocess_launch, testfunction, ["1", "2", "3", "4", "5", "6"], d, extraargs=(d,))
102 self.assertIn("KeyError: 'Invalid number 1'", out.getvalue())
103 self.assertIn("KeyError: 'Invalid number 2'", out.getvalue()) 102 self.assertIn("KeyError: 'Invalid number 2'", out.getvalue())
diff --git a/meta/lib/oeqa/selftest/cases/oescripts.py b/meta/lib/oeqa/selftest/cases/oescripts.py
index 726daff7c6..fb99be447e 100644
--- a/meta/lib/oeqa/selftest/cases/oescripts.py
+++ b/meta/lib/oeqa/selftest/cases/oescripts.py
@@ -133,7 +133,8 @@ class OEListPackageconfigTests(OEScriptTests):
133 def check_endlines(self, results, expected_endlines): 133 def check_endlines(self, results, expected_endlines):
134 for line in results.output.splitlines(): 134 for line in results.output.splitlines():
135 for el in expected_endlines: 135 for el in expected_endlines:
136 if line.split() == el.split(): 136 if line and line.split()[0] == el.split()[0] and \
137 ' '.join(sorted(el.split())) in ' '.join(sorted(line.split())):
137 expected_endlines.remove(el) 138 expected_endlines.remove(el)
138 break 139 break
139 140
diff --git a/meta/lib/oeqa/selftest/cases/prservice.py b/meta/lib/oeqa/selftest/cases/prservice.py
index 578b2b4dd9..fdc1e40058 100644
--- a/meta/lib/oeqa/selftest/cases/prservice.py
+++ b/meta/lib/oeqa/selftest/cases/prservice.py
@@ -75,7 +75,7 @@ class BitbakePrTests(OESelftestTestCase):
75 exported_db_path = os.path.join(self.builddir, 'export.inc') 75 exported_db_path = os.path.join(self.builddir, 'export.inc')
76 export_result = runCmd("bitbake-prserv-tool export %s" % exported_db_path, ignore_status=True) 76 export_result = runCmd("bitbake-prserv-tool export %s" % exported_db_path, ignore_status=True)
77 self.assertEqual(export_result.status, 0, msg="PR Service database export failed: %s" % export_result.output) 77 self.assertEqual(export_result.status, 0, msg="PR Service database export failed: %s" % export_result.output)
78 self.assertTrue(os.path.exists(exported_db_path)) 78 self.assertTrue(os.path.exists(exported_db_path), msg="%s didn't exist, tool output %s" % (exported_db_path, export_result.output))
79 79
80 if replace_current_db: 80 if replace_current_db:
81 current_db_path = os.path.join(get_bb_var('PERSISTENT_DIR'), 'prserv.sqlite3') 81 current_db_path = os.path.join(get_bb_var('PERSISTENT_DIR'), 'prserv.sqlite3')
diff --git a/meta/lib/oeqa/selftest/cases/recipetool.py b/meta/lib/oeqa/selftest/cases/recipetool.py
index c2ade2543a..e8aeea3023 100644
--- a/meta/lib/oeqa/selftest/cases/recipetool.py
+++ b/meta/lib/oeqa/selftest/cases/recipetool.py
@@ -370,7 +370,7 @@ class RecipetoolTests(RecipetoolBase):
370 tempsrc = os.path.join(self.tempdir, 'srctree') 370 tempsrc = os.path.join(self.tempdir, 'srctree')
371 os.makedirs(tempsrc) 371 os.makedirs(tempsrc)
372 recipefile = os.path.join(self.tempdir, 'libmatchbox.bb') 372 recipefile = os.path.join(self.tempdir, 'libmatchbox.bb')
373 srcuri = 'git://git.yoctoproject.org/libmatchbox' 373 srcuri = 'git://git.yoctoproject.org/libmatchbox;branch=master'
374 result = runCmd(['recipetool', 'create', '-o', recipefile, srcuri + ";rev=9f7cf8895ae2d39c465c04cc78e918c157420269", '-x', tempsrc]) 374 result = runCmd(['recipetool', 'create', '-o', recipefile, srcuri + ";rev=9f7cf8895ae2d39c465c04cc78e918c157420269", '-x', tempsrc])
375 self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output) 375 self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output)
376 checkvars = {} 376 checkvars = {}
@@ -456,7 +456,7 @@ class RecipetoolTests(RecipetoolBase):
456 self.assertTrue(os.path.isfile(recipefile)) 456 self.assertTrue(os.path.isfile(recipefile))
457 checkvars = {} 457 checkvars = {}
458 checkvars['LICENSE'] = set(['Apache-2.0']) 458 checkvars['LICENSE'] = set(['Apache-2.0'])
459 checkvars['SRC_URI'] = 'git://github.com/mesonbuild/meson;protocol=https' 459 checkvars['SRC_URI'] = 'git://github.com/mesonbuild/meson;protocol=https;branch=master'
460 inherits = ['setuptools3'] 460 inherits = ['setuptools3']
461 self._test_recipe_contents(recipefile, checkvars, inherits) 461 self._test_recipe_contents(recipefile, checkvars, inherits)
462 462
@@ -523,7 +523,7 @@ class RecipetoolTests(RecipetoolBase):
523 self.assertTrue(os.path.isfile(recipefile)) 523 self.assertTrue(os.path.isfile(recipefile))
524 checkvars = {} 524 checkvars = {}
525 checkvars['LICENSE'] = set(['GPLv2']) 525 checkvars['LICENSE'] = set(['GPLv2'])
526 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/matchbox-terminal;protocol=http' 526 checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/matchbox-terminal;protocol=http;branch=master'
527 inherits = ['pkgconfig', 'autotools'] 527 inherits = ['pkgconfig', 'autotools']
528 self._test_recipe_contents(recipefile, checkvars, inherits) 528 self._test_recipe_contents(recipefile, checkvars, inherits)
529 529
diff --git a/meta/lib/oeqa/selftest/cases/reproducible.py b/meta/lib/oeqa/selftest/cases/reproducible.py
index d4800022df..be4cdcc429 100644
--- a/meta/lib/oeqa/selftest/cases/reproducible.py
+++ b/meta/lib/oeqa/selftest/cases/reproducible.py
@@ -17,6 +17,57 @@ import stat
17import os 17import os
18import datetime 18import datetime
19 19
20# For sample packages, see:
21# https://autobuilder.yocto.io/pub/repro-fail/oe-reproducible-20201127-0t7wr_oo/
22# https://autobuilder.yocto.io/pub/repro-fail/oe-reproducible-20201127-4s9ejwyp/
23# https://autobuilder.yocto.io/pub/repro-fail/oe-reproducible-20201127-haiwdlbr/
24# https://autobuilder.yocto.io/pub/repro-fail/oe-reproducible-20201127-hwds3mcl/
25# https://autobuilder.yocto.io/pub/repro-fail/oe-reproducible-20201203-sua0pzvc/
26# (both packages/ and packages-excluded/)
27exclude_packages = [
28 'acpica-src',
29 'babeltrace2-ptest',
30 'bind',
31 'bootchart2-doc',
32 'epiphany',
33 'gcr',
34 'glide',
35 'go-dep',
36 'go-helloworld',
37 'go-runtime',
38 'go_',
39 'gstreamer1.0-python',
40 'hwlatdetect',
41 'kernel-devsrc',
42 'libcap-ng',
43 'libjson',
44 'libproxy',
45 'lttng-tools-dbg',
46 'lttng-tools-ptest',
47 'ltp',
48 'ovmf-shell-efi',
49 'parted-ptest',
50 'perf',
51 'piglit',
52 'pybootchartgui',
53 'qemu',
54 'quilt-ptest',
55 'rsync',
56 'ruby',
57 'stress-ng',
58 'systemd-bootchart',
59 'systemtap',
60 'valgrind-ptest',
61 'webkitgtk',
62 ]
63
64def is_excluded(package):
65 package_name = os.path.basename(package)
66 for i in exclude_packages:
67 if package_name.startswith(i):
68 return i
69 return None
70
20MISSING = 'MISSING' 71MISSING = 'MISSING'
21DIFFERENT = 'DIFFERENT' 72DIFFERENT = 'DIFFERENT'
22SAME = 'SAME' 73SAME = 'SAME'
@@ -39,14 +90,21 @@ class PackageCompareResults(object):
39 self.total = [] 90 self.total = []
40 self.missing = [] 91 self.missing = []
41 self.different = [] 92 self.different = []
93 self.different_excluded = []
42 self.same = [] 94 self.same = []
95 self.active_exclusions = set()
43 96
44 def add_result(self, r): 97 def add_result(self, r):
45 self.total.append(r) 98 self.total.append(r)
46 if r.status == MISSING: 99 if r.status == MISSING:
47 self.missing.append(r) 100 self.missing.append(r)
48 elif r.status == DIFFERENT: 101 elif r.status == DIFFERENT:
49 self.different.append(r) 102 exclusion = is_excluded(r.reference)
103 if exclusion:
104 self.different_excluded.append(r)
105 self.active_exclusions.add(exclusion)
106 else:
107 self.different.append(r)
50 else: 108 else:
51 self.same.append(r) 109 self.same.append(r)
52 110
@@ -54,10 +112,14 @@ class PackageCompareResults(object):
54 self.total.sort() 112 self.total.sort()
55 self.missing.sort() 113 self.missing.sort()
56 self.different.sort() 114 self.different.sort()
115 self.different_excluded.sort()
57 self.same.sort() 116 self.same.sort()
58 117
59 def __str__(self): 118 def __str__(self):
60 return 'same=%i different=%i missing=%i total=%i' % (len(self.same), len(self.different), len(self.missing), len(self.total)) 119 return 'same=%i different=%i different_excluded=%i missing=%i total=%i\nunused_exclusions=%s' % (len(self.same), len(self.different), len(self.different_excluded), len(self.missing), len(self.total), self.unused_exclusions())
120
121 def unused_exclusions(self):
122 return sorted(set(exclude_packages) - self.active_exclusions)
61 123
62def compare_file(reference, test, diffutils_sysroot): 124def compare_file(reference, test, diffutils_sysroot):
63 result = CompareResult() 125 result = CompareResult()
@@ -68,7 +130,7 @@ def compare_file(reference, test, diffutils_sysroot):
68 result.status = MISSING 130 result.status = MISSING
69 return result 131 return result
70 132
71 r = runCmd(['cmp', '--quiet', reference, test], native_sysroot=diffutils_sysroot, ignore_status=True) 133 r = runCmd(['cmp', '--quiet', reference, test], native_sysroot=diffutils_sysroot, ignore_status=True, sync=False)
72 134
73 if r.status: 135 if r.status:
74 result.status = DIFFERENT 136 result.status = DIFFERENT
@@ -77,9 +139,41 @@ def compare_file(reference, test, diffutils_sysroot):
77 result.status = SAME 139 result.status = SAME
78 return result 140 return result
79 141
142def run_diffoscope(a_dir, b_dir, html_dir, **kwargs):
143 return runCmd(['diffoscope', '--no-default-limits', '--exclude-directory-metadata', 'yes', '--html-dir', html_dir, a_dir, b_dir],
144 **kwargs)
145
146class DiffoscopeTests(OESelftestTestCase):
147 diffoscope_test_files = os.path.join(os.path.dirname(os.path.abspath(__file__)), "diffoscope")
148
149 def test_diffoscope(self):
150 bitbake("diffoscope-native -c addto_recipe_sysroot")
151 diffoscope_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "diffoscope-native")
152
153 # Check that diffoscope doesn't return an error when the files compare
154 # the same (a general check that diffoscope is working)
155 with tempfile.TemporaryDirectory() as tmpdir:
156 run_diffoscope('A', 'A', tmpdir,
157 native_sysroot=diffoscope_sysroot, cwd=self.diffoscope_test_files)
158
159 # Check that diffoscope generates an index.html file when the files are
160 # different
161 with tempfile.TemporaryDirectory() as tmpdir:
162 r = run_diffoscope('A', 'B', tmpdir,
163 native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=self.diffoscope_test_files)
164
165 self.assertNotEqual(r.status, 0, msg="diffoscope was successful when an error was expected")
166 self.assertTrue(os.path.exists(os.path.join(tmpdir, 'index.html')), "HTML index not found!")
167
80class ReproducibleTests(OESelftestTestCase): 168class ReproducibleTests(OESelftestTestCase):
169 # Test the reproducibility of whatever is built between sstate_targets and targets
170
81 package_classes = ['deb', 'ipk'] 171 package_classes = ['deb', 'ipk']
82 images = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline'] 172
173 # targets are the things we want to test the reproducibility of
174 targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'world']
175 # sstate targets are things to pull from sstate to potentially cut build/debugging time
176 sstate_targets = []
83 save_results = False 177 save_results = False
84 if 'OEQA_DEBUGGING_SAVED_OUTPUT' in os.environ: 178 if 'OEQA_DEBUGGING_SAVED_OUTPUT' in os.environ:
85 save_results = os.environ['OEQA_DEBUGGING_SAVED_OUTPUT'] 179 save_results = os.environ['OEQA_DEBUGGING_SAVED_OUTPUT']
@@ -94,7 +188,7 @@ class ReproducibleTests(OESelftestTestCase):
94 188
95 def setUpLocal(self): 189 def setUpLocal(self):
96 super().setUpLocal() 190 super().setUpLocal()
97 needed_vars = ['TOPDIR', 'TARGET_PREFIX', 'BB_NUMBER_THREADS'] 191 needed_vars = ['TOPDIR', 'TARGET_PREFIX', 'BB_NUMBER_THREADS', 'BB_HASHSERVE']
98 bb_vars = get_bb_vars(needed_vars) 192 bb_vars = get_bb_vars(needed_vars)
99 for v in needed_vars: 193 for v in needed_vars:
100 setattr(self, v.lower(), bb_vars[v]) 194 setattr(self, v.lower(), bb_vars[v])
@@ -150,21 +244,29 @@ class ReproducibleTests(OESelftestTestCase):
150 PACKAGE_CLASSES = "{package_classes}" 244 PACKAGE_CLASSES = "{package_classes}"
151 INHIBIT_PACKAGE_STRIP = "1" 245 INHIBIT_PACKAGE_STRIP = "1"
152 TMPDIR = "{tmpdir}" 246 TMPDIR = "{tmpdir}"
247 LICENSE_FLAGS_WHITELIST = "commercial"
248 DISTRO_FEATURES_append = ' systemd pam'
153 ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes), 249 ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes),
154 tmpdir=tmpdir) 250 tmpdir=tmpdir)
155 251
156 if not use_sstate: 252 if not use_sstate:
253 if self.sstate_targets:
254 self.logger.info("Building prebuild for %s (sstate allowed)..." % (name))
255 self.write_config(config)
256 bitbake(' '.join(self.sstate_targets))
257
157 # This config fragment will disable using shared and the sstate 258 # This config fragment will disable using shared and the sstate
158 # mirror, forcing a complete build from scratch 259 # mirror, forcing a complete build from scratch
159 config += textwrap.dedent('''\ 260 config += textwrap.dedent('''\
160 SSTATE_DIR = "${TMPDIR}/sstate" 261 SSTATE_DIR = "${TMPDIR}/sstate"
161 SSTATE_MIRRORS = "" 262 SSTATE_MIRRORS = "file://.*/.*-native.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH file://.*/.*-cross.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH"
162 ''') 263 ''')
163 264
164 self.logger.info("Building %s (sstate%s allowed)..." % (name, '' if use_sstate else ' NOT')) 265 self.logger.info("Building %s (sstate%s allowed)..." % (name, '' if use_sstate else ' NOT'))
165 self.write_config(config) 266 self.write_config(config)
166 d = get_bb_vars(capture_vars) 267 d = get_bb_vars(capture_vars)
167 bitbake(' '.join(self.images)) 268 # targets used to be called images
269 bitbake(' '.join(getattr(self, 'images', self.targets)))
168 return d 270 return d
169 271
170 def test_reproducible_builds(self): 272 def test_reproducible_builds(self):
@@ -212,6 +314,7 @@ class ReproducibleTests(OESelftestTestCase):
212 314
213 self.write_package_list(package_class, 'missing', result.missing) 315 self.write_package_list(package_class, 'missing', result.missing)
214 self.write_package_list(package_class, 'different', result.different) 316 self.write_package_list(package_class, 'different', result.different)
317 self.write_package_list(package_class, 'different_excluded', result.different_excluded)
215 self.write_package_list(package_class, 'same', result.same) 318 self.write_package_list(package_class, 'same', result.same)
216 319
217 if self.save_results: 320 if self.save_results:
@@ -219,8 +322,12 @@ class ReproducibleTests(OESelftestTestCase):
219 self.copy_file(d.reference, '/'.join([save_dir, 'packages', strip_topdir(d.reference)])) 322 self.copy_file(d.reference, '/'.join([save_dir, 'packages', strip_topdir(d.reference)]))
220 self.copy_file(d.test, '/'.join([save_dir, 'packages', strip_topdir(d.test)])) 323 self.copy_file(d.test, '/'.join([save_dir, 'packages', strip_topdir(d.test)]))
221 324
325 for d in result.different_excluded:
326 self.copy_file(d.reference, '/'.join([save_dir, 'packages-excluded', strip_topdir(d.reference)]))
327 self.copy_file(d.test, '/'.join([save_dir, 'packages-excluded', strip_topdir(d.test)]))
328
222 if result.missing or result.different: 329 if result.missing or result.different:
223 fails.append("The following %s packages are missing or different: %s" % 330 fails.append("The following %s packages are missing or different and not in exclusion list: %s" %
224 (c, '\n'.join(r.test for r in (result.missing + result.different)))) 331 (c, '\n'.join(r.test for r in (result.missing + result.different))))
225 332
226 # Clean up empty directories 333 # Clean up empty directories
@@ -235,7 +342,7 @@ class ReproducibleTests(OESelftestTestCase):
235 # Copy jquery to improve the diffoscope output usability 342 # Copy jquery to improve the diffoscope output usability
236 self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js')) 343 self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js'))
237 344
238 runCmd(['diffoscope', '--no-default-limits', '--exclude-directory-metadata', '--html-dir', package_html_dir, 'reproducibleA', 'reproducibleB'], 345 run_diffoscope('reproducibleA', 'reproducibleB', package_html_dir,
239 native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir) 346 native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir)
240 347
241 if fails: 348 if fails:
diff --git a/meta/lib/oeqa/selftest/cases/runcmd.py b/meta/lib/oeqa/selftest/cases/runcmd.py
index fa6113d7fa..e9612389fe 100644
--- a/meta/lib/oeqa/selftest/cases/runcmd.py
+++ b/meta/lib/oeqa/selftest/cases/runcmd.py
@@ -27,8 +27,8 @@ class RunCmdTests(OESelftestTestCase):
27 27
28 # The delta is intentionally smaller than the timeout, to detect cases where 28 # The delta is intentionally smaller than the timeout, to detect cases where
29 # we incorrectly apply the timeout more than once. 29 # we incorrectly apply the timeout more than once.
30 TIMEOUT = 5 30 TIMEOUT = 10
31 DELTA = 3 31 DELTA = 8
32 32
33 def test_result_okay(self): 33 def test_result_okay(self):
34 result = runCmd("true") 34 result = runCmd("true")
diff --git a/meta/lib/oeqa/selftest/cases/runqemu.py b/meta/lib/oeqa/selftest/cases/runqemu.py
index 7e676bcb41..da22f77b27 100644
--- a/meta/lib/oeqa/selftest/cases/runqemu.py
+++ b/meta/lib/oeqa/selftest/cases/runqemu.py
@@ -163,12 +163,11 @@ class QemuTest(OESelftestTestCase):
163 bitbake(cls.recipe) 163 bitbake(cls.recipe)
164 164
165 def _start_qemu_shutdown_check_if_shutdown_succeeded(self, qemu, timeout): 165 def _start_qemu_shutdown_check_if_shutdown_succeeded(self, qemu, timeout):
166 # Allow the runner's LoggingThread instance to exit without errors
167 # (such as the exception "Console connection closed unexpectedly")
168 # as qemu will disappear when we shut it down
169 qemu.runner.allowexit()
166 qemu.run_serial("shutdown -h now") 170 qemu.run_serial("shutdown -h now")
167 # Stop thread will stop the LoggingThread instance used for logging
168 # qemu through serial console, stop thread will prevent this code
169 # from facing exception (Console connection closed unexpectedly)
170 # when qemu was shutdown by the above shutdown command
171 qemu.runner.stop_thread()
172 time_track = 0 171 time_track = 0
173 try: 172 try:
174 while True: 173 while True:
diff --git a/meta/lib/oeqa/selftest/cases/runtime_test.py b/meta/lib/oeqa/selftest/cases/runtime_test.py
index 976b513727..cc4190c1d6 100644
--- a/meta/lib/oeqa/selftest/cases/runtime_test.py
+++ b/meta/lib/oeqa/selftest/cases/runtime_test.py
@@ -14,11 +14,6 @@ from oeqa.core.decorator.data import skipIfNotQemu
14 14
15class TestExport(OESelftestTestCase): 15class TestExport(OESelftestTestCase):
16 16
17 @classmethod
18 def tearDownClass(cls):
19 runCmd("rm -rf /tmp/sdk")
20 super(TestExport, cls).tearDownClass()
21
22 def test_testexport_basic(self): 17 def test_testexport_basic(self):
23 """ 18 """
24 Summary: Check basic testexport functionality with only ping test enabled. 19 Summary: Check basic testexport functionality with only ping test enabled.
@@ -95,19 +90,20 @@ class TestExport(OESelftestTestCase):
95 msg = "Couldn't find SDK tarball: %s" % tarball_path 90 msg = "Couldn't find SDK tarball: %s" % tarball_path
96 self.assertEqual(os.path.isfile(tarball_path), True, msg) 91 self.assertEqual(os.path.isfile(tarball_path), True, msg)
97 92
98 # Extract SDK and run tar from SDK 93 with tempfile.TemporaryDirectory() as tmpdirname:
99 result = runCmd("%s -y -d /tmp/sdk" % tarball_path) 94 # Extract SDK and run tar from SDK
100 self.assertEqual(0, result.status, "Couldn't extract SDK") 95 result = runCmd("%s -y -d %s" % (tarball_path, tmpdirname))
96 self.assertEqual(0, result.status, "Couldn't extract SDK")
101 97
102 env_script = result.output.split()[-1] 98 env_script = result.output.split()[-1]
103 result = runCmd(". %s; which tar" % env_script, shell=True) 99 result = runCmd(". %s; which tar" % env_script, shell=True)
104 self.assertEqual(0, result.status, "Couldn't setup SDK environment") 100 self.assertEqual(0, result.status, "Couldn't setup SDK environment")
105 is_sdk_tar = True if "/tmp/sdk" in result.output else False 101 is_sdk_tar = True if tmpdirname in result.output else False
106 self.assertTrue(is_sdk_tar, "Couldn't setup SDK environment") 102 self.assertTrue(is_sdk_tar, "Couldn't setup SDK environment")
107 103
108 tar_sdk = result.output 104 tar_sdk = result.output
109 result = runCmd("%s --version" % tar_sdk) 105 result = runCmd("%s --version" % tar_sdk)
110 self.assertEqual(0, result.status, "Couldn't run tar from SDK") 106 self.assertEqual(0, result.status, "Couldn't run tar from SDK")
111 107
112 108
113class TestImage(OESelftestTestCase): 109class TestImage(OESelftestTestCase):
@@ -179,12 +175,24 @@ class TestImage(OESelftestTestCase):
179 if "DISPLAY" not in os.environ: 175 if "DISPLAY" not in os.environ:
180 self.skipTest("virgl gtk test must be run inside a X session") 176 self.skipTest("virgl gtk test must be run inside a X session")
181 distro = oe.lsb.distro_identifier() 177 distro = oe.lsb.distro_identifier()
178 if distro and distro.startswith('almalinux'):
179 self.skipTest('virgl isn\'t working with Alma Linux')
180 if distro and distro.startswith('rocky'):
181 self.skipTest('virgl isn\'t working with Rocky Linux')
182 if distro and distro == 'debian-8': 182 if distro and distro == 'debian-8':
183 self.skipTest('virgl isn\'t working with Debian 8') 183 self.skipTest('virgl isn\'t working with Debian 8')
184 if distro and distro == 'centos-7': 184 if distro and distro == 'centos-7':
185 self.skipTest('virgl isn\'t working with Centos 7') 185 self.skipTest('virgl isn\'t working with Centos 7')
186 if distro and distro == 'centos-8':
187 self.skipTest('virgl isn\'t working with Centos 8')
188 if distro and distro.startswith('fedora'):
189 self.skipTest('virgl isn\'t working with Fedora')
186 if distro and distro == 'opensuseleap-15.0': 190 if distro and distro == 'opensuseleap-15.0':
187 self.skipTest('virgl isn\'t working with Opensuse 15.0') 191 self.skipTest('virgl isn\'t working with Opensuse 15.0')
192 if distro and distro == 'ubuntu-22.04':
193 self.skipTest('virgl isn\'t working with Ubuntu 22.04')
194 if distro and distro == 'ubuntu-22.10':
195 self.skipTest('virgl isn\'t working with Ubuntu 22.10')
188 196
189 qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native') 197 qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native')
190 sdl_packageconfig = get_bb_var('PACKAGECONFIG', 'libsdl2-native') 198 sdl_packageconfig = get_bb_var('PACKAGECONFIG', 'libsdl2-native')
@@ -220,6 +228,7 @@ class TestImage(OESelftestTestCase):
220 Author: Alexander Kanavin <alex.kanavin@gmail.com> 228 Author: Alexander Kanavin <alex.kanavin@gmail.com>
221 """ 229 """
222 import subprocess, os 230 import subprocess, os
231 self.skipTest("Crashes in mesa observed with this test on dunfell: https://bugzilla.yoctoproject.org/show_bug.cgi?id=14527")
223 try: 232 try:
224 content = os.listdir("/dev/dri") 233 content = os.listdir("/dev/dri")
225 if len([i for i in content if i.startswith('render')]) == 0: 234 if len([i for i in content if i.startswith('render')]) == 0:
@@ -227,7 +236,7 @@ class TestImage(OESelftestTestCase):
227 except FileNotFoundError: 236 except FileNotFoundError:
228 self.skipTest("/dev/dri directory does not exist; no render nodes available on this machine.") 237 self.skipTest("/dev/dri directory does not exist; no render nodes available on this machine.")
229 try: 238 try:
230 dripath = subprocess.check_output("pkg-config --variable=dridriverdir dri", shell=True) 239 dripath = subprocess.check_output("PATH=/bin:/usr/bin:$PATH pkg-config --variable=dridriverdir dri", shell=True)
231 except subprocess.CalledProcessError as e: 240 except subprocess.CalledProcessError as e:
232 self.skipTest("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.") 241 self.skipTest("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.")
233 qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native') 242 qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native')
diff --git a/meta/lib/oeqa/selftest/cases/sstatetests.py b/meta/lib/oeqa/selftest/cases/sstatetests.py
index c46e8ba489..1bfe88c87d 100644
--- a/meta/lib/oeqa/selftest/cases/sstatetests.py
+++ b/meta/lib/oeqa/selftest/cases/sstatetests.py
@@ -39,7 +39,7 @@ class SStateTests(SStateBase):
39 39
40 recipefile = os.path.join(tempdir, "recipes-test", "dbus-wait-test", 'dbus-wait-test_git.bb') 40 recipefile = os.path.join(tempdir, "recipes-test", "dbus-wait-test", 'dbus-wait-test_git.bb')
41 os.makedirs(os.path.dirname(recipefile)) 41 os.makedirs(os.path.dirname(recipefile))
42 srcuri = 'git://' + srcdir + ';protocol=file' 42 srcuri = 'git://' + srcdir + ';protocol=file;branch=master'
43 result = runCmd(['recipetool', 'create', '-o', recipefile, srcuri]) 43 result = runCmd(['recipetool', 'create', '-o', recipefile, srcuri])
44 self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output) 44 self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output)
45 45
@@ -137,7 +137,7 @@ class SStateTests(SStateBase):
137 filtered_results.append(r) 137 filtered_results.append(r)
138 self.assertTrue(filtered_results == [], msg="Found distro non-specific sstate for: %s (%s)" % (', '.join(map(str, targets)), str(filtered_results))) 138 self.assertTrue(filtered_results == [], msg="Found distro non-specific sstate for: %s (%s)" % (', '.join(map(str, targets)), str(filtered_results)))
139 file_tracker_1 = self.search_sstate('|'.join(map(str, [s + r'.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False) 139 file_tracker_1 = self.search_sstate('|'.join(map(str, [s + r'.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False)
140 self.assertTrue(len(file_tracker_1) >= len(targets), msg = "Not all sstate files ware created for: %s" % ', '.join(map(str, targets))) 140 self.assertTrue(len(file_tracker_1) >= len(targets), msg = "Not all sstate files were created for: %s" % ', '.join(map(str, targets)))
141 141
142 self.track_for_cleanup(self.distro_specific_sstate + "_old") 142 self.track_for_cleanup(self.distro_specific_sstate + "_old")
143 shutil.copytree(self.distro_specific_sstate, self.distro_specific_sstate + "_old") 143 shutil.copytree(self.distro_specific_sstate, self.distro_specific_sstate + "_old")
@@ -146,13 +146,13 @@ class SStateTests(SStateBase):
146 bitbake(['-cclean'] + targets) 146 bitbake(['-cclean'] + targets)
147 bitbake(targets) 147 bitbake(targets)
148 file_tracker_2 = self.search_sstate('|'.join(map(str, [s + r'.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False) 148 file_tracker_2 = self.search_sstate('|'.join(map(str, [s + r'.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False)
149 self.assertTrue(len(file_tracker_2) >= len(targets), msg = "Not all sstate files ware created for: %s" % ', '.join(map(str, targets))) 149 self.assertTrue(len(file_tracker_2) >= len(targets), msg = "Not all sstate files were created for: %s" % ', '.join(map(str, targets)))
150 150
151 not_recreated = [x for x in file_tracker_1 if x not in file_tracker_2] 151 not_recreated = [x for x in file_tracker_1 if x not in file_tracker_2]
152 self.assertTrue(not_recreated == [], msg="The following sstate files ware not recreated: %s" % ', '.join(map(str, not_recreated))) 152 self.assertTrue(not_recreated == [], msg="The following sstate files were not recreated: %s" % ', '.join(map(str, not_recreated)))
153 153
154 created_once = [x for x in file_tracker_2 if x not in file_tracker_1] 154 created_once = [x for x in file_tracker_2 if x not in file_tracker_1]
155 self.assertTrue(created_once == [], msg="The following sstate files ware created only in the second run: %s" % ', '.join(map(str, created_once))) 155 self.assertTrue(created_once == [], msg="The following sstate files were created only in the second run: %s" % ', '.join(map(str, created_once)))
156 156
157 def test_rebuild_distro_specific_sstate_cross_native_targets(self): 157 def test_rebuild_distro_specific_sstate_cross_native_targets(self):
158 self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch, 'binutils-native'], temp_sstate_location=True) 158 self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch, 'binutils-native'], temp_sstate_location=True)
@@ -202,9 +202,9 @@ class SStateTests(SStateBase):
202 actual_remaining_sstate = [x for x in self.search_sstate(target + r'.*?\.tgz$') if not any(pattern in x for pattern in ignore_patterns)] 202 actual_remaining_sstate = [x for x in self.search_sstate(target + r'.*?\.tgz$') if not any(pattern in x for pattern in ignore_patterns)]
203 203
204 actual_not_expected = [x for x in actual_remaining_sstate if x not in expected_remaining_sstate] 204 actual_not_expected = [x for x in actual_remaining_sstate if x not in expected_remaining_sstate]
205 self.assertFalse(actual_not_expected, msg="Files should have been removed but ware not: %s" % ', '.join(map(str, actual_not_expected))) 205 self.assertFalse(actual_not_expected, msg="Files should have been removed but were not: %s" % ', '.join(map(str, actual_not_expected)))
206 expected_not_actual = [x for x in expected_remaining_sstate if x not in actual_remaining_sstate] 206 expected_not_actual = [x for x in expected_remaining_sstate if x not in actual_remaining_sstate]
207 self.assertFalse(expected_not_actual, msg="Extra files ware removed: %s" ', '.join(map(str, expected_not_actual))) 207 self.assertFalse(expected_not_actual, msg="Extra files were removed: %s" ', '.join(map(str, expected_not_actual)))
208 208
209 def test_sstate_cache_management_script_using_pr_1(self): 209 def test_sstate_cache_management_script_using_pr_1(self):
210 global_config = [] 210 global_config = []
diff --git a/meta/lib/oeqa/selftest/cases/tinfoil.py b/meta/lib/oeqa/selftest/cases/tinfoil.py
index a51c6048d3..6668d7cdc8 100644
--- a/meta/lib/oeqa/selftest/cases/tinfoil.py
+++ b/meta/lib/oeqa/selftest/cases/tinfoil.py
@@ -65,6 +65,20 @@ class TinfoilTests(OESelftestTestCase):
65 localdata.setVar('PN', 'hello') 65 localdata.setVar('PN', 'hello')
66 self.assertEqual('hello', localdata.getVar('BPN')) 66 self.assertEqual('hello', localdata.getVar('BPN'))
67 67
68 # The config_data API tp parse_recipe_file is used by:
69 # layerindex-web layerindex/update_layer.py
70 def test_parse_recipe_custom_data(self):
71 with bb.tinfoil.Tinfoil() as tinfoil:
72 tinfoil.prepare(config_only=False, quiet=2)
73 localdata = bb.data.createCopy(tinfoil.config_data)
74 localdata.setVar("TESTVAR", "testval")
75 testrecipe = 'mdadm'
76 best = tinfoil.find_best_provider(testrecipe)
77 if not best:
78 self.fail('Unable to find recipe providing %s' % testrecipe)
79 rd = tinfoil.parse_recipe_file(best[3], config_data=localdata)
80 self.assertEqual("testval", rd.getVar('TESTVAR'))
81
68 def test_list_recipes(self): 82 def test_list_recipes(self):
69 with bb.tinfoil.Tinfoil() as tinfoil: 83 with bb.tinfoil.Tinfoil() as tinfoil:
70 tinfoil.prepare(config_only=False, quiet=2) 84 tinfoil.prepare(config_only=False, quiet=2)
@@ -87,23 +101,22 @@ class TinfoilTests(OESelftestTestCase):
87 with bb.tinfoil.Tinfoil() as tinfoil: 101 with bb.tinfoil.Tinfoil() as tinfoil:
88 tinfoil.prepare(config_only=True) 102 tinfoil.prepare(config_only=True)
89 103
90 tinfoil.set_event_mask(['bb.event.FilesMatchingFound', 'bb.command.CommandCompleted']) 104 tinfoil.set_event_mask(['bb.event.FilesMatchingFound', 'bb.command.CommandCompleted', 'bb.command.CommandFailed', 'bb.command.CommandExit'])
91 105
92 # Need to drain events otherwise events that were masked may still be in the queue 106 # Need to drain events otherwise events that were masked may still be in the queue
93 while tinfoil.wait_event(): 107 while tinfoil.wait_event():
94 pass 108 pass
95 109
96 pattern = 'conf' 110 pattern = 'conf'
97 res = tinfoil.run_command('findFilesMatchingInDir', pattern, 'conf/machine') 111 res = tinfoil.run_command('testCookerCommandEvent', pattern, handle_events=False)
98 self.assertTrue(res) 112 self.assertTrue(res)
99 113
100 eventreceived = False 114 eventreceived = False
101 commandcomplete = False 115 commandcomplete = False
102 start = time.time() 116 start = time.time()
103 # Wait for maximum 60s in total so we'd detect spurious heartbeat events for example 117 # Wait for maximum 120s in total so we'd detect spurious heartbeat events for example
104 # The test is IO load sensitive too
105 while (not (eventreceived == True and commandcomplete == True) 118 while (not (eventreceived == True and commandcomplete == True)
106 and (time.time() - start < 60)): 119 and (time.time() - start < 120)):
107 # if we received both events (on let's say a good day), we are done 120 # if we received both events (on let's say a good day), we are done
108 event = tinfoil.wait_event(1) 121 event = tinfoil.wait_event(1)
109 if event: 122 if event:
@@ -111,14 +124,15 @@ class TinfoilTests(OESelftestTestCase):
111 commandcomplete = True 124 commandcomplete = True
112 elif isinstance(event, bb.event.FilesMatchingFound): 125 elif isinstance(event, bb.event.FilesMatchingFound):
113 self.assertEqual(pattern, event._pattern) 126 self.assertEqual(pattern, event._pattern)
114 self.assertIn('qemuarm.conf', event._matches) 127 self.assertIn('A', event._matches)
128 self.assertIn('B', event._matches)
115 eventreceived = True 129 eventreceived = True
116 elif isinstance(event, logging.LogRecord): 130 elif isinstance(event, logging.LogRecord):
117 continue 131 continue
118 else: 132 else:
119 self.fail('Unexpected event: %s' % event) 133 self.fail('Unexpected event: %s' % event)
120 134
121 self.assertTrue(commandcomplete, 'Timed out waiting for CommandCompleted event from bitbake server') 135 self.assertTrue(commandcomplete, 'Timed out waiting for CommandCompleted event from bitbake server (Matching event received: %s)' % str(eventreceived))
122 self.assertTrue(eventreceived, 'Did not receive FilesMatchingFound event from bitbake server') 136 self.assertTrue(eventreceived, 'Did not receive FilesMatchingFound event from bitbake server')
123 137
124 def test_setvariable_clean(self): 138 def test_setvariable_clean(self):
diff --git a/meta/lib/oeqa/selftest/cases/wic.py b/meta/lib/oeqa/selftest/cases/wic.py
index 0435aa29c9..f7abdba015 100644
--- a/meta/lib/oeqa/selftest/cases/wic.py
+++ b/meta/lib/oeqa/selftest/cases/wic.py
@@ -905,14 +905,18 @@ class Wic2(WicTestCase):
905 @only_for_arch(['i586', 'i686', 'x86_64']) 905 @only_for_arch(['i586', 'i686', 'x86_64'])
906 def test_rawcopy_plugin_qemu(self): 906 def test_rawcopy_plugin_qemu(self):
907 """Test rawcopy plugin in qemu""" 907 """Test rawcopy plugin in qemu"""
908 # build ext4 and wic images 908 # build ext4 and then use it for a wic image
909 for fstype in ("ext4", "wic"): 909 config = 'IMAGE_FSTYPES = "ext4"\n'
910 config = 'IMAGE_FSTYPES = "%s"\nWKS_FILE = "test_rawcopy_plugin.wks.in"\n' % fstype 910 self.append_config(config)
911 self.append_config(config) 911 self.assertEqual(0, bitbake('core-image-minimal').status)
912 self.assertEqual(0, bitbake('core-image-minimal').status) 912 self.remove_config(config)
913 self.remove_config(config)
914 913
915 with runqemu('core-image-minimal', ssh=False, image_fstype='wic') as qemu: 914 config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "test_rawcopy_plugin.wks.in"\n'
915 self.append_config(config)
916 self.assertEqual(0, bitbake('core-image-minimal-mtdutils').status)
917 self.remove_config(config)
918
919 with runqemu('core-image-minimal-mtdutils', ssh=False, image_fstype='wic') as qemu:
916 cmd = "grep sda. /proc/partitions |wc -l" 920 cmd = "grep sda. /proc/partitions |wc -l"
917 status, output = qemu.run_serial(cmd) 921 status, output = qemu.run_serial(cmd)
918 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) 922 self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
diff --git a/meta/lib/oeqa/utils/buildproject.py b/meta/lib/oeqa/utils/buildproject.py
index e6d80cc8dc..dfb9661868 100644
--- a/meta/lib/oeqa/utils/buildproject.py
+++ b/meta/lib/oeqa/utils/buildproject.py
@@ -18,6 +18,7 @@ class BuildProject(metaclass=ABCMeta):
18 def __init__(self, uri, foldername=None, tmpdir=None, dl_dir=None): 18 def __init__(self, uri, foldername=None, tmpdir=None, dl_dir=None):
19 self.uri = uri 19 self.uri = uri
20 self.archive = os.path.basename(uri) 20 self.archive = os.path.basename(uri)
21 self.tempdirobj = None
21 if not tmpdir: 22 if not tmpdir:
22 self.tempdirobj = tempfile.TemporaryDirectory(prefix='buildproject-') 23 self.tempdirobj = tempfile.TemporaryDirectory(prefix='buildproject-')
23 tmpdir = self.tempdirobj.name 24 tmpdir = self.tempdirobj.name
@@ -57,6 +58,8 @@ class BuildProject(metaclass=ABCMeta):
57 return self._run('cd %s; make install %s' % (self.targetdir, install_args)) 58 return self._run('cd %s; make install %s' % (self.targetdir, install_args))
58 59
59 def clean(self): 60 def clean(self):
61 if self.tempdirobj:
62 self.tempdirobj.cleanup()
60 if not self.needclean: 63 if not self.needclean:
61 return 64 return
62 self._run('rm -rf %s' % self.targetdir) 65 self._run('rm -rf %s' % self.targetdir)
diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py
index a71c16ab14..024261410e 100644
--- a/meta/lib/oeqa/utils/commands.py
+++ b/meta/lib/oeqa/utils/commands.py
@@ -174,11 +174,8 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=T
174 if native_sysroot: 174 if native_sysroot:
175 extra_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin" % \ 175 extra_paths = "%s/sbin:%s/usr/sbin:%s/usr/bin" % \
176 (native_sysroot, native_sysroot, native_sysroot) 176 (native_sysroot, native_sysroot, native_sysroot)
177 extra_libpaths = "%s/lib:%s/usr/lib" % \
178 (native_sysroot, native_sysroot)
179 nenv = dict(options.get('env', os.environ)) 177 nenv = dict(options.get('env', os.environ))
180 nenv['PATH'] = extra_paths + ':' + nenv.get('PATH', '') 178 nenv['PATH'] = extra_paths + ':' + nenv.get('PATH', '')
181 nenv['LD_LIBRARY_PATH'] = extra_libpaths + ':' + nenv.get('LD_LIBRARY_PATH', '')
182 options['env'] = nenv 179 options['env'] = nenv
183 180
184 cmd = Command(command, timeout=timeout, output_log=output_log, **options) 181 cmd = Command(command, timeout=timeout, output_log=output_log, **options)
diff --git a/meta/lib/oeqa/utils/metadata.py b/meta/lib/oeqa/utils/metadata.py
index 8013aa684d..15ec190c4a 100644
--- a/meta/lib/oeqa/utils/metadata.py
+++ b/meta/lib/oeqa/utils/metadata.py
@@ -27,9 +27,9 @@ def metadata_from_bb():
27 data_dict = get_bb_vars() 27 data_dict = get_bb_vars()
28 28
29 # Distro information 29 # Distro information
30 info_dict['distro'] = {'id': data_dict['DISTRO'], 30 info_dict['distro'] = {'id': data_dict.get('DISTRO', 'NODISTRO'),
31 'version_id': data_dict['DISTRO_VERSION'], 31 'version_id': data_dict.get('DISTRO_VERSION', 'NO_DISTRO_VERSION'),
32 'pretty_name': '%s %s' % (data_dict['DISTRO'], data_dict['DISTRO_VERSION'])} 32 'pretty_name': '%s %s' % (data_dict.get('DISTRO', 'NODISTRO'), data_dict.get('DISTRO_VERSION', 'NO_DISTRO_VERSION'))}
33 33
34 # Host distro information 34 # Host distro information
35 os_release = get_os_release() 35 os_release = get_os_release()
diff --git a/meta/lib/oeqa/utils/nfs.py b/meta/lib/oeqa/utils/nfs.py
index a37686c914..c9bac050a4 100644
--- a/meta/lib/oeqa/utils/nfs.py
+++ b/meta/lib/oeqa/utils/nfs.py
@@ -8,7 +8,7 @@ from oeqa.utils.commands import bitbake, get_bb_var, Command
8from oeqa.utils.network import get_free_port 8from oeqa.utils.network import get_free_port
9 9
10@contextlib.contextmanager 10@contextlib.contextmanager
11def unfs_server(directory, logger = None): 11def unfs_server(directory, logger = None, udp = True):
12 unfs_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "unfs3-native") 12 unfs_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "unfs3-native")
13 if not os.path.exists(os.path.join(unfs_sysroot, "usr", "bin", "unfsd")): 13 if not os.path.exists(os.path.join(unfs_sysroot, "usr", "bin", "unfsd")):
14 # build native tool 14 # build native tool
@@ -22,7 +22,7 @@ def unfs_server(directory, logger = None):
22 exports.write("{0} (rw,no_root_squash,no_all_squash,insecure)\n".format(directory).encode()) 22 exports.write("{0} (rw,no_root_squash,no_all_squash,insecure)\n".format(directory).encode())
23 23
24 # find some ports for the server 24 # find some ports for the server
25 nfsport, mountport = get_free_port(udp = True), get_free_port(udp = True) 25 nfsport, mountport = get_free_port(udp), get_free_port(udp)
26 26
27 nenv = dict(os.environ) 27 nenv = dict(os.environ)
28 nenv['PATH'] = "{0}/sbin:{0}/usr/sbin:{0}/usr/bin:".format(unfs_sysroot) + nenv.get('PATH', '') 28 nenv['PATH'] = "{0}/sbin:{0}/usr/sbin:{0}/usr/bin:".format(unfs_sysroot) + nenv.get('PATH', '')
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py
index 77ec939ad7..c84d299a80 100644
--- a/meta/lib/oeqa/utils/qemurunner.py
+++ b/meta/lib/oeqa/utils/qemurunner.py
@@ -70,6 +70,8 @@ class QemuRunner:
70 self.monitorpipe = None 70 self.monitorpipe = None
71 71
72 self.logger = logger 72 self.logger = logger
73 # Whether we're expecting an exit and should show related errors
74 self.canexit = False
73 75
74 # Enable testing other OS's 76 # Enable testing other OS's
75 # Set commands for target communication, and default to Linux ALWAYS 77 # Set commands for target communication, and default to Linux ALWAYS
@@ -118,7 +120,10 @@ class QemuRunner:
118 import fcntl 120 import fcntl
119 fl = fcntl.fcntl(o, fcntl.F_GETFL) 121 fl = fcntl.fcntl(o, fcntl.F_GETFL)
120 fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK) 122 fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK)
121 return os.read(o.fileno(), 1000000).decode("utf-8") 123 try:
124 return os.read(o.fileno(), 1000000).decode("utf-8")
125 except BlockingIOError:
126 return ""
122 127
123 128
124 def handleSIGCHLD(self, signum, frame): 129 def handleSIGCHLD(self, signum, frame):
@@ -229,7 +234,7 @@ class QemuRunner:
229 r = os.fdopen(r) 234 r = os.fdopen(r)
230 x = r.read() 235 x = r.read()
231 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) 236 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM)
232 sys.exit(0) 237 os._exit(0)
233 238
234 self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) 239 self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid)
235 self.logger.debug("waiting at most %s seconds for qemu pid (%s)" % 240 self.logger.debug("waiting at most %s seconds for qemu pid (%s)" %
@@ -427,12 +432,17 @@ class QemuRunner:
427 except OSError as e: 432 except OSError as e:
428 if e.errno != errno.ESRCH: 433 if e.errno != errno.ESRCH:
429 raise 434 raise
430 endtime = time.time() + self.runqemutime 435 try:
431 while self.runqemu.poll() is None and time.time() < endtime: 436 outs, errs = self.runqemu.communicate(timeout = self.runqemutime)
432 time.sleep(1) 437 if outs:
433 if self.runqemu.poll() is None: 438 self.logger.info("Output from runqemu:\n%s", outs.decode("utf-8"))
439 if errs:
440 self.logger.info("Stderr from runqemu:\n%s", errs.decode("utf-8"))
441 except TimeoutExpired:
434 self.logger.debug("Sending SIGKILL to runqemu") 442 self.logger.debug("Sending SIGKILL to runqemu")
435 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) 443 os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL)
444 if not self.runqemu.stdout.closed:
445 self.logger.info("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout))
436 self.runqemu.stdin.close() 446 self.runqemu.stdin.close()
437 self.runqemu.stdout.close() 447 self.runqemu.stdout.close()
438 self.runqemu_exited = True 448 self.runqemu_exited = True
@@ -467,6 +477,11 @@ class QemuRunner:
467 self.thread.stop() 477 self.thread.stop()
468 self.thread.join() 478 self.thread.join()
469 479
480 def allowexit(self):
481 self.canexit = True
482 if self.thread:
483 self.thread.allowexit()
484
470 def restart(self, qemuparams = None): 485 def restart(self, qemuparams = None):
471 self.logger.warning("Restarting qemu process") 486 self.logger.warning("Restarting qemu process")
472 if self.runqemu.poll() is None: 487 if self.runqemu.poll() is None:
@@ -522,7 +537,9 @@ class QemuRunner:
522 if re.search(self.boot_patterns['search_cmd_finished'], data): 537 if re.search(self.boot_patterns['search_cmd_finished'], data):
523 break 538 break
524 else: 539 else:
525 raise Exception("No data on serial console socket") 540 if self.canexit:
541 return (1, "")
542 raise Exception("No data on serial console socket, connection closed?")
526 543
527 if data: 544 if data:
528 if raw: 545 if raw:
@@ -560,6 +577,7 @@ class LoggingThread(threading.Thread):
560 self.logger = logger 577 self.logger = logger
561 self.readsock = None 578 self.readsock = None
562 self.running = False 579 self.running = False
580 self.canexit = False
563 581
564 self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL 582 self.errorevents = select.POLLERR | select.POLLHUP | select.POLLNVAL
565 self.readevents = select.POLLIN | select.POLLPRI 583 self.readevents = select.POLLIN | select.POLLPRI
@@ -593,6 +611,9 @@ class LoggingThread(threading.Thread):
593 self.close_ignore_error(self.writepipe) 611 self.close_ignore_error(self.writepipe)
594 self.running = False 612 self.running = False
595 613
614 def allowexit(self):
615 self.canexit = True
616
596 def eventloop(self): 617 def eventloop(self):
597 poll = select.poll() 618 poll = select.poll()
598 event_read_mask = self.errorevents | self.readevents 619 event_read_mask = self.errorevents | self.readevents
@@ -638,7 +659,7 @@ class LoggingThread(threading.Thread):
638 data = self.readsock.recv(count) 659 data = self.readsock.recv(count)
639 except socket.error as e: 660 except socket.error as e:
640 if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK: 661 if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
641 return '' 662 return b''
642 else: 663 else:
643 raise 664 raise
644 665
@@ -649,7 +670,9 @@ class LoggingThread(threading.Thread):
649 # happened. But for this code it counts as an 670 # happened. But for this code it counts as an
650 # error since the connection shouldn't go away 671 # error since the connection shouldn't go away
651 # until qemu exits. 672 # until qemu exits.
652 raise Exception("Console connection closed unexpectedly") 673 if not self.canexit:
674 raise Exception("Console connection closed unexpectedly")
675 return b''
653 676
654 return data 677 return data
655 678
diff --git a/meta/lib/oeqa/utils/targetbuild.py b/meta/lib/oeqa/utils/targetbuild.py
index 1055810ca3..09738add1d 100644
--- a/meta/lib/oeqa/utils/targetbuild.py
+++ b/meta/lib/oeqa/utils/targetbuild.py
@@ -19,6 +19,7 @@ class BuildProject(metaclass=ABCMeta):
19 self.d = d 19 self.d = d
20 self.uri = uri 20 self.uri = uri
21 self.archive = os.path.basename(uri) 21 self.archive = os.path.basename(uri)
22 self.tempdirobj = None
22 if not tmpdir: 23 if not tmpdir:
23 tmpdir = self.d.getVar('WORKDIR') 24 tmpdir = self.d.getVar('WORKDIR')
24 if not tmpdir: 25 if not tmpdir:
@@ -71,9 +72,10 @@ class BuildProject(metaclass=ABCMeta):
71 return self._run('cd %s; make install %s' % (self.targetdir, install_args)) 72 return self._run('cd %s; make install %s' % (self.targetdir, install_args))
72 73
73 def clean(self): 74 def clean(self):
75 if self.tempdirobj:
76 self.tempdirobj.cleanup()
74 self._run('rm -rf %s' % self.targetdir) 77 self._run('rm -rf %s' % self.targetdir)
75 subprocess.check_call('rm -f %s' % self.localarchive, shell=True) 78 subprocess.check_call('rm -f %s' % self.localarchive, shell=True)
76 pass
77 79
78class TargetBuildProject(BuildProject): 80class TargetBuildProject(BuildProject):
79 81