From 82c905dc58a36aeae40b1b273a12f63fb1973cf4 Mon Sep 17 00:00:00 2001 From: Andrew Geissler Date: Mon, 13 Apr 2020 13:39:40 -0500 Subject: meta-openembedded and poky: subtree updates Squash of the following due to dependencies among them and OpenBMC changes: meta-openembedded: subtree update:d0748372d2..9201611135 meta-openembedded: subtree update:9201611135..17fd382f34 poky: subtree update:9052e5b32a..2e11d97b6c poky: subtree update:2e11d97b6c..a8544811d7 The change log was too large for the jenkins plugin to handle therefore it has been removed. Here is the first and last commit of each subtree: meta-openembedded:d0748372d2 cppzmq: bump to version 4.6.0 meta-openembedded:17fd382f34 mpv: Remove X11 dependency poky:9052e5b32a package_ipk: Remove pointless comment to trigger rebuild poky:a8544811d7 pbzip2: Fix license warning Change-Id: If0fc6c37629642ee207a4ca2f7aa501a2c673cd6 Signed-off-by: Andrew Geissler --- poky/meta/lib/oeqa/buildperf/base.py | 2 +- poky/meta/lib/oeqa/controllers/masterimage.py | 2 +- poky/meta/lib/oeqa/core/context.py | 48 +++- poky/meta/lib/oeqa/core/runner.py | 13 +- poky/meta/lib/oeqa/core/target/qemu.py | 11 +- poky/meta/lib/oeqa/core/target/ssh.py | 5 +- poky/meta/lib/oeqa/core/utils/concurrencytest.py | 58 +++-- poky/meta/lib/oeqa/manual/bsp-hw.json | 52 ----- poky/meta/lib/oeqa/manual/eclipse-plugin.json | 6 +- poky/meta/lib/oeqa/runtime/cases/apt.py | 4 +- poky/meta/lib/oeqa/runtime/cases/buildcpio.py | 4 +- poky/meta/lib/oeqa/runtime/cases/date.py | 4 +- poky/meta/lib/oeqa/runtime/cases/dnf.py | 3 +- poky/meta/lib/oeqa/runtime/cases/logrotate.py | 62 ++++-- poky/meta/lib/oeqa/runtime/cases/ltp.py | 4 +- poky/meta/lib/oeqa/runtime/cases/opkg.py | 4 +- poky/meta/lib/oeqa/runtime/cases/parselogs.py | 17 +- poky/meta/lib/oeqa/runtime/cases/ptest.py | 23 +- poky/meta/lib/oeqa/runtime/cases/weston.py | 69 ++++++ poky/meta/lib/oeqa/runtime/context.py | 11 +- poky/meta/lib/oeqa/sdk/cases/buildcpio.py | 6 +- poky/meta/lib/oeqa/sdk/context.py | 2 +- poky/meta/lib/oeqa/sdkext/testsdk.py | 7 +- poky/meta/lib/oeqa/selftest/case.py | 30 +-- poky/meta/lib/oeqa/selftest/cases/archiver.py | 125 +++++++++++ poky/meta/lib/oeqa/selftest/cases/devtool.py | 22 +- poky/meta/lib/oeqa/selftest/cases/distrodata.py | 33 ++- poky/meta/lib/oeqa/selftest/cases/gcc.py | 20 +- poky/meta/lib/oeqa/selftest/cases/imagefeatures.py | 6 +- .../lib/oeqa/selftest/cases/incompatible_lic.py | 58 ++++- poky/meta/lib/oeqa/selftest/cases/meta_ide.py | 2 +- .../lib/oeqa/selftest/cases/oelib/buildhistory.py | 2 +- poky/meta/lib/oeqa/selftest/cases/oescripts.py | 7 +- poky/meta/lib/oeqa/selftest/cases/package.py | 25 ++- poky/meta/lib/oeqa/selftest/cases/recipetool.py | 25 +++ poky/meta/lib/oeqa/selftest/cases/reproducible.py | 130 +++++++---- poky/meta/lib/oeqa/selftest/cases/runtime_test.py | 116 ++++++---- poky/meta/lib/oeqa/selftest/cases/signing.py | 4 +- poky/meta/lib/oeqa/selftest/cases/sstate.py | 4 +- poky/meta/lib/oeqa/selftest/cases/sstatetests.py | 40 ++++ poky/meta/lib/oeqa/selftest/cases/sysroot.py | 37 ++++ poky/meta/lib/oeqa/selftest/cases/tinfoil.py | 13 -- poky/meta/lib/oeqa/selftest/cases/wic.py | 244 +++++++++++++++++++-- poky/meta/lib/oeqa/selftest/context.py | 93 ++++---- poky/meta/lib/oeqa/targetcontrol.py | 10 +- poky/meta/lib/oeqa/utils/commands.py | 10 +- poky/meta/lib/oeqa/utils/dump.py | 5 +- poky/meta/lib/oeqa/utils/httpserver.py | 4 +- poky/meta/lib/oeqa/utils/logparser.py | 16 +- poky/meta/lib/oeqa/utils/qemurunner.py | 87 ++++++-- poky/meta/lib/oeqa/utils/sshcontrol.py | 2 +- 51 files changed, 1179 insertions(+), 408 deletions(-) create mode 100644 poky/meta/lib/oeqa/runtime/cases/weston.py create mode 100644 poky/meta/lib/oeqa/selftest/cases/sysroot.py (limited to 'poky/meta/lib/oeqa') diff --git a/poky/meta/lib/oeqa/buildperf/base.py b/poky/meta/lib/oeqa/buildperf/base.py index 3b2fed549..5f1805d86 100644 --- a/poky/meta/lib/oeqa/buildperf/base.py +++ b/poky/meta/lib/oeqa/buildperf/base.py @@ -462,7 +462,7 @@ class BuildPerfTestCase(unittest.TestCase): def rm_tmp(self): """Cleanup temporary/intermediate files and directories""" log.debug("Removing temporary and cache files") - for name in ['bitbake.lock', 'conf/sanity_info', + for name in ['bitbake.lock', 'cache/sanity_info', self.bb_vars['TMPDIR']]: oe.path.remove(name, recurse=True) diff --git a/poky/meta/lib/oeqa/controllers/masterimage.py b/poky/meta/lib/oeqa/controllers/masterimage.py index 0435dfa12..0bf5917e4 100644 --- a/poky/meta/lib/oeqa/controllers/masterimage.py +++ b/poky/meta/lib/oeqa/controllers/masterimage.py @@ -97,7 +97,7 @@ class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget, metaclass=ABCMeta if self.powercontrol_cmd: cmd = "%s %s" % (self.powercontrol_cmd, msg) try: - commands.runCmd(cmd, assert_error=False, preexec_fn=os.setsid, env=self.origenv) + commands.runCmd(cmd, assert_error=False, start_new_session=True, env=self.origenv) except CommandError as e: bb.fatal(str(e)) diff --git a/poky/meta/lib/oeqa/core/context.py b/poky/meta/lib/oeqa/core/context.py index 14fc6a54f..4705d608a 100644 --- a/poky/meta/lib/oeqa/core/context.py +++ b/poky/meta/lib/oeqa/core/context.py @@ -72,6 +72,9 @@ class OETestContext(object): modules_required, **kwargs) self.suites = self.loader.discover() + def prepareSuite(self, suites, processes): + return suites + def runTests(self, processes=None, skips=[]): self.runner = self.runnerClass(self, descriptions=False, verbosity=2) @@ -79,14 +82,9 @@ class OETestContext(object): self.skipTests(skips) self._run_start_time = time.time() - if processes: - from oeqa.core.utils.concurrencytest import ConcurrentTestSuite - - concurrent_suite = ConcurrentTestSuite(self.suites, processes) - result = self.runner.run(concurrent_suite) - else: + if not processes: self.runner.buffer = True - result = self.runner.run(self.suites) + result = self.runner.run(self.prepareSuite(self.suites, processes)) self._run_end_time = time.time() return result @@ -102,22 +100,27 @@ class OETestContextExecutor(object): name = 'core' help = 'core test component example' description = 'executes core test suite example' + datetime = time.strftime("%Y%m%d%H%M%S") default_cases = [os.path.join(os.path.abspath(os.path.dirname(__file__)), 'cases/example')] default_test_data = os.path.join(default_cases[0], 'data.json') default_tests = None + default_json_result_dir = None def register_commands(self, logger, subparsers): self.parser = subparsers.add_parser(self.name, help=self.help, description=self.description, group='components') - self.default_output_log = '%s-results-%s.log' % (self.name, - time.strftime("%Y%m%d%H%M%S")) + self.default_output_log = '%s-results-%s.log' % (self.name, self.datetime) self.parser.add_argument('--output-log', action='store', default=self.default_output_log, help="results output log, default: %s" % self.default_output_log) + self.parser.add_argument('--json-result-dir', action='store', + default=self.default_json_result_dir, + help="json result output dir, default: %s" % self.default_json_result_dir) + group = self.parser.add_mutually_exclusive_group() group.add_argument('--run-tests', action='store', nargs='+', default=self.default_tests, @@ -180,6 +183,22 @@ class OETestContextExecutor(object): self.module_paths = args.CASES_PATHS + def _get_json_result_dir(self, args): + return args.json_result_dir + + def _get_configuration(self): + td = self.tc_kwargs['init']['td'] + configuration = {'TEST_TYPE': self.name, + 'MACHINE': td.get("MACHINE"), + 'DISTRO': td.get("DISTRO"), + 'IMAGE_BASENAME': td.get("IMAGE_BASENAME"), + 'DATETIME': td.get("DATETIME")} + return configuration + + def _get_result_id(self, configuration): + return '%s_%s_%s_%s' % (configuration['TEST_TYPE'], configuration['IMAGE_BASENAME'], + configuration['MACHINE'], self.datetime) + def _pre_run(self): pass @@ -198,7 +217,16 @@ class OETestContextExecutor(object): else: self._pre_run() rc = self.tc.runTests(**self.tc_kwargs['run']) - rc.logDetails() + + json_result_dir = self._get_json_result_dir(args) + if json_result_dir: + configuration = self._get_configuration() + rc.logDetails(json_result_dir, + configuration, + self._get_result_id(configuration)) + else: + rc.logDetails() + rc.logSummary(self.name) output_link = os.path.join(os.path.dirname(args.output_log), diff --git a/poky/meta/lib/oeqa/core/runner.py b/poky/meta/lib/oeqa/core/runner.py index f656e1a9c..1284295c3 100644 --- a/poky/meta/lib/oeqa/core/runner.py +++ b/poky/meta/lib/oeqa/core/runner.py @@ -319,10 +319,17 @@ class OETestResultJSONHelper(object): the_file.write(file_content) def dump_testresult_file(self, write_dir, configuration, result_id, test_result): - bb.utils.mkdirhier(write_dir) - lf = bb.utils.lockfile(os.path.join(write_dir, 'jsontestresult.lock')) + try: + import bb + has_bb = True + bb.utils.mkdirhier(write_dir) + lf = bb.utils.lockfile(os.path.join(write_dir, 'jsontestresult.lock')) + except ImportError: + has_bb = False + os.makedirs(write_dir, exist_ok=True) test_results = self._get_existing_testresults_if_available(write_dir) test_results[result_id] = {'configuration': configuration, 'result': test_result} json_testresults = json.dumps(test_results, sort_keys=True, indent=4) self._write_file(write_dir, self.testresult_filename, json_testresults) - bb.utils.unlockfile(lf) + if has_bb: + bb.utils.unlockfile(lf) diff --git a/poky/meta/lib/oeqa/core/target/qemu.py b/poky/meta/lib/oeqa/core/target/qemu.py index 081c627b0..295e8765e 100644 --- a/poky/meta/lib/oeqa/core/target/qemu.py +++ b/poky/meta/lib/oeqa/core/target/qemu.py @@ -8,6 +8,7 @@ import os import sys import signal import time +from collections import defaultdict from .ssh import OESSHTarget from oeqa.utils.qemurunner import QemuRunner @@ -18,23 +19,29 @@ class OEQemuTarget(OESSHTarget): def __init__(self, logger, server_ip, timeout=300, user='root', port=None, machine='', rootfs='', kernel='', kvm=False, slirp=False, dump_dir='', dump_host_cmds='', display='', bootlog='', - tmpdir='', dir_image='', boottime=60, **kwargs): + tmpdir='', dir_image='', boottime=60, serial_ports=2, + boot_patterns = defaultdict(str), ovmf=False, **kwargs): super(OEQemuTarget, self).__init__(logger, None, server_ip, timeout, user, port) self.server_ip = server_ip + self.server_port = 0 self.machine = machine self.rootfs = rootfs self.kernel = kernel self.kvm = kvm + self.ovmf = ovmf self.use_slirp = slirp + self.boot_patterns = boot_patterns self.runner = QemuRunner(machine=machine, rootfs=rootfs, tmpdir=tmpdir, deploy_dir_image=dir_image, display=display, logfile=bootlog, boottime=boottime, use_kvm=kvm, use_slirp=slirp, dump_dir=dump_dir, - dump_host_cmds=dump_host_cmds, logger=logger) + dump_host_cmds=dump_host_cmds, logger=logger, + serial_ports=serial_ports, boot_patterns = boot_patterns, + use_ovmf=ovmf) def start(self, params=None, extra_bootparams=None, runqemuparams=''): if self.use_slirp and not self.server_ip: diff --git a/poky/meta/lib/oeqa/core/target/ssh.py b/poky/meta/lib/oeqa/core/target/ssh.py index 51032ef1a..090b40a81 100644 --- a/poky/meta/lib/oeqa/core/target/ssh.py +++ b/poky/meta/lib/oeqa/core/target/ssh.py @@ -15,7 +15,7 @@ from . import OETarget class OESSHTarget(OETarget): def __init__(self, logger, ip, server_ip, timeout=300, user='root', - port=None, **kwargs): + port=None, server_port=0, **kwargs): if not logger: logger = logging.getLogger('target') logger.setLevel(logging.INFO) @@ -30,6 +30,7 @@ class OESSHTarget(OETarget): super(OESSHTarget, self).__init__(logger) self.ip = ip self.server_ip = server_ip + self.server_port = server_port self.timeout = timeout self.user = user ssh_options = [ @@ -246,7 +247,7 @@ def SSHCall(command, logger, timeout=None, **opts): "stdin": None, "shell": False, "bufsize": -1, - "preexec_fn": os.setsid, + "start_new_session": True, } options.update(opts) output = '' diff --git a/poky/meta/lib/oeqa/core/utils/concurrencytest.py b/poky/meta/lib/oeqa/core/utils/concurrencytest.py index 0f7b3dcc1..fac59f765 100644 --- a/poky/meta/lib/oeqa/core/utils/concurrencytest.py +++ b/poky/meta/lib/oeqa/core/utils/concurrencytest.py @@ -146,6 +146,20 @@ def outSideTestaddError(self, offset, line): subunit._OutSideTest.addError = outSideTestaddError +# Like outSideTestaddError above, we need an equivalent for skips +# happening at the setUpClass() level, otherwise we will see "UNKNOWN" +# as a result for concurrent tests +# +def outSideTestaddSkip(self, offset, line): + """A 'skip:' directive has been read.""" + test_name = line[offset:-1].decode('utf8') + self.parser._current_test = subunit.RemotedTestCase(test_name) + self.parser.current_test_description = test_name + self.parser._state = self.parser._reading_skip_details + self.parser._reading_skip_details.set_simple() + self.parser.subunitLineReceived(line) + +subunit._OutSideTest.addSkip = outSideTestaddSkip # # A dummy structure to add to io.StringIO so that the .buffer object @@ -163,9 +177,10 @@ class dummybuf(object): # class ConcurrentTestSuite(unittest.TestSuite): - def __init__(self, suite, processes): + def __init__(self, suite, processes, setupfunc): super(ConcurrentTestSuite, self).__init__([suite]) self.processes = processes + self.setupfunc = setupfunc def run(self, result): tests, totaltests = fork_for_tests(self.processes, self) @@ -221,6 +236,15 @@ def removebuilddir(d): while delay and os.path.exists(d + "/bitbake.lock"): time.sleep(1) delay = delay - 1 + # Deleting these directories takes a lot of time, use autobuilder + # clobberdir if its available + clobberdir = os.path.expanduser("~/yocto-autobuilder-helper/janitor/clobberdir") + if os.path.exists(clobberdir): + try: + subprocess.check_call([clobberdir, d]) + return + except subprocess.CalledProcessError: + pass bb.utils.prunedir(d, ionice=True) def fork_for_tests(concurrency_num, suite): @@ -249,37 +273,7 @@ def fork_for_tests(concurrency_num, suite): stream = os.fdopen(c2pwrite, 'wb', 1) os.close(c2pread) - # Create a new separate BUILDDIR for each group of tests - if 'BUILDDIR' in os.environ: - builddir = os.environ['BUILDDIR'] - newbuilddir = builddir + "-st-" + str(ourpid) - newselftestdir = newbuilddir + "/meta-selftest" - - bb.utils.mkdirhier(newbuilddir) - oe.path.copytree(builddir + "/conf", newbuilddir + "/conf") - oe.path.copytree(builddir + "/cache", newbuilddir + "/cache") - oe.path.copytree(selftestdir, newselftestdir) - - for e in os.environ: - if builddir in os.environ[e]: - os.environ[e] = os.environ[e].replace(builddir, newbuilddir) - - subprocess.check_output("git init; git add *; git commit -a -m 'initial'", cwd=newselftestdir, shell=True) - - # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow - subprocess.check_output("sed %s/conf/bblayers.conf -i -e 's#%s#%s#g'" % (newbuilddir, selftestdir, newselftestdir), cwd=newbuilddir, shell=True) - - os.chdir(newbuilddir) - - for t in process_suite: - if not hasattr(t, "tc"): - continue - cp = t.tc.config_paths - for p in cp: - if selftestdir in cp[p] and newselftestdir not in cp[p]: - cp[p] = cp[p].replace(selftestdir, newselftestdir) - if builddir in cp[p] and newbuilddir not in cp[p]: - cp[p] = cp[p].replace(builddir, newbuilddir) + (builddir, newbuilddir) = suite.setupfunc("-st-" + str(ourpid), selftestdir, process_suite) # Leave stderr and stdout open so we can see test noise # Close stdin so that the child goes away if it decides to diff --git a/poky/meta/lib/oeqa/manual/bsp-hw.json b/poky/meta/lib/oeqa/manual/bsp-hw.json index 5c5b9b50b..a9bc7d450 100644 --- a/poky/meta/lib/oeqa/manual/bsp-hw.json +++ b/poky/meta/lib/oeqa/manual/bsp-hw.json @@ -915,28 +915,6 @@ "summary": "Check_if_RTC_(Real_Time_Clock)_can_work_correctly" } }, - { - "test": { - "@alias": "bsps-hw.bsps-hw.Check_if_target_can_support_EEPROM", - "author": [ - { - "email": "yi.zhao@windriver.com", - "name": "yi.zhao@windriver.com" - } - ], - "execution": { - "1": { - "action": "Check eeprom device exist in /sys/bus/i2c/devices/ ", - "expected_results": "Hexdump can read data from eeprom.\n" - }, - "2": { - "action": "Run \"hexdump eeprom\" commandroot@mpc8315e-rdb:/sys/bus/i2c/devices/1-0051> hexdump eeprom0000000 9210 0b02 0211 0009 0b52 0108 0c00 3c000000010 6978 6930 6911 208c 7003 3c3c 00f0 8381\u2026\n", - "expected_results": "" - } - }, - "summary": "Check_if_target_can_support_EEPROM" - } - }, { "test": { "@alias": "bsps-hw.bsps-hw.System_can_boot_up_via_NFS", @@ -966,35 +944,5 @@ }, "summary": "System_can_boot_up_via_NFS" } - }, - { - "test": { - "@alias": "bsps-hw.bsps-hw.Boot_from_JFFS2_image", - "author": [ - { - "email": "yi.zhao@windriver.com", - "name": "yi.zhao@windriver.com" - } - ], - "execution": { - "1": { - "action": "First boot the board with NFS root. ", - "expected_results": "The system can boot up without problem\n" - }, - "2": { - "action": "Install mtd-utils package. Erase the MTD partition which will be used as root: $ flash_eraseall /dev/mtd3 ", - "expected_results": "" - }, - "3": { - "action": "Copy the JFFS2 image to the MTD partition: $ flashcp core-image-minimal-mpc8315e-rdb.jffs2 /dev/mtd3 ", - "expected_results": "" - }, - "4": { - "action": "Then reboot the board and set up the environment in U-Boot: => setenv bootargs root=/dev/mtdblock3 rootfstype=jffs2 console=ttyS0,115200 ", - "expected_results": "" - } - }, - "summary": "Boot_from_JFFS2_image" - } } ] diff --git a/poky/meta/lib/oeqa/manual/eclipse-plugin.json b/poky/meta/lib/oeqa/manual/eclipse-plugin.json index 9869150dc..d77d0e673 100644 --- a/poky/meta/lib/oeqa/manual/eclipse-plugin.json +++ b/poky/meta/lib/oeqa/manual/eclipse-plugin.json @@ -82,7 +82,7 @@ ], "execution": { "1": { - "action": "Launch a QEMU of target enviroment.(Reference to case \"ADT - Launch qemu by eclipse\") ", + "action": "Launch a QEMU of target environment.(Reference to case \"ADT - Launch qemu by eclipse\") ", "expected_results": "" }, "2": { @@ -164,7 +164,7 @@ ], "execution": { "1": { - "action": "Launch a QEMU of target enviroment.(Reference to case \"ADT - Launch qemu by eclipse\") ", + "action": "Launch a QEMU of target environment.(Reference to case \"ADT - Launch qemu by eclipse\") ", "expected_results": "" }, "2": { @@ -319,4 +319,4 @@ "summary": "Eclipse_Poky_installation_and_setup" } } -] \ No newline at end of file +] diff --git a/poky/meta/lib/oeqa/runtime/cases/apt.py b/poky/meta/lib/oeqa/runtime/cases/apt.py index 74a940d80..c5378d90c 100644 --- a/poky/meta/lib/oeqa/runtime/cases/apt.py +++ b/poky/meta/lib/oeqa/runtime/cases/apt.py @@ -22,7 +22,9 @@ class AptRepoTest(AptTest): @classmethod def setUpClass(cls): service_repo = os.path.join(cls.tc.td['DEPLOY_DIR_DEB'], 'all') - cls.repo_server = HTTPService(service_repo, cls.tc.target.server_ip, logger=cls.tc.logger) + cls.repo_server = HTTPService(service_repo, + '0.0.0.0', port=cls.tc.target.server_port, + logger=cls.tc.logger) cls.repo_server.start() @classmethod diff --git a/poky/meta/lib/oeqa/runtime/cases/buildcpio.py b/poky/meta/lib/oeqa/runtime/cases/buildcpio.py index f4e871e42..d0f91668b 100644 --- a/poky/meta/lib/oeqa/runtime/cases/buildcpio.py +++ b/poky/meta/lib/oeqa/runtime/cases/buildcpio.py @@ -12,7 +12,7 @@ class BuildCpioTest(OERuntimeTestCase): @classmethod def setUpClass(cls): - uri = 'https://downloads.yoctoproject.org/mirror/sources/cpio-2.12.tar.gz' + uri = 'https://downloads.yoctoproject.org/mirror/sources/cpio-2.13.tar.gz' cls.project = TargetBuildProject(cls.tc.target, uri, dl_dir = cls.tc.td['DL_DIR']) @@ -27,6 +27,6 @@ class BuildCpioTest(OERuntimeTestCase): @OEHasPackage(['autoconf']) def test_cpio(self): self.project.download_archive() - self.project.run_configure() + self.project.run_configure('--disable-maintainer-mode','') self.project.run_make() self.project.run_install() diff --git a/poky/meta/lib/oeqa/runtime/cases/date.py b/poky/meta/lib/oeqa/runtime/cases/date.py index 7750a7293..fdd2a6ae5 100644 --- a/poky/meta/lib/oeqa/runtime/cases/date.py +++ b/poky/meta/lib/oeqa/runtime/cases/date.py @@ -13,12 +13,12 @@ class DateTest(OERuntimeTestCase): def setUp(self): if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd': self.logger.debug('Stopping systemd-timesyncd daemon') - self.target.run('systemctl stop systemd-timesyncd') + self.target.run('systemctl disable --now systemd-timesyncd') def tearDown(self): if self.tc.td.get('VIRTUAL-RUNTIME_init_manager') == 'systemd': self.logger.debug('Starting systemd-timesyncd daemon') - self.target.run('systemctl start systemd-timesyncd') + self.target.run('systemctl enable --now systemd-timesyncd') @OETestDepends(['ssh.SSHTest.test_ssh']) @OEHasPackage(['coreutils', 'busybox']) diff --git a/poky/meta/lib/oeqa/runtime/cases/dnf.py b/poky/meta/lib/oeqa/runtime/cases/dnf.py index de3759995..f40c63026 100644 --- a/poky/meta/lib/oeqa/runtime/cases/dnf.py +++ b/poky/meta/lib/oeqa/runtime/cases/dnf.py @@ -53,7 +53,8 @@ class DnfRepoTest(DnfTest): @classmethod def setUpClass(cls): cls.repo_server = HTTPService(os.path.join(cls.tc.td['WORKDIR'], 'oe-testimage-repo'), - cls.tc.target.server_ip, logger=cls.tc.logger) + '0.0.0.0', port=cls.tc.target.server_port, + logger=cls.tc.logger) cls.repo_server.start() @classmethod diff --git a/poky/meta/lib/oeqa/runtime/cases/logrotate.py b/poky/meta/lib/oeqa/runtime/cases/logrotate.py index bfa57c534..3938e9199 100644 --- a/poky/meta/lib/oeqa/runtime/cases/logrotate.py +++ b/poky/meta/lib/oeqa/runtime/cases/logrotate.py @@ -18,32 +18,58 @@ class LogrotateTest(OERuntimeTestCase): @classmethod def tearDownClass(cls): cls.tc.target.run('mv -f $HOME/wtmp.oeqabak /etc/logrotate.d/wtmp && rm -rf $HOME/logrotate_dir') + cls.tc.target.run('rm -rf /var/log/logrotate_testfile && rm -rf /etc/logrotate.d/logrotate_testfile') @OETestDepends(['ssh.SSHTest.test_ssh']) @OEHasPackage(['logrotate']) - def test_1_logrotate_setup(self): + def test_logrotate_wtmp(self): + + # /var/log/wtmp may not always exist initially, so use touch to ensure it is present + status, output = self.target.run('touch /var/log/wtmp') + msg = ('Could not create/update /var/log/wtmp with touch') + self.assertEqual(status, 0, msg = msg) + status, output = self.target.run('mkdir $HOME/logrotate_dir') - msg = 'Could not create logrotate_dir. Output: %s' % output + msg = ('Could not create logrotate_dir. Output: %s' % output) + self.assertEqual(status, 0, msg = msg) + + status, output = self.target.run('echo "create \n olddir $HOME/logrotate_dir \n include /etc/logrotate.d/wtmp" > /tmp/logrotate-test.conf') + msg = ('Could not write to /tmp/logrotate-test.conf') + self.assertEqual(status, 0, msg = msg) + + status, output = self.target.run('echo "/var/log/logrotate_test {\\n missingok \\n monthly \\n rotate 1" > /etc/logrotate.d/logrotate_test') + msg = ('Could not write to /etc/logrotate.d/logrotate_test') + self.assertEqual(status, 0, msg = msg) + + # If logrotate fails to rotate the log, view the verbose output of logrotate to see what prevented it + _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test.conf') + status, _ = self.target.run('find $HOME/logrotate_dir -type f | grep wtmp.1') + msg = ("logrotate did not successfully rotate the wtmp log. Output from logrotate -vf: \n%s" % (logrotate_output)) + self.assertEqual(status, 0, msg = msg) + + @OETestDepends(['logrotate.LogrotateTest.test_logrotate_wtmp']) + def test_logrotate_newlog(self): + + status, output = self.target.run('echo "oeqa logrotate test file" > /var/log/logrotate_testfile') + msg = ('Could not create logrotate test file in /var/log') + self.assertEqual(status, 0, msg = msg) + + status, output = self.target.run('echo "/var/log/logrotate_testfile {\n missingok \n monthly \n rotate 1" > /etc/logrotate.d/logrotate_testfile') + msg = ('Could not write to /etc/logrotate.d/logrotate_testfile') self.assertEqual(status, 0, msg = msg) - cmd = ('sed -i "s#wtmp {#wtmp {\\n olddir $HOME/logrotate_dir#"' - ' /etc/logrotate.d/wtmp') - status, output = self.target.run(cmd) - msg = ('Could not write to logrotate.d/wtmp file. Status and output: ' - ' %s and %s' % (status, output)) + status, output = self.target.run('echo "create \n olddir $HOME/logrotate_dir \n include /etc/logrotate.d/logrotate_testfile" > /tmp/logrotate-test2.conf') + msg = ('Could not write to /tmp/logrotate_test2.conf') self.assertEqual(status, 0, msg = msg) - @OETestDepends(['logrotate.LogrotateTest.test_1_logrotate_setup']) - def test_2_logrotate(self): - status, output = self.target.run('echo "create \n include /etc/logrotate.d" > /tmp/logrotate-test.conf') - status, output = self.target.run('logrotate -f /tmp/logrotate-test.conf') + status, output = self.target.run('find $HOME/logrotate_dir -type f | grep logrotate_testfile.1') + msg = ('A rotated log for logrotate_testfile is already present in logrotate_dir') + self.assertEqual(status, 1, msg = msg) - msg = ('logrotate service could not be reloaded. Status and output: ' - '%s and %s' % (status, output)) + # If logrotate fails to rotate the log, view the verbose output of logrotate instead of just listing the files in olddir + _, logrotate_output = self.target.run('logrotate -vf /tmp/logrotate-test2.conf') + status, _ = self.target.run('find $HOME/logrotate_dir -type f | grep logrotate_testfile.1') + msg = ('logrotate did not successfully rotate the logrotate_test log. Output from logrotate -vf: \n%s' % (logrotate_output)) self.assertEqual(status, 0, msg = msg) - _, output = self.target.run('ls -la $HOME/logrotate_dir/ | wc -l') - msg = ('new logfile could not be created. List of files within log ' - 'directory: %s' % ( - self.target.run('ls -la $HOME/logrotate_dir')[1])) - self.assertTrue(int(output)>=3, msg = msg) + diff --git a/poky/meta/lib/oeqa/runtime/cases/ltp.py b/poky/meta/lib/oeqa/runtime/cases/ltp.py index 30548640b..6dc5ef22a 100644 --- a/poky/meta/lib/oeqa/runtime/cases/ltp.py +++ b/poky/meta/lib/oeqa/runtime/cases/ltp.py @@ -57,9 +57,9 @@ class LtpTestBase(OERuntimeTestCase): class LtpTest(LtpTestBase): - ltp_groups = ["math", "syscalls", "dio", "io", "mm", "ipc", "sched", "nptl", "pty", "containers", "controllers", "filecaps", "cap_bounds", "fcntl-locktests", "connectors","timers", "commands", "net.ipv6_lib", "input","fs_perms_simple"] + ltp_groups = ["math", "syscalls", "dio", "io", "mm", "ipc", "sched", "nptl", "pty", "containers", "controllers", "filecaps", "cap_bounds", "fcntl-locktests", "connectors", "commands", "net.ipv6_lib", "input","fs_perms_simple"] - ltp_fs = ["fs", "fsx", "fs_bind", "fs_ext4"] + ltp_fs = ["fs", "fsx", "fs_bind"] # skip kernel cpuhotplug ltp_kernel = ["power_management_tests", "hyperthreading ", "kernel_misc", "hugetlb"] ltp_groups += ltp_fs diff --git a/poky/meta/lib/oeqa/runtime/cases/opkg.py b/poky/meta/lib/oeqa/runtime/cases/opkg.py index 750706161..9cfee1cd8 100644 --- a/poky/meta/lib/oeqa/runtime/cases/opkg.py +++ b/poky/meta/lib/oeqa/runtime/cases/opkg.py @@ -25,7 +25,9 @@ class OpkgRepoTest(OpkgTest): if cls.tc.td["MULTILIB_VARIANTS"]: allarchfeed = cls.tc.td["TUNE_PKGARCH"] service_repo = os.path.join(cls.tc.td['DEPLOY_DIR_IPK'], allarchfeed) - cls.repo_server = HTTPService(service_repo, cls.tc.target.server_ip, logger=cls.tc.logger) + cls.repo_server = HTTPService(service_repo, + '0.0.0.0', port=cls.tc.target.server_port, + logger=cls.tc.logger) cls.repo_server.start() @classmethod diff --git a/poky/meta/lib/oeqa/runtime/cases/parselogs.py b/poky/meta/lib/oeqa/runtime/cases/parselogs.py index 15343d7ab..a1791b5cc 100644 --- a/poky/meta/lib/oeqa/runtime/cases/parselogs.py +++ b/poky/meta/lib/oeqa/runtime/cases/parselogs.py @@ -55,11 +55,15 @@ common_errors = [ "Failed to read /var/lib/nfs/statd/state: Success", "error retry time-out =", "logind: cannot setup systemd-logind helper (-61), using legacy fallback", - "Error changing net interface name 'eth0' to " + "Failed to rename network interface", + "Failed to process device, ignoring: Device or resource busy", + "Cannot find a map file", + "[rdrand]: Initialization Failed", + "[pulseaudio] authkey.c: Failed to open cookie file", + "[pulseaudio] authkey.c: Failed to load authentication key", ] video_related = [ - "uvesafb", ] x86_common = [ @@ -81,11 +85,8 @@ qemux86_common = [ "fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.", "can't claim BAR ", 'amd_nb: Cannot enumerate AMD northbridges', - 'uvesafb: 5000 ms task timeout, infinitely waiting', 'tsc: HPET/PMTIMER calibration failed', "modeset(0): Failed to initialize the DRI2 extension", - "uvesafb: cannot reserve video memory at", - "uvesafb: probe of uvesafb.0 failed with error", "glamor initialization failed", ] + common_errors @@ -133,6 +134,7 @@ ignore_errors = { '(EE) Server terminated with error (1). Closing log file.', 'dmi: Firmware registration failed.', 'irq: type mismatch, failed to map hwirq-27 for /intc', + 'logind: failed to get session seat', ] + common_errors, 'intel-core2-32' : [ 'ACPI: No _BQC method, cannot determine initial brightness', @@ -184,11 +186,6 @@ ignore_errors = { 'Failed to make EGL context current', 'glamor initialization failed', ] + common_errors, - 'mpc8315e-rdb' : [ - 'of_irq_parse_pci: failed with', - 'Fatal server error:', - 'Server terminated with error', - ] + common_errors, } log_locations = ["/var/log/","/var/log/dmesg", "/tmp/dmesg_output.log"] diff --git a/poky/meta/lib/oeqa/runtime/cases/ptest.py b/poky/meta/lib/oeqa/runtime/cases/ptest.py index d8d1e1b34..99a44f076 100644 --- a/poky/meta/lib/oeqa/runtime/cases/ptest.py +++ b/poky/meta/lib/oeqa/runtime/cases/ptest.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: MIT # +import os import unittest import pprint import datetime @@ -18,7 +19,20 @@ class PtestRunnerTest(OERuntimeTestCase): @OETestDepends(['ssh.SSHTest.test_ssh']) @OEHasPackage(['ptest-runner']) @unittest.expectedFailure - def test_ptestrunner(self): + def test_ptestrunner_expectfail(self): + if not self.td.get('PTEST_EXPECT_FAILURE'): + self.skipTest('Cannot run ptests with @expectedFailure as ptests are required to pass') + self.do_ptestrunner() + + @skipIfNotFeature('ptest', 'Test requires ptest to be in DISTRO_FEATURES') + @OETestDepends(['ssh.SSHTest.test_ssh']) + @OEHasPackage(['ptest-runner']) + def test_ptestrunner_expectsuccess(self): + if self.td.get('PTEST_EXPECT_FAILURE'): + self.skipTest('Cannot run ptests without @expectedFailure as ptests are expected to fail') + self.do_ptestrunner() + + def do_ptestrunner(self): status, output = self.target.run('which ptest-runner', 0) if status != 0: self.skipTest("No -ptest packages are installed in the image") @@ -67,8 +81,13 @@ class PtestRunnerTest(OERuntimeTestCase): extras[testname] = {'status': result} failed_tests = {} + + for section in sections: + if 'exitcode' in sections[section].keys(): + failed_tests[section] = sections[section]["log"] + for section in results: - failed_testcases = [ "_".join(test.translate(trans).split()) for test in results[section] if results[section][test] == 'fail' ] + failed_testcases = [ "_".join(test.translate(trans).split()) for test in results[section] if results[section][test] == 'FAILED' ] if failed_testcases: failed_tests[section] = failed_testcases diff --git a/poky/meta/lib/oeqa/runtime/cases/weston.py b/poky/meta/lib/oeqa/runtime/cases/weston.py new file mode 100644 index 000000000..ac29eca6e --- /dev/null +++ b/poky/meta/lib/oeqa/runtime/cases/weston.py @@ -0,0 +1,69 @@ +# +# SPDX-License-Identifier: MIT +# + +from oeqa.runtime.case import OERuntimeTestCase +from oeqa.core.decorator.depends import OETestDepends +from oeqa.core.decorator.data import skipIfNotFeature +from oeqa.runtime.decorator.package import OEHasPackage +import threading +import time + +class WestonTest(OERuntimeTestCase): + weston_log_file = '/tmp/weston.log' + + @classmethod + def tearDownClass(cls): + cls.tc.target.run('rm %s' % cls.weston_log_file) + + @OETestDepends(['ssh.SSHTest.test_ssh']) + @OEHasPackage(['weston']) + def test_weston_running(self): + cmd ='%s | grep [w]eston-desktop-shell' % self.tc.target_cmds['ps'] + status, output = self.target.run(cmd) + msg = ('Weston does not appear to be running %s' % + self.target.run(self.tc.target_cmds['ps'])[1]) + self.assertEqual(status, 0, msg=msg) + + def get_processes_of(self, target, error_msg): + status, output = self.target.run('pidof %s' % target) + self.assertEqual(status, 0, msg='Retrieve %s (%s) processes error: %s' % (target, error_msg, output)) + return output.split(" ") + + def get_weston_command(self, cmd): + return 'export XDG_RUNTIME_DIR=/run/user/0; export WAYLAND_DISPLAY=wayland-0; %s' % cmd + + def run_weston_init(self): + self.target.run(self.get_weston_command('weston --log=%s' % self.weston_log_file)) + + def get_new_wayland_processes(self, existing_wl_processes): + try_cnt = 0 + while try_cnt < 5: + time.sleep(5 + 5*try_cnt) + try_cnt += 1 + wl_processes = self.get_processes_of('weston-desktop-shell', 'existing and new') + new_wl_processes = [x for x in wl_processes if x not in existing_wl_processes] + if new_wl_processes: + return new_wl_processes, try_cnt + + return new_wl_processes, try_cnt + + @OEHasPackage(['weston']) + def test_weston_info(self): + status, output = self.target.run(self.get_weston_command('weston-info')) + self.assertEqual(status, 0, msg='weston-info error: %s' % output) + + @OEHasPackage(['weston']) + def test_weston_can_initialize_new_wayland_compositor(self): + existing_wl_processes = self.get_processes_of('weston-desktop-shell', 'existing') + existing_weston_processes = self.get_processes_of('weston', 'existing') + + weston_thread = threading.Thread(target=self.run_weston_init) + weston_thread.start() + new_wl_processes, try_cnt = self.get_new_wayland_processes(existing_wl_processes) + existing_and_new_weston_processes = self.get_processes_of('weston', 'existing and new') + new_weston_processes = [x for x in existing_and_new_weston_processes if x not in existing_weston_processes] + for w in new_weston_processes: + self.target.run('kill -9 %s' % w) + __, weston_log = self.target.run('cat %s' % self.weston_log_file) + self.assertTrue(new_wl_processes, msg='Could not get new weston-desktop-shell processes (%s, try_cnt:%s) weston log: %s' % (new_wl_processes, try_cnt, weston_log)) diff --git a/poky/meta/lib/oeqa/runtime/context.py b/poky/meta/lib/oeqa/runtime/context.py index ef738a335..3826f2764 100644 --- a/poky/meta/lib/oeqa/runtime/context.py +++ b/poky/meta/lib/oeqa/runtime/context.py @@ -47,6 +47,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): default_data = None default_test_data = 'data/testdata.json' default_tests = '' + default_json_result_dir = '%s-results' % name default_target_type = 'simpleremote' default_manifest = 'data/manifest' @@ -77,7 +78,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): runtime_group.add_argument('--packages-manifest', action='store', default=self.default_manifest, - help="Package manifest of the image under testi, default: %s" \ + help="Package manifest of the image under test, default: %s" \ % self.default_manifest) runtime_group.add_argument('--extract-dir', action='store', @@ -98,6 +99,12 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): target_ip = target_ip_port[0] kwargs['port'] = target_ip_port[1] + if server_ip: + server_ip_port = server_ip.split(':') + if len(server_ip_port) == 2: + server_ip = server_ip_port[0] + kwargs['server_port'] = int(server_ip_port[1]) + if target_type == 'simpleremote': target = OESSHTarget(logger, target_ip, server_ip, **kwargs) elif target_type == 'qemu': @@ -178,7 +185,7 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): except: obj = None return obj - + @staticmethod def readPackagesManifest(manifest): if not manifest or not os.path.exists(manifest): diff --git a/poky/meta/lib/oeqa/sdk/cases/buildcpio.py b/poky/meta/lib/oeqa/sdk/cases/buildcpio.py index 0a5e68d5f..902e93f62 100644 --- a/poky/meta/lib/oeqa/sdk/cases/buildcpio.py +++ b/poky/meta/lib/oeqa/sdk/cases/buildcpio.py @@ -17,10 +17,10 @@ class BuildCpioTest(OESDKTestCase): """ def test_cpio(self): with tempfile.TemporaryDirectory(prefix="cpio-", dir=self.tc.sdk_dir) as testdir: - tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftp.gnu.org/gnu/cpio/cpio-2.12.tar.gz") + tarball = self.fetch(testdir, self.td["DL_DIR"], "https://ftp.gnu.org/gnu/cpio/cpio-2.13.tar.gz") dirs = {} - dirs["source"] = os.path.join(testdir, "cpio-2.12") + dirs["source"] = os.path.join(testdir, "cpio-2.13") dirs["build"] = os.path.join(testdir, "build") dirs["install"] = os.path.join(testdir, "install") @@ -28,7 +28,7 @@ class BuildCpioTest(OESDKTestCase): self.assertTrue(os.path.isdir(dirs["source"])) os.makedirs(dirs["build"]) - self._run("cd {build} && {source}/configure $CONFIGURE_FLAGS".format(**dirs)) + self._run("cd {build} && {source}/configure --disable-maintainer-mode $CONFIGURE_FLAGS".format(**dirs)) self._run("cd {build} && make -j".format(**dirs)) self._run("cd {build} && make install DESTDIR={install}".format(**dirs)) diff --git a/poky/meta/lib/oeqa/sdk/context.py b/poky/meta/lib/oeqa/sdk/context.py index 09e77c19f..01c38c24e 100644 --- a/poky/meta/lib/oeqa/sdk/context.py +++ b/poky/meta/lib/oeqa/sdk/context.py @@ -136,7 +136,7 @@ class OESDKTestContextExecutor(OETestContextExecutor): sdk_envs = OESDKTestContextExecutor._get_sdk_environs(args.sdk_dir) if not sdk_envs: raise argparse_oe.ArgumentUsageError("No available SDK "\ - "enviroments found at %s" % args.sdk_dir, self.name) + "environments found at %s" % args.sdk_dir, self.name) if args.list_sdk_env: self._display_sdk_envs(logger.info, args, sdk_envs) diff --git a/poky/meta/lib/oeqa/sdkext/testsdk.py b/poky/meta/lib/oeqa/sdkext/testsdk.py index 785b5dda5..c5c46df6c 100644 --- a/poky/meta/lib/oeqa/sdkext/testsdk.py +++ b/poky/meta/lib/oeqa/sdkext/testsdk.py @@ -25,11 +25,8 @@ class TestSDKExt(TestSDKBase): subprocesstweak.errors_have_output() - # extensible sdk can be contaminated if native programs are - # in PATH, i.e. use perl-native instead of eSDK one. - paths_to_avoid = [d.getVar('STAGING_DIR'), - d.getVar('BASE_WORKDIR')] - os.environ['PATH'] = avoid_paths_in_environ(paths_to_avoid) + # We need the original PATH for testing the eSDK, not with our manipulations + os.environ['PATH'] = d.getVar("BB_ORIGENV", False).getVar("PATH") tcname = d.expand("${SDK_DEPLOY}/${TOOLCHAINEXT_OUTPUTNAME}.sh") if not os.path.exists(tcname): diff --git a/poky/meta/lib/oeqa/selftest/case.py b/poky/meta/lib/oeqa/selftest/case.py index ac3308d8a..dcad4f76e 100644 --- a/poky/meta/lib/oeqa/selftest/case.py +++ b/poky/meta/lib/oeqa/selftest/case.py @@ -6,7 +6,6 @@ import sys import os -import shutil import glob import errno from unittest.util import safe_repr @@ -30,9 +29,7 @@ class OESelftestTestCase(OETestCase): cls.builddir = cls.tc.config_paths['builddir'] cls.localconf_path = cls.tc.config_paths['localconf'] - cls.localconf_backup = cls.tc.config_paths['localconf_class_backup'] cls.local_bblayers_path = cls.tc.config_paths['bblayers'] - cls.local_bblayers_backup = cls.tc.config_paths['bblayers_class_backup'] cls.testinc_path = os.path.join(cls.tc.config_paths['builddir'], "conf/selftest.inc") @@ -43,8 +40,7 @@ class OESelftestTestCase(OETestCase): cls._track_for_cleanup = [ cls.testinc_path, cls.testinc_bblayers_path, - cls.machineinc_path, cls.localconf_backup, - cls.local_bblayers_backup] + cls.machineinc_path] cls.add_include() @@ -102,30 +98,6 @@ class OESelftestTestCase(OETestCase): def setUp(self): super(OESelftestTestCase, self).setUp() os.chdir(self.builddir) - # Check if local.conf or bblayers.conf files backup exists - # from a previous failed test and restore them - if os.path.isfile(self.localconf_backup) or os.path.isfile( - self.local_bblayers_backup): - self.logger.debug("\ -Found a local.conf and/or bblayers.conf backup from a previously aborted test.\ -Restoring these files now, but tests should be re-executed from a clean environment\ -to ensure accurate results.") - try: - shutil.copyfile(self.localconf_backup, self.localconf_path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - try: - shutil.copyfile(self.local_bblayers_backup, - self.local_bblayers_path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - else: - # backup local.conf and bblayers.conf - shutil.copyfile(self.localconf_path, self.localconf_backup) - shutil.copyfile(self.local_bblayers_path, self.local_bblayers_backup) - self.logger.debug("Creating local.conf and bblayers.conf backups.") # we don't know what the previous test left around in config or inc files # if it failed so we need a fresh start try: diff --git a/poky/meta/lib/oeqa/selftest/cases/archiver.py b/poky/meta/lib/oeqa/selftest/cases/archiver.py index f8672f8ab..606eaabcb 100644 --- a/poky/meta/lib/oeqa/selftest/cases/archiver.py +++ b/poky/meta/lib/oeqa/selftest/cases/archiver.py @@ -129,3 +129,128 @@ class Archiver(OESelftestTestCase): self.write_config(features) bitbake('-n core-image-sato') + + def _test_archiver_mode(self, mode, target_file_name, extra_config=None): + target = "selftest-ed" + + features = 'INHERIT += "archiver"\n' + features += 'ARCHIVER_MODE[src] = "%s"\n' % (mode) + if extra_config: + features += extra_config + self.write_config(features) + + bitbake('-c clean %s' % (target)) + bitbake('-c deploy_archives %s' % (target)) + + bb_vars = get_bb_vars(['DEPLOY_DIR_SRC', 'TARGET_SYS']) + glob_str = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['TARGET_SYS'], '%s-*' % (target)) + glob_result = glob.glob(glob_str) + self.assertTrue(glob_result, 'Missing archiver directory for %s' % (target)) + + archive_path = os.path.join(glob_result[0], target_file_name) + self.assertTrue(os.path.exists(archive_path), 'Missing archive file %s' % (target_file_name)) + + def test_archiver_mode_original(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "original"`. + """ + + self._test_archiver_mode('original', 'ed-1.14.1.tar.lz') + + def test_archiver_mode_patched(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "patched"`. + """ + + self._test_archiver_mode('patched', 'selftest-ed-1.14.1-r0-patched.tar.gz') + + def test_archiver_mode_configured(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "configured"`. + """ + + self._test_archiver_mode('configured', 'selftest-ed-1.14.1-r0-configured.tar.gz') + + def test_archiver_mode_recipe(self): + """ + Test that the archiver works with `ARCHIVER_MODE[recipe] = "1"`. + """ + + self._test_archiver_mode('patched', 'selftest-ed-1.14.1-r0-recipe.tar.gz', + 'ARCHIVER_MODE[recipe] = "1"\n') + + def test_archiver_mode_diff(self): + """ + Test that the archiver works with `ARCHIVER_MODE[diff] = "1"`. + Exclusions controlled by `ARCHIVER_MODE[diff-exclude]` are not yet tested. + """ + + self._test_archiver_mode('patched', 'selftest-ed-1.14.1-r0-diff.gz', + 'ARCHIVER_MODE[diff] = "1"\n') + + def test_archiver_mode_dumpdata(self): + """ + Test that the archiver works with `ARCHIVER_MODE[dumpdata] = "1"`. + """ + + self._test_archiver_mode('patched', 'selftest-ed-1.14.1-r0-showdata.dump', + 'ARCHIVER_MODE[dumpdata] = "1"\n') + + def test_archiver_mode_mirror(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "mirror"`. + """ + + self._test_archiver_mode('mirror', 'ed-1.14.1.tar.lz', + 'BB_GENERATE_MIRROR_TARBALLS = "1"\n') + + def test_archiver_mode_mirror_excludes(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "mirror"` and + correctly excludes an archive when its URL matches + `ARCHIVER_MIRROR_EXCLUDE`. + """ + + target='selftest-ed' + target_file_name = 'ed-1.14.1.tar.lz' + + features = 'INHERIT += "archiver"\n' + features += 'ARCHIVER_MODE[src] = "mirror"\n' + features += 'BB_GENERATE_MIRROR_TARBALLS = "1"\n' + features += 'ARCHIVER_MIRROR_EXCLUDE = "${GNU_MIRROR}"\n' + self.write_config(features) + + bitbake('-c clean %s' % (target)) + bitbake('-c deploy_archives %s' % (target)) + + bb_vars = get_bb_vars(['DEPLOY_DIR_SRC', 'TARGET_SYS']) + glob_str = os.path.join(bb_vars['DEPLOY_DIR_SRC'], bb_vars['TARGET_SYS'], '%s-*' % (target)) + glob_result = glob.glob(glob_str) + self.assertTrue(glob_result, 'Missing archiver directory for %s' % (target)) + + archive_path = os.path.join(glob_result[0], target_file_name) + self.assertFalse(os.path.exists(archive_path), 'Failed to exclude archive file %s' % (target_file_name)) + + def test_archiver_mode_mirror_combined(self): + """ + Test that the archiver works with `ARCHIVER_MODE[src] = "mirror"` + and `ARCHIVER_MODE[mirror] = "combined"`. Archives for multiple recipes + should all end up in the 'mirror' directory. + """ + + features = 'INHERIT += "archiver"\n' + features += 'ARCHIVER_MODE[src] = "mirror"\n' + features += 'ARCHIVER_MODE[mirror] = "combined"\n' + features += 'BB_GENERATE_MIRROR_TARBALLS = "1"\n' + features += 'COPYLEFT_LICENSE_INCLUDE = "*"\n' + self.write_config(features) + + for target in ['selftest-ed', 'selftest-hardlink']: + bitbake('-c clean %s' % (target)) + bitbake('-c deploy_archives %s' % (target)) + + bb_vars = get_bb_vars(['DEPLOY_DIR_SRC']) + for target_file_name in ['ed-1.14.1.tar.lz', 'hello.c']: + glob_str = os.path.join(bb_vars['DEPLOY_DIR_SRC'], 'mirror', target_file_name) + glob_result = glob.glob(glob_str) + self.assertTrue(glob_result, 'Missing archive file %s' % (target_file_name)) diff --git a/poky/meta/lib/oeqa/selftest/cases/devtool.py b/poky/meta/lib/oeqa/selftest/cases/devtool.py index 57e6662e4..5886862d6 100644 --- a/poky/meta/lib/oeqa/selftest/cases/devtool.py +++ b/poky/meta/lib/oeqa/selftest/cases/devtool.py @@ -511,6 +511,26 @@ class DevtoolAddTests(DevtoolBase): checkvars['SRC_URI'] = url.replace(testver, '${PV}') self._test_recipe_contents(recipefile, checkvars, []) + def test_devtool_add_npm(self): + pn = 'savoirfairelinux-node-server-example' + pv = '1.0.0' + url = 'npm://registry.npmjs.org;package=@savoirfairelinux/node-server-example;version=' + pv + # Test devtool add + self.track_for_cleanup(self.workspacedir) + self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool add \'%s\'' % url) + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') + self.assertExists(os.path.join(self.workspacedir, 'recipes', pn, '%s_%s.bb' % (pn, pv)), 'Recipe not created') + self.assertExists(os.path.join(self.workspacedir, 'recipes', pn, pn, 'npm-shrinkwrap.json'), 'Shrinkwrap not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn(pn, result.output) + # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then) + bitbake('%s -c cleansstate' % pn) + # Test devtool build + result = runCmd('devtool build %s' % pn) + class DevtoolModifyTests(DevtoolBase): def test_devtool_modify(self): @@ -1721,7 +1741,7 @@ class DevtoolUpgradeTests(DevtoolBase): when building the kernel. """ kernel_provider = get_bb_var('PREFERRED_PROVIDER_virtual/kernel') - # Clean up the enviroment + # Clean up the environment bitbake('%s -c clean' % kernel_provider) tempdir = tempfile.mkdtemp(prefix='devtoolqa') tempdir_cfg = tempfile.mkdtemp(prefix='config_qa') diff --git a/poky/meta/lib/oeqa/selftest/cases/distrodata.py b/poky/meta/lib/oeqa/selftest/cases/distrodata.py index 68ba55648..e1cfc3b62 100644 --- a/poky/meta/lib/oeqa/selftest/cases/distrodata.py +++ b/poky/meta/lib/oeqa/selftest/cases/distrodata.py @@ -42,8 +42,9 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re def test_maintainers(self): """ - Summary: Test that oe-core recipes have a maintainer + Summary: Test that oe-core recipes have a maintainer and entries in maintainers list have a recipe Expected: All oe-core recipes (except a few special static/testing ones) should have a maintainer listed in maintainers.inc file. + Expected: All entries in maintainers list should have a recipe file that matches them Product: oe-core Author: Alexander Kanavin """ @@ -54,7 +55,15 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re return True return False - feature = 'require conf/distro/include/maintainers.inc\n' + def is_maintainer_exception(entry): + exceptions = ["musl", "newlib", "linux-yocto", "linux-dummy", "mesa-gl", "libgfortran", + "cve-update-db-native"] + for i in exceptions: + if i in entry: + return True + return False + + feature = 'require conf/distro/include/maintainers.inc\nLICENSE_FLAGS_WHITELIST += " commercial"\nPARSE_ALL_RECIPES = "1"\n' self.write_config(feature) with bb.tinfoil.Tinfoil() as tinfoil: @@ -62,6 +71,11 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re with_maintainer_list = [] no_maintainer_list = [] + + missing_recipes = [] + recipes = [] + prefix = "RECIPE_MAINTAINER_pn-" + # We could have used all_recipes() here, but this method will find # every recipe if we ever move to setting RECIPE_MAINTAINER in recipe files # instead of maintainers.inc @@ -71,6 +85,7 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re continue rd = tinfoil.parse_recipe_file(fn, appends=False) pn = rd.getVar('PN') + recipes.append(pn) if is_exception(pn): continue if rd.getVar('RECIPE_MAINTAINER'): @@ -78,6 +93,15 @@ but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please re else: no_maintainer_list.append((pn, fn)) + maintainers = tinfoil.config_data.keys() + for key in maintainers: + if key.startswith(prefix): + recipe = tinfoil.config_data.expand(key[len(prefix):]) + if is_maintainer_exception(recipe): + continue + if recipe not in recipes: + missing_recipes.append(recipe) + if no_maintainer_list: self.fail(""" The following recipes do not have a maintainer assigned to them. Please add an entry to meta/conf/distro/include/maintainers.inc file. @@ -87,3 +111,8 @@ The following recipes do not have a maintainer assigned to them. Please add an e self.fail(""" The list of oe-core recipes with maintainers is empty. This may indicate that the test has regressed and needs fixing. """) + + if missing_recipes: + self.fail(""" +Unable to find recipes for the following entries in maintainers.inc: +""" + "\n".join(['%s' % i for i in missing_recipes])) diff --git a/poky/meta/lib/oeqa/selftest/cases/gcc.py b/poky/meta/lib/oeqa/selftest/cases/gcc.py index 5a917b9c4..3efe15228 100644 --- a/poky/meta/lib/oeqa/selftest/cases/gcc.py +++ b/poky/meta/lib/oeqa/selftest/cases/gcc.py @@ -21,8 +21,10 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase): def run_check(self, *suites, ssh = None): targets = set() for s in suites: - if s in ["gcc", "g++"]: - targets.add("check-gcc") + if s == "gcc": + targets.add("check-gcc-c") + elif s == "g++": + targets.add("check-gcc-c++") else: targets.add("check-target-{}".format(s)) @@ -77,7 +79,12 @@ class GccSelfTestBase(OESelftestTestCase, OEPTestResultTestCase): @OETestTag("toolchain-user") class GccCrossSelfTest(GccSelfTestBase): def test_cross_gcc(self): - self.run_check("gcc", "g++") + self.run_check("gcc") + +@OETestTag("toolchain-user") +class GxxCrossSelfTest(GccSelfTestBase): + def test_cross_gxx(self): + self.run_check("g++") @OETestTag("toolchain-user") class GccLibAtomicSelfTest(GccSelfTestBase): @@ -109,7 +116,12 @@ class GccLibItmSelfTest(GccSelfTestBase): @OETestTag("toolchain-system") class GccCrossSelfTestSystemEmulated(GccSelfTestBase): def test_cross_gcc(self): - self.run_check_emulated("gcc", "g++") + self.run_check_emulated("gcc") + +@OETestTag("toolchain-system") +class GxxCrossSelfTestSystemEmulated(GccSelfTestBase): + def test_cross_gxx(self): + self.run_check_emulated("g++") @OETestTag("toolchain-system") class GccLibAtomicSelfTestSystemEmulated(GccSelfTestBase): diff --git a/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py b/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py index ef2eefa86..5c519ac3d 100644 --- a/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py +++ b/poky/meta/lib/oeqa/selftest/cases/imagefeatures.py @@ -208,13 +208,13 @@ class ImageFeatures(OESelftestTestCase): """ image_name = 'core-image-minimal' - img_types = [itype for itype in get_bb_var("IMAGE_TYPES", image_name).split() \ - if itype not in ('container', 'elf', 'f2fs', 'multiubi')] + all_image_types = set(get_bb_var("IMAGE_TYPES", image_name).split()) + blacklist = set(('container', 'elf', 'f2fs', 'multiubi', 'tar.zst')) + img_types = all_image_types - blacklist config = 'IMAGE_FSTYPES += "%s"\n'\ 'MKUBIFS_ARGS ?= "-m 2048 -e 129024 -c 2047"\n'\ 'UBINIZE_ARGS ?= "-m 2048 -p 128KiB -s 512"' % ' '.join(img_types) - self.write_config(config) bitbake(image_name) diff --git a/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py b/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py index 904b5b409..3eabd7909 100644 --- a/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py +++ b/poky/meta/lib/oeqa/selftest/cases/incompatible_lic.py @@ -4,7 +4,7 @@ from oeqa.utils.commands import bitbake class IncompatibleLicenseTests(OESelftestTestCase): def lic_test(self, pn, pn_lic, lic): - error_msg = 'ERROR: Nothing PROVIDES \'%s\'\n%s was skipped: it has an incompatible license: %s' % (pn, pn, pn_lic) + error_msg = 'ERROR: Nothing PROVIDES \'%s\'\n%s was skipped: it has incompatible license(s): %s' % (pn, pn, pn_lic) self.write_config("INCOMPATIBLE_LICENSE += \"%s\"" % (lic)) @@ -12,30 +12,72 @@ class IncompatibleLicenseTests(OESelftestTestCase): if error_msg not in result.output: raise AssertionError(result.output) - # Verify that a package with an SPDX license (from SRC_DISTRIBUTE_LICENSES) + # Verify that a package with an SPDX license (from AVAILABLE_LICENSES) # cannot be built when INCOMPATIBLE_LICENSE contains this SPDX license def test_incompatible_spdx_license(self): self.lic_test('incompatible-license', 'GPL-3.0', 'GPL-3.0') - # Verify that a package with an SPDX license (from SRC_DISTRIBUTE_LICENSES) + # Verify that a package with an SPDX license (from AVAILABLE_LICENSES) # cannot be built when INCOMPATIBLE_LICENSE contains an alias (in # SPDXLICENSEMAP) of this SPDX license def test_incompatible_alias_spdx_license(self): self.lic_test('incompatible-license', 'GPL-3.0', 'GPLv3') + # Verify that a package with an SPDX license (from AVAILABLE_LICENSES) + # cannot be built when INCOMPATIBLE_LICENSE contains a wildcarded license + # matching this SPDX license + def test_incompatible_spdx_license_wildcard(self): + self.lic_test('incompatible-license', 'GPL-3.0', '*GPL-3.0') + + # Verify that a package with an SPDX license (from AVAILABLE_LICENSES) + # cannot be built when INCOMPATIBLE_LICENSE contains a wildcarded alias + # license matching this SPDX license + def test_incompatible_alias_spdx_license_wildcard(self): + self.lic_test('incompatible-license', 'GPL-3.0', '*GPLv3') + # Verify that a package with an alias (from SPDXLICENSEMAP) to an SPDX # license cannot be built when INCOMPATIBLE_LICENSE contains this SPDX # license def test_incompatible_spdx_license_alias(self): - self.lic_test('incompatible-license-alias', 'GPLv3', 'GPL-3.0') + self.lic_test('incompatible-license-alias', 'GPL-3.0', 'GPL-3.0') # Verify that a package with an alias (from SPDXLICENSEMAP) to an SPDX # license cannot be built when INCOMPATIBLE_LICENSE contains this alias def test_incompatible_alias_spdx_license_alias(self): - self.lic_test('incompatible-license-alias', 'GPLv3', 'GPLv3') + self.lic_test('incompatible-license-alias', 'GPL-3.0', 'GPLv3') + + # Verify that a package with an alias (from SPDXLICENSEMAP) to an SPDX + # license cannot be built when INCOMPATIBLE_LICENSE contains a wildcarded + # license matching this SPDX license + def test_incompatible_spdx_license_alias_wildcard(self): + self.lic_test('incompatible-license-alias', 'GPL-3.0', '*GPL-3.0') + + # Verify that a package with an alias (from SPDXLICENSEMAP) to an SPDX + # license cannot be built when INCOMPATIBLE_LICENSE contains a wildcarded + # alias license matching the SPDX license + def test_incompatible_alias_spdx_license_alias_wildcard(self): + self.lic_test('incompatible-license-alias', 'GPL-3.0', '*GPLv3') + + # Verify that a package with multiple SPDX licenses (from + # AVAILABLE_LICENSES) cannot be built when INCOMPATIBLE_LICENSE contains + # some of them + def test_incompatible_spdx_licenses(self): + self.lic_test('incompatible-licenses', 'GPL-3.0 LGPL-3.0', 'GPL-3.0 LGPL-3.0') + + # Verify that a package with multiple SPDX licenses (from + # AVAILABLE_LICENSES) cannot be built when INCOMPATIBLE_LICENSE contains a + # wildcard to some of them + def test_incompatible_spdx_licenses_wildcard(self): + self.lic_test('incompatible-licenses', 'GPL-3.0 LGPL-3.0', '*GPL-3.0') + + # Verify that a package with multiple SPDX licenses (from + # AVAILABLE_LICENSES) cannot be built when INCOMPATIBLE_LICENSE contains a + # wildcard matching all licenses + def test_incompatible_all_licenses_wildcard(self): + self.lic_test('incompatible-licenses', 'GPL-2.0 GPL-3.0 LGPL-3.0', '*') # Verify that a package with a non-SPDX license (neither in - # SRC_DISTRIBUTE_LICENSES nor in SPDXLICENSEMAP) cannot be built when + # AVAILABLE_LICENSES nor in SPDXLICENSEMAP) cannot be built when # INCOMPATIBLE_LICENSE contains this license def test_incompatible_nonspdx_license(self): self.lic_test('incompatible-nonspdx-license', 'FooLicense', 'FooLicense') @@ -49,7 +91,7 @@ INCOMPATIBLE_LICENSE_pn-core-image-minimal = "GPL-3.0 LGPL-3.0" def test_bash_default(self): self.write_config(self.default_config()) - error_msg = "ERROR: core-image-minimal-1.0-r0 do_rootfs: Package bash has an incompatible license GPLv3+ and cannot be installed into the image." + error_msg = "ERROR: core-image-minimal-1.0-r0 do_rootfs: Package bash cannot be installed into the image because it has incompatible license(s): GPL-3.0+" result = bitbake('core-image-minimal', ignore_status=True) if error_msg not in result.output: @@ -57,7 +99,7 @@ INCOMPATIBLE_LICENSE_pn-core-image-minimal = "GPL-3.0 LGPL-3.0" def test_bash_and_license(self): self.write_config(self.default_config() + '\nLICENSE_append_pn-bash = " & SomeLicense"') - error_msg = "ERROR: core-image-minimal-1.0-r0 do_rootfs: Package bash has an incompatible license GPLv3+ & SomeLicense and cannot be installed into the image." + error_msg = "ERROR: core-image-minimal-1.0-r0 do_rootfs: Package bash cannot be installed into the image because it has incompatible license(s): GPL-3.0+" result = bitbake('core-image-minimal', ignore_status=True) if error_msg not in result.output: diff --git a/poky/meta/lib/oeqa/selftest/cases/meta_ide.py b/poky/meta/lib/oeqa/selftest/cases/meta_ide.py index 03901a2f3..809142559 100644 --- a/poky/meta/lib/oeqa/selftest/cases/meta_ide.py +++ b/poky/meta/lib/oeqa/selftest/cases/meta_ide.py @@ -40,7 +40,7 @@ class MetaIDE(OESelftestTestCase): def test_meta_ide_can_build_cpio_project(self): dl_dir = self.td.get('DL_DIR', None) self.project = SDKBuildProject(self.tmpdir_metaideQA + "/cpio/", self.environment_script_path, - "https://ftp.gnu.org/gnu/cpio/cpio-2.12.tar.gz", + "https://ftp.gnu.org/gnu/cpio/cpio-2.13.tar.gz", self.tmpdir_metaideQA, self.td['DATETIME'], dl_dir=dl_dir) self.project.download_archive() self.assertEqual(self.project.run_configure(), 0, diff --git a/poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py b/poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py index 6d8082765..d4664bd0d 100644 --- a/poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py +++ b/poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py @@ -45,7 +45,7 @@ class TestBlobParsing(OESelftestTestCase): def test_blob_to_dict(self): """ - Test convertion of git blobs to dictionary + Test conversion of git blobs to dictionary """ from oe.buildhistory_analysis import blob_to_dict valuesmap = { "foo" : "1", "bar" : "2" } diff --git a/poky/meta/lib/oeqa/selftest/cases/oescripts.py b/poky/meta/lib/oeqa/selftest/cases/oescripts.py index 41cbe0480..2f18d8f29 100644 --- a/poky/meta/lib/oeqa/selftest/cases/oescripts.py +++ b/poky/meta/lib/oeqa/selftest/cases/oescripts.py @@ -4,6 +4,7 @@ import os import shutil +import importlib import unittest from oeqa.selftest.case import OESelftestTestCase from oeqa.selftest.cases.buildhistory import BuildhistoryBase @@ -33,15 +34,13 @@ class BuildhistoryDiffTests(BuildhistoryBase): if expected_endlines: self.fail('Missing expected line endings:\n %s' % '\n '.join(expected_endlines)) +@unittest.skipUnless(importlib.util.find_spec("cairo"), "Python cairo module is not present") class OEScriptTests(OESelftestTestCase): @classmethod def setUpClass(cls): super(OEScriptTests, cls).setUpClass() - try: - import cairo - except ImportError: - raise unittest.SkipTest('Python module cairo is not present') + import cairo bitbake("core-image-minimal -c rootfs -f") cls.tmpdir = get_bb_var('TMPDIR') cls.buildstats = cls.tmpdir + "/buildstats/" + sorted(os.listdir(cls.tmpdir + "/buildstats"))[-1] diff --git a/poky/meta/lib/oeqa/selftest/cases/package.py b/poky/meta/lib/oeqa/selftest/cases/package.py index 291627877..3010b1af4 100644 --- a/poky/meta/lib/oeqa/selftest/cases/package.py +++ b/poky/meta/lib/oeqa/selftest/cases/package.py @@ -135,7 +135,7 @@ class PackageTests(OESelftestTestCase): return False # Check debugging symbols works correctly - elif re.match("Breakpoint 1.*hello\.c.*4", l): + elif re.match(r"Breakpoint 1.*hello\.c.*4", l): return True self.logger.error("GDB result:\n%d: %s", status, output) @@ -148,3 +148,26 @@ class PackageTests(OESelftestTestCase): '/usr/libexec/hello4']: if not gdbtest(qemu, binary): self.fail('GDB %s failed' % binary) + + def test_preserve_ownership(self): + import os, stat, oe.cachedpath + features = 'IMAGE_INSTALL_append = " selftest-chown"\n' + self.write_config(features) + bitbake("core-image-minimal") + + sysconfdir = get_bb_var('sysconfdir', 'selftest-chown') + def check_ownership(qemu, gid, uid, path): + self.logger.info("Check ownership of %s", path) + status, output = qemu.run_serial(r'/bin/stat -c "%U %G" ' + path, timeout=60) + output = output.split(" ") + if output[0] != uid or output[1] != gid : + self.logger.error("Incrrect ownership %s [%s:%s]", path, output[0], output[1]) + return False + return True + + with runqemu('core-image-minimal') as qemu: + for path in [ sysconfdir + "/selftest-chown/file", + sysconfdir + "/selftest-chown/dir", + sysconfdir + "/selftest-chown/symlink"]: + if not check_ownership(qemu, "test", "test", path): + self.fail('Test ownership %s failed' % path) diff --git a/poky/meta/lib/oeqa/selftest/cases/recipetool.py b/poky/meta/lib/oeqa/selftest/cases/recipetool.py index c1562c63b..6bfe8f177 100644 --- a/poky/meta/lib/oeqa/selftest/cases/recipetool.py +++ b/poky/meta/lib/oeqa/selftest/cases/recipetool.py @@ -421,6 +421,31 @@ class RecipetoolTests(RecipetoolBase): inherits = ['cmake'] self._test_recipe_contents(recipefile, checkvars, inherits) + def test_recipetool_create_npm(self): + temprecipe = os.path.join(self.tempdir, 'recipe') + os.makedirs(temprecipe) + recipefile = os.path.join(temprecipe, 'savoirfairelinux-node-server-example_1.0.0.bb') + shrinkwrap = os.path.join(temprecipe, 'savoirfairelinux-node-server-example', 'npm-shrinkwrap.json') + srcuri = 'npm://registry.npmjs.org;package=@savoirfairelinux/node-server-example;version=1.0.0' + result = runCmd('recipetool create -o %s \'%s\'' % (temprecipe, srcuri)) + self.assertTrue(os.path.isfile(recipefile)) + self.assertTrue(os.path.isfile(shrinkwrap)) + checkvars = {} + checkvars['SUMMARY'] = 'Node Server Example' + checkvars['HOMEPAGE'] = 'https://github.com/savoirfairelinux/node-server-example#readme' + checkvars['LICENSE'] = set(['MIT', 'ISC', 'Unknown']) + urls = [] + urls.append('npm://registry.npmjs.org/;package=@savoirfairelinux/node-server-example;version=${PV}') + urls.append('npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json') + checkvars['SRC_URI'] = set(urls) + checkvars['S'] = '${WORKDIR}/npm' + checkvars['LICENSE_${PN}'] = 'MIT' + checkvars['LICENSE_${PN}-base64'] = 'Unknown' + checkvars['LICENSE_${PN}-accepts'] = 'MIT' + checkvars['LICENSE_${PN}-inherits'] = 'ISC' + inherits = ['npm'] + self._test_recipe_contents(recipefile, checkvars, inherits) + def test_recipetool_create_github(self): # Basic test to see if github URL mangling works temprecipe = os.path.join(self.tempdir, 'recipe') diff --git a/poky/meta/lib/oeqa/selftest/cases/reproducible.py b/poky/meta/lib/oeqa/selftest/cases/reproducible.py index a9110565a..5d3959be7 100644 --- a/poky/meta/lib/oeqa/selftest/cases/reproducible.py +++ b/poky/meta/lib/oeqa/selftest/cases/reproducible.py @@ -1,7 +1,7 @@ # # SPDX-License-Identifier: MIT # -# Copyright 2019 by Garmin Ltd. or its subsidiaries +# Copyright 2019-2020 by Garmin Ltd. or its subsidiaries from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars @@ -15,6 +15,7 @@ import tempfile import shutil import stat import os +import datetime MISSING = 'MISSING' DIFFERENT = 'DIFFERENT' @@ -78,8 +79,18 @@ def compare_file(reference, test, diffutils_sysroot): class ReproducibleTests(OESelftestTestCase): package_classes = ['deb', 'ipk'] - images = ['core-image-minimal'] + images = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline'] save_results = False + if 'OEQA_DEBUGGING_SAVED_OUTPUT' in os.environ: + save_results = os.environ['OEQA_DEBUGGING_SAVED_OUTPUT'] + + # This variable controls if one of the test builds is allowed to pull from + # an sstate cache/mirror. The other build is always done clean as a point of + # comparison. + # If you know that your sstate archives are reproducible, enabling this + # will test that and also make the test run faster. If your sstate is not + # reproducible, disable this in your derived test class + build_from_sstate = True def setUpLocal(self): super().setUpLocal() @@ -88,12 +99,12 @@ class ReproducibleTests(OESelftestTestCase): for v in needed_vars: setattr(self, v.lower(), bb_vars[v]) - self.extrasresults = {} - self.extrasresults.setdefault('reproducible.rawlogs', {})['log'] = '' - self.extrasresults.setdefault('reproducible', {}).setdefault('files', {}) + self.extraresults = {} + self.extraresults.setdefault('reproducible.rawlogs', {})['log'] = '' + self.extraresults.setdefault('reproducible', {}).setdefault('files', {}) def append_to_log(self, msg): - self.extrasresults['reproducible.rawlogs']['log'] += msg + self.extraresults['reproducible.rawlogs']['log'] += msg def compare_packages(self, reference_dir, test_dir, diffutils_sysroot): result = PackageCompareResults() @@ -120,60 +131,69 @@ class ReproducibleTests(OESelftestTestCase): return result def write_package_list(self, package_class, name, packages): - self.extrasresults['reproducible']['files'].setdefault(package_class, {})[name] = [ + self.extraresults['reproducible']['files'].setdefault(package_class, {})[name] = [ {'reference': p.reference, 'test': p.test} for p in packages] def copy_file(self, source, dest): bb.utils.mkdirhier(os.path.dirname(dest)) shutil.copyfile(source, dest) - def test_reproducible_builds(self): + def do_test_build(self, name, use_sstate): capture_vars = ['DEPLOY_DIR_' + c.upper() for c in self.package_classes] - if self.save_results: - save_dir = tempfile.mkdtemp(prefix='oe-reproducible-') - os.chmod(save_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) - self.logger.info('Non-reproducible packages will be copied to %s', save_dir) + tmpdir = os.path.join(self.topdir, name, 'tmp') + if os.path.exists(tmpdir): + bb.utils.remove(tmpdir, recurse=True) + + config = textwrap.dedent('''\ + INHERIT += "reproducible_build" + PACKAGE_CLASSES = "{package_classes}" + INHIBIT_PACKAGE_STRIP = "1" + TMPDIR = "{tmpdir}" + ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes), + tmpdir=tmpdir) + + if not use_sstate: + # This config fragment will disable using shared and the sstate + # mirror, forcing a complete build from scratch + config += textwrap.dedent('''\ + SSTATE_DIR = "${TMPDIR}/sstate" + SSTATE_MIRROR = "" + ''') + + self.write_config(config) + d = get_bb_vars(capture_vars) + bitbake(' '.join(self.images)) + return d + + def test_reproducible_builds(self): + def strip_topdir(s): + if s.startswith(self.topdir): + return s[len(self.topdir):] + return s # Build native utilities self.write_config('') - bitbake("diffutils-native -c addto_recipe_sysroot") + bitbake("diffoscope-native diffutils-native jquery-native -c addto_recipe_sysroot") diffutils_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "diffutils-native") + diffoscope_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "diffoscope-native") + jquery_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "jquery-native") - # Reproducible builds should not pull from sstate or mirrors, but - # sharing DL_DIR is fine - common_config = textwrap.dedent('''\ - INHERIT += "reproducible_build" - PACKAGE_CLASSES = "%s" - SSTATE_DIR = "${TMPDIR}/sstate" - ''') % (' '.join('package_%s' % c for c in self.package_classes)) - - # Perform a build. - reproducibleA_tmp = os.path.join(self.topdir, 'reproducibleA', 'tmp') - if os.path.exists(reproducibleA_tmp): - bb.utils.remove(reproducibleA_tmp, recurse=True) - - self.write_config((textwrap.dedent('''\ - TMPDIR = "%s" - ''') % reproducibleA_tmp) + common_config) - vars_A = get_bb_vars(capture_vars) - bitbake(' '.join(self.images)) - - # Perform another build. - reproducibleB_tmp = os.path.join(self.topdir, 'reproducibleB', 'tmp') - if os.path.exists(reproducibleB_tmp): - bb.utils.remove(reproducibleB_tmp, recurse=True) + if self.save_results: + os.makedirs(self.save_results, exist_ok=True) + datestr = datetime.datetime.now().strftime('%Y%m%d') + save_dir = tempfile.mkdtemp(prefix='oe-reproducible-%s-' % datestr, dir=self.save_results) + os.chmod(save_dir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + self.logger.info('Non-reproducible packages will be copied to %s', save_dir) - self.write_config((textwrap.dedent('''\ - SSTATE_MIRROR = "" - TMPDIR = "%s" - ''') % reproducibleB_tmp) + common_config) - vars_B = get_bb_vars(capture_vars) - bitbake(' '.join(self.images)) + vars_A = self.do_test_build('reproducibleA', self.build_from_sstate) + vars_B = self.do_test_build('reproducibleB', False) # NOTE: The temp directories from the reproducible build are purposely # kept after the build so it can be diffed for debugging. + fails = [] + for c in self.package_classes: with self.subTest(package_class=c): package_class = 'package_' + c @@ -193,10 +213,28 @@ class ReproducibleTests(OESelftestTestCase): if self.save_results: for d in result.different: - self.copy_file(d.reference, '/'.join([save_dir, d.reference])) - self.copy_file(d.test, '/'.join([save_dir, d.test])) + self.copy_file(d.reference, '/'.join([save_dir, 'packages', strip_topdir(d.reference)])) + self.copy_file(d.test, '/'.join([save_dir, 'packages', strip_topdir(d.test)])) if result.missing or result.different: - self.fail("The following %s packages are missing or different: %s" % - (c, ' '.join(r.test for r in (result.missing + result.different)))) + fails.append("The following %s packages are missing or different: %s" % + (c, '\n'.join(r.test for r in (result.missing + result.different)))) + + # Clean up empty directories + if self.save_results: + if not os.listdir(save_dir): + os.rmdir(save_dir) + else: + self.logger.info('Running diffoscope') + package_dir = os.path.join(save_dir, 'packages') + package_html_dir = os.path.join(package_dir, 'diff-html') + + # Copy jquery to improve the diffoscope output usability + self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js')) + + runCmd(['diffoscope', '--no-default-limits', '--exclude-directory-metadata', '--html-dir', package_html_dir, 'reproducibleA', 'reproducibleB'], + native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir) + + if fails: + self.fail('\n'.join(fails)) diff --git a/poky/meta/lib/oeqa/selftest/cases/runtime_test.py b/poky/meta/lib/oeqa/selftest/cases/runtime_test.py index 4b56e5bec..60cb2e01a 100644 --- a/poky/meta/lib/oeqa/selftest/cases/runtime_test.py +++ b/poky/meta/lib/oeqa/selftest/cases/runtime_test.py @@ -10,6 +10,7 @@ import re import tempfile import shutil import oe.lsb +from oeqa.core.decorator.data import skipIfNotQemu class TestExport(OESelftestTestCase): @@ -166,9 +167,9 @@ class TestImage(OESelftestTestCase): bitbake('core-image-full-cmdline socat') bitbake('-c testimage core-image-full-cmdline') - def test_testimage_virgl_gtk(self): + def test_testimage_virgl_gtk_sdl(self): """ - Summary: Check host-assisted accelerate OpenGL functionality in qemu with gtk frontend + Summary: Check host-assisted accelerate OpenGL functionality in qemu with gtk and SDL frontends Expected: 1. Check that virgl kernel driver is loaded and 3d acceleration is enabled 2. Check that kmscube demo runs without crashing. Product: oe-core @@ -181,20 +182,31 @@ class TestImage(OESelftestTestCase): self.skipTest('virgl isn\'t working with Debian 8') if distro and distro == 'centos-7': self.skipTest('virgl isn\'t working with Centos 7') + if distro and distro == 'opensuseleap-15.0': + self.skipTest('virgl isn\'t working with Opensuse 15.0') qemu_packageconfig = get_bb_var('PACKAGECONFIG', 'qemu-system-native') + sdl_packageconfig = get_bb_var('PACKAGECONFIG', 'libsdl2-native') features = 'INHERIT += "testimage"\n' if 'gtk+' not in qemu_packageconfig: features += 'PACKAGECONFIG_append_pn-qemu-system-native = " gtk+"\n' + if 'sdl' not in qemu_packageconfig: + features += 'PACKAGECONFIG_append_pn-qemu-system-native = " sdl"\n' if 'virglrenderer' not in qemu_packageconfig: features += 'PACKAGECONFIG_append_pn-qemu-system-native = " virglrenderer"\n' if 'glx' not in qemu_packageconfig: features += 'PACKAGECONFIG_append_pn-qemu-system-native = " glx"\n' + if 'opengl' not in sdl_packageconfig: + features += 'PACKAGECONFIG_append_pn-libsdl2-native = " opengl"\n' features += 'TEST_SUITES = "ping ssh virgl"\n' features += 'IMAGE_FEATURES_append = " ssh-server-dropbear"\n' features += 'IMAGE_INSTALL_append = " kmscube"\n' - features += 'TEST_RUNQEMUPARAMS = "gtk gl"\n' - self.write_config(features) + features_gtk = features + 'TEST_RUNQEMUPARAMS = "gtk gl"\n' + self.write_config(features_gtk) + bitbake('core-image-minimal') + bitbake('-c testimage core-image-minimal') + features_sdl = features + 'TEST_RUNQEMUPARAMS = "sdl gl"\n' + self.write_config(features_sdl) bitbake('core-image-minimal') bitbake('-c testimage core-image-minimal') @@ -232,7 +244,47 @@ class TestImage(OESelftestTestCase): bitbake('-c testimage core-image-minimal') class Postinst(OESelftestTestCase): - def test_postinst_rootfs_and_boot(self): + + def init_manager_loop(self, init_manager): + import oe.path + + vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal") + rootfs = vars["IMAGE_ROOTFS"] + self.assertIsNotNone(rootfs) + sysconfdir = vars["sysconfdir"] + self.assertIsNotNone(sysconfdir) + # Need to use oe.path here as sysconfdir starts with / + hosttestdir = oe.path.join(rootfs, sysconfdir, "postinst-test") + targettestdir = os.path.join(sysconfdir, "postinst-test") + + for classes in ("package_rpm", "package_deb", "package_ipk"): + with self.subTest(init_manager=init_manager, package_class=classes): + features = 'CORE_IMAGE_EXTRA_INSTALL = "postinst-delayed-b"\n' + features += 'IMAGE_FEATURES += "package-management empty-root-password"\n' + features += 'PACKAGE_CLASSES = "%s"\n' % classes + if init_manager == "systemd": + features += 'DISTRO_FEATURES_append = " systemd"\n' + features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n' + features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n' + features += 'VIRTUAL-RUNTIME_initscripts = ""\n' + self.write_config(features) + + bitbake('core-image-minimal') + + self.assertTrue(os.path.isfile(os.path.join(hosttestdir, "rootfs")), + "rootfs state file was not created") + + with runqemu('core-image-minimal') as qemu: + # Make the test echo a string and search for that as + # run_serial()'s status code is useless.' + for filename in ("rootfs", "delayed-a", "delayed-b"): + status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename)) + self.assertEqual(output, "found", "%s was not present on boot" % filename) + + + + @skipIfNotQemu('qemuall', 'Test only runs in qemu') + def test_postinst_rootfs_and_boot_sysvinit(self): """ Summary: The purpose of this test case is to verify Post-installation scripts are called when rootfs is created and also test @@ -246,46 +298,32 @@ class Postinst(OESelftestTestCase): created by postinst_boot recipe is present on image. Expected: The files are successfully created during rootfs and boot time for 3 different package managers: rpm,ipk,deb and - for initialization managers: sysvinit and systemd. + for initialization managers: sysvinit. """ + self.init_manager_loop("sysvinit") - import oe.path - vars = get_bb_vars(("IMAGE_ROOTFS", "sysconfdir"), "core-image-minimal") - rootfs = vars["IMAGE_ROOTFS"] - self.assertIsNotNone(rootfs) - sysconfdir = vars["sysconfdir"] - self.assertIsNotNone(sysconfdir) - # Need to use oe.path here as sysconfdir starts with / - hosttestdir = oe.path.join(rootfs, sysconfdir, "postinst-test") - targettestdir = os.path.join(sysconfdir, "postinst-test") + @skipIfNotQemu('qemuall', 'Test only runs in qemu') + def test_postinst_rootfs_and_boot_systemd(self): + """ + Summary: The purpose of this test case is to verify Post-installation + scripts are called when rootfs is created and also test + that script can be delayed to run at first boot. + Dependencies: NA + Steps: 1. Add proper configuration to local.conf file + 2. Build a "core-image-minimal" image + 3. Verify that file created by postinst_rootfs recipe is + present on rootfs dir. + 4. Boot the image created on qemu and verify that the file + created by postinst_boot recipe is present on image. + Expected: The files are successfully created during rootfs and boot + time for 3 different package managers: rpm,ipk,deb and + for initialization managers: systemd. - for init_manager in ("sysvinit", "systemd"): - for classes in ("package_rpm", "package_deb", "package_ipk"): - with self.subTest(init_manager=init_manager, package_class=classes): - features = 'CORE_IMAGE_EXTRA_INSTALL = "postinst-delayed-b"\n' - features += 'IMAGE_FEATURES += "package-management empty-root-password"\n' - features += 'PACKAGE_CLASSES = "%s"\n' % classes - if init_manager == "systemd": - features += 'DISTRO_FEATURES_append = " systemd"\n' - features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n' - features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n' - features += 'VIRTUAL-RUNTIME_initscripts = ""\n' - self.write_config(features) - - bitbake('core-image-minimal') - - self.assertTrue(os.path.isfile(os.path.join(hosttestdir, "rootfs")), - "rootfs state file was not created") - - with runqemu('core-image-minimal') as qemu: - # Make the test echo a string and search for that as - # run_serial()'s status code is useless.' - for filename in ("rootfs", "delayed-a", "delayed-b"): - status, output = qemu.run_serial("test -f %s && echo found" % os.path.join(targettestdir, filename)) - self.assertEqual(output, "found", "%s was not present on boot" % filename) + """ + self.init_manager_loop("systemd") def test_failing_postinst(self): diff --git a/poky/meta/lib/oeqa/selftest/cases/signing.py b/poky/meta/lib/oeqa/selftest/cases/signing.py index 93b15ae68..202d54994 100644 --- a/poky/meta/lib/oeqa/selftest/cases/signing.py +++ b/poky/meta/lib/oeqa/selftest/cases/signing.py @@ -157,8 +157,8 @@ class Signing(OESelftestTestCase): bitbake('-c clean %s' % test_recipe) bitbake('-c populate_lic %s' % test_recipe) - recipe_sig = glob.glob(sstatedir + '/*/*:ed:*_populate_lic.tgz.sig') - recipe_tgz = glob.glob(sstatedir + '/*/*:ed:*_populate_lic.tgz') + recipe_sig = glob.glob(sstatedir + '/*/*/*:ed:*_populate_lic.tgz.sig') + recipe_tgz = glob.glob(sstatedir + '/*/*/*:ed:*_populate_lic.tgz') self.assertEqual(len(recipe_sig), 1, 'Failed to find .sig file.') self.assertEqual(len(recipe_tgz), 1, 'Failed to find .tgz file.') diff --git a/poky/meta/lib/oeqa/selftest/cases/sstate.py b/poky/meta/lib/oeqa/selftest/cases/sstate.py index 410dec64f..80ce9e353 100644 --- a/poky/meta/lib/oeqa/selftest/cases/sstate.py +++ b/poky/meta/lib/oeqa/selftest/cases/sstate.py @@ -56,11 +56,11 @@ class SStateBase(OESelftestTestCase): def search_sstate(self, filename_regex, distro_specific=True, distro_nonspecific=True): result = [] for root, dirs, files in os.walk(self.sstate_path): - if distro_specific and re.search("%s/[a-z0-9]{2}$" % self.hostdistro, root): + if distro_specific and re.search(r"%s/%s/[a-z0-9]{2}/[a-z0-9]{2}$" % (self.sstate_path, self.hostdistro), root): for f in files: if re.search(filename_regex, f): result.append(f) - if distro_nonspecific and re.search("%s/[a-z0-9]{2}$" % self.sstate_path, root): + if distro_nonspecific and re.search(r"%s/[a-z0-9]{2}/[a-z0-9]{2}$" % self.sstate_path, root): for f in files: if re.search(filename_regex, f): result.append(f) diff --git a/poky/meta/lib/oeqa/selftest/cases/sstatetests.py b/poky/meta/lib/oeqa/selftest/cases/sstatetests.py index 6757a0ec6..9adb51196 100644 --- a/poky/meta/lib/oeqa/selftest/cases/sstatetests.py +++ b/poky/meta/lib/oeqa/selftest/cases/sstatetests.py @@ -446,6 +446,46 @@ BB_SIGNATURE_HANDLER = "OEBasicHash" self.assertCountEqual(files1, files2) + def test_sstate_multilib_or_not_native_samesigs(self): + """The sstate checksums of two native recipes (and their dependencies) + where the target is using multilib in one but not the other + should be the same. We use the qemux86copy machine to test + this. + """ + + self.write_config(""" +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" +TCLIBCAPPEND = \"\" +MACHINE = \"qemux86\" +require conf/multilib.conf +MULTILIBS = "multilib:lib32" +DEFAULTTUNE_virtclass-multilib-lib32 = "x86" +BB_SIGNATURE_HANDLER = "OEBasicHash" +""") + self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash") + bitbake("binutils-native -S none") + self.write_config(""" +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" +TCLIBCAPPEND = \"\" +MACHINE = \"qemux86copy\" +BB_SIGNATURE_HANDLER = "OEBasicHash" +""") + self.track_for_cleanup(self.topdir + "/tmp-sstatesamehash2") + bitbake("binutils-native -S none") + + def get_files(d): + f = [] + for root, dirs, files in os.walk(d): + for name in files: + f.append(os.path.join(root, name)) + return f + files1 = get_files(self.topdir + "/tmp-sstatesamehash/stamps") + files2 = get_files(self.topdir + "/tmp-sstatesamehash2/stamps") + files2 = [x.replace("tmp-sstatesamehash2", "tmp-sstatesamehash") for x in files2] + self.maxDiff = None + self.assertCountEqual(files1, files2) + + def test_sstate_noop_samesigs(self): """ The sstate checksums of two builds with these variables changed or diff --git a/poky/meta/lib/oeqa/selftest/cases/sysroot.py b/poky/meta/lib/oeqa/selftest/cases/sysroot.py new file mode 100644 index 000000000..6e34927c9 --- /dev/null +++ b/poky/meta/lib/oeqa/selftest/cases/sysroot.py @@ -0,0 +1,37 @@ +# +# SPDX-License-Identifier: MIT +# + +import uuid + +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import bitbake + +class SysrootTests(OESelftestTestCase): + def test_sysroot_cleanup(self): + """ + Build sysroot test which depends on virtual/sysroot-test for one machine, + switch machine, switch provider of virtual/sysroot-test and check that the + sysroot is correctly cleaned up. The files in the two providers overlap + so can cause errors if the sysroot code doesn't function correctly. + Yes, sysroot-test should be machine specific really to avoid this, however + the sysroot cleanup should also work [YOCTO #13702]. + """ + + uuid1 = uuid.uuid4() + uuid2 = uuid.uuid4() + + self.write_config(""" +PREFERRED_PROVIDER_virtual/sysroot-test = "sysroot-test-arch1" +MACHINE = "qemux86" +TESTSTRING_pn-sysroot-test-arch1 = "%s" +TESTSTRING_pn-sysroot-test-arch2 = "%s" +""" % (uuid1, uuid2)) + bitbake("sysroot-test") + self.write_config(""" +PREFERRED_PROVIDER_virtual/sysroot-test = "sysroot-test-arch2" +MACHINE = "qemux86copy" +TESTSTRING_pn-sysroot-test-arch1 = "%s" +TESTSTRING_pn-sysroot-test-arch2 = "%s" +""" % (uuid1, uuid2)) + bitbake("sysroot-test") diff --git a/poky/meta/lib/oeqa/selftest/cases/tinfoil.py b/poky/meta/lib/oeqa/selftest/cases/tinfoil.py index 42a1b6b4f..d1aa7b9af 100644 --- a/poky/meta/lib/oeqa/selftest/cases/tinfoil.py +++ b/poky/meta/lib/oeqa/selftest/cases/tinfoil.py @@ -65,19 +65,6 @@ class TinfoilTests(OESelftestTestCase): localdata.setVar('PN', 'hello') self.assertEqual('hello', localdata.getVar('BPN')) - def test_parse_recipe_initial_datastore(self): - with bb.tinfoil.Tinfoil() as tinfoil: - tinfoil.prepare(config_only=False, quiet=2) - testrecipe = 'mdadm' - best = tinfoil.find_best_provider(testrecipe) - if not best: - self.fail('Unable to find recipe providing %s' % testrecipe) - dcopy = bb.data.createCopy(tinfoil.config_data) - dcopy.setVar('MYVARIABLE', 'somevalue') - rd = tinfoil.parse_recipe_file(best[3], config_data=dcopy) - # Check we can get variable values - self.assertEqual('somevalue', rd.getVar('MYVARIABLE')) - def test_list_recipes(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=False, quiet=2) diff --git a/poky/meta/lib/oeqa/selftest/cases/wic.py b/poky/meta/lib/oeqa/selftest/cases/wic.py index 0c03b4b02..c8765e533 100644 --- a/poky/meta/lib/oeqa/selftest/cases/wic.py +++ b/poky/meta/lib/oeqa/selftest/cases/wic.py @@ -44,6 +44,30 @@ def only_for_arch(archs, image='core-image-minimal'): return wrapped_f return wrapper +def extract_files(debugfs_output): + """ + extract file names from the output of debugfs -R 'ls -p', + which looks like this: + + /2/040755/0/0/.//\n + /2/040755/0/0/..//\n + /11/040700/0/0/lost+found^M//\n + /12/040755/1002/1002/run//\n + /13/040755/1002/1002/sys//\n + /14/040755/1002/1002/bin//\n + /80/040755/1002/1002/var//\n + /92/040755/1002/1002/tmp//\n + """ + # NOTE the occasional ^M in file names + return [line.split('/')[5].strip() for line in \ + debugfs_output.strip().split('/\n')] + +def files_own_by_root(debugfs_output): + for line in debugfs_output.strip().split('/\n'): + if line.split('/')[3:5] != ['0', '0']: + print(debugfs_output) + return False + return True class WicTestCase(OESelftestTestCase): """Wic test class.""" @@ -66,6 +90,7 @@ class WicTestCase(OESelftestTestCase): self.skipTest('wic-tools cannot be built due its (intltool|gettext)-native dependency and NLS disable') bitbake('core-image-minimal') + bitbake('core-image-minimal-mtdutils') WicTestCase.image_is_ready = True rmtree(self.resultdir, ignore_errors=True) @@ -393,24 +418,6 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r runCmd("dd if=%s of=%s skip=%d count=%d" % (wicimg, part_file, start, length)) - def extract_files(debugfs_output): - """ - extract file names from the output of debugfs -R 'ls -p', - which looks like this: - - /2/040755/0/0/.//\n - /2/040755/0/0/..//\n - /11/040700/0/0/lost+found^M//\n - /12/040755/1002/1002/run//\n - /13/040755/1002/1002/sys//\n - /14/040755/1002/1002/bin//\n - /80/040755/1002/1002/var//\n - /92/040755/1002/1002/tmp//\n - """ - # NOTE the occasional ^M in file names - return [line.split('/')[5].strip() for line in \ - debugfs_output.strip().split('/\n')] - # Test partition 1, should contain the normal root directories, except # /usr. res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % \ @@ -451,6 +458,104 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r finally: os.environ['PATH'] = oldpath + def test_include_path(self): + """Test --include-path wks option.""" + + oldpath = os.environ['PATH'] + os.environ['PATH'] = get_bb_var("PATH", "wic-tools") + + try: + include_path = os.path.join(self.resultdir, 'test-include') + os.makedirs(include_path) + with open(os.path.join(include_path, 'test-file'), 'w') as t: + t.write("test\n") + wks_file = os.path.join(include_path, 'temp.wks') + with open(wks_file, 'w') as wks: + rootfs_dir = get_bb_var('IMAGE_ROOTFS', 'core-image-minimal') + wks.write(""" +part /part1 --source rootfs --ondisk mmcblk0 --fstype=ext4 +part /part2 --source rootfs --ondisk mmcblk0 --fstype=ext4 --include-path %s""" + % (include_path)) + runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir)) + + part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] + part2 = glob(os.path.join(self.resultdir, 'temp-*.direct.p2'))[0] + + # Test partition 1, should not contain 'test-file' + res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) + files = extract_files(res.output) + self.assertNotIn('test-file', files) + self.assertEqual(True, files_own_by_root(res.output)) + + # Test partition 2, should contain 'test-file' + res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part2)) + files = extract_files(res.output) + self.assertIn('test-file', files) + self.assertEqual(True, files_own_by_root(res.output)) + + finally: + os.environ['PATH'] = oldpath + + def test_include_path_embeded(self): + """Test --include-path wks option.""" + + oldpath = os.environ['PATH'] + os.environ['PATH'] = get_bb_var("PATH", "wic-tools") + + try: + include_path = os.path.join(self.resultdir, 'test-include') + os.makedirs(include_path) + with open(os.path.join(include_path, 'test-file'), 'w') as t: + t.write("test\n") + wks_file = os.path.join(include_path, 'temp.wks') + with open(wks_file, 'w') as wks: + wks.write(""" +part / --source rootfs --fstype=ext4 --include-path %s --include-path core-image-minimal-mtdutils export/""" + % (include_path)) + runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir)) + + part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] + + res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) + files = extract_files(res.output) + self.assertIn('test-file', files) + self.assertEqual(True, files_own_by_root(res.output)) + + res = runCmd("debugfs -R 'ls -p /export/etc/' %s 2>/dev/null" % (part1)) + files = extract_files(res.output) + self.assertIn('passwd', files) + self.assertEqual(True, files_own_by_root(res.output)) + + finally: + os.environ['PATH'] = oldpath + + def test_include_path_errors(self): + """Test --include-path wks option error handling.""" + wks_file = 'temp.wks' + + # Absolute argument. + with open(wks_file, 'w') as wks: + wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils /export") + self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir), ignore_status=True).status) + os.remove(wks_file) + + # Argument pointing to parent directory. + with open(wks_file, 'w') as wks: + wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils ././..") + self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir), ignore_status=True).status) + os.remove(wks_file) + + # 3 Argument pointing to parent directory. + with open(wks_file, 'w') as wks: + wks.write("part / --source rootfs --fstype=ext4 --include-path core-image-minimal-mtdutils export/ dummy") + self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir), ignore_status=True).status) + os.remove(wks_file) + def test_exclude_path_errors(self): """Test --exclude-path wks option error handling.""" wks_file = 'temp.wks' @@ -469,6 +574,89 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r % (wks_file, self.resultdir), ignore_status=True).status) os.remove(wks_file) + def test_permissions(self): + """Test permissions are respected""" + + oldpath = os.environ['PATH'] + os.environ['PATH'] = get_bb_var("PATH", "wic-tools") + + t_normal = """ +part / --source rootfs --fstype=ext4 +""" + t_exclude = """ +part / --source rootfs --fstype=ext4 --exclude-path=home +""" + t_multi = """ +part / --source rootfs --ondisk sda --fstype=ext4 +part /export --source rootfs --rootfs=core-image-minimal-mtdutils --fstype=ext4 +""" + t_change = """ +part / --source rootfs --ondisk sda --fstype=ext4 --exclude-path=etc/    +part /etc --source rootfs --fstype=ext4 --change-directory=etc +""" + tests = [t_normal, t_exclude, t_multi, t_change] + + try: + for test in tests: + include_path = os.path.join(self.resultdir, 'test-include') + os.makedirs(include_path) + wks_file = os.path.join(include_path, 'temp.wks') + with open(wks_file, 'w') as wks: + wks.write(test) + runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir)) + + for part in glob(os.path.join(self.resultdir, 'temp-*.direct.p*')): + res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part)) + self.assertEqual(True, files_own_by_root(res.output)) + + rmtree(self.resultdir, ignore_errors=True) + + finally: + os.environ['PATH'] = oldpath + + def test_change_directory(self): + """Test --change-directory wks option.""" + + oldpath = os.environ['PATH'] + os.environ['PATH'] = get_bb_var("PATH", "wic-tools") + + try: + include_path = os.path.join(self.resultdir, 'test-include') + os.makedirs(include_path) + wks_file = os.path.join(include_path, 'temp.wks') + with open(wks_file, 'w') as wks: + wks.write("part /etc --source rootfs --fstype=ext4 --change-directory=etc") + runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir)) + + part1 = glob(os.path.join(self.resultdir, 'temp-*.direct.p1'))[0] + + res = runCmd("debugfs -R 'ls -p' %s 2>/dev/null" % (part1)) + files = extract_files(res.output) + self.assertIn('passwd', files) + + finally: + os.environ['PATH'] = oldpath + + def test_change_directory_errors(self): + """Test --change-directory wks option error handling.""" + wks_file = 'temp.wks' + + # Absolute argument. + with open(wks_file, 'w') as wks: + wks.write("part / --source rootfs --fstype=ext4 --change-directory /usr") + self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir), ignore_status=True).status) + os.remove(wks_file) + + # Argument pointing to parent directory. + with open(wks_file, 'w') as wks: + wks.write("part / --source rootfs --fstype=ext4 --change-directory ././..") + self.assertNotEqual(0, runCmd("wic create %s -e core-image-minimal -o %s" \ + % (wks_file, self.resultdir), ignore_status=True).status) + os.remove(wks_file) + class Wic2(WicTestCase): def test_bmap_short(self): @@ -500,7 +688,8 @@ class Wic2(WicTestCase): # filter out optional variables wicvars = wicvars.difference(('DEPLOY_DIR_IMAGE', 'IMAGE_BOOT_FILES', 'INITRD', 'INITRD_LIVE', 'ISODIR','INITRAMFS_IMAGE', - 'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME')) + 'INITRAMFS_IMAGE_BUNDLE', 'INITRAMFS_LINK_NAME', + 'APPEND')) with open(path) as envfile: content = dict(line.split("=", 1) for line in envfile) # test if variables used by wic present in the .env file @@ -866,6 +1055,13 @@ class Wic2(WicTestCase): self.assertEqual(8, len(result.output.split('\n'))) self.assertTrue(os.path.basename(testdir) in result.output) + # copy the file from the partition and check if it success + dest = '%s-cp' % testfile.name + runCmd("wic cp %s:1/%s %s -n %s" % (images[0], + os.path.basename(testfile.name), dest, sysroot)) + self.assertTrue(os.path.exists(dest)) + + def test_wic_rm(self): """Test removing files and directories from the the wic image.""" runCmd("wic create mkefidisk " @@ -1005,6 +1201,16 @@ class Wic2(WicTestCase): newdirs = set(line.split()[-1] for line in result.output.split('\n') if line) self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)])) + # check if the file to copy is in the partition + result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot)) + self.assertTrue('fstab' in [line.split()[-1] for line in result.output.split('\n') if line]) + + # copy file from the partition, replace the temporary file content with it and + # check for the file size to validate the copy + runCmd("wic cp %s:2/etc/fstab %s -n %s" % (images[0], testfile.name, sysroot)) + self.assertTrue(os.stat(testfile.name).st_size > 0) + + def test_wic_rm_ext(self): """Test removing files from the ext partition.""" runCmd("wic create mkefidisk " diff --git a/poky/meta/lib/oeqa/selftest/context.py b/poky/meta/lib/oeqa/selftest/context.py index c4eb5d614..48ec5d419 100644 --- a/poky/meta/lib/oeqa/selftest/context.py +++ b/poky/meta/lib/oeqa/selftest/context.py @@ -9,12 +9,12 @@ import time import glob import sys import importlib -import signal -from shutil import copyfile +import subprocess from random import choice import oeqa import oe +import bb.utils from oeqa.core.context import OETestContext, OETestContextExecutor from oeqa.core.exception import OEQAPreRun, OEQATestNotFound @@ -29,6 +29,54 @@ class OESelftestTestContext(OETestContext): self.custommachine = None self.config_paths = config_paths + def setup_builddir(self, suffix, selftestdir, suite): + builddir = os.environ['BUILDDIR'] + if not selftestdir: + selftestdir = get_test_layer() + newbuilddir = builddir + suffix + newselftestdir = newbuilddir + "/meta-selftest" + + if os.path.exists(newbuilddir): + self.logger.error("Build directory %s already exists, aborting" % newbuilddir) + sys.exit(1) + + bb.utils.mkdirhier(newbuilddir) + oe.path.copytree(builddir + "/conf", newbuilddir + "/conf") + oe.path.copytree(builddir + "/cache", newbuilddir + "/cache") + oe.path.copytree(selftestdir, newselftestdir) + + for e in os.environ: + if builddir + "/" in os.environ[e] or os.environ[e].endswith(builddir): + os.environ[e] = os.environ[e].replace(builddir, newbuilddir) + + subprocess.check_output("git init; git add *; git commit -a -m 'initial'", cwd=newselftestdir, shell=True) + + # Tried to used bitbake-layers add/remove but it requires recipe parsing and hence is too slow + subprocess.check_output("sed %s/conf/bblayers.conf -i -e 's#%s#%s#g'" % (newbuilddir, selftestdir, newselftestdir), cwd=newbuilddir, shell=True) + + os.chdir(newbuilddir) + + for t in suite: + if not hasattr(t, "tc"): + continue + cp = t.tc.config_paths + for p in cp: + if selftestdir in cp[p] and newselftestdir not in cp[p]: + cp[p] = cp[p].replace(selftestdir, newselftestdir) + if builddir in cp[p] and newbuilddir not in cp[p]: + cp[p] = cp[p].replace(builddir, newbuilddir) + + return (builddir, newbuilddir) + + def prepareSuite(self, suites, processes): + if processes: + from oeqa.core.utils.concurrencytest import ConcurrentTestSuite + + return ConcurrentTestSuite(suites, processes, self.setup_builddir) + else: + self.setup_builddir("-st", None, suites) + return suites + def runTests(self, processes=None, machine=None, skips=[]): if machine: self.custommachine = machine @@ -135,26 +183,10 @@ class OESelftestTestContextExecutor(OETestContextExecutor): builddir = os.environ.get("BUILDDIR") self.tc_kwargs['init']['config_paths'] = {} - self.tc_kwargs['init']['config_paths']['testlayer_path'] = \ - get_test_layer() + self.tc_kwargs['init']['config_paths']['testlayer_path'] = get_test_layer() self.tc_kwargs['init']['config_paths']['builddir'] = builddir - self.tc_kwargs['init']['config_paths']['localconf'] = \ - os.path.join(builddir, "conf/local.conf") - self.tc_kwargs['init']['config_paths']['localconf_backup'] = \ - os.path.join(builddir, "conf/local.conf.orig") - self.tc_kwargs['init']['config_paths']['localconf_class_backup'] = \ - os.path.join(builddir, "conf/local.conf.bk") - self.tc_kwargs['init']['config_paths']['bblayers'] = \ - os.path.join(builddir, "conf/bblayers.conf") - self.tc_kwargs['init']['config_paths']['bblayers_backup'] = \ - os.path.join(builddir, "conf/bblayers.conf.orig") - self.tc_kwargs['init']['config_paths']['bblayers_class_backup'] = \ - os.path.join(builddir, "conf/bblayers.conf.bk") - - copyfile(self.tc_kwargs['init']['config_paths']['localconf'], - self.tc_kwargs['init']['config_paths']['localconf_backup']) - copyfile(self.tc_kwargs['init']['config_paths']['bblayers'], - self.tc_kwargs['init']['config_paths']['bblayers_backup']) + self.tc_kwargs['init']['config_paths']['localconf'] = os.path.join(builddir, "conf/local.conf") + self.tc_kwargs['init']['config_paths']['bblayers'] = os.path.join(builddir, "conf/bblayers.conf") def tag_filter(tags): if args.exclude_tags: @@ -279,14 +311,9 @@ class OESelftestTestContextExecutor(OETestContextExecutor): return rc - def _signal_clean_handler(self, signum, frame): - sys.exit(1) - def run(self, logger, args): self._process_args(logger, args) - signal.signal(signal.SIGTERM, self._signal_clean_handler) - rc = None try: if args.machine: @@ -315,20 +342,6 @@ class OESelftestTestContextExecutor(OETestContextExecutor): rc = self._internal_run(logger, args) finally: config_paths = self.tc_kwargs['init']['config_paths'] - if os.path.exists(config_paths['localconf_backup']): - copyfile(config_paths['localconf_backup'], - config_paths['localconf']) - os.remove(config_paths['localconf_backup']) - - if os.path.exists(config_paths['bblayers_backup']): - copyfile(config_paths['bblayers_backup'], - config_paths['bblayers']) - os.remove(config_paths['bblayers_backup']) - - if os.path.exists(config_paths['localconf_class_backup']): - os.remove(config_paths['localconf_class_backup']) - if os.path.exists(config_paths['bblayers_class_backup']): - os.remove(config_paths['bblayers_class_backup']) output_link = os.path.join(os.path.dirname(args.output_log), "%s-results.log" % self.name) diff --git a/poky/meta/lib/oeqa/targetcontrol.py b/poky/meta/lib/oeqa/targetcontrol.py index 1445e3ecf..2aa548e1a 100644 --- a/poky/meta/lib/oeqa/targetcontrol.py +++ b/poky/meta/lib/oeqa/targetcontrol.py @@ -117,9 +117,9 @@ class QemuTarget(BaseTarget): import oe.path bb.utils.mkdirhier(self.testdir) self.qemurunnerlog = os.path.join(self.testdir, 'qemurunner_log.%s' % self.datetime) - loggerhandler = logging.FileHandler(self.qemurunnerlog) - loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) - self.logger.addHandler(loggerhandler) + self.loggerhandler = logging.FileHandler(self.qemurunnerlog) + self.loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) + self.logger.addHandler(self.loggerhandler) oe.path.symlink(os.path.basename(self.qemurunnerlog), os.path.join(self.testdir, 'qemurunner_log'), force=True) if d.getVar("DISTRO") == "poky-tiny": @@ -143,7 +143,8 @@ class QemuTarget(BaseTarget): use_kvm = use_kvm, dump_dir = dump_dir, dump_host_cmds = d.getVar("testimage_dump_host"), - logger = logger) + logger = logger, + serial_ports = len(d.getVar("SERIAL_CONSOLES").split())) self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) @@ -182,6 +183,7 @@ class QemuTarget(BaseTarget): def stop(self): self.runner.stop() + self.logger.removeHandler(self.loggerhandler) self.connection = None self.ip = None self.server_ip = None diff --git a/poky/meta/lib/oeqa/utils/commands.py b/poky/meta/lib/oeqa/utils/commands.py index dc1e286da..f1679875d 100644 --- a/poky/meta/lib/oeqa/utils/commands.py +++ b/poky/meta/lib/oeqa/utils/commands.py @@ -315,15 +315,15 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, try: tinfoil.logger.setLevel(logging.WARNING) import oeqa.targetcontrol - tinfoil.config_data.setVar("TEST_LOG_DIR", "${WORKDIR}/testimage") - tinfoil.config_data.setVar("TEST_QEMUBOOT_TIMEOUT", "1000") + recipedata = tinfoil.parse_recipe(pn) + recipedata.setVar("TEST_LOG_DIR", "${WORKDIR}/testimage") + recipedata.setVar("TEST_QEMUBOOT_TIMEOUT", "1000") # Tell QemuTarget() whether need find rootfs/kernel or not if launch_cmd: - tinfoil.config_data.setVar("FIND_ROOTFS", '0') + recipedata.setVar("FIND_ROOTFS", '0') else: - tinfoil.config_data.setVar("FIND_ROOTFS", '1') + recipedata.setVar("FIND_ROOTFS", '1') - recipedata = tinfoil.parse_recipe(pn) for key, value in overrides.items(): recipedata.setVar(key, value) diff --git a/poky/meta/lib/oeqa/utils/dump.py b/poky/meta/lib/oeqa/utils/dump.py index d34e05e2b..09a44329e 100644 --- a/poky/meta/lib/oeqa/utils/dump.py +++ b/poky/meta/lib/oeqa/utils/dump.py @@ -71,8 +71,11 @@ class HostDumper(BaseDumper): def dump_host(self, dump_dir=""): if dump_dir: self.dump_dir = dump_dir + env = os.environ.copy() + env['PATH'] = '/usr/sbin:/sbin:/usr/bin:/bin' + env['COLUMNS'] = '9999' for cmd in self.cmds: - result = runCmd(cmd, ignore_status=True) + result = runCmd(cmd, ignore_status=True, env=env) self._write_dump(cmd.split()[0], result.output) class TargetDumper(BaseDumper): diff --git a/poky/meta/lib/oeqa/utils/httpserver.py b/poky/meta/lib/oeqa/utils/httpserver.py index aa435590f..58d3c3b3f 100644 --- a/poky/meta/lib/oeqa/utils/httpserver.py +++ b/poky/meta/lib/oeqa/utils/httpserver.py @@ -22,10 +22,10 @@ class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): class HTTPService(object): - def __init__(self, root_dir, host='', logger=None): + def __init__(self, root_dir, host='', port=0, logger=None): self.root_dir = root_dir self.host = host - self.port = 0 + self.port = port self.logger = logger def start(self): diff --git a/poky/meta/lib/oeqa/utils/logparser.py b/poky/meta/lib/oeqa/utils/logparser.py index 7313df8ec..60e16d500 100644 --- a/poky/meta/lib/oeqa/utils/logparser.py +++ b/poky/meta/lib/oeqa/utils/logparser.py @@ -25,13 +25,20 @@ class PtestParser(object): section_regex['exitcode'] = re.compile(r"^ERROR: Exit status is (.+)") section_regex['timeout'] = re.compile(r"^TIMEOUT: .*/(.+)/ptest") + # Cache markers so we don't take the re.search() hit all the time. + markers = ("PASS:", "FAIL:", "SKIP:", "BEGIN:", "END:", "DURATION:", "ERROR: Exit", "TIMEOUT:") + def newsection(): - return { 'name': "No-section", 'log': "" } + return { 'name': "No-section", 'log': [] } current_section = newsection() with open(logfile, errors='replace') as f: for line in f: + if not line.startswith(markers): + current_section['log'].append(line) + continue + result = section_regex['begin'].search(line) if result: current_section['name'] = result.group(1) @@ -61,7 +68,7 @@ class PtestParser(object): current_section[t] = result.group(1) continue - current_section['log'] = current_section['log'] + line + current_section['log'].append(line) for t in test_regex: result = test_regex[t].search(line) @@ -70,6 +77,11 @@ class PtestParser(object): self.results[current_section['name']] = {} self.results[current_section['name']][result.group(1).strip()] = t + # Python performance for repeatedly joining long strings is poor, do it all at once at the end. + # For 2.1 million lines in a log this reduces 18 hours to 12s. + for section in self.sections: + self.sections[section]['log'] = "".join(self.sections[section]['log']) + return self.results, self.sections # Log the results as files. The file name is the section name and the contents are the tests in that section. diff --git a/poky/meta/lib/oeqa/utils/qemurunner.py b/poky/meta/lib/oeqa/utils/qemurunner.py index fe8b77d97..4b7433765 100644 --- a/poky/meta/lib/oeqa/utils/qemurunner.py +++ b/poky/meta/lib/oeqa/utils/qemurunner.py @@ -21,6 +21,7 @@ import threading import codecs import logging from oeqa.utils.dump import HostDumper +from collections import defaultdict # Get Unicode non printable control chars control_range = list(range(0,32))+list(range(127,160)) @@ -31,10 +32,11 @@ re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) class QemuRunner: def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, - use_kvm, logger, use_slirp=False): + use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str), use_ovmf=False): # Popen object for runqemu self.runqemu = None + self.runqemu_exited = False # pid of the qemu process that runqemu will start self.qemupid = None # target ip - from the command line or runqemu output @@ -54,8 +56,11 @@ class QemuRunner: self.logged = False self.thread = None self.use_kvm = use_kvm + self.use_ovmf = use_ovmf self.use_slirp = use_slirp + self.serial_ports = serial_ports self.msg = '' + self.boot_patterns = boot_patterns self.runqemutime = 120 self.qemu_pidfile = 'pidfile_'+str(os.getpid()) @@ -64,6 +69,25 @@ class QemuRunner: self.logger = logger + # Enable testing other OS's + # Set commands for target communication, and default to Linux ALWAYS + # Other OS's or baremetal applications need to provide their + # own implementation passing it through QemuRunner's constructor + # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag] + # provided variables, where is one of the mentioned below. + accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished'] + default_boot_patterns = defaultdict(str) + # Default to the usual paterns used to communicate with the target + default_boot_patterns['search_reached_prompt'] = b' login:' + default_boot_patterns['send_login_user'] = 'root\n' + default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#" + default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#" + + # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" + for pattern in accepted_patterns: + if not self.boot_patterns[pattern]: + self.boot_patterns[pattern] = default_boot_patterns[pattern] + def create_socket(self): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -98,11 +122,10 @@ class QemuRunner: def handleSIGCHLD(self, signum, frame): if self.runqemu and self.runqemu.poll(): if self.runqemu.returncode: - self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) - self.logger.debug("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout)) + self.logger.error('runqemu exited with code %d' % self.runqemu.returncode) + self.logger.error('Output from runqemu:\n%s' % self.getOutput(self.runqemu.stdout)) self.stop() self._dump_host() - raise SystemExit def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True): env = os.environ.copy() @@ -136,13 +159,16 @@ class QemuRunner: launch_cmd += ' nographic' if self.use_slirp: launch_cmd += ' slirp' + if self.use_ovmf: + launch_cmd += ' ovmf' launch_cmd += ' %s %s %s' % (runqemuparams, self.machine, self.rootfs) return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): try: - self.threadsock, threadport = self.create_socket() + if self.serial_ports >= 2: + self.threadsock, threadport = self.create_socket() self.server_socket, self.serverport = self.create_socket() except socket.error as msg: self.logger.error("Failed to create listening socket: %s" % msg[1]) @@ -160,7 +186,10 @@ class QemuRunner: if qemuparams: self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' - launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams) + if self.serial_ports >= 2: + launch_cmd += ' tcpserial=%s:%s %s' % (threadport, self.serverport, self.qemuparams) + else: + launch_cmd += ' tcpserial=%s %s' % (self.serverport, self.qemuparams) self.origchldhandler = signal.getsignal(signal.SIGCHLD) signal.signal(signal.SIGCHLD, self.handleSIGCHLD) @@ -206,6 +235,8 @@ class QemuRunner: endtime = time.time() + self.runqemutime while not self.is_alive() and time.time() < endtime: if self.runqemu.poll(): + if self.runqemu_exited: + return False if self.runqemu.returncode: # No point waiting any longer self.logger.warning('runqemu exited with code %d' % self.runqemu.returncode) @@ -215,6 +246,9 @@ class QemuRunner: return False time.sleep(0.5) + if self.runqemu_exited: + return False + if not self.is_alive(): self.logger.error("Qemu pid didn't appear in %s seconds (%s)" % (self.runqemutime, time.strftime("%D %H:%M:%S"))) @@ -237,8 +271,8 @@ class QemuRunner: self.logger.debug("qemu started in %s seconds - qemu procces pid is %s (%s)" % (time.time() - (endtime - self.runqemutime), self.qemupid, time.strftime("%D %H:%M:%S"))) + cmdline = '' if get_ip: - cmdline = '' with open('/proc/%s/cmdline' % self.qemupid) as p: cmdline = p.read() # It is needed to sanitize the data received @@ -275,14 +309,15 @@ class QemuRunner: self.logger.debug("Target IP: %s" % self.ip) self.logger.debug("Server IP: %s" % self.server_ip) - self.thread = LoggingThread(self.log, self.threadsock, self.logger) - self.thread.start() - if not self.thread.connection_established.wait(self.boottime): - self.logger.error("Didn't receive a console connection from qemu. " - "Here is the qemu command line used:\n%s\nand " - "output from runqemu:\n%s" % (cmdline, out)) - self.stop_thread() - return False + if self.serial_ports >= 2: + self.thread = LoggingThread(self.log, self.threadsock, self.logger) + self.thread.start() + if not self.thread.connection_established.wait(self.boottime): + self.logger.error("Didn't receive a console connection from qemu. " + "Here is the qemu command line used:\n%s\nand " + "output from runqemu:\n%s" % (cmdline, out)) + self.stop_thread() + return False self.logger.debug("Output from runqemu:\n%s", out) self.logger.debug("Waiting at most %d seconds for login banner (%s)" % @@ -310,8 +345,12 @@ class QemuRunner: data = data + sock.recv(1024) if data: bootlog += data + if self.serial_ports < 2: + # this socket has mixed console/kernel data, log it to logfile + self.log(data) + data = b'' - if b' login:' in bootlog: + if self.boot_patterns['search_reached_prompt'] in bootlog: self.server_socket = qemusock stopread = True reachedlogin = True @@ -343,8 +382,8 @@ class QemuRunner: # If we are not able to login the tests can continue try: - (status, output) = self.run_serial("root\n", raw=True) - if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): + (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True) + if re.search(self.boot_patterns['search_login_succeeded'], output): self.logged = True self.logger.debug("Logged as root in serial console") if netconf: @@ -385,7 +424,7 @@ class QemuRunner: os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) self.runqemu.stdin.close() self.runqemu.stdout.close() - self.runqemu = None + self.runqemu_exited = True if hasattr(self, 'server_socket') and self.server_socket: self.server_socket.close() @@ -396,7 +435,11 @@ class QemuRunner: self.qemupid = None self.ip = None if os.path.exists(self.qemu_pidfile): - os.remove(self.qemu_pidfile) + try: + os.remove(self.qemu_pidfile) + except FileNotFoundError as e: + # We raced, ignore + pass if self.monitorpipe: self.monitorpipe.close() @@ -422,7 +465,7 @@ class QemuRunner: return False def is_alive(self): - if not self.runqemu or self.runqemu.poll() is not None: + if not self.runqemu or self.runqemu.poll() is not None or self.runqemu_exited: return False if os.path.isfile(self.qemu_pidfile): # when handling pidfile, qemu creates the file, stat it, lock it and then write to it @@ -465,7 +508,7 @@ class QemuRunner: if answer: data += answer.decode('utf-8') # Search the prompt to stop - if re.search(r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data): + if re.search(self.boot_patterns['search_cmd_finished'], data): break else: raise Exception("No data on serial console socket") diff --git a/poky/meta/lib/oeqa/utils/sshcontrol.py b/poky/meta/lib/oeqa/utils/sshcontrol.py index 49a07264c..36c2ecb3d 100644 --- a/poky/meta/lib/oeqa/utils/sshcontrol.py +++ b/poky/meta/lib/oeqa/utils/sshcontrol.py @@ -23,7 +23,7 @@ class SSHProcess(object): "stdin": None, "shell": False, "bufsize": -1, - "preexec_fn": os.setsid, + "start_new_session": True, } self.options = dict(self.defaultopts) self.options.update(options) -- cgit v1.2.3