diff options
Diffstat (limited to 'meta/lib/oeqa/selftest/cases/reproducible.py')
-rw-r--r-- | meta/lib/oeqa/selftest/cases/reproducible.py | 112 |
1 files changed, 92 insertions, 20 deletions
diff --git a/meta/lib/oeqa/selftest/cases/reproducible.py b/meta/lib/oeqa/selftest/cases/reproducible.py index a7ef336143..80e830136f 100644 --- a/meta/lib/oeqa/selftest/cases/reproducible.py +++ b/meta/lib/oeqa/selftest/cases/reproducible.py @@ -9,14 +9,22 @@ import bb.utils import functools import multiprocessing import textwrap -import json -import unittest import tempfile import shutil import stat import os import datetime +exclude_packages = [ + ] + +def is_excluded(package): + package_name = os.path.basename(package) + for i in exclude_packages: + if package_name.startswith(i): + return i + return None + MISSING = 'MISSING' DIFFERENT = 'DIFFERENT' SAME = 'SAME' @@ -35,18 +43,26 @@ class CompareResult(object): return (self.status, self.test) < (other.status, other.test) class PackageCompareResults(object): - def __init__(self): + def __init__(self, exclusions): self.total = [] self.missing = [] self.different = [] + self.different_excluded = [] self.same = [] + self.active_exclusions = set() + exclude_packages.extend((exclusions or "").split()) def add_result(self, r): self.total.append(r) if r.status == MISSING: self.missing.append(r) elif r.status == DIFFERENT: - self.different.append(r) + exclusion = is_excluded(r.reference) + if exclusion: + self.different_excluded.append(r) + self.active_exclusions.add(exclusion) + else: + self.different.append(r) else: self.same.append(r) @@ -54,10 +70,14 @@ class PackageCompareResults(object): self.total.sort() self.missing.sort() self.different.sort() + self.different_excluded.sort() self.same.sort() def __str__(self): - return 'same=%i different=%i missing=%i total=%i' % (len(self.same), len(self.different), len(self.missing), len(self.total)) + return 'same=%i different=%i different_excluded=%i missing=%i total=%i\nunused_exclusions=%s' % (len(self.same), len(self.different), len(self.different_excluded), len(self.missing), len(self.total), self.unused_exclusions()) + + def unused_exclusions(self): + return sorted(set(exclude_packages) - self.active_exclusions) def compare_file(reference, test, diffutils_sysroot): result = CompareResult() @@ -68,7 +88,7 @@ def compare_file(reference, test, diffutils_sysroot): result.status = MISSING return result - r = runCmd(['cmp', '--quiet', reference, test], native_sysroot=diffutils_sysroot, ignore_status=True) + r = runCmd(['cmp', '--quiet', reference, test], native_sysroot=diffutils_sysroot, ignore_status=True, sync=False) if r.status: result.status = DIFFERENT @@ -77,8 +97,9 @@ def compare_file(reference, test, diffutils_sysroot): result.status = SAME return result -def run_diffoscope(a_dir, b_dir, html_dir, **kwargs): - return runCmd(['diffoscope', '--no-default-limits', '--exclude-directory-metadata', 'yes', '--html-dir', html_dir, a_dir, b_dir], +def run_diffoscope(a_dir, b_dir, html_dir, max_report_size=0, **kwargs): + return runCmd(['diffoscope', '--no-default-limits', '--max-report-size', str(max_report_size), + '--exclude-directory-metadata', 'yes', '--html-dir', html_dir, a_dir, b_dir], **kwargs) class DiffoscopeTests(OESelftestTestCase): @@ -104,8 +125,19 @@ class DiffoscopeTests(OESelftestTestCase): self.assertTrue(os.path.exists(os.path.join(tmpdir, 'index.html')), "HTML index not found!") class ReproducibleTests(OESelftestTestCase): - package_classes = ['deb', 'ipk'] - images = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline'] + # Test the reproducibility of whatever is built between sstate_targets and targets + + package_classes = ['deb', 'ipk', 'rpm'] + + # Maximum report size, in bytes + max_report_size = 250 * 1024 * 1024 + + # targets are the things we want to test the reproducibility of + targets = ['core-image-minimal', 'core-image-sato', 'core-image-full-cmdline', 'core-image-weston', 'world'] + + # sstate targets are things to pull from sstate to potentially cut build/debugging time + sstate_targets = [] + save_results = False if 'OEQA_DEBUGGING_SAVED_OUTPUT' in os.environ: save_results = os.environ['OEQA_DEBUGGING_SAVED_OUTPUT'] @@ -120,11 +152,29 @@ class ReproducibleTests(OESelftestTestCase): def setUpLocal(self): super().setUpLocal() - needed_vars = ['TOPDIR', 'TARGET_PREFIX', 'BB_NUMBER_THREADS'] + needed_vars = [ + 'TOPDIR', + 'TARGET_PREFIX', + 'BB_NUMBER_THREADS', + 'BB_HASHSERVE', + 'OEQA_REPRODUCIBLE_TEST_PACKAGE', + 'OEQA_REPRODUCIBLE_TEST_TARGET', + 'OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS', + 'OEQA_REPRODUCIBLE_EXCLUDED_PACKAGES', + ] bb_vars = get_bb_vars(needed_vars) for v in needed_vars: setattr(self, v.lower(), bb_vars[v]) + if bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE']: + self.package_classes = bb_vars['OEQA_REPRODUCIBLE_TEST_PACKAGE'].split() + + if bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET']: + self.targets = bb_vars['OEQA_REPRODUCIBLE_TEST_TARGET'].split() + + if bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS']: + self.sstate_targets = bb_vars['OEQA_REPRODUCIBLE_TEST_SSTATE_TARGETS'].split() + self.extraresults = {} self.extraresults.setdefault('reproducible.rawlogs', {})['log'] = '' self.extraresults.setdefault('reproducible', {}).setdefault('files', {}) @@ -133,7 +183,7 @@ class ReproducibleTests(OESelftestTestCase): self.extraresults['reproducible.rawlogs']['log'] += msg def compare_packages(self, reference_dir, test_dir, diffutils_sysroot): - result = PackageCompareResults() + result = PackageCompareResults(self.oeqa_reproducible_excluded_packages) old_cwd = os.getcwd() try: @@ -172,24 +222,35 @@ class ReproducibleTests(OESelftestTestCase): bb.utils.remove(tmpdir, recurse=True) config = textwrap.dedent('''\ - INHERIT += "reproducible_build" PACKAGE_CLASSES = "{package_classes}" - INHIBIT_PACKAGE_STRIP = "1" TMPDIR = "{tmpdir}" + LICENSE_FLAGS_ACCEPTED = "commercial" + DISTRO_FEATURES:append = ' pam' + USERADDEXTENSION = "useradd-staticids" + USERADD_ERROR_DYNAMIC = "skip" + USERADD_UID_TABLES += "files/static-passwd" + USERADD_GID_TABLES += "files/static-group" ''').format(package_classes=' '.join('package_%s' % c for c in self.package_classes), tmpdir=tmpdir) if not use_sstate: + if self.sstate_targets: + self.logger.info("Building prebuild for %s (sstate allowed)..." % (name)) + self.write_config(config) + bitbake(' '.join(self.sstate_targets)) + # This config fragment will disable using shared and the sstate # mirror, forcing a complete build from scratch config += textwrap.dedent('''\ SSTATE_DIR = "${TMPDIR}/sstate" - SSTATE_MIRROR = "" + SSTATE_MIRRORS = "file://.*/.*-native.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH file://.*/.*-cross.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH" ''') + self.logger.info("Building %s (sstate%s allowed)..." % (name, '' if use_sstate else ' NOT')) self.write_config(config) d = get_bb_vars(capture_vars) - bitbake(' '.join(self.images)) + # targets used to be called images + bitbake(' '.join(getattr(self, 'images', self.targets))) return d def test_reproducible_builds(self): @@ -213,6 +274,7 @@ class ReproducibleTests(OESelftestTestCase): self.logger.info('Non-reproducible packages will be copied to %s', save_dir) vars_A = self.do_test_build('reproducibleA', self.build_from_sstate) + vars_B = self.do_test_build('reproducibleB', False) # NOTE: The temp directories from the reproducible build are purposely @@ -227,6 +289,7 @@ class ReproducibleTests(OESelftestTestCase): deploy_A = vars_A['DEPLOY_DIR_' + c.upper()] deploy_B = vars_B['DEPLOY_DIR_' + c.upper()] + self.logger.info('Checking %s packages for differences...' % c) result = self.compare_packages(deploy_A, deploy_B, diffutils_sysroot) self.logger.info('Reproducibility summary for %s: %s' % (c, result)) @@ -235,6 +298,7 @@ class ReproducibleTests(OESelftestTestCase): self.write_package_list(package_class, 'missing', result.missing) self.write_package_list(package_class, 'different', result.different) + self.write_package_list(package_class, 'different_excluded', result.different_excluded) self.write_package_list(package_class, 'same', result.same) if self.save_results: @@ -242,9 +306,17 @@ class ReproducibleTests(OESelftestTestCase): self.copy_file(d.reference, '/'.join([save_dir, 'packages', strip_topdir(d.reference)])) self.copy_file(d.test, '/'.join([save_dir, 'packages', strip_topdir(d.test)])) - if result.missing or result.different: - fails.append("The following %s packages are missing or different: %s" % - (c, '\n'.join(r.test for r in (result.missing + result.different)))) + for d in result.different_excluded: + self.copy_file(d.reference, '/'.join([save_dir, 'packages-excluded', strip_topdir(d.reference)])) + self.copy_file(d.test, '/'.join([save_dir, 'packages-excluded', strip_topdir(d.test)])) + + if result.different: + fails.append("The following %s packages are different and not in exclusion list:\n%s" % + (c, '\n'.join(r.test for r in (result.different)))) + + if result.missing and len(self.sstate_targets) == 0: + fails.append("The following %s packages are missing and not in exclusion list:\n%s" % + (c, '\n'.join(r.test for r in (result.missing)))) # Clean up empty directories if self.save_results: @@ -258,7 +330,7 @@ class ReproducibleTests(OESelftestTestCase): # Copy jquery to improve the diffoscope output usability self.copy_file(os.path.join(jquery_sysroot, 'usr/share/javascript/jquery/jquery.min.js'), os.path.join(package_html_dir, 'jquery.js')) - run_diffoscope('reproducibleA', 'reproducibleB', package_html_dir, + run_diffoscope('reproducibleA', 'reproducibleB', package_html_dir, max_report_size=self.max_report_size, native_sysroot=diffoscope_sysroot, ignore_status=True, cwd=package_dir) if fails: |