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.
bzr-import-20160707
Martin Pitt 9 years ago
parent ec420195bc
commit 6269ca4813

@ -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": '<span style="background:#87d96c">Pass</span>',
"FAIL": '<span style="background:#ff6666">Regression</span>',
"RUNNING": '<span style="background:#99ddff">Test in progress</span>',
}
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 "<binname>/<arch>" 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)

@ -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

@ -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):
["<a href=\"http://bugs.debian.org/%s\">#%s</a>" % (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: <a href=\"%s\">public</a>"
", <a href=\"%s\">private</a>)" % (
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

@ -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 = []

@ -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')

@ -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 '<name> 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</a> to .*>1.1~beta<',
r'<li>Boottest result: {} \(Jenkins: '
r'<a href="{}">public</a>, <a href="{}">private</a>\)'.format(
boottest.BootTest.EXCUSE_LABELS['RUNNING'],
public_jenkins_url, private_jenkins_url),
'<li>Not considered'])
# The `boottest-britney` input (recorded for testing purposes),
# contains a line matching the requested boottest attempt.
# '<source> <version>\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<',
'<li>Boottest result: {}'.format(
boottest.BootTest.EXCUSE_LABELS['PASS']),
'<li>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<',
'<li>Boottest result: {}'.format(
boottest.BootTest.EXCUSE_LABELS['FAIL']),
'<li>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'<li>Boottest result: UNKNOWN STATUS \(Jenkins: .*\)',
'<li>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<',
'<li>boottest skipped from hints by cjwatson',
'<li>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<',
'<li>Boottest result: {}'.format(
boottest.BootTest.EXCUSE_LABELS['FAIL']),
'<li>Should wait for pyqt5-src 1.1 boottest, '
'but forced by cjwatson',
'<li>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</a> to .*>1.1~beta<',
'<li>Boottest result: {}'.format(
boottest.BootTest.EXCUSE_LABELS['RUNNING']),
'<li>Should wait for green 1.1~beta boottest, but forced '
'by cjwatson',
'<li>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<',
'<li>Valid candidate'],
['<li>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</a> to .*>1.1<',
r'<li>missing build on .*>armhf</a>: pyqt5 \(from .*>1</a>\)',
'<li>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<',
'<li>autopkgtest for purple 1.1: .*amd64.*in progress.*i386.*in progress',
'<li>Boottest result: {}'.format(
boottest.BootTest.EXCUSE_LABELS['RUNNING']),
'<li>Not considered'])
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save