diff options
Diffstat (limited to 'import-layers/yocto-poky/meta/lib/oeqa')
57 files changed, 2501 insertions, 556 deletions
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/__init__.py +++ /dev/null diff --git a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/__init__.py new file mode 100644 index 000000000..605f429ec --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/__init__.py @@ -0,0 +1,19 @@ +# Copyright (c) 2016, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +"""Build performance tests""" +from .base import (BuildPerfTestCase, + BuildPerfTestLoader, + BuildPerfTestResult, + BuildPerfTestRunner, + KernelDropCaches, + runCmd2) +from .test_basic import * diff --git a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py new file mode 100644 index 000000000..59dd02521 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py @@ -0,0 +1,551 @@ +# Copyright (c) 2016, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +"""Build performance test base classes and functionality""" +import glob +import json +import logging +import os +import re +import resource +import shutil +import socket +import time +import traceback +import unittest +from datetime import datetime, timedelta +from functools import partial +from multiprocessing import Process +from multiprocessing import SimpleQueue + +import oe.path +from oeqa.utils.commands import CommandError, runCmd, get_bb_vars +from oeqa.utils.git import GitError, GitRepo + +# Get logger for this module +log = logging.getLogger('build-perf') + +# Our own version of runCmd which does not raise AssertErrors which would cause +# errors to interpreted as failures +runCmd2 = partial(runCmd, assert_error=False) + + +class KernelDropCaches(object): + """Container of the functions for dropping kernel caches""" + sudo_passwd = None + + @classmethod + def check(cls): + """Check permssions for dropping kernel caches""" + from getpass import getpass + from locale import getdefaultlocale + cmd = ['sudo', '-k', '-n', 'tee', '/proc/sys/vm/drop_caches'] + ret = runCmd2(cmd, ignore_status=True, data=b'0') + if ret.output.startswith('sudo:'): + pass_str = getpass( + "\nThe script requires sudo access to drop caches between " + "builds (echo 3 > /proc/sys/vm/drop_caches).\n" + "Please enter your sudo password: ") + cls.sudo_passwd = bytes(pass_str, getdefaultlocale()[1]) + + @classmethod + def drop(cls): + """Drop kernel caches""" + cmd = ['sudo', '-k'] + if cls.sudo_passwd: + cmd.append('-S') + input_data = cls.sudo_passwd + b'\n' + else: + cmd.append('-n') + input_data = b'' + cmd += ['tee', '/proc/sys/vm/drop_caches'] + input_data += b'3' + runCmd2(cmd, data=input_data) + + +def str_to_fn(string): + """Convert string to a sanitized filename""" + return re.sub(r'(\W+)', '-', string, flags=re.LOCALE) + + +class ResultsJsonEncoder(json.JSONEncoder): + """Extended encoder for build perf test results""" + unix_epoch = datetime.utcfromtimestamp(0) + + def default(self, obj): + """Encoder for our types""" + if isinstance(obj, datetime): + # NOTE: we assume that all timestamps are in UTC time + return (obj - self.unix_epoch).total_seconds() + if isinstance(obj, timedelta): + return obj.total_seconds() + return json.JSONEncoder.default(self, obj) + + +class BuildPerfTestResult(unittest.TextTestResult): + """Runner class for executing the individual tests""" + # List of test cases to run + test_run_queue = [] + + def __init__(self, out_dir, *args, **kwargs): + super(BuildPerfTestResult, self).__init__(*args, **kwargs) + + self.out_dir = out_dir + # Get Git parameters + try: + self.repo = GitRepo('.') + except GitError: + self.repo = None + self.git_commit, self.git_commit_count, self.git_branch = \ + self.get_git_revision() + self.hostname = socket.gethostname() + self.product = os.getenv('OE_BUILDPERFTEST_PRODUCT', 'oe-core') + self.start_time = self.elapsed_time = None + self.successes = [] + log.info("Using Git branch:commit %s:%s (%s)", self.git_branch, + self.git_commit, self.git_commit_count) + + def get_git_revision(self): + """Get git branch and commit under testing""" + commit = os.getenv('OE_BUILDPERFTEST_GIT_COMMIT') + commit_cnt = os.getenv('OE_BUILDPERFTEST_GIT_COMMIT_COUNT') + branch = os.getenv('OE_BUILDPERFTEST_GIT_BRANCH') + if not self.repo and (not commit or not commit_cnt or not branch): + log.info("The current working directory doesn't seem to be a Git " + "repository clone. You can specify branch and commit " + "displayed in test results with OE_BUILDPERFTEST_GIT_BRANCH, " + "OE_BUILDPERFTEST_GIT_COMMIT and " + "OE_BUILDPERFTEST_GIT_COMMIT_COUNT environment variables") + else: + if not commit: + commit = self.repo.rev_parse('HEAD^0') + commit_cnt = self.repo.run_cmd(['rev-list', '--count', 'HEAD^0']) + if not branch: + branch = self.repo.get_current_branch() + if not branch: + log.debug('Currently on detached HEAD') + return str(commit), str(commit_cnt), str(branch) + + def addSuccess(self, test): + """Record results from successful tests""" + super(BuildPerfTestResult, self).addSuccess(test) + self.successes.append((test, None)) + + def startTest(self, test): + """Pre-test hook""" + test.base_dir = self.out_dir + os.mkdir(test.out_dir) + log.info("Executing test %s: %s", test.name, test.shortDescription()) + self.stream.write(datetime.now().strftime("[%Y-%m-%d %H:%M:%S] ")) + super(BuildPerfTestResult, self).startTest(test) + + def startTestRun(self): + """Pre-run hook""" + self.start_time = datetime.utcnow() + + def stopTestRun(self): + """Pre-run hook""" + self.elapsed_time = datetime.utcnow() - self.start_time + self.write_results_json() + + def all_results(self): + result_map = {'SUCCESS': self.successes, + 'FAIL': self.failures, + 'ERROR': self.errors, + 'EXP_FAIL': self.expectedFailures, + 'UNEXP_SUCCESS': self.unexpectedSuccesses, + 'SKIPPED': self.skipped} + for status, tests in result_map.items(): + for test in tests: + yield (status, test) + + + def update_globalres_file(self, filename): + """Write results to globalres csv file""" + # Map test names to time and size columns in globalres + # The tuples represent index and length of times and sizes + # respectively + gr_map = {'test1': ((0, 1), (8, 1)), + 'test12': ((1, 1), (None, None)), + 'test13': ((2, 1), (9, 1)), + 'test2': ((3, 1), (None, None)), + 'test3': ((4, 3), (None, None)), + 'test4': ((7, 1), (10, 2))} + + if self.repo: + git_tag_rev = self.repo.run_cmd(['describe', self.git_commit]) + else: + git_tag_rev = self.git_commit + + values = ['0'] * 12 + for status, (test, msg) in self.all_results(): + if status in ['ERROR', 'SKIPPED']: + continue + (t_ind, t_len), (s_ind, s_len) = gr_map[test.name] + if t_ind is not None: + values[t_ind:t_ind + t_len] = test.times + if s_ind is not None: + values[s_ind:s_ind + s_len] = test.sizes + + log.debug("Writing globalres log to %s", filename) + with open(filename, 'a') as fobj: + fobj.write('{},{}:{},{},'.format(self.hostname, + self.git_branch, + self.git_commit, + git_tag_rev)) + fobj.write(','.join(values) + '\n') + + def write_results_json(self): + """Write test results into a json-formatted file""" + results = {'tester_host': self.hostname, + 'git_branch': self.git_branch, + 'git_commit': self.git_commit, + 'git_commit_count': self.git_commit_count, + 'product': self.product, + 'start_time': self.start_time, + 'elapsed_time': self.elapsed_time} + + tests = {} + for status, (test, reason) in self.all_results(): + tests[test.name] = {'name': test.name, + 'description': test.shortDescription(), + 'status': status, + 'start_time': test.start_time, + 'elapsed_time': test.elapsed_time, + 'cmd_log_file': os.path.relpath(test.cmd_log_file, + self.out_dir), + 'measurements': test.measurements} + results['tests'] = tests + + with open(os.path.join(self.out_dir, 'results.json'), 'w') as fobj: + json.dump(results, fobj, indent=4, sort_keys=True, + cls=ResultsJsonEncoder) + + + def git_commit_results(self, repo_path, branch=None, tag=None): + """Commit results into a Git repository""" + repo = GitRepo(repo_path, is_topdir=True) + if not branch: + branch = self.git_branch + else: + # Replace keywords + branch = branch.format(git_branch=self.git_branch, + tester_host=self.hostname) + + log.info("Committing test results into %s %s", repo_path, branch) + tmp_index = os.path.join(repo_path, '.git', 'index.oe-build-perf') + try: + # Create new commit object from the new results + env_update = {'GIT_INDEX_FILE': tmp_index, + 'GIT_WORK_TREE': self.out_dir} + repo.run_cmd('add .', env_update) + tree = repo.run_cmd('write-tree', env_update) + parent = repo.rev_parse(branch) + msg = "Results of {}:{}\n".format(self.git_branch, self.git_commit) + git_cmd = ['commit-tree', tree, '-m', msg] + if parent: + git_cmd += ['-p', parent] + commit = repo.run_cmd(git_cmd, env_update) + + # Update branch head + git_cmd = ['update-ref', 'refs/heads/' + branch, commit] + if parent: + git_cmd.append(parent) + repo.run_cmd(git_cmd) + + # Update current HEAD, if we're on branch 'branch' + if repo.get_current_branch() == branch: + log.info("Updating %s HEAD to latest commit", repo_path) + repo.run_cmd('reset --hard') + + # Create (annotated) tag + if tag: + # Find tags matching the pattern + tag_keywords = dict(git_branch=self.git_branch, + git_commit=self.git_commit, + git_commit_count=self.git_commit_count, + tester_host=self.hostname, + tag_num='[0-9]{1,5}') + tag_re = re.compile(tag.format(**tag_keywords) + '$') + tag_keywords['tag_num'] = 0 + for existing_tag in repo.run_cmd('tag').splitlines(): + if tag_re.match(existing_tag): + tag_keywords['tag_num'] += 1 + + tag = tag.format(**tag_keywords) + msg = "Test run #{} of {}:{}\n".format(tag_keywords['tag_num'], + self.git_branch, + self.git_commit) + repo.run_cmd(['tag', '-a', '-m', msg, tag, commit]) + + finally: + if os.path.exists(tmp_index): + os.unlink(tmp_index) + + +class BuildPerfTestCase(unittest.TestCase): + """Base class for build performance tests""" + SYSRES = 'sysres' + DISKUSAGE = 'diskusage' + build_target = None + + def __init__(self, *args, **kwargs): + super(BuildPerfTestCase, self).__init__(*args, **kwargs) + self.name = self._testMethodName + self.base_dir = None + self.start_time = None + self.elapsed_time = None + self.measurements = [] + self.bb_vars = get_bb_vars() + # TODO: remove 'times' and 'sizes' arrays when globalres support is + # removed + self.times = [] + self.sizes = [] + + @property + def out_dir(self): + return os.path.join(self.base_dir, self.name) + + @property + def cmd_log_file(self): + return os.path.join(self.out_dir, 'commands.log') + + def setUp(self): + """Set-up fixture for each test""" + if self.build_target: + self.log_cmd_output(['bitbake', self.build_target, + '-c', 'fetchall']) + + def run(self, *args, **kwargs): + """Run test""" + self.start_time = datetime.now() + super(BuildPerfTestCase, self).run(*args, **kwargs) + self.elapsed_time = datetime.now() - self.start_time + + def log_cmd_output(self, cmd): + """Run a command and log it's output""" + cmd_str = cmd if isinstance(cmd, str) else ' '.join(cmd) + log.info("Logging command: %s", cmd_str) + try: + with open(self.cmd_log_file, 'a') as fobj: + runCmd2(cmd, stdout=fobj) + except CommandError as err: + log.error("Command failed: %s", err.retcode) + raise + + def measure_cmd_resources(self, cmd, name, legend, save_bs=False): + """Measure system resource usage of a command""" + def _worker(data_q, cmd, **kwargs): + """Worker process for measuring resources""" + try: + start_time = datetime.now() + ret = runCmd2(cmd, **kwargs) + etime = datetime.now() - start_time + rusage_struct = resource.getrusage(resource.RUSAGE_CHILDREN) + iostat = {} + with open('/proc/{}/io'.format(os.getpid())) as fobj: + for line in fobj.readlines(): + key, val = line.split(':') + iostat[key] = int(val) + rusage = {} + # Skip unused fields, (i.e. 'ru_ixrss', 'ru_idrss', 'ru_isrss', + # 'ru_nswap', 'ru_msgsnd', 'ru_msgrcv' and 'ru_nsignals') + for key in ['ru_utime', 'ru_stime', 'ru_maxrss', 'ru_minflt', + 'ru_majflt', 'ru_inblock', 'ru_oublock', + 'ru_nvcsw', 'ru_nivcsw']: + rusage[key] = getattr(rusage_struct, key) + data_q.put({'ret': ret, + 'start_time': start_time, + 'elapsed_time': etime, + 'rusage': rusage, + 'iostat': iostat}) + except Exception as err: + data_q.put(err) + + cmd_str = cmd if isinstance(cmd, str) else ' '.join(cmd) + log.info("Timing command: %s", cmd_str) + data_q = SimpleQueue() + try: + with open(self.cmd_log_file, 'a') as fobj: + proc = Process(target=_worker, args=(data_q, cmd,), + kwargs={'stdout': fobj}) + proc.start() + data = data_q.get() + proc.join() + if isinstance(data, Exception): + raise data + except CommandError: + log.error("Command '%s' failed, see %s for more details", cmd_str, + self.cmd_log_file) + raise + etime = data['elapsed_time'] + + measurement = {'type': self.SYSRES, + 'name': name, + 'legend': legend} + measurement['values'] = {'start_time': data['start_time'], + 'elapsed_time': etime, + 'rusage': data['rusage'], + 'iostat': data['iostat']} + if save_bs: + bs_file = self.save_buildstats(legend) + measurement['values']['buildstats_file'] = \ + os.path.relpath(bs_file, self.base_dir) + + self.measurements.append(measurement) + + # Append to 'times' array for globalres log + e_sec = etime.total_seconds() + self.times.append('{:d}:{:02d}:{:05.2f}'.format(int(e_sec / 3600), + int((e_sec % 3600) / 60), + e_sec % 60)) + + def measure_disk_usage(self, path, name, legend, apparent_size=False): + """Estimate disk usage of a file or directory""" + cmd = ['du', '-s', '--block-size', '1024'] + if apparent_size: + cmd.append('--apparent-size') + cmd.append(path) + + ret = runCmd2(cmd) + size = int(ret.output.split()[0]) + log.debug("Size of %s path is %s", path, size) + measurement = {'type': self.DISKUSAGE, + 'name': name, + 'legend': legend} + measurement['values'] = {'size': size} + self.measurements.append(measurement) + # Append to 'sizes' array for globalres log + self.sizes.append(str(size)) + + def save_buildstats(self, label=None): + """Save buildstats""" + def split_nevr(nevr): + """Split name and version information from recipe "nevr" string""" + n_e_v, revision = nevr.rsplit('-', 1) + match = re.match(r'^(?P<name>\S+)-((?P<epoch>[0-9]{1,5})_)?(?P<version>[0-9]\S*)$', + n_e_v) + if not match: + # If we're not able to parse a version starting with a number, just + # take the part after last dash + match = re.match(r'^(?P<name>\S+)-((?P<epoch>[0-9]{1,5})_)?(?P<version>[^-]+)$', + n_e_v) + name = match.group('name') + version = match.group('version') + epoch = match.group('epoch') + return name, epoch, version, revision + + def bs_to_json(filename): + """Convert (task) buildstats file into json format""" + bs_json = {'iostat': {}, + 'rusage': {}, + 'child_rusage': {}} + with open(filename) as fobj: + for line in fobj.readlines(): + key, val = line.split(':', 1) + val = val.strip() + if key == 'Started': + start_time = datetime.utcfromtimestamp(float(val)) + bs_json['start_time'] = start_time + elif key == 'Ended': + end_time = datetime.utcfromtimestamp(float(val)) + elif key.startswith('IO '): + split = key.split() + bs_json['iostat'][split[1]] = int(val) + elif key.find('rusage') >= 0: + split = key.split() + ru_key = split[-1] + if ru_key in ('ru_stime', 'ru_utime'): + val = float(val) + else: + val = int(val) + ru_type = 'rusage' if split[0] == 'rusage' else \ + 'child_rusage' + bs_json[ru_type][ru_key] = val + elif key == 'Status': + bs_json['status'] = val + bs_json['elapsed_time'] = end_time - start_time + return bs_json + + log.info('Saving buildstats in JSON format') + bs_dirs = sorted(os.listdir(self.bb_vars['BUILDSTATS_BASE'])) + if len(bs_dirs) > 1: + log.warning("Multiple buildstats found for test %s, only " + "archiving the last one", self.name) + bs_dir = os.path.join(self.bb_vars['BUILDSTATS_BASE'], bs_dirs[-1]) + + buildstats = [] + for fname in os.listdir(bs_dir): + recipe_dir = os.path.join(bs_dir, fname) + if not os.path.isdir(recipe_dir): + continue + name, epoch, version, revision = split_nevr(fname) + recipe_bs = {'name': name, + 'epoch': epoch, + 'version': version, + 'revision': revision, + 'tasks': {}} + for task in os.listdir(recipe_dir): + recipe_bs['tasks'][task] = bs_to_json(os.path.join(recipe_dir, + task)) + buildstats.append(recipe_bs) + + # Write buildstats into json file + postfix = '.' + str_to_fn(label) if label else '' + postfix += '.json' + outfile = os.path.join(self.out_dir, 'buildstats' + postfix) + with open(outfile, 'w') as fobj: + json.dump(buildstats, fobj, indent=4, sort_keys=True, + cls=ResultsJsonEncoder) + return outfile + + 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', + self.bb_vars['TMPDIR']]: + oe.path.remove(name, recurse=True) + + def rm_sstate(self): + """Remove sstate directory""" + log.debug("Removing sstate-cache") + oe.path.remove(self.bb_vars['SSTATE_DIR'], recurse=True) + + def rm_cache(self): + """Drop bitbake caches""" + oe.path.remove(self.bb_vars['PERSISTENT_DIR'], recurse=True) + + @staticmethod + def sync(): + """Sync and drop kernel caches""" + log.debug("Syncing and dropping kernel caches""") + KernelDropCaches.drop() + os.sync() + # Wait a bit for all the dirty blocks to be written onto disk + time.sleep(3) + + +class BuildPerfTestLoader(unittest.TestLoader): + """Test loader for build performance tests""" + sortTestMethodsUsing = None + + +class BuildPerfTestRunner(unittest.TextTestRunner): + """Test loader for build performance tests""" + sortTestMethodsUsing = None + + def __init__(self, out_dir, *args, **kwargs): + super(BuildPerfTestRunner, self).__init__(*args, **kwargs) + self.out_dir = out_dir + + def _makeResult(self): + return BuildPerfTestResult(self.out_dir, self.stream, self.descriptions, + self.verbosity) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py new file mode 100644 index 000000000..7a48c1e77 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py @@ -0,0 +1,127 @@ +# Copyright (c) 2016, Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +"""Basic set of build performance tests""" +import os +import shutil + +import oe.path +from oeqa.buildperf import BuildPerfTestCase +from oeqa.utils.commands import get_bb_vars + + +class Test1P1(BuildPerfTestCase): + build_target = 'core-image-sato' + + def test1(self): + """Measure wall clock of bitbake core-image-sato and size of tmp dir""" + self.rm_tmp() + self.rm_sstate() + self.rm_cache() + self.sync() + self.measure_cmd_resources(['bitbake', self.build_target], 'build', + 'bitbake ' + self.build_target, save_bs=True) + self.measure_disk_usage(self.bb_vars['TMPDIR'], 'tmpdir', 'tmpdir') + + +class Test1P2(BuildPerfTestCase): + build_target = 'virtual/kernel' + + def test12(self): + """Measure bitbake virtual/kernel""" + # Build and cleans state in order to get all dependencies pre-built + self.log_cmd_output(['bitbake', self.build_target]) + self.log_cmd_output(['bitbake', self.build_target, '-c', 'cleansstate']) + + self.sync() + self.measure_cmd_resources(['bitbake', self.build_target], 'build', + 'bitbake ' + self.build_target) + + +class Test1P3(BuildPerfTestCase): + build_target = 'core-image-sato' + + def test13(self): + """Build core-image-sato with rm_work enabled""" + postfile = os.path.join(self.out_dir, 'postfile.conf') + with open(postfile, 'w') as fobj: + fobj.write('INHERIT += "rm_work"\n') + try: + self.rm_tmp() + self.rm_sstate() + self.rm_cache() + self.sync() + cmd = ['bitbake', '-R', postfile, self.build_target] + self.measure_cmd_resources(cmd, 'build', + 'bitbake' + self.build_target, + save_bs=True) + self.measure_disk_usage(self.bb_vars['TMPDIR'], 'tmpdir', 'tmpdir') + finally: + os.unlink(postfile) + + +class Test2(BuildPerfTestCase): + build_target = 'core-image-sato' + + def test2(self): + """Measure bitbake core-image-sato -c rootfs with sstate""" + # Build once in order to populate sstate cache + self.log_cmd_output(['bitbake', self.build_target]) + + self.rm_tmp() + self.rm_cache() + self.sync() + cmd = ['bitbake', self.build_target, '-c', 'rootfs'] + self.measure_cmd_resources(cmd, 'do_rootfs', 'bitbake do_rootfs') + + +class Test3(BuildPerfTestCase): + + def test3(self): + """Parsing time metrics (bitbake -p)""" + # Drop all caches and parse + self.rm_cache() + oe.path.remove(os.path.join(self.bb_vars['TMPDIR'], 'cache'), True) + self.measure_cmd_resources(['bitbake', '-p'], 'parse_1', + 'bitbake -p (no caches)') + # Drop tmp/cache + oe.path.remove(os.path.join(self.bb_vars['TMPDIR'], 'cache'), True) + self.measure_cmd_resources(['bitbake', '-p'], 'parse_2', + 'bitbake -p (no tmp/cache)') + # Parse with fully cached data + self.measure_cmd_resources(['bitbake', '-p'], 'parse_3', + 'bitbake -p (cached)') + + +class Test4(BuildPerfTestCase): + build_target = 'core-image-sato' + + def test4(self): + """eSDK metrics""" + self.log_cmd_output("bitbake {} -c do_populate_sdk_ext".format( + self.build_target)) + self.bb_vars = get_bb_vars(None, self.build_target) + tmp_dir = self.bb_vars['TMPDIR'] + installer = os.path.join( + self.bb_vars['SDK_DEPLOY'], + self.bb_vars['TOOLCHAINEXT_OUTPUTNAME'] + '.sh') + # Measure installer size + self.measure_disk_usage(installer, 'installer_bin', 'eSDK installer', + apparent_size=True) + # Measure deployment time and deployed size + deploy_dir = os.path.join(tmp_dir, 'esdk-deploy') + if os.path.exists(deploy_dir): + shutil.rmtree(deploy_dir) + self.sync() + self.measure_cmd_resources([installer, '-y', '-d', deploy_dir], + 'deploy', 'eSDK deploy') + self.measure_disk_usage(deploy_dir, 'deploy_dir', 'deploy dir', + apparent_size=True) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py b/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py index 522f9ebd7..9ce3bf803 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py @@ -24,9 +24,7 @@ from oeqa.utils import CommandError from abc import ABCMeta, abstractmethod -class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget): - - __metaclass__ = ABCMeta +class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget, metaclass=ABCMeta): supported_image_fstypes = ['tar.gz', 'tar.bz2'] @@ -199,3 +197,43 @@ class GummibootTarget(MasterImageHardwareTarget): self.power_cycle(self.master) # there are better ways than a timeout but this should work for now time.sleep(120) + + +class SystemdbootTarget(MasterImageHardwareTarget): + + def __init__(self, d): + super(SystemdbootTarget, self).__init__(d) + # this the value we need to set in the LoaderEntryOneShot EFI variable + # so the system boots the 'test' bootloader label and not the default + # The first four bytes are EFI bits, and the rest is an utf-16le string + # (EFI vars values need to be utf-16) + # $ echo -en "test\0" | iconv -f ascii -t utf-16le | hexdump -C + # 00000000 74 00 65 00 73 00 74 00 00 00 |t.e.s.t...| + self.efivarvalue = r'\x07\x00\x00\x00\x74\x00\x65\x00\x73\x00\x74\x00\x00\x00' + self.deploy_cmds = [ + 'mount -L boot /boot', + 'mkdir -p /mnt/testrootfs', + 'mount -L testrootfs /mnt/testrootfs', + 'modprobe efivarfs', + 'mount -t efivarfs efivarfs /sys/firmware/efi/efivars', + 'cp ~/test-kernel /boot', + 'rm -rf /mnt/testrootfs/*', + 'tar xvf ~/test-rootfs.%s -C /mnt/testrootfs' % self.image_fstype, + 'printf "%s" > /sys/firmware/efi/efivars/LoaderEntryOneShot-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f' % self.efivarvalue + ] + + def _deploy(self): + # make sure these aren't mounted + self.master.run("umount /boot; umount /mnt/testrootfs; umount /sys/firmware/efi/efivars;") + # from now on, every deploy cmd should return 0 + # else an exception will be thrown by sshcontrol + self.master.ignore_status = False + self.master.copy_to(self.rootfs, "~/test-rootfs." + self.image_fstype) + self.master.copy_to(self.kernel, "~/test-kernel") + for cmd in self.deploy_cmds: + self.master.run(cmd) + + def _start(self, params=None): + self.power_cycle(self.master) + # there are better ways than a timeout but this should work for now + time.sleep(120) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/oetest.py b/import-layers/yocto-poky/meta/lib/oeqa/oetest.py index 3ed5bb8c2..95d3bf72f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/oetest.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/oetest.py @@ -12,6 +12,8 @@ import unittest import inspect import subprocess import signal +import shutil +import functools try: import bb except ImportError: @@ -54,18 +56,29 @@ def filterByTagExp(testsuite, tagexp): @LogResults class oeTest(unittest.TestCase): + pscmd = "ps" longMessage = True @classmethod def hasPackage(self, pkg): - for item in oeTest.tc.pkgmanifest.split('\n'): - if re.match(pkg, item): + """ + True if the full package name exists in the manifest, False otherwise. + """ + return pkg in oeTest.tc.pkgmanifest + + @classmethod + def hasPackageMatch(self, match): + """ + True if match exists in the manifest as a regular expression substring, + False otherwise. + """ + for s in oeTest.tc.pkgmanifest: + if re.match(match, s): return True return False @classmethod def hasFeature(self,feature): - if feature in oeTest.tc.imagefeatures or \ feature in oeTest.tc.distrofeatures: return True @@ -78,6 +91,9 @@ class oeRuntimeTest(oeTest): super(oeRuntimeTest, self).__init__(methodName) def setUp(self): + # Install packages in the DUT + self.tc.install_uninstall_packages(self.id()) + # Check if test needs to run if self.tc.sigterm: self.fail("Got SIGTERM") @@ -91,6 +107,9 @@ class oeRuntimeTest(oeTest): pass def tearDown(self): + # Unistall packages in the DUT + self.tc.install_uninstall_packages(self.id(), False) + res = getResults() # If a test fails or there is an exception dump # for QemuTarget only @@ -117,6 +136,15 @@ class oeRuntimeTest(oeTest): if status != 0: return status +class OETestCalledProcessError(subprocess.CalledProcessError): + def __str__(self): + if hasattr(self, "stderr"): + return "Command '%s' returned non-zero exit status %d with output %s and stderr %s" % (self.cmd, self.returncode, self.output, self.stderr) + else: + return "Command '%s' returned non-zero exit status %d with output %s" % (self.cmd, self.returncode, self.output) + +subprocess.CalledProcessError = OETestCalledProcessError + class oeSDKTest(oeTest): def __init__(self, methodName='runTest'): self.sdktestdir = oeSDKTest.tc.sdktestdir @@ -124,13 +152,12 @@ class oeSDKTest(oeTest): @classmethod def hasHostPackage(self, pkg): - if re.search(pkg, oeTest.tc.hostpkgmanifest): return True return False def _run(self, cmd): - return subprocess.check_output(". %s > /dev/null; %s;" % (self.tc.sdkenv, cmd), shell=True) + return subprocess.check_output(". %s > /dev/null; %s;" % (self.tc.sdkenv, cmd), shell=True, stderr=subprocess.STDOUT).decode("utf-8") class oeSDKExtTest(oeSDKTest): def _run(self, cmd): @@ -142,7 +169,7 @@ class oeSDKExtTest(oeSDKTest): env['PATH'] = avoid_paths_in_environ(paths_to_avoid) return subprocess.check_output(". %s > /dev/null;"\ - " %s;" % (self.tc.sdkenv, cmd), shell=True, env=env) + " %s;" % (self.tc.sdkenv, cmd), stderr=subprocess.STDOUT, shell=True, env=env).decode("utf-8") def getmodule(pos=2): # stack returns a list of tuples containg frame information @@ -185,15 +212,22 @@ def custom_verbose(msg, *args, **kwargs): _buffer_logger = "" class TestContext(object): - def __init__(self, d): + def __init__(self, d, exported=False): self.d = d self.testsuites = self._get_test_suites() - self.testslist = self._get_tests_list(d.getVar("BBPATH", True).split(':')) + + if exported: + path = [os.path.dirname(os.path.abspath(__file__))] + extrapath = "" + else: + path = d.getVar("BBPATH", True).split(':') + extrapath = "lib/oeqa" + + self.testslist = self._get_tests_list(path, extrapath) self.testsrequired = self._get_test_suites_required() - self.filesdir = os.path.join(os.path.dirname(os.path.abspath( - oeqa.runtime.__file__)), "files") + self.filesdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "runtime/files") self.imagefeatures = d.getVar("IMAGE_FEATURES", True).split() self.distrofeatures = d.getVar("DISTRO_FEATURES", True).split() @@ -212,7 +246,7 @@ class TestContext(object): return " ".join(tcs) # return test list by type also filter if TEST_SUITES is specified - def _get_tests_list(self, bbpath): + def _get_tests_list(self, bbpath, extrapath): testslist = [] type = self._get_test_namespace() @@ -226,11 +260,11 @@ class TestContext(object): continue found = False for p in bbpath: - if os.path.exists(os.path.join(p, 'lib', 'oeqa', type, testname + '.py')): + if os.path.exists(os.path.join(p, extrapath, type, testname + ".py")): testslist.append("oeqa." + type + "." + testname) found = True break - elif os.path.exists(os.path.join(p, 'lib', 'oeqa', type, testname.split(".")[0] + '.py')): + elif os.path.exists(os.path.join(p, extrapath, type, testname.split(".")[0] + ".py")): testslist.append("oeqa." + type + "." + testname) found = True break @@ -239,8 +273,6 @@ class TestContext(object): if "auto" in self.testsuites: def add_auto_list(path): - if not os.path.exists(os.path.join(path, '__init__.py')): - bb.fatal('Tests directory %s exists but is missing __init__.py' % path) files = sorted([f for f in os.listdir(path) if f.endswith('.py') and not f.startswith('_')]) for f in files: module = 'oeqa.' + type + '.' + f[:-3] @@ -255,6 +287,51 @@ class TestContext(object): return testslist + def getTestModules(self): + """ + Returns all the test modules in the testlist. + """ + + import pkgutil + + modules = [] + for test in self.testslist: + if re.search("\w+\.\w+\.test_\S+", test): + test = '.'.join(t.split('.')[:3]) + module = pkgutil.get_loader(test) + modules.append(module) + + return modules + + def getModulefromID(self, test_id): + """ + Returns the test module based on a test id. + """ + + module_name = ".".join(test_id.split(".")[:3]) + modules = self.getTestModules() + for module in modules: + if module.name == module_name: + return module + + return None + + def getTests(self, test): + '''Return all individual tests executed when running the suite.''' + # Unfortunately unittest does not have an API for this, so we have + # to rely on implementation details. This only needs to work + # for TestSuite containing TestCase. + method = getattr(test, '_testMethodName', None) + if method: + # leaf case: a TestCase + yield test + else: + # Look into TestSuite. + tests = getattr(test, '_tests', []) + for t1 in tests: + for t2 in self.getTests(t1): + yield t2 + def loadTests(self): setattr(oeTest, "tc", self) @@ -263,36 +340,20 @@ class TestContext(object): suites = [testloader.loadTestsFromName(name) for name in self.testslist] suites = filterByTagExp(suites, getattr(self, "tagexp", None)) - def getTests(test): - '''Return all individual tests executed when running the suite.''' - # Unfortunately unittest does not have an API for this, so we have - # to rely on implementation details. This only needs to work - # for TestSuite containing TestCase. - method = getattr(test, '_testMethodName', None) - if method: - # leaf case: a TestCase - yield test - else: - # Look into TestSuite. - tests = getattr(test, '_tests', []) - for t1 in tests: - for t2 in getTests(t1): - yield t2 - # Determine dependencies between suites by looking for @skipUnlessPassed # method annotations. Suite A depends on suite B if any method in A # depends on a method on B. for suite in suites: suite.dependencies = [] suite.depth = 0 - for test in getTests(suite): + for test in self.getTests(suite): methodname = getattr(test, '_testMethodName', None) if methodname: method = getattr(test, methodname) depends_on = getattr(method, '_depends_on', None) if depends_on: for dep_suite in suites: - if depends_on in [getattr(t, '_testMethodName', None) for t in getTests(dep_suite)]: + if depends_on in [getattr(t, '_testMethodName', None) for t in self.getTests(dep_suite)]: if dep_suite not in suite.dependencies and \ dep_suite is not suite: suite.dependencies.append(dep_suite) @@ -314,7 +375,14 @@ class TestContext(object): for index, suite in enumerate(suites): set_suite_depth(suite) suite.index = index - suites.sort(cmp=lambda a,b: cmp((a.depth, a.index), (b.depth, b.index))) + + def cmp(a, b): + return (a > b) - (a < b) + + def cmpfunc(a, b): + return cmp((a.depth, a.index), (b.depth, b.index)) + + suites.sort(key=functools.cmp_to_key(cmpfunc)) self.suite = testloader.suiteClass(suites) @@ -331,35 +399,24 @@ class TestContext(object): return runner.run(self.suite) -class ImageTestContext(TestContext): - def __init__(self, d, target, host_dumper): - super(ImageTestContext, self).__init__(d) - - self.tagexp = d.getVar("TEST_SUITES_TAGS", True) +class RuntimeTestContext(TestContext): + def __init__(self, d, target, exported=False): + super(RuntimeTestContext, self).__init__(d, exported) self.target = target - self.host_dumper = host_dumper + self.pkgmanifest = {} manifest = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + ".manifest") nomanifest = d.getVar("IMAGE_NO_MANIFEST", True) if nomanifest is None or nomanifest != "1": try: with open(manifest) as f: - self.pkgmanifest = f.read() + for line in f: + (pkg, arch, version) = line.strip().split() + self.pkgmanifest[pkg] = (version, arch) except IOError as e: bb.fatal("No package manifest file found. Did you build the image?\n%s" % e) - else: - self.pkgmanifest = "" - - self.sigterm = False - self.origsigtermhandler = signal.getsignal(signal.SIGTERM) - signal.signal(signal.SIGTERM, self._sigterm_exception) - - def _sigterm_exception(self, signum, stackframe): - bb.warn("TestImage received SIGTERM, shutting down...") - self.sigterm = True - self.target.stop() def _get_test_namespace(self): return "runtime" @@ -382,8 +439,223 @@ class ImageTestContext(TestContext): return [t for t in self.d.getVar("TEST_SUITES", True).split() if t != "auto"] def loadTests(self): - super(ImageTestContext, self).loadTests() - setattr(oeRuntimeTest, "pscmd", "ps -ef" if oeTest.hasPackage("procps") else "ps") + super(RuntimeTestContext, self).loadTests() + if oeTest.hasPackage("procps"): + oeRuntimeTest.pscmd = "ps -ef" + + def extract_packages(self): + """ + Find packages that will be needed during runtime. + """ + + modules = self.getTestModules() + bbpaths = self.d.getVar("BBPATH", True).split(":") + + shutil.rmtree(self.d.getVar("TEST_EXTRACTED_DIR", True)) + shutil.rmtree(self.d.getVar("TEST_PACKAGED_DIR", True)) + for module in modules: + json_file = self._getJsonFile(module) + if json_file: + needed_packages = self._getNeededPackages(json_file) + self._perform_package_extraction(needed_packages) + + def _perform_package_extraction(self, needed_packages): + """ + Extract packages that will be needed during runtime. + """ + + import oe.path + + extracted_path = self.d.getVar("TEST_EXTRACTED_DIR", True) + packaged_path = self.d.getVar("TEST_PACKAGED_DIR", True) + + for key,value in needed_packages.items(): + packages = () + if isinstance(value, dict): + packages = (value, ) + elif isinstance(value, list): + packages = value + else: + bb.fatal("Failed to process needed packages for %s; " + "Value must be a dict or list" % key) + + for package in packages: + pkg = package["pkg"] + rm = package.get("rm", False) + extract = package.get("extract", True) + if extract: + dst_dir = os.path.join(extracted_path, pkg) + else: + dst_dir = os.path.join(packaged_path) + + # Extract package and copy it to TEST_EXTRACTED_DIR + pkg_dir = self._extract_in_tmpdir(pkg) + if extract: + + # Same package used for more than one test, + # don't need to extract again. + if os.path.exists(dst_dir): + continue + oe.path.copytree(pkg_dir, dst_dir) + shutil.rmtree(pkg_dir) + + # Copy package to TEST_PACKAGED_DIR + else: + self._copy_package(pkg) + + def _getJsonFile(self, module): + """ + Returns the path of the JSON file for a module, empty if doesn't exitst. + """ + + module_file = module.path + json_file = "%s.json" % module_file.rsplit(".", 1)[0] + if os.path.isfile(module_file) and os.path.isfile(json_file): + return json_file + else: + return "" + + def _getNeededPackages(self, json_file, test=None): + """ + Returns a dict with needed packages based on a JSON file. + + + If a test is specified it will return the dict just for that test. + """ + + import json + + needed_packages = {} + + with open(json_file) as f: + test_packages = json.load(f) + for key,value in test_packages.items(): + needed_packages[key] = value + + if test: + if test in needed_packages: + needed_packages = needed_packages[test] + else: + needed_packages = {} + + return needed_packages + + def _extract_in_tmpdir(self, pkg): + """" + Returns path to a temp directory where the package was + extracted without dependencies. + """ + + from oeqa.utils.package_manager import get_package_manager + + pkg_path = os.path.join(self.d.getVar("TEST_INSTALL_TMP_DIR", True), pkg) + pm = get_package_manager(self.d, pkg_path) + extract_dir = pm.extract(pkg) + shutil.rmtree(pkg_path) + + return extract_dir + + def _copy_package(self, pkg): + """ + Copy the RPM, DEB or IPK package to dst_dir + """ + + from oeqa.utils.package_manager import get_package_manager + + pkg_path = os.path.join(self.d.getVar("TEST_INSTALL_TMP_DIR", True), pkg) + dst_dir = self.d.getVar("TEST_PACKAGED_DIR", True) + pm = get_package_manager(self.d, pkg_path) + pkg_info = pm.package_info(pkg) + file_path = pkg_info[pkg]["filepath"] + shutil.copy2(file_path, dst_dir) + shutil.rmtree(pkg_path) + + def install_uninstall_packages(self, test_id, pkg_dir, install): + """ + Check if the test requires a package and Install/Unistall it in the DUT + """ + + test = test_id.split(".")[4] + module = self.getModulefromID(test_id) + json = self._getJsonFile(module) + if json: + needed_packages = self._getNeededPackages(json, test) + if needed_packages: + self._install_uninstall_packages(needed_packages, pkg_dir, install) + + def _install_uninstall_packages(self, needed_packages, pkg_dir, install=True): + """ + Install/Unistall packages in the DUT without using a package manager + """ + + if isinstance(needed_packages, dict): + packages = [needed_packages] + elif isinstance(needed_packages, list): + packages = needed_packages + + for package in packages: + pkg = package["pkg"] + rm = package.get("rm", False) + extract = package.get("extract", True) + src_dir = os.path.join(pkg_dir, pkg) + + # Install package + if install and extract: + self.target.connection.copy_dir_to(src_dir, "/") + + # Unistall package + elif not install and rm: + self.target.connection.delete_dir_structure(src_dir, "/") + +class ImageTestContext(RuntimeTestContext): + def __init__(self, d, target, host_dumper): + super(ImageTestContext, self).__init__(d, target) + + self.tagexp = d.getVar("TEST_SUITES_TAGS", True) + + self.host_dumper = host_dumper + + self.sigterm = False + self.origsigtermhandler = signal.getsignal(signal.SIGTERM) + signal.signal(signal.SIGTERM, self._sigterm_exception) + + def _sigterm_exception(self, signum, stackframe): + bb.warn("TestImage received SIGTERM, shutting down...") + self.sigterm = True + self.target.stop() + + def install_uninstall_packages(self, test_id, install=True): + """ + Check if the test requires a package and Install/Unistall it in the DUT + """ + + pkg_dir = self.d.getVar("TEST_EXTRACTED_DIR", True) + super(ImageTestContext, self).install_uninstall_packages(test_id, pkg_dir, install) + +class ExportTestContext(RuntimeTestContext): + def __init__(self, d, target, exported=False, parsedArgs={}): + """ + This class is used when exporting tests and when are executed outside OE environment. + + parsedArgs can contain the following: + - tag: Filter test by tag. + """ + super(ExportTestContext, self).__init__(d, target, exported) + + tag = parsedArgs.get("tag", None) + self.tagexp = tag if tag != None else d.getVar("TEST_SUITES_TAGS", True) + + self.sigterm = None + + def install_uninstall_packages(self, test_id, install=True): + """ + Check if the test requires a package and Install/Unistall it in the DUT + """ + + export_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + extracted_dir = self.d.getVar("TEST_EXPORT_EXTRACTED_DIR", True) + pkg_dir = os.path.join(export_dir, extracted_dir) + super(ExportTestContext, self).install_uninstall_packages(test_id, pkg_dir, install) class SDKTestContext(TestContext): def __init__(self, d, sdktestdir, sdkenv, tcname, *args): @@ -396,8 +668,11 @@ class SDKTestContext(TestContext): if not hasattr(self, 'target_manifest'): self.target_manifest = d.getVar("SDK_TARGET_MANIFEST", True) try: + self.pkgmanifest = {} with open(self.target_manifest) as f: - self.pkgmanifest = f.read() + for line in f: + (pkg, arch, version) = line.strip().split() + self.pkgmanifest[pkg] = (version, arch) except IOError as e: bb.fatal("No package manifest file found. Did you build the sdk image?\n%s" % e) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runexported.py b/import-layers/yocto-poky/meta/lib/oeqa/runexported.py index cc89e13c0..7e245c412 100755 --- a/import-layers/yocto-poky/meta/lib/oeqa/runexported.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runexported.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (C) 2013 Intel Corporation @@ -30,9 +30,9 @@ except ImportError: sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "oeqa"))) -from oeqa.oetest import TestContext +from oeqa.oetest import ExportTestContext +from oeqa.utils.commands import runCmd, updateEnv from oeqa.utils.sshcontrol import SSHControl -from oeqa.utils.dump import get_host_dumper # this isn't pretty but we need a fake target object # for running the tests externally as we don't care @@ -69,10 +69,6 @@ class MyDataDict(dict): def getVar(self, key, unused = None): return self.get(key, "") -class ExportTestContext(TestContext): - def __init__(self, d): - self.d = d - def main(): parser = argparse.ArgumentParser() @@ -85,6 +81,7 @@ def main(): specified in the json if that directory actually exists or it will error out.") parser.add_argument("-l", "--log-dir", dest="log_dir", help="This sets the path for TEST_LOG_DIR. If not specified \ the current dir is used. This is used for usually creating a ssh log file and a scp test file.") + parser.add_argument("-a", "--tag", dest="tag", help="Only run test with specified tag.") parser.add_argument("json", help="The json file exported by the build system", default="testdata.json", nargs='?') args = parser.parse_args() @@ -111,30 +108,41 @@ def main(): if not os.path.isdir(d["DEPLOY_DIR"]): print("WARNING: The path to DEPLOY_DIR does not exist: %s" % d["DEPLOY_DIR"]) + parsedArgs = {} + parsedArgs["tag"] = args.tag + + extract_sdk(d) target = FakeTarget(d) for key in loaded["target"].keys(): setattr(target, key, loaded["target"][key]) - host_dumper = get_host_dumper(d) - host_dumper.parent_dir = loaded["host_dumper"]["parent_dir"] - host_dumper.cmds = loaded["host_dumper"]["cmds"] - target.exportStart() - tc = ExportTestContext(d) - - setattr(tc, "d", d) - setattr(tc, "target", target) - setattr(tc, "host_dumper", host_dumper) - for key in loaded.keys(): - if key != "d" and key != "target" and key != "host_dumper": - setattr(tc, key, loaded[key]) - + tc = ExportTestContext(d, target, True, parsedArgs) tc.loadTests() tc.runTests() return 0 +def extract_sdk(d): + """ + Extract SDK if needed + """ + + export_dir = os.path.dirname(os.path.realpath(__file__)) + tools_dir = d.getVar("TEST_EXPORT_SDK_DIR", True) + tarball_name = "%s.sh" % d.getVar("TEST_EXPORT_SDK_NAME", True) + tarball_path = os.path.join(export_dir, tools_dir, tarball_name) + extract_path = os.path.join(export_dir, "sysroot") + if os.path.isfile(tarball_path): + print ("Found SDK tarball %s. Extracting..." % tarball_path) + result = runCmd("%s -y -d %s" % (tarball_path, extract_path)) + for f in os.listdir(extract_path): + if f.startswith("environment-setup"): + print("Setting up SDK environment...") + env_file = os.path.join(extract_path, f) + updateEnv(env_file) + if __name__ == "__main__": try: ret = main() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/__init__.py deleted file mode 100644 index 4cf3fa76b..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Enable other layers to have tests in the same named directory -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/_ptest.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/_ptest.py index 0621028b8..71324d3da 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/_ptest.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/_ptest.py @@ -11,7 +11,7 @@ import subprocess def setUpModule(): if not oeRuntimeTest.hasFeature("package-management"): skipModule("Image doesn't have package management feature") - if not oeRuntimeTest.hasPackage("smart"): + if not oeRuntimeTest.hasPackage("smartpm"): skipModule("Image doesn't have smart installed") if "package_rpm" != oeRuntimeTest.tc.d.getVar("PACKAGE_CLASSES", True).split()[0]: skipModule("Rpm is not the primary package manager") @@ -105,7 +105,7 @@ class PtestRunnerTest(oeRuntimeTest): def test_ptestrunner(self): self.add_smart_channel() (runnerstatus, result) = self.target.run('which ptest-runner', 0) - cond = oeRuntimeTest.hasPackage("ptest-runner") and oeRuntimeTest.hasFeature("ptest") and oeRuntimeTest.hasPackage("-ptest") and (runnerstatus != 0) + cond = oeRuntimeTest.hasPackage("ptest-runner") and oeRuntimeTest.hasFeature("ptest") and oeRuntimeTest.hasPackageMatch("-ptest") and (runnerstatus != 0) if cond: self.install_packages(self.install_complementary("*-ptest")) self.install_packages(['ptest-runner']) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildgalculator.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildgalculator.py new file mode 100644 index 000000000..28ba29e5c --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildgalculator.py @@ -0,0 +1,23 @@ +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import TargetBuildProject + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + +class GalculatorTest(oeRuntimeTest): + @skipUnlessPassed("test_ssh") + def test_galculator(self): + try: + project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, + "http://galculator.mnim.org/downloads/galculator-2.1.4.tar.bz2") + project.download_archive() + + self.assertEqual(project.run_configure(), 0, + msg="Running configure failed") + + self.assertEqual(project.run_make(), 0, + msg="Running make failed") + finally: + project.clean() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildiptables.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildiptables.py index 09e252df8..bc75d0a0c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildiptables.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildiptables.py @@ -11,7 +11,7 @@ class BuildIptablesTest(oeRuntimeTest): @classmethod def setUpClass(self): self.project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, - "http://netfilter.org/projects/iptables/files/iptables-1.4.13.tar.bz2") + "http://downloads.yoctoproject.org/mirror/sources/iptables-1.4.13.tar.bz2") self.project.download_archive() @testcase(206) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildsudoku.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildsudoku.py deleted file mode 100644 index 802b06001..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/buildsudoku.py +++ /dev/null @@ -1,28 +0,0 @@ -from oeqa.oetest import oeRuntimeTest, skipModule -from oeqa.utils.decorators import * -from oeqa.utils.targetbuild import TargetBuildProject - -def setUpModule(): - if not oeRuntimeTest.hasFeature("tools-sdk"): - skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") - -class SudokuTest(oeRuntimeTest): - - @classmethod - def setUpClass(self): - self.project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, - "http://downloads.sourceforge.net/project/sudoku-savant/sudoku-savant/sudoku-savant-1.3/sudoku-savant-1.3.tar.bz2") - self.project.download_archive() - - @testcase(207) - @skipUnlessPassed("test_ssh") - def test_sudoku(self): - self.assertEqual(self.project.run_configure(), 0, - msg="Running configure failed") - - self.assertEqual(self.project.run_make(), 0, - msg="Running make failed") - - @classmethod - def tearDownClass(self): - self.project.clean() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/connman.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/connman.py index bd9dba3bd..003fefe2c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/connman.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/connman.py @@ -27,5 +27,5 @@ class ConnmanTest(oeRuntimeTest): def test_connmand_running(self): (status, output) = self.target.run(oeRuntimeTest.pscmd + ' | grep [c]onnmand') if status != 0: - print self.service_status("connman") + print(self.service_status("connman")) self.fail("No connmand process running") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/files/test.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/files/test.py index f3a2273c5..f389225d7 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/files/test.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/files/test.py @@ -3,4 +3,4 @@ import os os.system('touch /tmp/testfile.python') a = 9.01e+21 - 9.01e+21 + 0.01 -print "the value of a is %s" % a +print("the value of a is %s" % a) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/parselogs.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/parselogs.py index dec9ebe87..8efe2d1de 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/parselogs.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/parselogs.py @@ -55,18 +55,25 @@ x86_common = [ 'Could not enable PowerButton event', 'probe of LNXPWRBN:00 failed with error -22', 'pmd_set_huge: Cannot satisfy', + 'failed to setup card detect gpio', + 'amd_nb: Cannot enumerate AMD northbridges', + 'failed to retrieve link info, disabling eDP', ] + common_errors qemux86_common = [ 'wrong ELF class', "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', ] + common_errors ignore_errors = { 'default' : common_errors, 'qemux86' : [ 'Failed to access perfctr msr (MSR', + 'pci 0000:00:00.0: [Firmware Bug]: reg 0x..: invalid BAR (can\'t size)', ] + qemux86_common, 'qemux86-64' : qemux86_common, 'qemumips' : [ @@ -81,16 +88,28 @@ ignore_errors = { 'host side 80-wire cable detection failed, limiting max speed', 'mode "640x480" test failed', 'Failed to load module "glx"', + 'can\'t handle BAR above 4GB', + 'Cannot reserve Legacy IO', ] + common_errors, 'qemuarm' : [ 'mmci-pl18x: probe of fpga:05 failed with error -22', 'mmci-pl18x: probe of fpga:0b failed with error -22', - 'Failed to load module "glx"' + 'Failed to load module "glx"', + 'OF: amba_device_add() failed (-19) for /amba/smc@10100000', + 'OF: amba_device_add() failed (-19) for /amba/mpmc@10110000', + 'OF: amba_device_add() failed (-19) for /amba/sctl@101e0000', + 'OF: amba_device_add() failed (-19) for /amba/watchdog@101e1000', + 'OF: amba_device_add() failed (-19) for /amba/sci@101f0000', + 'OF: amba_device_add() failed (-19) for /amba/ssp@101f4000', + 'OF: amba_device_add() failed (-19) for /amba/fpga/sci@a000', + 'Failed to initialize \'/amba/timer@101e3000\': -22', + 'jitterentropy: Initialization failed with host not compliant with requirements: 2', ] + common_errors, 'qemuarm64' : [ 'Fatal server error:', '(EE) Server terminated with error (1). Closing log file.', 'dmi: Firmware registration failed.', + 'irq: type mismatch, failed to map hwirq-27 for /intc', ] + common_errors, 'emenlow' : [ '[Firmware Bug]: ACPI: No _BQC method, cannot determine initial brightness', @@ -110,11 +129,19 @@ ignore_errors = { '(EE) Failed to load module psbdrv', '(EE) open /dev/fb0: No such file or directory', '(EE) AIGLX: reverting to software rendering', + 'dmi: Firmware registration failed.', + 'ioremap error for 0x78', ] + x86_common, 'intel-corei7-64' : x86_common, 'crownbay' : x86_common, 'genericx86' : x86_common, - 'genericx86-64' : x86_common, + 'genericx86-64' : [ + 'Direct firmware load for i915', + 'Failed to load firmware i915', + 'Failed to fetch GuC', + 'Failed to initialize GuC', + 'The driver is built-in, so to load the firmware you need to', + ] + x86_common, 'edgerouter' : [ 'Fatal server error:', ] + common_errors, @@ -153,6 +180,9 @@ class ParseLogsTest(oeRuntimeTest): def getMachine(self): return oeRuntimeTest.tc.d.getVar("MACHINE", True) + def getWorkdir(self): + return oeRuntimeTest.tc.d.getVar("WORKDIR", True) + #get some information on the CPU of the machine to display at the beginning of the output. This info might be useful in some cases. def getHardwareInfo(self): hwi = "" @@ -190,16 +220,19 @@ class ParseLogsTest(oeRuntimeTest): #copy the log files to be parsed locally def transfer_logs(self, log_list): - target_logs = 'target_logs' + workdir = self.getWorkdir() + self.target_logs = workdir + '/' + 'target_logs' + target_logs = self.target_logs if not os.path.exists(target_logs): os.makedirs(target_logs) + bb.utils.remove(self.target_logs + "/*") for f in log_list: self.target.copy_from(f, target_logs) #get the local list of logs def get_local_log_list(self, log_locations): self.transfer_logs(self.getLogList(log_locations)) - logs = [ os.path.join('target_logs',f) for f in os.listdir('target_logs') if os.path.isfile(os.path.join('target_logs',f)) ] + logs = [ os.path.join(self.target_logs, f) for f in os.listdir(self.target_logs) if os.path.isfile(os.path.join(self.target_logs, f)) ] return logs #build the grep command to be used with filters and exclusions @@ -238,7 +271,7 @@ class ParseLogsTest(oeRuntimeTest): result = None thegrep = self.build_grepcmd(errors, ignore_errors, log) try: - result = subprocess.check_output(thegrep, shell=True) + result = subprocess.check_output(thegrep, shell=True).decode("utf-8") except: pass if (result is not None): @@ -246,7 +279,7 @@ class ParseLogsTest(oeRuntimeTest): rez = result.splitlines() for xrez in rez: try: - grep_output = subprocess.check_output(['grep', '-F', xrez, '-B', str(lines_before), '-A', str(lines_after), log]) + grep_output = subprocess.check_output(['grep', '-F', xrez, '-B', str(lines_before), '-A', str(lines_after), log]).decode("utf-8") except: pass results[log.replace('target_logs/','')][xrez]=grep_output @@ -262,7 +295,7 @@ class ParseLogsTest(oeRuntimeTest): self.write_dmesg() log_list = self.get_local_log_list(self.log_locations) result = self.parse_logs(self.errors, self.ignore_errors, log_list) - print self.getHardwareInfo() + print(self.getHardwareInfo()) errcount = 0 for log in result: self.msg += "Log: "+log+"\n" diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/ping.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/ping.py index 80c460161..0f2744792 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/ping.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/ping.py @@ -14,7 +14,7 @@ class PingTest(oeRuntimeTest): endtime = time.time() + 60 while count < 5 and time.time() < endtime: proc = subprocess.Popen("ping -c 1 %s" % self.target.ip, shell=True, stdout=subprocess.PIPE) - output += proc.communicate()[0] + output += proc.communicate()[0].decode("utf-8") if proc.poll() == 0: count += 1 else: diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/python.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/python.py index 26edb7a9b..29a231c7c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/python.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/python.py @@ -4,7 +4,7 @@ from oeqa.oetest import oeRuntimeTest, skipModule from oeqa.utils.decorators import * def setUpModule(): - if not oeRuntimeTest.hasPackage("python"): + if not oeRuntimeTest.hasPackage("python-core"): skipModule("No python package in the image") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/rpm.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/rpm.py index 624c515aa..7f514ca00 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/rpm.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/rpm.py @@ -51,12 +51,32 @@ class RpmInstallRemoveTest(oeRuntimeTest): @testcase(1096) @skipUnlessPassed('test_ssh') def test_rpm_query_nonroot(self): - (status, output) = self.target.run('useradd test1') - self.assertTrue(status == 0, msg="Failed to create new user: " + output) - (status, output) = self.target.run('sudo -u test1 id') - self.assertTrue('(test1)' in output, msg="Failed to execute as new user") - (status, output) = self.target.run('sudo -u test1 rpm -qa') - self.assertEqual(status, 0, msg="status: %s. Cannot run rpm -qa: %s" % (status, output)) + + def set_up_test_user(u): + (status, output) = self.target.run("id -u %s" % u) + if status == 0: + pass + else: + (status, output) = self.target.run("useradd %s" % u) + self.assertTrue(status == 0, msg="Failed to create new user: " + output) + + def exec_as_test_user(u): + (status, output) = self.target.run("su -c id %s" % u) + self.assertTrue("({0})".format(u) in output, msg="Failed to execute as new user") + (status, output) = self.target.run("su -c \"rpm -qa\" %s " % u) + self.assertEqual(status, 0, msg="status: %s. Cannot run rpm -qa: %s" % (status, output)) + + def unset_up_test_user(u): + (status, output) = self.target.run("userdel -r %s" % u) + self.assertTrue(status == 0, msg="Failed to erase user: %s" % output) + + tuser = 'test1' + + try: + set_up_test_user(tuser) + exec_as_test_user(tuser) + finally: + unset_up_test_user(tuser) @testcase(195) @skipUnlessPassed('test_rpm_install') @@ -98,4 +118,3 @@ class RpmInstallRemoveTest(oeRuntimeTest): @classmethod def tearDownClass(self): oeRuntimeTest.tc.target.run('rm -f /tmp/rpm-doc.rpm') - diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/smart.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/smart.py index 126d61463..6cdb10d63 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/smart.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/smart.py @@ -1,5 +1,7 @@ import unittest import re +import oe +import subprocess from oeqa.oetest import oeRuntimeTest, skipModule from oeqa.utils.decorators import * from oeqa.utils.httpserver import HTTPService @@ -7,7 +9,7 @@ from oeqa.utils.httpserver import HTTPService def setUpModule(): if not oeRuntimeTest.hasFeature("package-management"): skipModule("Image doesn't have package management feature") - if not oeRuntimeTest.hasPackage("smart"): + if not oeRuntimeTest.hasPackage("smartpm"): skipModule("Image doesn't have smart installed") if "package_rpm" != oeRuntimeTest.tc.d.getVar("PACKAGE_CLASSES", True).split()[0]: skipModule("Rpm is not the primary package manager") @@ -53,9 +55,50 @@ class SmartBasicTest(SmartTest): class SmartRepoTest(SmartTest): @classmethod + def create_index(self, arg): + index_cmd = arg + try: + bb.note("Executing '%s' ..." % index_cmd) + result = subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8") + except subprocess.CalledProcessError as e: + return("Index creation command '%s' failed with return code %d:\n%s" % + (e.cmd, e.returncode, e.output.decode("utf-8"))) + if result: + bb.note(result) + return None + + @classmethod def setUpClass(self): self.repolist = [] - self.repo_server = HTTPService(oeRuntimeTest.tc.d.getVar('DEPLOY_DIR', True), oeRuntimeTest.tc.target.server_ip) + + # Index RPMs + rpm_createrepo = bb.utils.which(os.getenv('PATH'), "createrepo") + index_cmds = [] + rpm_dirs_found = False + archs = (oeRuntimeTest.tc.d.getVar('ALL_MULTILIB_PACKAGE_ARCHS', True) or "").replace('-', '_').split() + for arch in archs: + rpm_dir = os.path.join(oeRuntimeTest.tc.d.getVar('DEPLOY_DIR_RPM', True), arch) + idx_path = os.path.join(oeRuntimeTest.tc.d.getVar('WORKDIR', True), 'rpm', arch) + db_path = os.path.join(oeRuntimeTest.tc.d.getVar('WORKDIR', True), 'rpmdb', arch) + if not os.path.isdir(rpm_dir): + continue + if os.path.exists(db_path): + bb.utils.remove(dbpath, True) + lockfilename = oeRuntimeTest.tc.d.getVar('DEPLOY_DIR_RPM', True) + "/rpm.lock" + lf = bb.utils.lockfile(lockfilename, False) + oe.path.copyhardlinktree(rpm_dir, idx_path) + # Full indexes overload a 256MB image so reduce the number of rpms + # in the feed. Filter to p* since we use the psplash packages and + # this leaves some allarch and machine arch packages too. + bb.utils.remove(idx_path + "*/[a-oq-z]*.rpm") + bb.utils.unlockfile(lf) + index_cmds.append("%s --dbpath %s --update -q %s" % (rpm_createrepo, db_path, idx_path)) + rpm_dirs_found = True + # Create repodata¬ + result = oe.utils.multiprocess_exec(index_cmds, self.create_index) + if result: + bb.fatal('%s' % ('\n'.join(result))) + self.repo_server = HTTPService(oeRuntimeTest.tc.d.getVar('WORKDIR', True), oeRuntimeTest.tc.target.server_ip) self.repo_server.start() @classmethod diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/syslog.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/syslog.py index 2601dd9ea..8f550329e 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/syslog.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/syslog.py @@ -9,7 +9,6 @@ def setUpModule(): class SyslogTest(oeRuntimeTest): @testcase(201) - @skipUnlessPassed("test_syslog_help") def test_syslog_running(self): (status,output) = self.target.run(oeRuntimeTest.pscmd + ' | grep -i [s]yslogd') self.assertEqual(status, 0, msg="no syslogd process, ps output: %s" % self.target.run(oeRuntimeTest.pscmd)[1]) @@ -19,8 +18,16 @@ class SyslogTestConfig(oeRuntimeTest): @testcase(1149) @skipUnlessPassed("test_syslog_running") def test_syslog_logger(self): - (status,output) = self.target.run('logger foobar && test -e /var/log/messages && grep foobar /var/log/messages || logread | grep foobar') - self.assertEqual(status, 0, msg="Test log string not found in /var/log/messages. Output: %s " % output) + (status, output) = self.target.run('logger foobar') + self.assertEqual(status, 0, msg="Can't log into syslog. Output: %s " % output) + + (status, output) = self.target.run('grep foobar /var/log/messages') + if status != 0: + if oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager", "") == "systemd": + (status, output) = self.target.run('journalctl -o cat | grep foobar') + else: + (status, output) = self.target.run('logread | grep foobar') + self.assertEqual(status, 0, msg="Test log string not found in /var/log/messages or logread. Output: %s " % output) @testcase(1150) @skipUnlessPassed("test_syslog_running") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/systemd.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/systemd.py index 2b2f10d71..8de799cd6 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/systemd.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/systemd.py @@ -57,7 +57,7 @@ class SystemdBasicTests(SystemdTest): self.systemctl('--version') @testcase(551) - @skipUnlessPassed('test_system_basic') + @skipUnlessPassed('test_systemd_basic') def test_systemd_list(self): self.systemctl('list-unit-files') @@ -153,7 +153,7 @@ class SystemdJournalTests(SystemdTest): if check_match: break # put the startup time in the test log if check_match: - print "%s" % check_match + print("%s" % check_match) else: self.skipTest("Error at obtaining the boot time from journalctl") boot_time_sec = 0 @@ -174,5 +174,5 @@ class SystemdJournalTests(SystemdTest): self.skipTest("Error when parsing time from boot string") #Assert the target boot time against systemd's unit start timeout if boot_time_sec > systemd_TimeoutStartSec: - print "Target boot time %s exceeds systemd's TimeoutStartSec %s"\ - %(boot_time_sec, systemd_TimeoutStartSec) + print("Target boot time %s exceeds systemd's TimeoutStartSec %s"\ + %(boot_time_sec, systemd_TimeoutStartSec)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildgalculator.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildgalculator.py new file mode 100644 index 000000000..dc2fa9ce1 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildgalculator.py @@ -0,0 +1,27 @@ +from oeqa.oetest import oeSDKTest, skipModule +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import SDKBuildProject + +def setUpModule(): + if not (oeSDKTest.hasPackage("gtk+3") or oeSDKTest.hasPackage("libgtk-3.0")): + skipModule("Image doesn't have gtk+3 in manifest") + +class GalculatorTest(oeSDKTest): + def test_galculator(self): + try: + project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/galculator/", + oeSDKTest.tc.sdkenv, oeSDKTest.tc.d, + "http://galculator.mnim.org/downloads/galculator-2.1.4.tar.bz2") + + project.download_archive() + + # regenerate configure to get support for --with-libtool-sysroot + legacy_preconf=("autoreconf -i -f -I ${OECORE_TARGET_SYSROOT}/usr/share/aclocal -I m4;") + + self.assertEqual(project.run_configure(extra_cmds=legacy_preconf), + 0, msg="Running configure failed") + + self.assertEqual(project.run_make(), 0, + msg="Running make failed") + finally: + project.clean() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildiptables.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildiptables.py index 062e5316e..f0cb8a428 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildiptables.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildiptables.py @@ -8,7 +8,7 @@ class BuildIptablesTest(oeSDKTest): @classmethod def setUpClass(self): self.project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/iptables/", oeSDKTest.tc.sdkenv, oeSDKTest.tc.d, - "http://netfilter.org/projects/iptables/files/iptables-1.4.13.tar.bz2") + "http://downloads.yoctoproject.org/mirror/sources/iptables-1.4.13.tar.bz2") self.project.download_archive() def test_iptables(self): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildsudoku.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildsudoku.py deleted file mode 100644 index dea77c659..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/buildsudoku.py +++ /dev/null @@ -1,26 +0,0 @@ -from oeqa.oetest import oeSDKTest, skipModule -from oeqa.utils.decorators import * -from oeqa.utils.targetbuild import SDKBuildProject - -def setUpModule(): - if not oeSDKTest.hasPackage("gtk\+"): - skipModule("Image doesn't have gtk+ in manifest") - -class SudokuTest(oeSDKTest): - - @classmethod - def setUpClass(self): - self.project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/sudoku/", oeSDKTest.tc.sdkenv, oeSDKTest.tc.d, - "http://downloads.sourceforge.net/project/sudoku-savant/sudoku-savant/sudoku-savant-1.3/sudoku-savant-1.3.tar.bz2") - self.project.download_archive() - - def test_sudoku(self): - self.assertEqual(self.project.run_configure(), 0, - msg="Running configure failed") - - self.assertEqual(self.project.run_make(), 0, - msg="Running make failed") - - @classmethod - def tearDownClass(self): - self.project.clean() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/devtool.py b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/devtool.py index c5bb3102a..65f41f687 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/devtool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/devtool.py @@ -1,32 +1,108 @@ import shutil - +import subprocess +import urllib.request from oeqa.oetest import oeSDKExtTest from oeqa.utils.decorators import * class DevtoolTest(oeSDKExtTest): - @classmethod def setUpClass(self): self.myapp_src = os.path.join(self.tc.sdkextfilesdir, "myapp") self.myapp_dst = os.path.join(self.tc.sdktestdir, "myapp") shutil.copytree(self.myapp_src, self.myapp_dst) + self.myapp_cmake_src = os.path.join(self.tc.sdkextfilesdir, "myapp_cmake") + self.myapp_cmake_dst = os.path.join(self.tc.sdktestdir, "myapp_cmake") + shutil.copytree(self.myapp_cmake_src, self.myapp_cmake_dst) + + def _test_devtool_build(self, directory): + self._run('devtool add myapp %s' % directory) + try: + self._run('devtool build myapp') + except Exception as e: + print(e.output) + self._run('devtool reset myapp') + raise e + self._run('devtool reset myapp') + + def _test_devtool_build_package(self, directory): + self._run('devtool add myapp %s' % directory) + try: + self._run('devtool package myapp') + except Exception as e: + print(e.output) + self._run('devtool reset myapp') + raise e + self._run('devtool reset myapp') + def test_devtool_location(self): output = self._run('which devtool') self.assertEqual(output.startswith(self.tc.sdktestdir), True, \ msg="Seems that devtool isn't the eSDK one: %s" % output) - + @skipUnlessPassed('test_devtool_location') def test_devtool_add_reset(self): self._run('devtool add myapp %s' % self.myapp_dst) self._run('devtool reset myapp') + + @testcase(1473) + @skipUnlessPassed('test_devtool_location') + def test_devtool_build_make(self): + self._test_devtool_build(self.myapp_dst) + + @testcase(1474) + @skipUnlessPassed('test_devtool_location') + def test_devtool_build_esdk_package(self): + self._test_devtool_build_package(self.myapp_dst) + @testcase(1479) @skipUnlessPassed('test_devtool_location') - def test_devtool_build(self): - self._run('devtool add myapp %s' % self.myapp_dst) - self._run('devtool build myapp') - self._run('devtool reset myapp') + def test_devtool_build_cmake(self): + self._test_devtool_build(self.myapp_cmake_dst) + + @testcase(1482) + @skipUnlessPassed('test_devtool_location') + def test_extend_autotools_recipe_creation(self): + req = 'https://github.com/rdfa/librdfa' + recipe = "bbexample" + self._run('devtool add %s %s' % (recipe, req) ) + try: + self._run('devtool build %s' % recipe) + except Exception as e: + print(e.output) + self._run('devtool reset %s' % recipe) + raise e + self._run('devtool reset %s' % recipe) + + @testcase(1484) + @skipUnlessPassed('test_devtool_location') + def test_devtool_kernelmodule(self): + docfile = 'https://github.com/umlaeute/v4l2loopback.git' + recipe = 'v4l2loopback-driver' + self._run('devtool add %s %s' % (recipe, docfile) ) + try: + self._run('devtool build %s' % recipe) + except Exception as e: + print(e.output) + self._run('devtool reset %s' % recipe) + raise e + self._run('devtool reset %s' % recipe) + + @testcase(1478) + @skipUnlessPassed('test_devtool_location') + def test_recipes_for_nodejs(self): + package_nodejs = "npm://registry.npmjs.org;name=winston;version=2.2.0" + self._run('devtool add %s ' % package_nodejs) + try: + self._run('devtool build %s ' % package_nodejs) + except Exception as e: + print(e.output) + self._run('devtool reset %s' % package_nodejs) + raise e + self._run('devtool reset %s '% package_nodejs) + @classmethod def tearDownClass(self): shutil.rmtree(self.myapp_dst) + shutil.rmtree(self.myapp_cmake_dst) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt new file mode 100644 index 000000000..19d773dd6 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required (VERSION 2.6) +project (myapp) +# The version number. +set (myapp_VERSION_MAJOR 1) +set (myapp_VERSION_MINOR 0) + +# add the executable +add_executable (myapp myapp.c) + +install(TARGETS myapp + RUNTIME DESTINATION bin) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/myapp.c b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/myapp.c new file mode 100644 index 000000000..f0b63f03f --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/files/myapp_cmake/myapp.c @@ -0,0 +1,9 @@ +#include <stdio.h> + +int +main(int argc, char *argv[]) +{ + printf("Hello world\n"); + + return 0; +} diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/sdk_update.py b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/sdk_update.py index 7a2a6fe7c..2ade839c0 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/sdk_update.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/sdk_update.py @@ -30,9 +30,6 @@ class SdkUpdateTest(oeSDKExtTest): def test_sdk_update_http(self): output = self._run("devtool sdk-update \"%s\"" % self.http_url) - def test_sdk_update_local(self): - output = self._run("devtool sdk-update \"%s\"" % self.publish_dir) - @classmethod def tearDownClass(self): self.http_service.stop() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_toaster.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/_toaster.py index c424659fd..15ea9df9e 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_toaster.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/_toaster.py @@ -2,7 +2,7 @@ import unittest import os import sys import shlex, subprocess -import urllib, commands, time, getpass, re, json, shlex +import urllib.request, urllib.parse, urllib.error, subprocess, time, getpass, re, json, shlex import oeqa.utils.ftools as ftools from oeqa.selftest.base import oeSelfTest @@ -290,7 +290,7 @@ class Toaster_DB_Tests(ToasterSetup): layers = Layer.objects.values('id', 'layer_index_url') cnt_err = [] for layer in layers: - resp = urllib.urlopen(layer['layer_index_url']) + resp = urllib.request.urlopen(layer['layer_index_url']) if (resp.getcode() != 200): cnt_err.append(layer['id']) self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py index e10455edc..26c93f905 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py @@ -28,17 +28,47 @@ class oeSelfTest(unittest.TestCase): def __init__(self, methodName="runTest"): self.builddir = os.environ.get("BUILDDIR") self.localconf_path = os.path.join(self.builddir, "conf/local.conf") + self.localconf_backup = os.path.join(self.builddir, "conf/local.bk") self.testinc_path = os.path.join(self.builddir, "conf/selftest.inc") self.local_bblayers_path = os.path.join(self.builddir, "conf/bblayers.conf") + self.local_bblayers_backup = os.path.join(self.builddir, + "conf/bblayers.bk") self.testinc_bblayers_path = os.path.join(self.builddir, "conf/bblayers.inc") self.machineinc_path = os.path.join(self.builddir, "conf/machine.inc") self.testlayer_path = oeSelfTest.testlayer_path self._extra_tear_down_commands = [] - self._track_for_cleanup = [self.testinc_path, self.testinc_bblayers_path, self.machineinc_path] + self._track_for_cleanup = [ + self.testinc_path, self.testinc_bblayers_path, + self.machineinc_path, self.localconf_backup, + self.local_bblayers_backup] super(oeSelfTest, self).__init__(methodName) def setUp(self): 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.log.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.log.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: @@ -67,7 +97,7 @@ class oeSelfTest(unittest.TestCase): machine = custommachine machine_conf = 'MACHINE ??= "%s"\n' % machine self.set_machine_config(machine_conf) - print 'MACHINE: %s' % machine + print('MACHINE: %s' % machine) # tests might need their own setup # but if they overwrite this one they have to call diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py index 26728a4b4..baae1e0e5 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py @@ -29,7 +29,7 @@ class BitbakeTests(oeSelfTest): def test_event_handler(self): self.write_config("INHERIT += \"test_events\"") result = bitbake('m4-native') - find_build_started = re.search("NOTE: Test for bb\.event\.BuildStarted(\n.*)*NOTE: Preparing RunQueue", result.output) + find_build_started = re.search("NOTE: Test for bb\.event\.BuildStarted(\n.*)*NOTE: Executing RunQueue Tasks", result.output) find_build_completed = re.search("Tasks Summary:.*(\n.*)*NOTE: Test for bb\.event\.BuildCompleted", result.output) self.assertTrue(find_build_started, msg = "Match failed in:\n%s" % result.output) self.assertTrue(find_build_completed, msg = "Match failed in:\n%s" % result.output) @@ -64,12 +64,15 @@ class BitbakeTests(oeSelfTest): @testcase(108) def test_invalid_patch(self): + # This patch already exists in SRC_URI so adding it again will cause the + # patch to fail. self.write_recipeinc('man', 'SRC_URI += "file://man-1.5h1-make.patch"') + self.write_config("INHERIT_remove = \"report-error\"") result = bitbake('man -c patch', ignore_status=True) self.delete_recipeinc('man') bitbake('-cclean man') line = self.getline(result, "Function failed: patch_do_patch") - self.assertTrue(line and line.startswith("ERROR:"), msg = "Though no man-1.5h1-make.patch file exists, bitbake didn't output any err. message. bitbake output: %s" % result.output) + self.assertTrue(line and line.startswith("ERROR:"), msg = "Repeated patch application didn't fail. bitbake output: %s" % result.output) @testcase(1354) def test_force_task_1(self): @@ -131,6 +134,7 @@ class BitbakeTests(oeSelfTest): self.write_recipeinc('man', data) self.write_config("""DL_DIR = \"${TOPDIR}/download-selftest\" SSTATE_DIR = \"${TOPDIR}/download-selftest\" +INHERIT_remove = \"report-error\" """) self.track_for_cleanup(os.path.join(self.builddir, "download-selftest")) @@ -141,7 +145,7 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\" self.assertEqual(result.status, 1, msg="Command succeded when it should have failed. bitbake output: %s" % result.output) self.assertTrue('Fetcher failure: Unable to find file file://invalid anywhere. The paths that were searched were:' in result.output, msg = "\"invalid\" file \ doesn't exist, yet no error message encountered. bitbake output: %s" % result.output) - line = self.getline(result, 'Function failed: Fetcher failure for URL: \'file://invalid\'. Unable to fetch URL from any source.') + line = self.getline(result, 'Fetcher failure for URL: \'file://invalid\'. Unable to fetch URL from any source.') self.assertTrue(line and line.startswith("ERROR:"), msg = "\"invalid\" file \ doesn't exist, yet fetcher didn't report any error. bitbake output: %s" % result.output) @@ -212,6 +216,7 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\" def test_continue(self): self.write_config("""DL_DIR = \"${TOPDIR}/download-selftest\" SSTATE_DIR = \"${TOPDIR}/download-selftest\" +INHERIT_remove = \"report-error\" """) self.track_for_cleanup(os.path.join(self.builddir, "download-selftest")) self.write_recipeinc('man',"\ndo_fail_task () {\nexit 1 \n}\n\naddtask do_fail_task before do_fetch\n" ) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py index 35d5dfd29..9487898b0 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py @@ -30,22 +30,6 @@ class ImageOptionsTests(oeSelfTest): incremental_removed = re.search("NOTE: load old install solution for incremental install\nNOTE: creating new install solution for incremental install(\n.*)*NOTE: incremental removed:.*openssh-sshd-.*", log_data_removed) self.assertTrue(incremental_removed, msg = "Match failed in:\n%s" % log_data_removed) - @testcase(925) - def test_rm_old_image(self): - bitbake("core-image-minimal") - deploydir = get_bb_var("DEPLOY_DIR_IMAGE", target="core-image-minimal") - imagename = get_bb_var("IMAGE_LINK_NAME", target="core-image-minimal") - deploydir_files = os.listdir(deploydir) - track_original_files = [] - for image_file in deploydir_files: - if imagename in image_file and os.path.islink(os.path.join(deploydir, image_file)): - track_original_files.append(os.path.realpath(os.path.join(deploydir, image_file))) - self.write_config("RM_OLD_IMAGE = \"1\"") - bitbake("-C rootfs core-image-minimal") - deploydir_files = os.listdir(deploydir) - remaining_not_expected = [path for path in track_original_files if os.path.basename(path) in deploydir_files] - self.assertFalse(remaining_not_expected, msg="\nThe following image files were not removed: %s" % ', '.join(map(str, remaining_not_expected))) - @testcase(286) def test_ccache_tool(self): bitbake("ccache-native") @@ -89,8 +73,9 @@ class SanityOptionsTest(oeSelfTest): def test_options_warnqa_errorqa_switch(self): bitbake("xcursor-transparent-theme -ccleansstate") + self.write_config("INHERIT_remove = \"report-error\"") if "packages-list" not in get_bb_var("ERROR_QA"): - self.write_config("ERROR_QA_append = \" packages-list\"") + self.append_config("ERROR_QA_append = \" packages-list\"") self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') res = bitbake("xcursor-transparent-theme", ignore_status=True) @@ -199,78 +184,6 @@ class BuildhistoryTests(BuildhistoryBase): self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True) self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True, error_regex=error) - @testcase(1386) - def test_buildhistory_does_not_change_signatures(self): - """ - Summary: Ensure that buildhistory does not change signatures - Expected: Only 'do_rootfs' task should be rerun - Product: oe-core - Author: Daniel Istrate <daniel.alexandrux.istrate@intel.com> - AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com> - """ - - tmpdir1_name = 'tmpsig1' - tmpdir2_name = 'tmpsig2' - builddir = os.environ.get('BUILDDIR') - tmpdir1 = os.path.join(builddir, tmpdir1_name) - tmpdir2 = os.path.join(builddir, tmpdir2_name) - - self.track_for_cleanup(tmpdir1) - self.track_for_cleanup(tmpdir2) - - features = 'TMPDIR = "%s"\n' % tmpdir1 - self.write_config(features) - bitbake('core-image-minimal -S none -c rootfs') - - features = 'TMPDIR = "%s"\n' % tmpdir2 - features += 'INHERIT += "buildhistory"\n' - self.write_config(features) - bitbake('core-image-minimal -S none -c rootfs') - - 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(tmpdir1 + '/stamps') - files2 = get_files(tmpdir2 + '/stamps') - files2 = [x.replace(tmpdir2_name, tmpdir1_name) for x in files2] - - f1 = set(files1) - f2 = set(files2) - sigdiff = f1 - f2 - - self.assertEqual(len(sigdiff), 1, 'Expected 1 signature differences. Out: %s' % list(sigdiff)) - - unexpected_diff = [] - - # No new signatures should appear apart from do_rootfs - found_do_rootfs_flag = False - - for sig in sigdiff: - if 'do_rootfs' in sig: - found_do_rootfs_flag = True - else: - unexpected_diff.append(sig) - - self.assertTrue(found_do_rootfs_flag, 'Task do_rootfs did not rerun.') - self.assertFalse(unexpected_diff, 'Found unexpected signature differences. Out: %s' % unexpected_diff) - - -class BuildImagesTest(oeSelfTest): - @testcase(563) - def test_directfb(self): - """ - This method is used to test the build of directfb image for arm arch. - In essence we build a coreimagedirectfb and test the exitcode of bitbake that in case of success is 0. - """ - self.add_command_to_tearDown('cleanup-workdir') - self.write_config("DISTRO_FEATURES_remove = \"x11\"\nDISTRO_FEATURES_append = \" directfb\"\nMACHINE ??= \"qemuarm\"") - res = bitbake("core-image-directfb", ignore_status=True) - self.assertEqual(res.status, 0, "\ncoreimagedirectfb failed to build. Please check logs for further details.\nbitbake output %s" % res.output) - class ArchiverTest(oeSelfTest): @testcase(926) def test_arch_work_dir_and_export_source(self): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py index 132a73d0e..e992dcf77 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py @@ -5,10 +5,11 @@ import re import shutil import tempfile import glob +import fnmatch import oeqa.utils.ftools as ftools from oeqa.selftest.base import oeSelfTest -from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer, runqemu +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer, runqemu, get_test_layer from oeqa.utils.decorators import testcase class DevtoolBase(oeSelfTest): @@ -50,7 +51,7 @@ class DevtoolBase(oeSelfTest): missingvars = {} - for var, value in checkvars.iteritems(): + for var, value in checkvars.items(): if value is not None: missingvars[var] = value self.assertEqual(missingvars, {}, 'Some expected variables not found in recipe: %s' % checkvars) @@ -207,12 +208,14 @@ class DevtoolTests(DevtoolBase): tempdir = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir) pn = 'dbus-wait' + srcrev = '6cc6077a36fe2648a5f993fe7c16c9632f946517' # We choose an https:// git URL here to check rewriting the URL works url = 'https://git.yoctoproject.org/git/dbus-wait' # Force fetching to "noname" subdir so we verify we're picking up the name from autoconf # instead of the directory name result = runCmd('git clone %s noname' % url, cwd=tempdir) srcdir = os.path.join(tempdir, 'noname') + result = runCmd('git reset --hard %s' % srcrev, cwd=srcdir) self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure.ac')), 'Unable to find configure script in source directory') # Test devtool add self.track_for_cleanup(self.workspacedir) @@ -235,7 +238,7 @@ class DevtoolTests(DevtoolBase): checkvars['S'] = '${WORKDIR}/git' checkvars['PV'] = '0.1+git${SRCPV}' checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/dbus-wait;protocol=https' - checkvars['SRCREV'] = '${AUTOREV}' + checkvars['SRCREV'] = srcrev checkvars['DEPENDS'] = set(['dbus']) self._test_recipe_contents(recipefile, checkvars, []) @@ -345,7 +348,7 @@ class DevtoolTests(DevtoolBase): self.track_for_cleanup(self.workspacedir) self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') - result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url)) + result = runCmd('devtool add %s %s -a -f %s' % (testrecipe, srcdir, url)) self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created: %s' % result.output) self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure.ac')), 'Unable to find configure.ac in source directory') # Test devtool status @@ -357,7 +360,7 @@ class DevtoolTests(DevtoolBase): self.assertIn('_git.bb', recipefile, 'Recipe file incorrectly named') checkvars = {} checkvars['S'] = '${WORKDIR}/git' - checkvars['PV'] = '1.11+git${SRCPV}' + checkvars['PV'] = '1.12+git${SRCPV}' checkvars['SRC_URI'] = url checkvars['SRCREV'] = '${AUTOREV}' self._test_recipe_contents(recipefile, checkvars, []) @@ -465,12 +468,11 @@ class DevtoolTests(DevtoolBase): testrecipes = 'perf kernel-devsrc package-index core-image-minimal meta-toolchain packagegroup-core-sdk meta-ide-support'.split() # Find actual name of gcc-source since it now includes the version - crude, but good enough for this purpose result = runCmd('bitbake-layers show-recipes gcc-source*') - reading = False for line in result.output.splitlines(): - if line.startswith('=='): - reading = True - elif reading and not line.startswith(' '): - testrecipes.append(line.split(':')[0]) + # just match those lines that contain a real target + m = re.match('(?P<recipe>^[a-zA-Z0-9.-]+)(?P<colon>:$)', line) + if m: + testrecipes.append(m.group('recipe')) for testrecipe in testrecipes: # Check it's a valid recipe bitbake('%s -e' % testrecipe) @@ -816,28 +818,28 @@ class DevtoolTests(DevtoolBase): # Check bbappend contents result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) - expectedlines = ['SRCREV = "%s"\n' % result.output, - '\n', - 'SRC_URI = "%s"\n' % git_uri, - '\n'] + expectedlines = set(['SRCREV = "%s"\n' % result.output, + '\n', + 'SRC_URI = "%s"\n' % git_uri, + '\n']) with open(bbappendfile, 'r') as f: - self.assertEqual(expectedlines, f.readlines()) + self.assertEqual(expectedlines, set(f.readlines())) # Check we can run it again and bbappend isn't modified result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) with open(bbappendfile, 'r') as f: - self.assertEqual(expectedlines, f.readlines()) + self.assertEqual(expectedlines, set(f.readlines())) # Drop new commit and check SRCREV changes result = runCmd('git reset HEAD^', cwd=tempsrcdir) result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created') result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) - expectedlines = ['SRCREV = "%s"\n' % result.output, - '\n', - 'SRC_URI = "%s"\n' % git_uri, - '\n'] + expectedlines = set(['SRCREV = "%s"\n' % result.output, + '\n', + 'SRC_URI = "%s"\n' % git_uri, + '\n']) with open(bbappendfile, 'r') as f: - self.assertEqual(expectedlines, f.readlines()) + self.assertEqual(expectedlines, set(f.readlines())) # Put commit back and check we can run it if layer isn't in bblayers.conf os.remove(bbappendfile) result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempsrcdir) @@ -846,12 +848,12 @@ class DevtoolTests(DevtoolBase): self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output) self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created') result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) - expectedlines = ['SRCREV = "%s"\n' % result.output, - '\n', - 'SRC_URI = "%s"\n' % git_uri, - '\n'] + expectedlines = set(['SRCREV = "%s"\n' % result.output, + '\n', + 'SRC_URI = "%s"\n' % git_uri, + '\n']) with open(bbappendfile, 'r') as f: - self.assertEqual(expectedlines, f.readlines()) + self.assertEqual(expectedlines, set(f.readlines())) # Deleting isn't expected to work under these circumstances @testcase(1370) @@ -1188,3 +1190,159 @@ class DevtoolTests(DevtoolBase): s = "Microsoft Made No Profit From Anyone's Zunes Yo" result = runCmd("devtool --quiet selftest-reverse \"%s\"" % s) self.assertEqual(result.output, s[::-1]) + + def _setup_test_devtool_finish_upgrade(self): + # Check preconditions + self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') + self.track_for_cleanup(self.workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + # Use a "real" recipe from meta-selftest + recipe = 'devtool-upgrade-test1' + oldversion = '1.5.3' + newversion = '1.6.0' + oldrecipefile = get_bb_var('FILE', recipe) + recipedir = os.path.dirname(oldrecipefile) + result = runCmd('git status --porcelain .', cwd=recipedir) + if result.output.strip(): + self.fail('Recipe directory for %s contains uncommitted changes' % recipe) + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + # Check that recipe is not already under devtool control + result = runCmd('devtool status') + self.assertNotIn(recipe, result.output) + # Do the upgrade + result = runCmd('devtool upgrade %s %s -V %s' % (recipe, tempdir, newversion)) + # Check devtool status and make sure recipe is present + result = runCmd('devtool status') + self.assertIn(recipe, result.output) + self.assertIn(tempdir, result.output) + # Make a change to the source + result = runCmd('sed -i \'/^#include "pv.h"/a \\/* Here is a new comment *\\/\' src/pv/number.c', cwd=tempdir) + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertIn('M src/pv/number.c', result.output) + result = runCmd('git commit src/pv/number.c -m "Add a comment to the code"', cwd=tempdir) + # Check if patch is there + recipedir = os.path.dirname(oldrecipefile) + olddir = os.path.join(recipedir, recipe + '-' + oldversion) + patchfn = '0001-Add-a-note-line-to-the-quick-reference.patch' + self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Original patch file does not exist') + return recipe, oldrecipefile, recipedir, olddir, newversion, patchfn + + def test_devtool_finish_upgrade_origlayer(self): + recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade() + # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) + self.assertIn('/meta-selftest/', recipedir) + # Try finish to the original layer + self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir)) + result = runCmd('devtool finish %s meta-selftest' % recipe) + result = runCmd('devtool status') + self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') + self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + self.assertFalse(os.path.exists(oldrecipefile), 'Old recipe file should have been deleted but wasn\'t') + self.assertFalse(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should have been deleted but wasn\'t') + newrecipefile = os.path.join(recipedir, '%s_%s.bb' % (recipe, newversion)) + newdir = os.path.join(recipedir, recipe + '-' + newversion) + self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t') + self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t') + self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t') + + def test_devtool_finish_upgrade_otherlayer(self): + recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade() + # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) + self.assertIn('/meta-selftest/', recipedir) + # Try finish to a different layer - should create a bbappend + # This cleanup isn't strictly necessary but do it anyway just in case it goes wrong and writes to here + self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir)) + oe_core_dir = os.path.join(get_bb_var('COREBASE'), 'meta') + newrecipedir = os.path.join(oe_core_dir, 'recipes-test', 'devtool') + newrecipefile = os.path.join(newrecipedir, '%s_%s.bb' % (recipe, newversion)) + self.track_for_cleanup(newrecipedir) + result = runCmd('devtool finish %s oe-core' % recipe) + result = runCmd('devtool status') + self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') + self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + self.assertTrue(os.path.exists(oldrecipefile), 'Old recipe file should not have been deleted') + self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should not have been deleted') + newdir = os.path.join(newrecipedir, recipe + '-' + newversion) + self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t') + self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t') + self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t') + + def _setup_test_devtool_finish_modify(self): + # Check preconditions + self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') + # Try modifying a recipe + self.track_for_cleanup(self.workspacedir) + recipe = 'mdadm' + oldrecipefile = get_bb_var('FILE', recipe) + recipedir = os.path.dirname(oldrecipefile) + result = runCmd('git status --porcelain .', cwd=recipedir) + if result.output.strip(): + self.fail('Recipe directory for %s contains uncommitted changes' % recipe) + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool modify %s %s' % (recipe, tempdir)) + self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found') + # Test devtool status + result = runCmd('devtool status') + self.assertIn(recipe, result.output) + self.assertIn(tempdir, result.output) + # Make a change to the source + result = runCmd('sed -i \'/^#include "mdadm.h"/a \\/* Here is a new comment *\\/\' maps.c', cwd=tempdir) + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertIn('M maps.c', result.output) + result = runCmd('git commit maps.c -m "Add a comment to the code"', cwd=tempdir) + for entry in os.listdir(recipedir): + filesdir = os.path.join(recipedir, entry) + if os.path.isdir(filesdir): + break + else: + self.fail('Unable to find recipe files directory for %s' % recipe) + return recipe, oldrecipefile, recipedir, filesdir + + def test_devtool_finish_modify_origlayer(self): + recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify() + # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) + self.assertIn('/meta/', recipedir) + # Try finish to the original layer + self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir)) + result = runCmd('devtool finish %s meta' % recipe) + result = runCmd('devtool status') + self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') + self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + expected_status = [(' M', '.*/%s$' % os.path.basename(oldrecipefile)), + ('??', '.*/.*-Add-a-comment-to-the-code.patch$')] + self._check_repo_status(recipedir, expected_status) + + def test_devtool_finish_modify_otherlayer(self): + recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify() + # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) + self.assertIn('/meta/', recipedir) + relpth = os.path.relpath(recipedir, os.path.join(get_bb_var('COREBASE'), 'meta')) + appenddir = os.path.join(get_test_layer(), relpth) + self.track_for_cleanup(appenddir) + # Try finish to the original layer + self.add_command_to_tearDown('rm -rf %s ; cd %s ; git checkout %s' % (recipedir, os.path.dirname(recipedir), recipedir)) + result = runCmd('devtool finish %s meta-selftest' % recipe) + result = runCmd('devtool status') + self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') + self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + result = runCmd('git status --porcelain .', cwd=recipedir) + if result.output.strip(): + self.fail('Recipe directory for %s contains the following unexpected changes after finish:\n%s' % (recipe, result.output.strip())) + recipefn = os.path.splitext(os.path.basename(oldrecipefile))[0] + recipefn = recipefn.split('_')[0] + '_%' + appendfile = os.path.join(appenddir, recipefn + '.bbappend') + self.assertTrue(os.path.exists(appendfile), 'bbappend %s should have been created but wasn\'t' % appendfile) + newdir = os.path.join(appenddir, recipe) + files = os.listdir(newdir) + foundpatch = None + for fn in files: + if fnmatch.fnmatch(fn, '*-Add-a-comment-to-the-code.patch'): + foundpatch = fn + if not foundpatch: + self.fail('No patch file created next to bbappend') + files.remove(foundpatch) + if files: + self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py new file mode 100644 index 000000000..9d5c68094 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py @@ -0,0 +1,103 @@ +import unittest +import tempfile +import shutil +import os +import glob +import logging +import subprocess +import oeqa.utils.ftools as ftools +from oeqa.utils.decorators import testcase +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd, bitbake, get_bb_var +from oeqa.utils.httpserver import HTTPService + +class oeSDKExtSelfTest(oeSelfTest): + """ + # Bugzilla Test Plan: 6033 + # This code is planned to be part of the automation for eSDK containig + # Install libraries and headers, image generation binary feeds. + """ + + @staticmethod + def get_esdk_environment(env_eSDK, tmpdir_eSDKQA): + # XXX: at this time use the first env need to investigate + # what environment load oe-selftest, i586, x86_64 + pattern = os.path.join(tmpdir_eSDKQA, 'environment-setup-*') + return glob.glob(pattern)[0] + + @staticmethod + def run_esdk_cmd(env_eSDK, tmpdir_eSDKQA, cmd, postconfig=None, **options): + if postconfig: + esdk_conf_file = os.path.join(tmpdir_eSDKQA, 'conf', 'local.conf') + with open(esdk_conf_file, 'a+') as f: + f.write(postconfig) + if not options: + options = {} + if not 'shell' in options: + options['shell'] = True + + runCmd("cd %s; . %s; %s" % (tmpdir_eSDKQA, env_eSDK, cmd), **options) + + @staticmethod + def generate_eSDK(image): + pn_task = '%s -c populate_sdk_ext' % image + bitbake(pn_task) + + @staticmethod + def get_eSDK_toolchain(image): + pn_task = '%s -c populate_sdk_ext' % image + + sdk_deploy = get_bb_var('SDK_DEPLOY', pn_task) + toolchain_name = get_bb_var('TOOLCHAINEXT_OUTPUTNAME', pn_task) + return os.path.join(sdk_deploy, toolchain_name + '.sh') + + + @classmethod + def setUpClass(cls): + # Start to serve sstate dir + sstate_dir = os.path.join(os.environ['BUILDDIR'], 'sstate-cache') + cls.http_service = HTTPService(sstate_dir) + cls.http_service.start() + + http_url = "127.0.0.1:%d" % cls.http_service.port + + image = 'core-image-minimal' + + cls.tmpdir_eSDKQA = tempfile.mkdtemp(prefix='eSDKQA') + oeSDKExtSelfTest.generate_eSDK(image) + + # Install eSDK + ext_sdk_path = oeSDKExtSelfTest.get_eSDK_toolchain(image) + runCmd("%s -y -d \"%s\"" % (ext_sdk_path, cls.tmpdir_eSDKQA)) + + cls.env_eSDK = oeSDKExtSelfTest.get_esdk_environment('', cls.tmpdir_eSDKQA) + + # Configure eSDK to use sstate mirror from poky + sstate_config=""" +SDK_LOCAL_CONF_WHITELIST = "SSTATE_MIRRORS" +SSTATE_MIRRORS = "file://.* http://%s/PATH" + """ % http_url + with open(os.path.join(cls.tmpdir_eSDKQA, 'conf', 'local.conf'), 'a+') as f: + f.write(sstate_config) + + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls.tmpdir_eSDKQA) + cls.http_service.stop() + + @testcase (1471) + def test_install_libraries_headers(self): + pn_sstate = 'bc' + bitbake(pn_sstate) + cmd = "devtool sdk-install %s " % pn_sstate + oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd) + + @testcase(1472) + def test_image_generation_binary_feeds(self): + image = 'core-image-minimal' + cmd = "devtool build-image %s" % image + oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd) + +if __name__ == '__main__': + unittest.main() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/esdk_prepare.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/esdk_prepare.py deleted file mode 100755 index 1b36a0d68..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/esdk_prepare.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python - -import shutil, tempfile -import sys -import os -import imp -import unittest -try: - from oeqa.utils.commands import get_bb_var -except ImportError: - pass - -# module under test -module_file_name = "ext-sdk-prepare.py" -module_path = "" - -class ExtSdkPrepareTest(unittest.TestCase): - - """ unit test for fix for Yocto #9019 """ - - @classmethod - def setUpClass(self): - # copy module under test to temp dir - self.test_dir = tempfile.mkdtemp() - module_dest_path = os.path.join(self.test_dir, module_file_name) - try: - shutil.copy(module_path, self.test_dir) - # load module under test - self.test_mod = imp.load_source("", module_dest_path) - except: - print "error: unable to copy or load %s [src: %s, dst: %s]" % \ - (module_file_name, module_path, module_dest_path) - sys.exit(1) - - def test_prepare_unexpected(self): - # test data - # note: pathnames have been truncated from the actual bitbake - # output as they are not important for the test. - test_data = ( - 'NOTE: Running noexec task 9 of 6539 (ID: 28, quilt/quilt-native_0.64.bb, do_build)\n' - 'NOTE: Running task 10 of 6539 (ID: 29, quilt/quilt-native_0.64.bb, do_package)\n' - 'NOTE: Running task 11 of 6539 (ID: 30, quilt/quilt-native_0.64.bb, do_rm_work)\n' - 'NOTE: Running noexec task 6402 of 6539 (ID: 1, images/core-image-sato.bb, do_patch)\n' - 'NOTE: Running task 6538 of 6539 (ID: 14, images/core-image-sato.bb, do_rm_work)\n' - ) - # expected warning output - expected = [ (' task 10 of 6539 (ID: 29, quilt/quilt-native_0.64.bb, do_package)') ] - # recipe to test, matching test input data - recipes = [ "core-image-sato.bb" ] - - # run the test - output = self.test_mod.check_unexpected(test_data, recipes) - self.assertEqual(output, expected) - - @classmethod - def tearDownClass(self): - # remove temp dir - shutil.rmtree(self.test_dir) - -if __name__ == '__main__': - # running from command line - i.e., not under oe-selftest - # directory containing module under test comes from command line - if len(sys.argv) == 2 and os.path.isdir(sys.argv[1]): - module_path = os.path.join(sys.argv[1], module_file_name) - suite = unittest.TestLoader().loadTestsFromTestCase(ExtSdkPrepareTest) - unittest.TextTestRunner().run(suite) - else: - progname = os.path.basename(sys.argv[0]) - print "%s: missing directory path" % progname - print "usage: %s /path/to/directory-of(ext-sdk-prepare.py)" % progname - sys.exit(1) -else: - # running under oe-selftest - # determine module source dir from COREBASE and expected path - module_path = os.path.join(get_bb_var("COREBASE"), "meta", "files", module_file_name) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py index 8a53899c7..d015c4908 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py @@ -98,3 +98,30 @@ class ImageFeatures(oeSelfTest): # Build a core-image-weston bitbake('core-image-weston') + def test_bmap(self): + """ + Summary: Check bmap support + Expected: 1. core-image-minimal can be build with bmap support + 2. core-image-minimal is sparse + Product: oe-core + Author: Ed Bartosh <ed.bartosh@linux.intel.com> + """ + + features = 'IMAGE_FSTYPES += " ext4 ext4.bmap"' + self.write_config(features) + + image_name = 'core-image-minimal' + bitbake(image_name) + + deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') + link_name = get_bb_var('IMAGE_LINK_NAME', image_name) + image_path = os.path.join(deploy_dir_image, "%s.ext4" % link_name) + bmap_path = "%s.bmap" % image_path + + # check if result image and bmap file are in deploy directory + self.assertTrue(os.path.exists(image_path)) + self.assertTrue(os.path.exists(bmap_path)) + + # check if result image is sparse + image_stat = os.stat(image_path) + self.assertTrue(image_stat.st_size > image_stat.st_blocks * 512) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py new file mode 100644 index 000000000..35131eb24 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py @@ -0,0 +1,93 @@ +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import get_bb_var, bitbake, runCmd +import oe.path +import glob +import os +import os.path + +class LibOE(oeSelfTest): + def test_copy_tree_special(self): + """ + Summary: oe.path.copytree() should copy files with special character + Expected: 'test file with sp£c!al @nd spaces' should exist in + copy destination + Product: OE-Core + Author: Joshua Lock <joshua.g.lock@intel.com> + """ + tmp_dir = get_bb_var('TMPDIR') + testloc = oe.path.join(tmp_dir, 'liboetests') + src = oe.path.join(testloc, 'src') + dst = oe.path.join(testloc, 'dst') + bb.utils.mkdirhier(testloc) + bb.utils.mkdirhier(src) + testfilename = 'test file with sp£c!al @nd spaces' + + # create the test file and copy it + open(oe.path.join(src, testfilename), 'w+b').close() + oe.path.copytree(src, dst) + + # ensure path exists in dest + fileindst = os.path.isfile(oe.path.join(dst, testfilename)) + self.assertTrue(fileindst, "File with spaces doesn't exist in dst") + + oe.path.remove(testloc) + + def test_copy_tree_xattr(self): + """ + Summary: oe.path.copytree() should preserve xattr on copied files + Expected: testxattr file in destination should have user.oetest + extended attribute + Product: OE-Core + Author: Joshua Lock <joshua.g.lock@intel.com> + """ + tmp_dir = get_bb_var('TMPDIR') + testloc = oe.path.join(tmp_dir, 'liboetests') + src = oe.path.join(testloc, 'src') + dst = oe.path.join(testloc, 'dst') + bb.utils.mkdirhier(testloc) + bb.utils.mkdirhier(src) + testfilename = 'testxattr' + + # ensure we have setfattr available + bitbake("attr-native") + bindir = get_bb_var('STAGING_BINDIR_NATIVE') + + # create a file with xattr and copy it + open(oe.path.join(src, testfilename), 'w+b').close() + runCmd('%s/setfattr -n user.oetest -v "testing liboe" %s' % (bindir, oe.path.join(src, testfilename))) + oe.path.copytree(src, dst) + + # ensure file in dest has user.oetest xattr + result = runCmd('%s/getfattr -n user.oetest %s' % (bindir, oe.path.join(dst, testfilename))) + self.assertIn('user.oetest="testing liboe"', result.output, 'Extended attribute not sert in dst') + + oe.path.remove(testloc) + + def test_copy_hardlink_tree_count(self): + """ + Summary: oe.path.copyhardlinktree() shouldn't miss out files + Expected: src and dst should have the same number of files + Product: OE-Core + Author: Joshua Lock <joshua.g.lock@intel.com> + """ + tmp_dir = get_bb_var('TMPDIR') + testloc = oe.path.join(tmp_dir, 'liboetests') + src = oe.path.join(testloc, 'src') + dst = oe.path.join(testloc, 'dst') + bb.utils.mkdirhier(testloc) + bb.utils.mkdirhier(src) + testfiles = ['foo', 'bar', '.baz', 'quux'] + + def touchfile(tf): + open(oe.path.join(src, tf), 'w+b').close() + + for f in testfiles: + touchfile(f) + + oe.path.copyhardlinktree(src, dst) + + dstcnt = len(os.listdir(dst)) + srccnt = len(os.listdir(src)) + self.assertEquals(dstcnt, len(testfiles), "Number of files in dst (%s) differs from number of files in src(%s)." % (dstcnt, srccnt)) + + oe.path.remove(testloc) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py index cac6d8445..2e81373ae 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py @@ -12,20 +12,24 @@ class LicenseTests(oeSelfTest): # the license qa to fail due to a mismatched md5sum. @testcase(1197) def test_nonmatching_checksum(self): - bitbake_cmd = '-c configure emptytest' + bitbake_cmd = '-c populate_lic emptytest' error_msg = 'emptytest: The new md5 checksum is 8d777f385d3dfec8815d20f7496026dc' lic_file, lic_path = tempfile.mkstemp() os.close(lic_file) self.track_for_cleanup(lic_path) - self.write_recipeinc('emptytest', 'INHIBIT_DEFAULT_DEPS = "1"') - self.append_recipeinc('emptytest', 'LIC_FILES_CHKSUM = "file://%s;md5=d41d8cd98f00b204e9800998ecf8427e"' % lic_path) + self.write_recipeinc('emptytest', """ +INHIBIT_DEFAULT_DEPS = "1" +LIC_FILES_CHKSUM = "file://%s;md5=d41d8cd98f00b204e9800998ecf8427e" +SRC_URI = "file://%s;md5=d41d8cd98f00b204e9800998ecf8427e" +""" % (lic_path, lic_path)) result = bitbake(bitbake_cmd) with open(lic_path, "w") as f: f.write("data") + self.write_config("INHERIT_remove = \"report-error\"") result = bitbake(bitbake_cmd, ignore_status=True) if error_msg not in result.output: raise AssertionError(result.output) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py index 138b03aad..5a63f89ff 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py @@ -131,15 +131,15 @@ class OePkgdataUtilTests(oeSelfTest): # Test recipe-space package name result = runCmd('oe-pkgdata-util list-pkg-files zlib-dev zlib-doc') files = splitoutput(result.output) - self.assertIn('zlib-dev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-doc', files.keys(), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-doc', list(files.keys()), "listed pkgs. files: %s" %result.output) self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) # Test runtime package name result = runCmd('oe-pkgdata-util list-pkg-files -r libz1 libz-dev') files = splitoutput(result.output) - self.assertIn('libz1', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-dev', files.keys(), "listed pkgs. files: %s" %result.output) + self.assertIn('libz1', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) self.assertGreater(len(files['libz1']), 1) libspec = os.path.join(base_libdir, 'libz.so.1.*') found = False @@ -152,12 +152,12 @@ class OePkgdataUtilTests(oeSelfTest): # Test recipe result = runCmd('oe-pkgdata-util list-pkg-files -p zlib') files = splitoutput(result.output) - self.assertIn('zlib-dbg', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-doc', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-dev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-staticdev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertNotIn('zlib-locale', files.keys(), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-dbg', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-doc', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-staticdev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertNotIn('zlib-locale', list(files.keys()), "listed pkgs. files: %s" %result.output) # (ignore ptest, might not be there depending on config) self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) @@ -165,36 +165,36 @@ class OePkgdataUtilTests(oeSelfTest): # Test recipe, runtime result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -r') files = splitoutput(result.output) - self.assertIn('libz-dbg', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-doc', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-dev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-staticdev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz1', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertNotIn('libz-locale', files.keys(), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-dbg', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-doc', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-staticdev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz1', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertNotIn('libz-locale', list(files.keys()), "listed pkgs. files: %s" %result.output) self.assertIn(os.path.join(includedir, 'zlib.h'), files['libz-dev']) self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc']) self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev']) # Test recipe, unpackaged result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -u') files = splitoutput(result.output) - self.assertIn('zlib-dbg', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-doc', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-dev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-staticdev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('zlib-locale', files.keys(), "listed pkgs. files: %s" %result.output) # this is the key one + self.assertIn('zlib-dbg', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-doc', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-staticdev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('zlib-locale', list(files.keys()), "listed pkgs. files: %s" %result.output) # this is the key one self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) self.assertIn(os.path.join(libdir, 'libz.a'), files['zlib-staticdev']) # Test recipe, runtime, unpackaged result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -r -u') files = splitoutput(result.output) - self.assertIn('libz-dbg', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-doc', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-dev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-staticdev', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz1', files.keys(), "listed pkgs. files: %s" %result.output) - self.assertIn('libz-locale', files.keys(), "listed pkgs. files: %s" %result.output) # this is the key one + self.assertIn('libz-dbg', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-doc', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-dev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-staticdev', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz1', list(files.keys()), "listed pkgs. files: %s" %result.output) + self.assertIn('libz-locale', list(files.keys()), "listed pkgs. files: %s" %result.output) # this is the key one self.assertIn(os.path.join(includedir, 'zlib.h'), files['libz-dev']) self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc']) self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev']) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py index e72911b0a..db1f8deeb 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py @@ -1,7 +1,7 @@ import os import logging import tempfile -import urlparse +import urllib.parse from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer from oeqa.utils.decorators import testcase @@ -278,7 +278,7 @@ class RecipetoolTests(RecipetoolBase): '}\n'] _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/etc/selftest-replaceme-patched', self.testfile, '', expectedlines, ['testfile']) for line in output.splitlines(): - if line.startswith('WARNING: '): + if 'WARNING: ' in line: self.assertIn('add-file.patch', line, 'Unexpected warning found in output:\n%s' % line) break else: @@ -383,13 +383,13 @@ class RecipetoolTests(RecipetoolBase): @testcase(1194) def test_recipetool_create_git(self): # Ensure we have the right data in shlibs/pkgdata - bitbake('libpng pango libx11 libxext jpeg libxsettings-client libcheck') + bitbake('libpng pango libx11 libxext jpeg libcheck') # Try adding a recipe tempsrc = os.path.join(self.tempdir, 'srctree') os.makedirs(tempsrc) recipefile = os.path.join(self.tempdir, 'libmatchbox.bb') srcuri = 'git://git.yoctoproject.org/libmatchbox' - result = runCmd('recipetool create -o %s %s -x %s' % (recipefile, srcuri, tempsrc)) + result = runCmd(['recipetool', 'create', '-o', recipefile, srcuri + ";rev=9f7cf8895ae2d39c465c04cc78e918c157420269", '-x', tempsrc]) self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output) checkvars = {} checkvars['LICENSE'] = 'LGPLv2.1' @@ -397,7 +397,7 @@ class RecipetoolTests(RecipetoolBase): checkvars['S'] = '${WORKDIR}/git' checkvars['PV'] = '1.11+git${SRCPV}' checkvars['SRC_URI'] = srcuri - checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxsettings-client', 'libxext', 'pango']) + checkvars['DEPENDS'] = set(['libcheck', 'libjpeg-turbo', 'libpng', 'libx11', 'libxext', 'pango']) inherits = ['autotools', 'pkgconfig'] self._test_recipe_contents(recipefile, checkvars, inherits) @@ -442,6 +442,49 @@ class RecipetoolTests(RecipetoolBase): inherits = ['cmake', 'python-dir', 'gettext', 'pkgconfig'] 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') + os.makedirs(temprecipe) + recipefile = os.path.join(temprecipe, 'meson_git.bb') + srcuri = 'https://github.com/mesonbuild/meson' + result = runCmd('recipetool create -o %s %s' % (temprecipe, srcuri)) + self.assertTrue(os.path.isfile(recipefile)) + checkvars = {} + checkvars['LICENSE'] = set(['Apache-2.0']) + checkvars['SRC_URI'] = 'git://github.com/mesonbuild/meson;protocol=https' + inherits = ['setuptools'] + self._test_recipe_contents(recipefile, checkvars, inherits) + + def test_recipetool_create_github_tarball(self): + # Basic test to ensure github URL mangling doesn't apply to release tarballs + temprecipe = os.path.join(self.tempdir, 'recipe') + os.makedirs(temprecipe) + pv = '0.32.0' + recipefile = os.path.join(temprecipe, 'meson_%s.bb' % pv) + srcuri = 'https://github.com/mesonbuild/meson/releases/download/%s/meson-%s.tar.gz' % (pv, pv) + result = runCmd('recipetool create -o %s %s' % (temprecipe, srcuri)) + self.assertTrue(os.path.isfile(recipefile)) + checkvars = {} + checkvars['LICENSE'] = set(['Apache-2.0']) + checkvars['SRC_URI'] = 'https://github.com/mesonbuild/meson/releases/download/${PV}/meson-${PV}.tar.gz' + inherits = ['setuptools'] + self._test_recipe_contents(recipefile, checkvars, inherits) + + def test_recipetool_create_git_http(self): + # Basic test to check http git URL mangling works + temprecipe = os.path.join(self.tempdir, 'recipe') + os.makedirs(temprecipe) + recipefile = os.path.join(temprecipe, 'matchbox-terminal_git.bb') + srcuri = 'http://git.yoctoproject.org/git/matchbox-terminal' + result = runCmd('recipetool create -o %s %s' % (temprecipe, srcuri)) + self.assertTrue(os.path.isfile(recipefile)) + checkvars = {} + checkvars['LICENSE'] = set(['GPLv2']) + checkvars['SRC_URI'] = 'git://git.yoctoproject.org/git/matchbox-terminal;protocol=http' + inherits = ['pkgconfig', 'autotools'] + self._test_recipe_contents(recipefile, checkvars, inherits) + class RecipetoolAppendsrcBase(RecipetoolBase): def _try_recipetool_appendsrcfile(self, testrecipe, newfile, destfile, options, expectedlines, expectedfiles): cmd = 'recipetool appendsrcfile %s %s %s %s %s' % (options, self.templayerdir, testrecipe, newfile, destfile) @@ -471,7 +514,7 @@ class RecipetoolAppendsrcBase(RecipetoolBase): '''Return the first file:// in SRC_URI for the specified recipe.''' src_uri = get_bb_var('SRC_URI', recipe).split() for uri in src_uri: - p = urlparse.urlparse(uri) + p = urllib.parse.urlparse(uri) if p.scheme == 'file': return p.netloc + p.path diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py new file mode 100644 index 000000000..c2d5b45a4 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py @@ -0,0 +1,105 @@ +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, runqemu +from oeqa.utils.decorators import testcase +import os + +class TestExport(oeSelfTest): + + def test_testexport_basic(self): + """ + Summary: Check basic testexport functionality with only ping test enabled. + Expected: 1. testexport directory must be created. + 2. runexported.py must run without any error/exception. + 3. ping test must succeed. + Product: oe-core + Author: Mariano Lopez <mariano.lopez@intel.com> + """ + + features = 'INHERIT += "testexport"\n' + # These aren't the actual IP addresses but testexport class needs something defined + features += 'TEST_SERVER_IP = "192.168.7.1"\n' + features += 'TEST_TARGET_IP = "192.168.7.1"\n' + features += 'TEST_SUITES = "ping"\n' + self.write_config(features) + + # Build tesexport for core-image-minimal + bitbake('core-image-minimal') + bitbake('-c testexport core-image-minimal') + + # Verify if TEST_EXPORT_DIR was created + testexport_dir = get_bb_var('TEST_EXPORT_DIR', 'core-image-minimal') + isdir = os.path.isdir(testexport_dir) + self.assertEqual(True, isdir, 'Failed to create testexport dir: %s' % testexport_dir) + + with runqemu('core-image-minimal') as qemu: + # Attempt to run runexported.py to perform ping test + runexported_path = os.path.join(testexport_dir, "runexported.py") + testdata_path = os.path.join(testexport_dir, "testdata.json") + cmd = "%s -t %s -s %s %s" % (runexported_path, qemu.ip, qemu.server_ip, testdata_path) + result = runCmd(cmd) + self.assertEqual(0, result.status, 'runexported.py returned a non 0 status') + + # Verify ping test was succesful + failure = True if 'FAIL' in result.output else False + self.assertNotEqual(True, failure, 'ping test failed') + + def test_testexport_sdk(self): + """ + Summary: Check sdk functionality for testexport. + Expected: 1. testexport directory must be created. + 2. SDK tarball must exists. + 3. Uncompressing of tarball must succeed. + 4. Check if the SDK directory is added to PATH. + 5. Run tar from the SDK directory. + Product: oe-core + Author: Mariano Lopez <mariano.lopez@intel.com> + """ + + features = 'INHERIT += "testexport"\n' + # These aren't the actual IP addresses but testexport class needs something defined + features += 'TEST_SERVER_IP = "192.168.7.1"\n' + features += 'TEST_TARGET_IP = "192.168.7.1"\n' + features += 'TEST_SUITES = "ping"\n' + features += 'TEST_SUITES_TAGS = "selftest_sdk"\n' + features += 'TEST_EXPORT_SDK_ENABLED = "1"\n' + features += 'TEST_EXPORT_SDK_PACKAGES = "nativesdk-tar"\n' + self.write_config(features) + + # Build tesexport for core-image-minimal + bitbake('core-image-minimal') + bitbake('-c testexport core-image-minimal') + + # Check for SDK + testexport_dir = get_bb_var('TEST_EXPORT_DIR', 'core-image-minimal') + sdk_dir = get_bb_var('TEST_EXPORT_SDK_DIR', 'core-image-minimal') + tarball_name = "%s.sh" % get_bb_var('TEST_EXPORT_SDK_NAME', 'core-image-minimal') + tarball_path = os.path.join(testexport_dir, sdk_dir, tarball_name) + self.assertEqual(os.path.isfile(tarball_path), True, "Couldn't find SDK tarball: %s" % tarball_path) + + # Run runexported.py + runexported_path = os.path.join(testexport_dir, "runexported.py") + testdata_path = os.path.join(testexport_dir, "testdata.json") + cmd = "%s %s" % (runexported_path, testdata_path) + result = runCmd(cmd) + self.assertEqual(0, result.status, 'runexported.py returned a non 0 status') + + +class TestImage(oeSelfTest): + + def test_testimage_install(self): + """ + Summary: Check install packages functionality for testimage/testexport. + Expected: 1. Import tests from a directory other than meta. + 2. Check install/unistall of socat. + Product: oe-core + Author: Mariano Lopez <mariano.lopez@intel.com> + """ + + features = 'INHERIT += "testimage"\n' + features += 'TEST_SUITES = "ping ssh selftest"\n' + features += 'TEST_SUITES_TAGS = "selftest_package_install"\n' + self.write_config(features) + + # Build core-image-sato and testimage + bitbake('core-image-full-cmdline socat') + bitbake('-c testimage core-image-full-cmdline') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py index 1babca07d..4c12d6d94 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py @@ -12,30 +12,22 @@ from oeqa.utils.ftools import write_file class Signing(oeSelfTest): gpg_dir = "" - pub_key_name = 'key.pub' - secret_key_name = 'key.secret' + pub_key_path = "" + secret_key_path = "" @classmethod def setUpClass(cls): - # Import the gpg keys + # Check that we can find the gpg binary and fail early if we can't + if not shutil.which("gpg"): + raise AssertionError("This test needs GnuPG") - cls.gpg_dir = os.path.join(cls.testlayer_path, 'files/signing/') + cls.gpg_home_dir = tempfile.TemporaryDirectory(prefix="oeqa-signing-") + cls.gpg_dir = cls.gpg_home_dir.name - # key.secret key.pub are located in gpg_dir - pub_key_location = cls.gpg_dir + cls.pub_key_name - secret_key_location = cls.gpg_dir + cls.secret_key_name - runCmd('gpg --homedir %s --import %s %s' % (cls.gpg_dir, pub_key_location, secret_key_location)) + cls.pub_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.pub") + cls.secret_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.secret") - @classmethod - def tearDownClass(cls): - # Delete the files generated by 'gpg --import' - - gpg_files = glob.glob(cls.gpg_dir + '*.gpg*') - random_seed_file = cls.gpg_dir + 'random_seed' - gpg_files.append(random_seed_file) - - for gpg_file in gpg_files: - runCmd('rm -f ' + gpg_file) + runCmd('gpg --homedir %s --import %s %s' % (cls.gpg_dir, cls.pub_key_path, cls.secret_key_path)) @testcase(1362) def test_signing_packages(self): @@ -57,7 +49,7 @@ class Signing(oeSelfTest): feature = 'INHERIT += "sign_rpm"\n' feature += 'RPM_GPG_PASSPHRASE = "test123"\n' feature += 'RPM_GPG_NAME = "testuser"\n' - feature += 'RPM_GPG_PUBKEY = "%s%s"\n' % (self.gpg_dir, self.pub_key_name) + feature += 'RPM_GPG_PUBKEY = "%s"\n' % self.pub_key_path feature += 'GPG_PATH = "%s"\n' % self.gpg_dir self.write_config(feature) @@ -81,8 +73,8 @@ class Signing(oeSelfTest): # Use a temporary rpmdb rpmdb = tempfile.mkdtemp(prefix='oeqa-rpmdb') - runCmd('%s/rpm --define "_dbpath %s" --import %s%s' % - (staging_bindir_native, rpmdb, self.gpg_dir, self.pub_key_name)) + runCmd('%s/rpm --define "_dbpath %s" --import %s' % + (staging_bindir_native, rpmdb, self.pub_key_path)) ret = runCmd('%s/rpm --define "_dbpath %s" --checksig %s' % (staging_bindir_native, rpmdb, pkg_deploy)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py index acaf405ac..6642539eb 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py @@ -237,6 +237,7 @@ TMPDIR = "${TOPDIR}/tmp-sstatesamehash" BUILD_ARCH = "x86_64" BUILD_OS = "linux" SDKMACHINE = "x86_64" +PACKAGE_CLASSES = "package_rpm package_ipk package_deb" """) self.track_for_cleanup(topdir + "/tmp-sstatesamehash") bitbake("core-image-sato -S none") @@ -246,6 +247,7 @@ TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" BUILD_ARCH = "i686" BUILD_OS = "linux" SDKMACHINE = "i686" +PACKAGE_CLASSES = "package_rpm package_ipk package_deb" """) self.track_for_cleanup(topdir + "/tmp-sstatesamehash2") bitbake("core-image-sato -S none") @@ -264,7 +266,7 @@ SDKMACHINE = "i686" files2 = get_files(topdir + "/tmp-sstatesamehash2/stamps/") files2 = [x.replace("tmp-sstatesamehash2", "tmp-sstatesamehash").replace("i686-linux", "x86_64-linux").replace("i686" + targetvendor + "-linux", "x86_64" + targetvendor + "-linux", ) for x in files2] self.maxDiff = None - self.assertItemsEqual(files1, files2) + self.assertCountEqual(files1, files2) @testcase(1271) @@ -298,7 +300,7 @@ NATIVELSBSTRING = \"DistroB\" files2 = get_files(topdir + "/tmp-sstatesamehash2/stamps/") files2 = [x.replace("tmp-sstatesamehash2", "tmp-sstatesamehash") for x in files2] self.maxDiff = None - self.assertItemsEqual(files1, files2) + self.assertCountEqual(files1, files2) @testcase(1368) def test_sstate_allarch_samesigs(self): @@ -309,19 +311,48 @@ NATIVELSBSTRING = \"DistroB\" the two MACHINE values. """ + configA = """ +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" +MACHINE = \"qemux86-64\" +""" + configB = """ +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" +MACHINE = \"qemuarm\" +""" + self.sstate_allarch_samesigs(configA, configB) + + def test_sstate_allarch_samesigs_multilib(self): + """ + The sstate checksums of allarch multilib packages should be independent of whichever + MACHINE is set. Check this using bitbake -S. + Also, rather than duplicate the test, check nativesdk stamps are the same between + the two MACHINE values. + """ + + configA = """ +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" +MACHINE = \"qemux86-64\" +require conf/multilib.conf +MULTILIBS = \"multilib:lib32\" +DEFAULTTUNE_virtclass-multilib-lib32 = \"x86\" +""" + configB = """ +TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" +MACHINE = \"qemuarm\" +require conf/multilib.conf +MULTILIBS = \"\" +""" + self.sstate_allarch_samesigs(configA, configB) + + def sstate_allarch_samesigs(self, configA, configB): + topdir = get_bb_var('TOPDIR') targetos = get_bb_var('TARGET_OS') targetvendor = get_bb_var('TARGET_VENDOR') - self.write_config(""" -TMPDIR = \"${TOPDIR}/tmp-sstatesamehash\" -MACHINE = \"qemux86\" -""") + self.write_config(configA) self.track_for_cleanup(topdir + "/tmp-sstatesamehash") bitbake("world meta-toolchain -S none") - self.write_config(""" -TMPDIR = \"${TOPDIR}/tmp-sstatesamehash2\" -MACHINE = \"qemuarm\" -""") + self.write_config(configB) self.track_for_cleanup(topdir + "/tmp-sstatesamehash2") bitbake("world meta-toolchain -S none") @@ -393,7 +424,7 @@ DEFAULTTUNE_virtclass-multilib-lib32 = "x86" files2 = get_files(topdir + "/tmp-sstatesamehash2/stamps") files2 = [x.replace("tmp-sstatesamehash2", "tmp-sstatesamehash") for x in files2] self.maxDiff = None - self.assertItemsEqual(files1, files2) + self.assertCountEqual(files1, files2) def test_sstate_noop_samesigs(self): @@ -411,9 +442,11 @@ PARALLEL_MAKE = "-j 1" DL_DIR = "${TOPDIR}/download1" TIME = "111111" DATE = "20161111" -INHERIT_remove = "buildstats-summary buildhistory" +INHERIT_remove = "buildstats-summary buildhistory uninative" +http_proxy = "" """) self.track_for_cleanup(topdir + "/tmp-sstatesamehash") + self.track_for_cleanup(topdir + "/download1") bitbake("world meta-toolchain -S none") self.write_config(""" TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" @@ -422,9 +455,13 @@ PARALLEL_MAKE = "-j 2" DL_DIR = "${TOPDIR}/download2" TIME = "222222" DATE = "20161212" +# Always remove uninative as we're changing proxies +INHERIT_remove = "uninative" INHERIT += "buildstats-summary buildhistory" +http_proxy = "http://example.com/" """) self.track_for_cleanup(topdir + "/tmp-sstatesamehash2") + self.track_for_cleanup(topdir + "/download2") bitbake("world meta-toolchain -S none") def get_files(d): @@ -439,23 +476,23 @@ INHERIT += "buildstats-summary buildhistory" files1 = get_files(topdir + "/tmp-sstatesamehash/stamps/") files2 = get_files(topdir + "/tmp-sstatesamehash2/stamps/") # Remove items that are identical in both sets - for k,v in files1.viewitems() & files2.viewitems(): + for k,v in files1.items() & files2.items(): del files1[k] del files2[k] if not files1 and not files2: # No changes, so we're done return - for k in files1.viewkeys() | files2.viewkeys(): + for k in files1.keys() | files2.keys(): if k in files1 and k in files2: - print "%s differs:" % k - print subprocess.check_output(("bitbake-diffsigs", + print("%s differs:" % k) + print(subprocess.check_output(("bitbake-diffsigs", topdir + "/tmp-sstatesamehash/stamps/" + k + "." + files1[k], - topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k])) + topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k]))) elif k in files1 and k not in files2: - print "%s in files1" % k + print("%s in files1" % k) elif k not in files1 and k in files2: - print "%s in files2" % k + print("%s in files2" % k) else: assert "shouldn't reach here" self.fail("sstate hashes not identical.") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py index a569fbf74..faac11e21 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py @@ -49,7 +49,7 @@ class Wic(oeSelfTest): # setUpClass being unavailable. if not Wic.image_is_ready: bitbake('syslinux syslinux-native parted-native gptfdisk-native ' - 'dosfstools-native mtools-native') + 'dosfstools-native mtools-native bmap-tools-native') bitbake('core-image-minimal') Wic.image_is_ready = True @@ -276,3 +276,26 @@ class Wic(oeSelfTest): status, output = qemu.run_serial(command) self.assertEqual(1, status, 'Failed to run command "%s": %s' % (command, output)) self.assertEqual(output, '/dev/root /\r\n/dev/vda3 /mnt') + + def test_bmap(self): + """Test generation of .bmap file""" + image = "directdisk" + status = runCmd("wic create %s -e core-image-minimal --bmap" % image).status + self.assertEqual(0, status) + self.assertEqual(1, len(glob(self.resultdir + "%s-*direct" % image))) + self.assertEqual(1, len(glob(self.resultdir + "%s-*direct.bmap" % image))) + + def test_systemd_bootdisk(self): + """Test creation of systemd-bootdisk image""" + image = "systemd-bootdisk" + self.assertEqual(0, runCmd("wic create %s -e core-image-minimal" \ + % image).status) + self.assertEqual(1, len(glob(self.resultdir + "%s-*direct" % image))) + + def test_sdimage_bootpart(self): + """Test creation of sdimage-bootpart image""" + image = "sdimage-bootpart" + self.write_config('IMAGE_BOOT_FILES = "bzImage"\n') + self.assertEqual(0, runCmd("wic create %s -e core-image-minimal" \ + % image).status) + self.assertEqual(1, len(glob(self.resultdir + "%s-*direct" % image))) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py b/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py index 5422a617c..24669f461 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py @@ -43,9 +43,7 @@ def get_target_controller(d): return controller(d) -class BaseTarget(object): - - __metaclass__ = ABCMeta +class BaseTarget(object, metaclass=ABCMeta): supported_image_fstypes = [] @@ -68,7 +66,7 @@ class BaseTarget(object): bb.note("SSH log file: %s" % self.sshlog) @abstractmethod - def start(self, params=None, ssh=True): + def start(self, params=None, ssh=True, extra_bootparams=None): pass @abstractmethod @@ -121,12 +119,17 @@ class QemuTarget(BaseTarget): self.image_fstype = self.get_image_fstype(d) self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime) - self.origrootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.' + self.image_fstype) - self.rootfs = os.path.join(self.testdir, d.getVar("IMAGE_LINK_NAME", True) + '-testimage.' + self.image_fstype) + self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.' + self.image_fstype) self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("KERNEL_IMAGETYPE", False) + '-' + d.getVar('MACHINE', False) + '.bin') dump_target_cmds = d.getVar("testimage_dump_target", True) dump_host_cmds = d.getVar("testimage_dump_host", True) dump_dir = d.getVar("TESTIMAGE_DUMP_DIR", True) + if d.getVar("QEMU_USE_KVM", False) is not None \ + and d.getVar("QEMU_USE_KVM", False) == "True" \ + and "x86" in d.getVar("MACHINE", True): + use_kvm = True + else: + use_kvm = False # Log QemuRunner log output to a file import oe.path @@ -155,17 +158,14 @@ class QemuTarget(BaseTarget): display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY", True), logfile = self.qemulog, boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT", True)), + use_kvm = use_kvm, dump_dir = dump_dir, dump_host_cmds = d.getVar("testimage_dump_host", True)) self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) def deploy(self): - try: - bb.utils.mkdirhier(self.testdir) - shutil.copyfile(self.origrootfs, self.rootfs) - except Exception as e: - bb.fatal("Error copying rootfs: %s" % e) + bb.utils.mkdirhier(self.testdir) qemuloglink = os.path.join(self.testdir, "qemu_boot_log") if os.path.islink(qemuloglink): @@ -176,8 +176,8 @@ class QemuTarget(BaseTarget): bb.note("Qemu log file: %s" % self.qemulog) super(QemuTarget, self).deploy() - def start(self, params=None, ssh=True): - if self.runner.start(params, get_ip=ssh): + def start(self, params=None, ssh=True, extra_bootparams=None): + if self.runner.start(params, get_ip=ssh, extra_bootparams=extra_bootparams): if ssh: self.ip = self.runner.ip self.server_ip = self.runner.server_ip @@ -232,7 +232,7 @@ class SimpleRemoteTarget(BaseTarget): def deploy(self): super(SimpleRemoteTarget, self).deploy() - def start(self, params=None, ssh=True): + def start(self, params=None, ssh=True, extra_bootparams=None): if ssh: self.connection = SSHControl(self.ip, logfile=self.sshlog, port=self.port) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py index 48f644129..5cd0f7477 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py @@ -41,7 +41,7 @@ class Command(object): self.data = data self.options = dict(self.defaultopts) - if isinstance(self.cmd, basestring): + if isinstance(self.cmd, str): self.options["shell"] = True if self.data: self.options['stdin'] = subprocess.PIPE @@ -78,7 +78,10 @@ class Command(object): self.process.kill() self.thread.join() - self.output = self.output.rstrip() + if not self.output: + self.output = "" + else: + self.output = self.output.decode("utf-8", errors='replace').rstrip() self.status = self.process.poll() self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status)) @@ -103,6 +106,7 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, **opti result.command = command result.status = cmd.status result.output = cmd.output + result.error = cmd.error result.pid = cmd.process.pid if result.status and not ignore_status: @@ -123,7 +127,7 @@ def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **optio else: extra_args = "" - if isinstance(command, basestring): + if isinstance(command, str): cmd = "bitbake " + extra_args + " " + command else: cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]] @@ -141,22 +145,45 @@ def get_bb_env(target=None, postconfig=None): else: return bitbake("-e", postconfig=postconfig).output -def get_bb_var(var, target=None, postconfig=None): - val = None +def get_bb_vars(variables=None, target=None, postconfig=None): + """Get values of multiple bitbake variables""" bbenv = get_bb_env(target, postconfig=postconfig) + + var_re = re.compile(r'^(export )?(?P<var>\w+)="(?P<value>.*)"$') + unset_re = re.compile(r'^unset (?P<var>\w+)$') lastline = None + values = {} for line in bbenv.splitlines(): - if re.search("^(export )?%s=" % var, line): - val = line.split('=', 1)[1] - val = val.strip('\"') - break - elif re.match("unset %s$" % var, line): - # Handle [unexport] variables - if lastline.startswith('# "'): - val = lastline.split('\"')[1] - break + match = var_re.match(line) + val = None + if match: + val = match.group('value') + else: + match = unset_re.match(line) + if match: + # Handle [unexport] variables + if lastline.startswith('# "'): + val = lastline.split('"')[1] + if val: + var = match.group('var') + if variables is None: + values[var] = val + else: + if var in variables: + values[var] = val + variables.remove(var) + # Stop after all required variables have been found + if not variables: + break lastline = line - return val + if variables: + # Fill in missing values + for var in variables: + values[var] = None + return values + +def get_bb_var(var, target=None, postconfig=None): + return get_bb_vars([var], target, postconfig)[var] def get_test_layer(): layers = get_bb_var("BBLAYERS").split() @@ -196,7 +223,7 @@ def runqemu(pn, ssh=True): tinfoil.config_data.setVar("TEST_QEMUBOOT_TIMEOUT", "1000") import oe.recipeutils recipefile = oe.recipeutils.pn_to_recipe(tinfoil.cooker, pn) - recipedata = oe.recipeutils.parse_recipe(recipefile, [], tinfoil.config_data) + recipedata = oe.recipeutils.parse_recipe(tinfoil.cooker, recipefile, []) # The QemuRunner log is saved out, but we need to ensure it is at the right # log level (and then ensure that since it's a child of the BitBake logger, @@ -237,3 +264,15 @@ def runqemu(pn, ssh=True): qemu.stop() except: pass + +def updateEnv(env_file): + """ + Source a file and update environment. + """ + + cmd = ". %s; env -0" % env_file + result = runCmd(cmd) + + for line in result.output.split("\0"): + (key, _, value) = line.partition("=") + os.environ[key] = value diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/decorators.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/decorators.py index 0d79223a2..25f9c54e6 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/decorators.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/decorators.py @@ -57,6 +57,7 @@ class skipIfFailure(object): self.testcase = testcase def __call__(self,f): + @wraps(f) def wrapped_f(*args, **kwargs): res = getResults() if self.testcase in (res.getFailList() or res.getErrorList()): @@ -71,6 +72,7 @@ class skipIfSkipped(object): self.testcase = testcase def __call__(self,f): + @wraps(f) def wrapped_f(*args, **kwargs): res = getResults() if self.testcase in res.getSkipList(): @@ -85,6 +87,7 @@ class skipUnlessPassed(object): self.testcase = testcase def __call__(self,f): + @wraps(f) def wrapped_f(*args, **kwargs): res = getResults() if self.testcase in res.getSkipList() or \ @@ -97,11 +100,11 @@ class skipUnlessPassed(object): return wrapped_f class testcase(object): - def __init__(self, test_case): self.test_case = test_case def __call__(self, func): + @wraps(func) def wrapped_f(*args, **kwargs): return func(*args, **kwargs) wrapped_f.test_case = self.test_case @@ -112,6 +115,8 @@ class NoParsingFilter(logging.Filter): def filter(self, record): return record.levelno == 100 +import inspect + def LogResults(original_class): orig_method = original_class.run @@ -121,6 +126,19 @@ def LogResults(original_class): logfile = os.path.join(os.getcwd(),'results-'+caller+'.'+timestamp+'.log') linkfile = os.path.join(os.getcwd(),'results-'+caller+'.log') + def get_class_that_defined_method(meth): + if inspect.ismethod(meth): + for cls in inspect.getmro(meth.__self__.__class__): + if cls.__dict__.get(meth.__name__) is meth: + return cls + meth = meth.__func__ # fallback to __qualname__ parsing + if inspect.isfunction(meth): + cls = getattr(inspect.getmodule(meth), + meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0]) + if isinstance(cls, type): + return cls + return None + #rewrite the run method of unittest.TestCase to add testcase logging def run(self, result, *args, **kws): orig_method(self, result, *args, **kws) @@ -132,7 +150,7 @@ def LogResults(original_class): except AttributeError: test_case = self._testMethodName - class_name = str(testMethod.im_class).split("'")[1] + class_name = str(get_class_that_defined_method(testMethod)).split("'")[1] #create custom logging level for filtering. custom_log_level = 100 @@ -171,10 +189,24 @@ def LogResults(original_class): if passed: local_log.results("Testcase "+str(test_case)+": PASSED") + # XXX: In order to avoid race condition when test if exists the linkfile + # use bb.utils.lock, the best solution is to create a unique name for the + # link file. + try: + import bb + has_bb = True + lockfilename = linkfile + '.lock' + except ImportError: + has_bb = False + + if has_bb: + lf = bb.utils.lockfile(lockfilename, block=True) # Create symlink to the current log - if os.path.exists(linkfile): + if os.path.lexists(linkfile): os.remove(linkfile) os.symlink(logfile, linkfile) + if has_bb: + bb.utils.unlockfile(lf) original_class.run = run @@ -212,7 +244,7 @@ def tag(*args, **kwargs): def wrap_ob(ob): for name in args: setattr(ob, __tag_prefix + name, True) - for name, value in kwargs.iteritems(): + for name, value in kwargs.items(): setattr(ob, __tag_prefix + name, value) return ob return wrap_ob diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/dump.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/dump.py index 63a591d36..71422a9ae 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/dump.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/dump.py @@ -3,7 +3,7 @@ import sys import errno import datetime import itertools -from commands import runCmd +from .commands import runCmd def get_host_dumper(d): cmds = d.getVar("testimage_dump_host", True) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py new file mode 100644 index 000000000..ae85d2766 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py @@ -0,0 +1,68 @@ +# +# Copyright (C) 2016 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) +# +"""Git repository interactions""" +import os + +from oeqa.utils.commands import runCmd + + +class GitError(Exception): + """Git error handling""" + pass + +class GitRepo(object): + """Class representing a Git repository clone""" + def __init__(self, path, is_topdir=False): + self.top_dir = self._run_git_cmd_at(['rev-parse', '--show-toplevel'], + path) + realpath = os.path.realpath(path) + if is_topdir and realpath != self.top_dir: + raise GitError("{} is not a Git top directory".format(realpath)) + + @staticmethod + def _run_git_cmd_at(git_args, cwd, **kwargs): + """Run git command at a specified directory""" + git_cmd = 'git ' if isinstance(git_args, str) else ['git'] + git_cmd += git_args + ret = runCmd(git_cmd, ignore_status=True, cwd=cwd, **kwargs) + if ret.status: + cmd_str = git_cmd if isinstance(git_cmd, str) \ + else ' '.join(git_cmd) + raise GitError("'{}' failed with exit code {}: {}".format( + cmd_str, ret.status, ret.output)) + return ret.output.strip() + + @staticmethod + def init(path): + """Initialize a new Git repository""" + GitRepo._run_git_cmd_at('init', cwd=path) + return GitRepo(path, is_topdir=True) + + def run_cmd(self, git_args, env_update=None): + """Run Git command""" + env = None + if env_update: + env = os.environ.copy() + env.update(env_update) + return self._run_git_cmd_at(git_args, self.top_dir, env=env) + + def rev_parse(self, revision): + """Do git rev-parse""" + try: + return self.run_cmd(['rev-parse', revision]) + except GitError: + # Revision does not exist + return None + + def get_current_branch(self): + """Get current branch""" + try: + # Strip 11 chars, i.e. 'refs/heads' from the beginning + return self.run_cmd(['symbolic-ref', 'HEAD'])[11:] + except GitError: + return None + + diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/httpserver.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/httpserver.py index 76518d8ef..7d1233145 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/httpserver.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/httpserver.py @@ -1,8 +1,9 @@ -import SimpleHTTPServer +import http.server import multiprocessing import os +from socketserver import ThreadingMixIn -class HTTPServer(SimpleHTTPServer.BaseHTTPServer.HTTPServer): +class HTTPServer(ThreadingMixIn, http.server.HTTPServer): def server_start(self, root_dir): import signal @@ -10,7 +11,7 @@ class HTTPServer(SimpleHTTPServer.BaseHTTPServer.HTTPServer): os.chdir(root_dir) self.serve_forever() -class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): +class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): def log_message(self, format_str, *args): pass diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py index 87b50354c..b377dcd27 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py @@ -3,7 +3,7 @@ import sys import os import re -import ftools +from . import ftools # A parser that can be used to identify weather a line is a test result or a section statement. diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/package_manager.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/package_manager.py new file mode 100644 index 000000000..099ecc972 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/package_manager.py @@ -0,0 +1,29 @@ +def get_package_manager(d, root_path): + """ + Returns an OE package manager that can install packages in root_path. + """ + from oe.package_manager import RpmPM, OpkgPM, DpkgPM + + pkg_class = d.getVar("IMAGE_PKGTYPE", True) + if pkg_class == "rpm": + pm = RpmPM(d, + root_path, + d.getVar('TARGET_VENDOR', True)) + pm.create_configs() + + elif pkg_class == "ipk": + pm = OpkgPM(d, + root_path, + d.getVar("IPKGCONF_TARGET", True), + d.getVar("ALL_MULTILIB_PACKAGE_ARCHS", True)) + + elif pkg_class == "deb": + pm = DpkgPM(d, + root_path, + d.getVar('PACKAGE_ARCHS', True), + d.getVar('DPKG_ARCH', True)) + + pm.write_index() + pm.update() + + return pm diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py index 784cf964f..8f1b5b980 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py @@ -20,16 +20,17 @@ from oeqa.utils.dump import HostDumper import logging logger = logging.getLogger("BitBake.QemuRunner") +logger.addHandler(logging.StreamHandler()) # Get Unicode non printable control chars -control_range = range(0,32)+range(127,160) -control_chars = [unichr(x) for x in control_range - if unichr(x) not in string.printable] +control_range = list(range(0,32))+list(range(127,160)) +control_chars = [chr(x) for x in control_range + if chr(x) not in string.printable] 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): + def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, use_kvm): # Popen object for runqemu self.runqemu = None @@ -49,6 +50,7 @@ class QemuRunner: self.boottime = boottime self.logged = False self.thread = None + self.use_kvm = use_kvm self.runqemutime = 60 self.host_dumper = HostDumper(dump_host_cmds, dump_dir) @@ -71,7 +73,8 @@ class QemuRunner: if self.logfile: # It is needed to sanitize the data received from qemu # because is possible to have control characters - msg = re_control_char.sub('', unicode(msg, 'utf-8')) + msg = msg.decode("utf-8") + msg = re_control_char.sub('', msg) with codecs.open(self.logfile, "a", encoding="utf-8") as f: f.write("%s" % msg) @@ -79,7 +82,7 @@ class QemuRunner: import fcntl fl = fcntl.fcntl(o, fcntl.F_GETFL) fcntl.fcntl(o, fcntl.F_SETFL, fl | os.O_NONBLOCK) - return os.read(o.fileno(), 1000000) + return os.read(o.fileno(), 1000000).decode("utf-8") def handleSIGCHLD(self, signum, frame): @@ -91,7 +94,7 @@ class QemuRunner: self._dump_host() raise SystemExit - def start(self, qemuparams = None, get_ip = True): + def start(self, qemuparams = None, get_ip = True, extra_bootparams = None): if self.display: os.environ["DISPLAY"] = self.display # Set this flag so that Qemu doesn't do any grabs as SDL grabs @@ -114,12 +117,16 @@ class QemuRunner: try: threadsock, threadport = self.create_socket() self.server_socket, self.serverport = self.create_socket() - except socket.error, msg: + except socket.error as msg: logger.error("Failed to create listening socket: %s" % msg[1]) return False - self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8 printk.time=1" qemuparams="-serial tcp:127.0.0.1:{}"'.format(threadport) + bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1' + if extra_bootparams: + bootparams = bootparams + ' ' + extra_bootparams + + self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1}"'.format(bootparams, threadport) if not self.display: self.qemuparams = 'nographic ' + self.qemuparams if qemuparams: @@ -128,7 +135,15 @@ class QemuRunner: self.origchldhandler = signal.getsignal(signal.SIGCHLD) signal.signal(signal.SIGCHLD, self.handleSIGCHLD) - launch_cmd = 'runqemu tcpserial=%s %s %s %s' % (self.serverport, self.machine, self.rootfs, self.qemuparams) + launch_cmd = 'runqemu snapshot ' + if self.use_kvm: + logger.info('Using kvm for runqemu') + launch_cmd += 'kvm ' + else: + logger.info('Not using kvm for runqemu') + launch_cmd += 'tcpserial=%s %s %s %s' % (self.serverport, self.machine, self.rootfs, self.qemuparams) + logger.info('launchcmd=%s'%(launch_cmd)) + # FIXME: We pass in stdin=subprocess.PIPE here to work around stty # blocking at the end of the runqemu script when using this within # oe-selftest (this makes stty error out immediately). There ought @@ -137,12 +152,12 @@ class QemuRunner: output = self.runqemu.stdout # - # We need the preexec_fn above so that all runqemu processes can easily be killed + # We need the preexec_fn above so that all runqemu processes can easily be killed # (by killing their process group). This presents a problem if this controlling - # process itself is killed however since those processes don't notice the death + # process itself is killed however since those processes don't notice the death # of the parent and merrily continue on. # - # Rather than hack runqemu to deal with this, we add something here instead. + # Rather than hack runqemu to deal with this, we add something here instead. # Basically we fork off another process which holds an open pipe to the parent # and also is setpgrp. If/when the pipe sees EOF from the parent dieing, it kills # the process group. This is like pctrl's PDEATHSIG but for a process group @@ -192,7 +207,7 @@ class QemuRunner: else: self.ip = ips[0] self.server_ip = ips[1] - except IndexError, ValueError: + except (IndexError, ValueError): logger.info("Couldn't get ip from qemu process arguments! Here is the qemu command line used:\n%s\nand output from runqemu:\n%s" % (cmdline, self.getOutput(output))) self._dump_host() self.stop() @@ -219,6 +234,7 @@ class QemuRunner: stopread = False qemusock = None bootlog = '' + data = b'' while time.time() < endtime and not stopread: sread, swrite, serror = select.select(socklist, [], [], 5) for sock in sread: @@ -229,14 +245,19 @@ class QemuRunner: socklist.remove(self.server_socket) logger.info("Connection from %s:%s" % addr) else: - data = sock.recv(1024) + data = data + sock.recv(1024) if data: - bootlog += data - if re.search(".* login:", bootlog): - self.server_socket = qemusock - stopread = True - reachedlogin = True - logger.info("Reached login banner") + try: + data = data.decode("utf-8", errors="surrogateescape") + bootlog += data + data = b'' + if re.search(".* login:", bootlog): + self.server_socket = qemusock + stopread = True + reachedlogin = True + logger.info("Reached login banner") + except UnicodeDecodeError: + continue else: socklist.remove(sock) sock.close() @@ -277,13 +298,14 @@ class QemuRunner: if hasattr(self, "origchldhandler"): signal.signal(signal.SIGCHLD, self.origchldhandler) if self.runqemu: - os.kill(self.monitorpid, signal.SIGKILL) - logger.info("Sending SIGTERM to runqemu") - try: - os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) - except OSError as e: - if e.errno != errno.ESRCH: - raise + if hasattr(self, "monitorpid"): + os.kill(self.monitorpid, signal.SIGKILL) + logger.info("Sending SIGTERM to runqemu") + try: + os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) + except OSError as e: + if e.errno != errno.ESRCH: + raise endtime = time.time() + self.runqemutime while self.runqemu.poll() is None and time.time() < endtime: time.sleep(1) @@ -325,7 +347,7 @@ class QemuRunner: # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd] # ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'], stdout=subprocess.PIPE).communicate()[0] - processes = ps.split('\n') + processes = ps.decode("utf-8").split('\n') nfields = len(processes[0].split()) - 1 pids = {} commands = {} @@ -354,9 +376,9 @@ class QemuRunner: if p not in parents: parents.append(p) newparents = next - #print "Children matching %s:" % str(parents) + #print("Children matching %s:" % str(parents)) for p in parents: - # Need to be careful here since runqemu-internal runs "ldd qemu-system-xxxx" + # Need to be careful here since runqemu runs "ldd qemu-system-xxxx" # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" basecmd = commands[p].split()[0] basecmd = os.path.basename(basecmd) @@ -370,14 +392,14 @@ class QemuRunner: data = '' status = 0 - self.server_socket.sendall(command) + self.server_socket.sendall(command.encode('utf-8')) keepreading = True while keepreading: sread, _, _ = select.select([self.server_socket],[],[],5) if sread: answer = self.server_socket.recv(1024) if answer: - data += answer + data += answer.decode('utf-8') # Search the prompt to stop if re.search("[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data): keepreading = False @@ -442,7 +464,7 @@ class LoggingThread(threading.Thread): def stop(self): self.logger.info("Stopping logging thread") if self.running: - os.write(self.writepipe, "stop") + os.write(self.writepipe, bytes("stop", "utf-8")) def teardown(self): self.logger.info("Tearing down logging thread") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py index 4f95101f3..d554f0dbc 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py @@ -13,7 +13,7 @@ import re import socket import select import bb -from qemurunner import QemuRunner +from .qemurunner import QemuRunner class QemuTinyRunner(QemuRunner): @@ -50,7 +50,7 @@ class QemuTinyRunner(QemuRunner): self.server_socket.connect(self.socketfile) bb.note("Created listening socket for qemu serial console.") tries = 0 - except socket.error, msg: + except socket.error as msg: self.server_socket.close() bb.fatal("Failed to create listening socket.") tries -= 1 @@ -60,7 +60,7 @@ class QemuTinyRunner(QemuRunner): with open(self.logfile, "a") as f: f.write("%s" % msg) - def start(self, qemuparams = None): + def start(self, qemuparams = None, ssh=True, extra_bootparams=None): if self.display: os.environ["DISPLAY"] = self.display @@ -102,7 +102,7 @@ class QemuTinyRunner(QemuRunner): bb.note("Qemu pid didn't appeared in %s seconds" % self.runqemutime) output = self.runqemu.stdout self.stop() - bb.note("Output from runqemu:\n%s" % output.read()) + bb.note("Output from runqemu:\n%s" % output.read().decode("utf-8")) return False return self.is_alive() @@ -131,7 +131,7 @@ class QemuTinyRunner(QemuRunner): # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd] # ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'], stdout=subprocess.PIPE).communicate()[0] - processes = ps.split('\n') + processes = ps.decode("utf-8").split('\n') nfields = len(processes[0].split()) - 1 pids = {} commands = {} @@ -160,11 +160,11 @@ class QemuTinyRunner(QemuRunner): if p not in parents: parents.append(p) newparents = next - #print "Children matching %s:" % str(parents) + #print("Children matching %s:" % str(parents)) for p in parents: - # Need to be careful here since runqemu-internal runs "ldd qemu-system-xxxx" + # Need to be careful here since runqemu runs "ldd qemu-system-xxxx" # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" basecmd = commands[p].split()[0] basecmd = os.path.basename(basecmd) if "qemu-system" in basecmd and "-serial unix" in commands[p]: - return [int(p),commands[p]]
\ No newline at end of file + return [int(p),commands[p]] diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py index 165874416..05d650255 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Copyright (C) 2013 Intel Corporation # # Released under the MIT license (see COPYING.MIT) @@ -51,16 +52,19 @@ class SSHProcess(object): endtime = self.starttime + timeout eof = False while time.time() < endtime and not eof: - if select.select([self.process.stdout], [], [], 5)[0] != []: - data = os.read(self.process.stdout.fileno(), 1024) - if not data: - self.process.stdout.close() - eof = True - else: - output += data - self.log(data) - endtime = time.time() + timeout - + try: + if select.select([self.process.stdout], [], [], 5)[0] != []: + data = os.read(self.process.stdout.fileno(), 1024) + if not data: + self.process.stdout.close() + eof = True + else: + data = data.decode("utf-8") + output += data + self.log(data) + endtime = time.time() + timeout + except InterruptedError: + continue # process hasn't returned yet if not eof: @@ -145,9 +149,97 @@ class SSHControl(object): return self._internal_run(command, timeout, self.ignore_status) def copy_to(self, localpath, remotepath): - command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] - return self._internal_run(command, ignore_status=False) + if os.path.islink(localpath): + link = os.readlink(localpath) + dst_dir, dst_base = os.path.split(remotepath) + return self.run("cd %s; ln -s %s %s" % (dst_dir, link, dst_base)) + else: + command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] + return self._internal_run(command, ignore_status=False) def copy_from(self, remotepath, localpath): command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] return self._internal_run(command, ignore_status=False) + + def copy_dir_to(self, localpath, remotepath): + """ + Copy recursively localpath directory to remotepath in target. + """ + + for root, dirs, files in os.walk(localpath): + # Create directories in the target as needed + for d in dirs: + tmp_dir = os.path.join(root, d).replace(localpath, "") + new_dir = os.path.join(remotepath, tmp_dir.lstrip("/")) + cmd = "mkdir -p %s" % new_dir + self.run(cmd) + + # Copy files into the target + for f in files: + tmp_file = os.path.join(root, f).replace(localpath, "") + dst_file = os.path.join(remotepath, tmp_file.lstrip("/")) + src_file = os.path.join(root, f) + self.copy_to(src_file, dst_file) + + + def delete_files(self, remotepath, files): + """ + Delete files in target's remote path. + """ + + cmd = "rm" + if not isinstance(files, list): + files = [files] + + for f in files: + cmd = "%s %s" % (cmd, os.path.join(remotepath, f)) + + self.run(cmd) + + + def delete_dir(self, remotepath): + """ + Delete remotepath directory in target. + """ + + cmd = "rmdir %s" % remotepath + self.run(cmd) + + + def delete_dir_structure(self, localpath, remotepath): + """ + Delete recursively localpath structure directory in target's remotepath. + + This function is very usefult to delete a package that is installed in + the DUT and the host running the test has such package extracted in tmp + directory. + + Example: + pwd: /home/user/tmp + tree: . + └── work + ├── dir1 + │ └── file1 + └── dir2 + + localpath = "/home/user/tmp" and remotepath = "/home/user" + + With the above variables this function will try to delete the + directory in the DUT in this order: + /home/user/work/dir1/file1 + /home/user/work/dir1 (if dir is empty) + /home/user/work/dir2 (if dir is empty) + /home/user/work (if dir is empty) + """ + + for root, dirs, files in os.walk(localpath, topdown=False): + # Delete files first + tmpdir = os.path.join(root).replace(localpath, "") + remotedir = os.path.join(remotepath, tmpdir.lstrip("/")) + self.delete_files(remotedir, files) + + # Remove dirs if empty + for d in dirs: + tmpdir = os.path.join(root, d).replace(localpath, "") + remotedir = os.path.join(remotepath, tmpdir.lstrip("/")) + self.delete_dir(remotepath) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py index f850d78df..59593f5ef 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py @@ -10,18 +10,17 @@ import bb.utils import subprocess from abc import ABCMeta, abstractmethod -class BuildProject(): - - __metaclass__ = ABCMeta +class BuildProject(metaclass=ABCMeta): def __init__(self, d, uri, foldername=None, tmpdir="/tmp/"): self.d = d self.uri = uri self.archive = os.path.basename(uri) self.localarchive = os.path.join(tmpdir,self.archive) - self.fname = re.sub(r'.tar.bz2|tar.gz$', '', self.archive) if foldername: self.fname = foldername + else: + self.fname = re.sub(r'\.tar\.bz2$|\.tar\.gz$|\.tar\.xz$', '', self.archive) # Download self.archive to self.localarchive def _download_archive(self): @@ -118,10 +117,10 @@ class SDKBuildProject(BuildProject): subprocess.check_call(cmd, shell=True) #Change targetdir to project folder - self.targetdir = self.targetdir + self.fname + self.targetdir = os.path.join(self.targetdir, self.fname) - def run_configure(self, configure_args=''): - return super(SDKBuildProject, self).run_configure(configure_args=(configure_args or '$CONFIGURE_FLAGS'), extra_cmds=' gnu-configize; ') + def run_configure(self, configure_args='', extra_cmds=' gnu-configize; '): + return super(SDKBuildProject, self).run_configure(configure_args=(configure_args or '$CONFIGURE_FLAGS'), extra_cmds=extra_cmds) def run_install(self, install_args=''): return super(SDKBuildProject, self).run_install(install_args=(install_args or "DESTDIR=%s/../install" % self.targetdir)) @@ -134,4 +133,3 @@ class SDKBuildProject(BuildProject): def _run(self, cmd): self.log("Running . %s; " % self.sdkenv + cmd) return subprocess.call(". %s; " % self.sdkenv + cmd, shell=True) - diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/testexport.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/testexport.py index 243463bc1..57be2ca44 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/testexport.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/testexport.py @@ -6,7 +6,7 @@ import os, re, glob as g, shutil as sh,sys from time import sleep -from commands import runCmd +from .commands import runCmd from difflib import SequenceMatcher as SM try: @@ -17,13 +17,13 @@ except ImportError: pass def plain(self, msg): if msg: - print msg + print(msg) def warn(self, msg): if msg: - print "WARNING: " + msg + print("WARNING: " + msg) def fatal(self, msg): if msg: - print "FATAL:" + msg + print("FATAL:" + msg) sys.exit(1) bb = my_log() |