From 6269ca4813880231f3617b09819f82f9d769280f Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Wed, 27 Jan 2016 09:34:22 +0100 Subject: [PATCH] Drop boottest support This was a giant copy&paste, was disabled four months ago, and the infrastructure for this ceased to exist. If this comes back, the AutoPackageTest class should be generalized to also issue phone boot tests (exposed as new architectures, which should then be called "platforms"), to avoid all this duplicated code. --- boottest.py | 293 ------------------------- britney.conf | 5 - britney.py | 100 --------- excuse.py | 1 - tests/test_autopkgtest.py | 6 +- tests/test_boottest.py | 445 -------------------------------------- 6 files changed, 2 insertions(+), 848 deletions(-) delete mode 100644 boottest.py delete mode 100755 tests/test_boottest.py diff --git a/boottest.py b/boottest.py deleted file mode 100644 index d402655..0000000 --- a/boottest.py +++ /dev/null @@ -1,293 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2015 Canonical Ltd. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that 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. - - -from collections import defaultdict -from contextlib import closing -import os -import subprocess -import tempfile -from textwrap import dedent -import time -import urllib.request - -import apt_pkg - -from consts import BINARIES - - -FETCH_RETRIES = 3 - - -class TouchManifest(object): - """Parses a corresponding touch image manifest. - - Based on http://cdimage.u.c/ubuntu-touch/daily-preinstalled/pending/vivid-preinstalled-touch-armhf.manifest - - Assumes the deployment is arranged in a way the manifest is available - and fresh on: - - '{britney_cwd}/boottest/images/{distribution}/{series}/manifest' - - Only binary name matters, version is ignored, so callsites can: - - >>> manifest = TouchManifest('ubuntu-touch', 'vivid') - >>> 'webbrowser-app' in manifest - True - >>> 'firefox' in manifest - False - - """ - - def __init__(self, project, series, verbose=False, fetch=True): - self.verbose = verbose - self.path = "boottest/images/{}/{}/manifest".format( - project, series) - success = False - if fetch: - retries = FETCH_RETRIES - success = self.__fetch_manifest(project, series) - - while retries > 0 and not success: - success = self.__fetch_manifest(project, series) - retries -= 1 - if not success: - print("E: [%s] - Unable to fetch manifest: %s %s" % ( - time.asctime(), project, series)) - - self._manifest = self._load() - - def __fetch_manifest(self, project, series): - # There are two url formats that may lead to the proper manifest - # file. The first form is for series that have been released, - # the second form is for the current development series. - # Only one of these is expected to exist for any given series. - url_list = [ - "http://cdimage.ubuntu.com/{}/{}/daily-preinstalled/pending/" \ - "{}-preinstalled-touch-armhf.manifest".format( - project, series, series), - "http://cdimage.ubuntu.com/{}/daily-preinstalled/pending/" \ - "{}-preinstalled-touch-armhf.manifest".format( - project, series), - ] - - success = False - for url in url_list: - if self.verbose: - print("I: [%s] - Fetching manifest from %s" % - (time.asctime(), url)) - print("I: [%s] - saving it to %s" % - (time.asctime(), self.path)) - try: - response = urllib.request.urlopen(url) - if response.code == 200: - # Only [re]create the manifest file if one was successfully - # downloaded. This allows for an existing image to be used - # if the download fails. - path_dir = os.path.dirname(self.path) - if not os.path.exists(path_dir): - os.makedirs(path_dir) - with open(self.path, 'wb') as fp: - fp.write(response.read()) - success = True - break - - except IOError as e: - print("W: [%s] - error connecting to %s: %s" % ( - time.asctime(), self.path, e)) - - return success - - def _load(self): - pkg_list = [] - - if not os.path.exists(self.path): - return pkg_list - - with open(self.path) as fd: - for line in fd.readlines(): - # skip headers and metadata - if 'DOCTYPE' in line: - continue - name, version = line.split() - name = name.split(':')[0] - if name == 'click': - continue - pkg_list.append(name) - - return sorted(pkg_list) - - def __contains__(self, key): - return key in self._manifest - - -class BootTest(object): - """Boottest criteria for Britney. - - This class provides an API for handling the boottest-jenkins - integration layer (mostly derived from auto-package-testing/adt): - """ - VALID_STATUSES = ('PASS',) - - EXCUSE_LABELS = { - "PASS": 'Pass', - "FAIL": 'Regression', - "RUNNING": 'Test in progress', - } - - script_path = os.path.expanduser( - "~/auto-package-testing/jenkins/boottest-britney") - - def __init__(self, britney, distribution, series, debug=False): - self.britney = britney - self.distribution = distribution - self.series = series - self.debug = debug - self.rc_path = None - self._read() - manifest_fetch = getattr( - self.britney.options, "boottest_fetch", "no") == "yes" - self.phone_manifest = TouchManifest( - 'ubuntu-touch', self.series, fetch=manifest_fetch, - verbose=self.britney.options.verbose) - - @property - def _request_path(self): - return "boottest/work/adt.request.%s" % self.series - - @property - def _result_path(self): - return "boottest/work/adt.result.%s" % self.series - - def _ensure_rc_file(self): - if self.rc_path: - return - self.rc_path = os.path.abspath("boottest/rc.%s" % self.series) - with open(self.rc_path, "w") as rc_file: - home = os.path.expanduser("~") - print(dedent("""\ - release: %s - aptroot: ~/.chdist/%s-proposed-armhf/ - apturi: file:%s/mirror/%s - components: main restricted universe multiverse - rsync_host: rsync://tachash.ubuntu-ci/boottest/ - datadir: ~/proposed-migration/boottest/data""" % - (self.series, self.series, home, self.distribution)), - file=rc_file) - - def _run(self, *args): - self._ensure_rc_file() - if not os.path.exists(self.script_path): - print("E: [%s] - Boottest/Jenking glue script missing: %s" % ( - time.asctime(), self.script_path)) - return '-' - command = [ - self.script_path, - "-c", self.rc_path, - "-r", self.series, - "-PU", - ] - if self.debug: - command.append("-d") - command.extend(args) - return subprocess.check_output(command).strip() - - def _read(self): - """Loads a list of results (sources tests and their status). - - Provides internal data for `get_status()`. - """ - self.pkglist = defaultdict(dict) - if not os.path.exists(self._result_path): - return - with open(self._result_path) as f: - for line in f: - line = line.strip() - if line.startswith("Suite:") or line.startswith("Date:"): - continue - linebits = line.split() - if len(linebits) < 2: - print("W: Invalid line format: '%s', skipped" % line) - continue - (src, ver, status) = linebits[:3] - if not (src in self.pkglist and ver in self.pkglist[src]): - self.pkglist[src][ver] = status - - def get_status(self, name, version): - """Return test status for the given source name and version.""" - try: - return self.pkglist[name][version] - except KeyError: - # This error handling accounts for outdated apt caches, when - # `boottest-britney` erroneously reports results for the - # current source version, instead of the proposed. - # Returning None here will block source promotion with: - # 'UNKNOWN STATUS' excuse. If the jobs are retried and its - # results find an up-to-date cache, the problem is gone. - print("E: [%s] - Missing boottest results for %s_%s" % ( - time.asctime(), name, version)) - return None - - def request(self, packages): - """Requests boottests for the given sources list ([(src, ver),]).""" - request_path = self._request_path - if os.path.exists(request_path): - os.unlink(request_path) - with closing(tempfile.NamedTemporaryFile(mode="w")) as request_file: - for src, ver in packages: - if src in self.pkglist and ver in self.pkglist[src]: - continue - print("%s %s" % (src, ver), file=request_file) - # Update 'pkglist' so even if submit/collect is not called - # (dry-run), britney has some results. - self.pkglist[src][ver] = 'RUNNING' - request_file.flush() - self._run("request", "-O", request_path, request_file.name) - - def submit(self): - """Submits the current boottests requests for processing.""" - self._run("submit", self._request_path) - - def collect(self): - """Collects boottests results and updates internal registry.""" - self._run("collect", "-O", self._result_path) - self._read() - if not self.britney.options.verbose: - return - for src in sorted(self.pkglist): - for ver in sorted(self.pkglist[src]): - status = self.pkglist[src][ver] - print("I: [%s] - Collected boottest status for %s_%s: " - "%s" % (time.asctime(), src, ver, status)) - - def needs_test(self, name, version): - """Whether or not the given source and version should be tested. - - Sources are only considered for boottesting if they produce binaries - that are part of the phone image manifest. See `TouchManifest`. - """ - # Discover all binaries for the 'excused' source. - unstable_sources = self.britney.sources['unstable'] - # Dismiss if source is not yet recognized (??). - if name not in unstable_sources: - return False - # Binaries are a seq of "/" and, practically, boottest - # is only concerned about armhf binaries mentioned in the phone - # manifest. Anything else should be skipped. - phone_binaries = [ - b for b in unstable_sources[name][BINARIES] - if b.split('/')[1] in self.britney.options.boottest_arches.split() - and b.split('/')[0] in self.phone_manifest - ] - return bool(phone_binaries) diff --git a/britney.conf b/britney.conf index 99991ec..bf278c2 100644 --- a/britney.conf +++ b/britney.conf @@ -72,8 +72,3 @@ ADT_SWIFT_URL = https://objectstorage.prodstack4-5.canonical.com/v1/AUTH_77e # space separate list of PPAs to add for test requests and for polling results; # the *last* one determines the swift container name ADT_PPAS = - -BOOTTEST_ENABLE = no -BOOTTEST_DEBUG = yes -BOOTTEST_ARCHES = armhf amd64 -BOOTTEST_FETCH = yes diff --git a/britney.py b/britney.py index 5751867..941989d 100755 --- a/britney.py +++ b/britney.py @@ -213,7 +213,6 @@ from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC, SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS, PROVIDES, RDEPENDS, RCONFLICTS, MULTIARCH, ESSENTIAL) from autopkgtest import AutoPackageTest, srchash -from boottest import BootTest __author__ = 'Fabio Tranchitella and the Debian Release Team' @@ -1454,7 +1453,6 @@ class Britney(object): # the starting point is that we will update the candidate and run autopkgtests update_candidate = True run_autopkgtest = True - run_boottest = True # if the version in unstable is older, then stop here with a warning in the excuse and return False if source_t and apt_pkg.version_compare(source_u[VERSION], source_t[VERSION]) < 0: @@ -1468,7 +1466,6 @@ class Britney(object): excuse.addhtml("%s source package doesn't exist" % (src)) update_candidate = False run_autopkgtest = False - run_boottest = False # retrieve the urgency for the upload, ignoring it if this is a NEW package (not present in testing) urgency = self.urgencies.get(src, self.options.default_urgency) @@ -1487,7 +1484,6 @@ class Britney(object): excuse.addreason("remove") update_candidate = False run_autopkgtest = False - run_boottest = False # check if there is a `block' or `block-udeb' hint for this package, or a `block-all source' hint blocked = {} @@ -1570,7 +1566,6 @@ class Britney(object): else: update_candidate = False run_autopkgtest = False - run_boottest = False excuse.addreason("age") if suite in ['pu', 'tpu']: @@ -1606,8 +1601,6 @@ class Britney(object): update_candidate = False if arch in self.options.adt_arches: run_autopkgtest = False - if arch in self.options.boottest_arches.split(): - run_boottest = False excuse.addreason("arch") excuse.addreason("arch-%s" % arch) excuse.addreason("build-arch") @@ -1650,8 +1643,6 @@ class Britney(object): update_candidate = False if arch in self.options.adt_arches: run_autopkgtest = False - if arch in self.options.boottest_arches.split(): - run_boottest = False # if there are out-of-date packages, warn about them in the excuse and set update_candidate # to False to block the update; if the architecture where the package is out-of-date is @@ -1678,8 +1669,6 @@ class Britney(object): update_candidate = False if arch in self.options.adt_arches: run_autopkgtest = False - if arch in self.options.boottest_arches.split(): - run_boottest = False excuse.addreason("arch") excuse.addreason("arch-%s" % arch) if uptodatebins: @@ -1697,13 +1686,11 @@ class Britney(object): excuse.addreason("no-binaries") update_candidate = False run_autopkgtest = False - run_boottest = False elif not built_anywhere: excuse.addhtml("%s has no up-to-date binaries on any arch" % src) excuse.addreason("no-binaries") update_candidate = False run_autopkgtest = False - run_boottest = False # if the suite is unstable, then we have to check the release-critical bug lists before # updating testing; if the unstable package has RC bugs that do not apply to the testing @@ -1736,7 +1723,6 @@ class Britney(object): ["#%s" % (quote(a), a) for a in new_bugs]))) update_candidate = False run_autopkgtest = False - run_boottest = False excuse.addreason("buggy") if len(old_bugs) > 0: @@ -1755,7 +1741,6 @@ class Britney(object): excuse.force() update_candidate = True run_autopkgtest = True - run_boottest = True # if the package can be updated, it is a valid candidate if update_candidate: @@ -1765,7 +1750,6 @@ class Britney(object): # TODO excuse.addhtml("Not considered") excuse.run_autopkgtest = run_autopkgtest - excuse.run_boottest = run_boottest self.excuses.append(excuse) return update_candidate @@ -2010,90 +1994,6 @@ class Britney(object): e.addreason("autopkgtest") e.is_valid = False - if (getattr(self.options, "boottest_enable", "no") == "yes" and - self.options.series): - # trigger 'boottest'ing for valid candidates. - boottest_debug = getattr( - self.options, "boottest_debug", "no") == "yes" - boottest = BootTest( - self, self.options.distribution, self.options.series, - debug=boottest_debug) - boottest_excuses = [] - for excuse in self.excuses: - # Skip already invalid excuses. - if not excuse.run_boottest: - continue - # Also skip removals, binary-only candidates, proposed-updates - # and unknown versions. - if (excuse.name.startswith("-") or - "/" in excuse.name or - "_" in excuse.name or - excuse.ver[1] == "-"): - continue - # Allows hints to skip boottest attempts - hints = self.hints.search( - 'force-skiptest', package=excuse.name) - forces = [x for x in hints - if same_source(excuse.ver[1], x.version)] - if forces: - excuse.addhtml( - "boottest skipped from hints by %s" % forces[0].user) - continue - # Only sources whitelisted in the boottest context should - # be tested (currently only sources building phone binaries). - if not boottest.needs_test(excuse.name, excuse.ver[1]): - # Silently skipping. - continue - # Okay, aggregate required boottests requests. - boottest_excuses.append(excuse) - boottest.request([(e.name, e.ver[1]) for e in boottest_excuses]) - # Dry-run avoids data exchange with external systems. - if not self.options.dry_run: - boottest.submit() - boottest.collect() - # Boottest Jenkins views location. - jenkins_public = "https://jenkins.qa.ubuntu.com/job" - jenkins_private = ( - "http://d-jenkins.ubuntu-ci:8080/view/%s/view/BootTest/job" % - self.options.series.title()) - # Update excuses from the boottest context. - for excuse in boottest_excuses: - status = boottest.get_status(excuse.name, excuse.ver[1]) - label = BootTest.EXCUSE_LABELS.get(status, 'UNKNOWN STATUS') - public_url = "%s/%s-boottest-%s/lastBuild" % ( - jenkins_public, self.options.series, - excuse.name.replace("+", "-")) - private_url = "%s/%s-boottest-%s/lastBuild" % ( - jenkins_private, self.options.series, - excuse.name.replace("+", "-")) - excuse.addhtml( - "Boottest result: %s (Jenkins: public" - ", private)" % ( - label, public_url, private_url)) - # Allows hints to force boottest failures/attempts - # to be ignored. - hints = self.hints.search('force', package=excuse.name) - hints.extend( - self.hints.search('force-badtest', package=excuse.name)) - forces = [x for x in hints - if same_source(excuse.ver[1], x.version)] - if forces: - excuse.addhtml( - "Should wait for %s %s boottest, but forced by " - "%s" % (excuse.name, excuse.ver[1], - forces[0].user)) - continue - # Block promotion if the excuse is still valid (adt tests - # passed) but the boottests attempt has failed or still in - # progress. - if status not in BootTest.VALID_STATUSES: - excuse.addreason("boottest") - if excuse.is_valid: - excuse.is_valid = False - excuse.addhtml("Not considered") - upgrade_me.remove(excuse.name) - unconsidered.append(excuse.name) - # invalidate impossible excuses for e in self.excuses: # parts[0] == package name diff --git a/excuse.py b/excuse.py index 85900bf..bcb1e26 100644 --- a/excuse.py +++ b/excuse.py @@ -60,7 +60,6 @@ class Excuse(object): self._dontinvalidate = False self.forced = False self.run_autopkgtest = False - self.run_boottest = False self.distribution = "ubuntu" self.invalid_deps = [] diff --git a/tests/test_autopkgtest.py b/tests/test_autopkgtest.py index 8a3fee4..d0c8f14 100755 --- a/tests/test_autopkgtest.py +++ b/tests/test_autopkgtest.py @@ -41,11 +41,9 @@ class T(TestBase): super().setUp() self.fake_amqp = os.path.join(self.data.path, 'amqp') - # Disable boottests and set fake AMQP and Swift server + # Set fake AMQP and Swift server for line in fileinput.input(self.britney_conf, inplace=True): - if 'BOOTTEST_ENABLE' in line: - print('BOOTTEST_ENABLE = no') - elif 'ADT_AMQP' in line: + if 'ADT_AMQP' in line: print('ADT_AMQP = file://%s' % self.fake_amqp) elif 'ADT_SWIFT_URL' in line: print('ADT_SWIFT_URL = http://localhost:18085') diff --git a/tests/test_boottest.py b/tests/test_boottest.py deleted file mode 100755 index 3a78a09..0000000 --- a/tests/test_boottest.py +++ /dev/null @@ -1,445 +0,0 @@ -#!/usr/bin/python3 -# (C) 2014 Canonical Ltd. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. - -import os -import shutil -import sys -import tempfile -import fileinput -import unittest -from unittest import mock - - -PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -sys.path.insert(0, PROJECT_DIR) - -import boottest -from tests import TestBase - - -def create_manifest(manifest_dir, lines): - """Helper function for writing touch image manifests.""" - os.makedirs(manifest_dir) - with open(os.path.join(manifest_dir, 'manifest'), 'w') as fd: - fd.write('\n'.join(lines)) - - -class FakeResponse(object): - - def __init__(self, code=404, content=''): - self.code = code - self.content = content - - def read(self): - return self.content - - -class TestTouchManifest(unittest.TestCase): - - def setUp(self): - super(TestTouchManifest, self).setUp() - self.path = tempfile.mkdtemp(prefix='boottest') - os.chdir(self.path) - self.imagesdir = os.path.join(self.path, 'boottest/images') - os.makedirs(self.imagesdir) - self.addCleanup(shutil.rmtree, self.path) - _p = mock.patch('urllib.request.urlopen') - self.mocked_urlopen = _p.start() - self.mocked_urlopen.side_effect = [ - FakeResponse(code=404), - FakeResponse(code=404), - ] - self.addCleanup(_p.stop) - self.fetch_retries_orig = boottest.FETCH_RETRIES - - def restore_fetch_retries(): - boottest.FETCH_RETRIES = self.fetch_retries_orig - boottest.FETCH_RETRIES = 0 - self.addCleanup(restore_fetch_retries) - - def test_missing(self): - # Missing manifest file silently results in empty contents. - manifest = boottest.TouchManifest('I-dont-exist', 'vivid') - self.assertEqual([], manifest._manifest) - self.assertNotIn('foo', manifest) - - def test_fetch(self): - # Missing manifest file is fetched dynamically - self.mocked_urlopen.side_effect = [ - FakeResponse(code=200, content=b'foo 1.0'), - ] - manifest = boottest.TouchManifest('ubuntu-touch', 'vivid') - self.assertNotEqual([], manifest._manifest) - - def test_fetch_disabled(self): - # Manifest auto-fetching can be disabled. - manifest = boottest.TouchManifest('ubuntu-touch', 'vivid', fetch=False) - self.mocked_urlopen.assert_not_called() - self.assertEqual([], manifest._manifest) - - def test_fetch_fails(self): - project = 'fake' - series = 'fake' - manifest_dir = os.path.join(self.imagesdir, project, series) - manifest_lines = [ - 'foo:armhf 1~beta1', - ] - create_manifest(manifest_dir, manifest_lines) - manifest = boottest.TouchManifest(project, series) - self.assertEqual(1, len(manifest._manifest)) - self.assertIn('foo', manifest) - - def test_fetch_exception(self): - self.mocked_urlopen.side_effect = [ - IOError("connection refused"), - IOError("connection refused"), - ] - manifest = boottest.TouchManifest('not-real', 'not-real') - self.assertEqual(0, len(manifest._manifest)) - - def test_simple(self): - # Existing manifest file allows callsites to properly check presence. - manifest_dir = os.path.join(self.imagesdir, 'ubuntu/vivid') - manifest_lines = [ - 'bar 1234', - 'foo:armhf 1~beta1', - 'boing1-1.2\t666', - 'click:com.ubuntu.shorts 0.2.346' - ] - create_manifest(manifest_dir, manifest_lines) - - manifest = boottest.TouchManifest('ubuntu', 'vivid') - # We can dig deeper on the manifest package names list ... - self.assertEqual( - ['bar', 'boing1-1.2', 'foo'], manifest._manifest) - # but the ' in manifest' API reads better. - self.assertIn('foo', manifest) - self.assertIn('boing1-1.2', manifest) - self.assertNotIn('baz', manifest) - # 'click' name is blacklisted due to the click package syntax. - self.assertNotIn('click', manifest) - - -class TestBoottestEnd2End(TestBase): - """End2End tests (calling `britney`) for the BootTest criteria.""" - - def setUp(self): - super(TestBoottestEnd2End, self).setUp() - - # Modify shared configuration file. - with open(self.britney_conf, 'r') as fp: - original_config = fp.read() - # Disable autopkgtests. - new_config = original_config.replace( - 'ADT_ENABLE = yes', 'ADT_ENABLE = no') - # Enable boottest. - new_config = new_config.replace( - 'BOOTTEST_ENABLE = no', 'BOOTTEST_ENABLE = yes') - # Disable TouchManifest auto-fetching. - new_config = new_config.replace( - 'BOOTTEST_FETCH = yes', 'BOOTTEST_FETCH = no') - with open(self.britney_conf, 'w') as fp: - fp.write(new_config) - - self.data.add('libc6', False, {'Architecture': 'armhf'}), - - self.data.add( - 'libgreen1', - False, - {'Source': 'green', 'Architecture': 'armhf', - 'Depends': 'libc6 (>= 0.9)'}) - self.data.add( - 'green', - False, - {'Source': 'green', 'Architecture': 'armhf', - 'Depends': 'libc6 (>= 0.9), libgreen1'}) - self.create_manifest([ - 'green 1.0', - 'pyqt5:armhf 1.0', - 'signon 1.0', - 'purple 1.1', - ]) - - def create_manifest(self, lines): - """Create a manifest for this britney run context.""" - path = os.path.join( - self.data.path, - 'boottest/images/ubuntu-touch/{}'.format(self.data.series)) - create_manifest(path, lines) - - def make_boottest(self): - """Create a stub version of boottest-britney script.""" - script_path = os.path.expanduser( - "~/auto-package-testing/jenkins/boottest-britney") - if not os.path.exists(os.path.dirname(script_path)): - os.makedirs(os.path.dirname(script_path)) - with open(script_path, 'w') as f: - f.write('''#!%(py)s -import argparse -import os -import shutil -import sys - -template = """ -green 1.1~beta RUNNING -pyqt5-src 1.1~beta PASS -pyqt5-src 1.1 FAIL -signon 1.1 PASS -purple 1.1 RUNNING -""" - -def request(): - work_path = os.path.dirname(args.output) - os.makedirs(work_path) - shutil.copy(args.input, os.path.join(work_path, 'test_input')) - with open(args.output, 'w') as f: - f.write(template) - -def submit(): - pass - -def collect(): - with open(args.output, 'w') as f: - f.write(template) - -p = argparse.ArgumentParser() -p.add_argument('-r') -p.add_argument('-c') -p.add_argument('-d', default=False, action='store_true') -p.add_argument('-P', default=False, action='store_true') -p.add_argument('-U', default=False, action='store_true') - -sp = p.add_subparsers() - -psubmit = sp.add_parser('submit') -psubmit.add_argument('input') -psubmit.set_defaults(func=submit) - -prequest = sp.add_parser('request') -prequest.add_argument('-O', dest='output') -prequest.add_argument('input') -prequest.set_defaults(func=request) - -pcollect = sp.add_parser('collect') -pcollect.add_argument('-O', dest='output') -pcollect.set_defaults(func=collect) - -args = p.parse_args() -args.func() - ''' % {'py': sys.executable}) - os.chmod(script_path, 0o755) - - def do_test(self, context, expect=None, no_expect=None): - """Process the given package context and assert britney results.""" - for (pkg, fields) in context: - self.data.add(pkg, True, fields, testsuite='autopkgtest') - self.make_boottest() - (excuses_yaml, excuses, out) = self.run_britney() - # print('-------\nexcuses: %s\n-----' % excuses) - # print('-------\nout: %s\n-----' % out) - if expect: - for re in expect: - self.assertRegex(excuses, re) - if no_expect: - for re in no_expect: - self.assertNotRegex(excuses, re) - - def test_runs(self): - # `Britney` runs and considers binary packages for boottesting - # when it is enabled in the configuration, only binaries needed - # in the phone image are considered for boottesting. - # The boottest status is presented along with its corresponding - # jenkins job urls for the public and the private servers. - # 'in progress' tests blocks package promotion. - context = [ - ('green', {'Source': 'green', 'Version': '1.1~beta', - 'Architecture': 'armhf', 'Depends': 'libc6 (>= 0.9)'}), - ('libgreen1', {'Source': 'green', 'Version': '1.1~beta', - 'Architecture': 'armhf', - 'Depends': 'libc6 (>= 0.9)'}), - ] - public_jenkins_url = ( - 'https://jenkins.qa.ubuntu.com/job/series-boottest-green/' - 'lastBuild') - private_jenkins_url = ( - 'http://d-jenkins.ubuntu-ci:8080/view/Series/view/BootTest/' - 'job/series-boottest-green/lastBuild') - self.do_test( - context, - [r'\bgreen\b.*>1 to .*>1.1~beta<', - r'
  • Boottest result: {} \(Jenkins: ' - r'public, private\)'.format( - boottest.BootTest.EXCUSE_LABELS['RUNNING'], - public_jenkins_url, private_jenkins_url), - '
  • Not considered']) - - # The `boottest-britney` input (recorded for testing purposes), - # contains a line matching the requested boottest attempt. - # ' \n' - test_input_path = os.path.join( - self.data.path, 'boottest/work/test_input') - with open(test_input_path) as f: - self.assertEqual( - ['green 1.1~beta\n'], f.readlines()) - - def test_pass(self): - # `Britney` updates boottesting information in excuses when the - # package test pass and marks the package as a valid candidate for - # promotion. - context = [] - context.append( - ('signon', {'Version': '1.1', 'Architecture': 'armhf'})) - self.do_test( - context, - [r'\bsignon\b.*\(- to .*>1.1<', - '
  • Boottest result: {}'.format( - boottest.BootTest.EXCUSE_LABELS['PASS']), - '
  • Valid candidate']) - - def test_fail(self): - # `Britney` updates boottesting information in excuses when the - # package test fails and blocks the package promotion - # ('Not considered.') - context = [] - context.append( - ('pyqt5', {'Source': 'pyqt5-src', 'Version': '1.1', - 'Architecture': 'all'})) - self.do_test( - context, - [r'\bpyqt5-src\b.*\(- to .*>1.1<', - '
  • Boottest result: {}'.format( - boottest.BootTest.EXCUSE_LABELS['FAIL']), - '
  • Not considered']) - - def test_unknown(self): - # `Britney` does not block on missing boottest results for a - # particular source/version, in this case pyqt5-src_1.2 (not - # listed in the testing result history). Instead it renders - # excuses with 'UNKNOWN STATUS' and links to the corresponding - # jenkins jobs for further investigation. Source promotion is - # blocked, though. - context = [ - ('pyqt5', {'Source': 'pyqt5-src', 'Version': '1.2', - 'Architecture': 'armhf'})] - self.do_test( - context, - [r'\bpyqt5-src\b.*\(- to .*>1.2<', - r'
  • Boottest result: UNKNOWN STATUS \(Jenkins: .*\)', - '
  • Not considered']) - - def test_skipped_by_hints(self): - # `Britney` allows boottests to be skipped by hinting the - # corresponding source with 'force-skiptest'. The boottest - # attempt will not be requested. - context = [ - ('pyqt5', {'Source': 'pyqt5-src', 'Version': '1.1', - 'Architecture': 'all'}), - ] - self.create_hint('cjwatson', 'force-skiptest pyqt5-src/1.1') - self.do_test( - context, - [r'\bpyqt5-src\b.*\(- to .*>1.1<', - '
  • boottest skipped from hints by cjwatson', - '
  • Valid candidate']) - - def test_fail_but_forced_by_hints(self): - # `Britney` allows boottests results to be ignored by hinting the - # corresponding source with 'force' or 'force-badtest'. The boottest - # attempt will still be requested and its results would be considered - # for other non-forced sources. - context = [ - ('pyqt5', {'Source': 'pyqt5-src', 'Version': '1.1', - 'Architecture': 'all'}), - ] - self.create_hint('cjwatson', 'force pyqt5-src/1.1') - self.do_test( - context, - [r'\bpyqt5-src\b.*\(- to .*>1.1<', - '
  • Boottest result: {}'.format( - boottest.BootTest.EXCUSE_LABELS['FAIL']), - '
  • Should wait for pyqt5-src 1.1 boottest, ' - 'but forced by cjwatson', - '
  • Valid candidate']) - - def test_fail_but_ignored_by_hints(self): - # See `test_fail_but_forced_by_hints`. - context = [ - ('green', {'Source': 'green', 'Version': '1.1~beta', - 'Architecture': 'armhf', 'Depends': 'libc6 (>= 0.9)'}), - ] - self.create_hint('cjwatson', 'force-badtest green/1.1~beta') - self.do_test( - context, - [r'\bgreen\b.*>1 to .*>1.1~beta<', - '
  • Boottest result: {}'.format( - boottest.BootTest.EXCUSE_LABELS['RUNNING']), - '
  • Should wait for green 1.1~beta boottest, but forced ' - 'by cjwatson', - '
  • Valid candidate']) - - def test_skipped_not_on_phone(self): - # `Britney` updates boottesting information in excuses when the - # package was skipped and marks the package as a valid candidate for - # promotion, but no notice about 'boottest' is added to the excuse. - context = [] - context.append( - ('apache2', {'Source': 'apache2-src', 'Architecture': 'all', - 'Version': '2.4.8-1ubuntu1'})) - self.do_test( - context, - [r'\bapache2-src\b.*\(- to .*>2.4.8-1ubuntu1<', - '
  • Valid candidate'], - ['
  • Boottest result:'], - ) - - def test_skipped_architecture_not_allowed(self): - # `Britney` does not trigger boottests for source not yet built on - # the allowed architectures. - self.data.add( - 'pyqt5', False, {'Source': 'pyqt5-src', 'Architecture': 'armhf'}) - context = [ - ('pyqt5', {'Source': 'pyqt5-src', 'Version': '1.1', - 'Architecture': 'amd64'}), - ] - self.do_test( - context, - [r'\bpyqt5-src\b.*>1 to .*>1.1<', - r'
  • missing build on .*>armhf: pyqt5 \(from .*>1\)', - '
  • Not considered']) - - def test_with_adt(self): - # Boottest can run simultaneously with autopkgtest (adt). - - fake_amqp = os.path.join(self.data.path, 'amqp') - - # Enable ADT in britney configuration. - for line in fileinput.input(self.britney_conf, inplace=True): - if 'ADT_ENABLE' in line: - print('ADT_ENABLE = yes') - elif 'ADT_AMQP' in line: - print('ADT_AMQP = file://%s' % fake_amqp) - else: - sys.stdout.write(line) - - # Britney blocks testing source promotion while ADT and boottest - # are running. - self.data.add('purple', False, {'Version': '1.0'}, testsuite='autopkgtest') - context = [ - ('purple', {'Version': '1.1'}), - ] - self.do_test( - context, - [r'\bpurple\b.*>1.0<.* to .*>1.1<', - '
  • autopkgtest for purple 1.1: .*amd64.*in progress.*i386.*in progress', - '
  • Boottest result: {}'.format( - boottest.BootTest.EXCUSE_LABELS['RUNNING']), - '
  • Not considered']) - - -if __name__ == '__main__': - unittest.main()