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
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)
|
@ -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…
Reference in new issue