Merge lp:~jibel/britney/fix_missing_results into lp:~ubuntu-release/britney/britney2-ubuntu

Proposed by Jean-Baptiste Lallement
Status: Merged
Merged at revision: 398
Proposed branch: lp:~jibel/britney/fix_missing_results
Merge into: lp:~ubuntu-release/britney/britney2-ubuntu
Diff against target: 536 lines (+244/-73)
3 files modified
autopkgtest.py (+56/-32)
britney.py (+6/-5)
tests/test_autopkgtest.py (+182/-36)
To merge this branch: bzr merge lp:~jibel/britney/fix_missing_results
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Martin Pitt (community) Approve
Review via email: mp+218467@code.launchpad.net

Description of the change

This branch fix a case of "vanishing" results in excuses.

Only the last result in history for a given package was considered, results being sorted in alphabetic order, a failed test could be hidden by another test that passed, and the package promoted while it should have been blocked.

For example if we had the following result in history:
udisk2 1 PASS udisks2 1

An upload of systemd will trigger a test of udisk2, if we suppose that this test fails, it will generate the following list of results:
udisk2 1 FAIL systemd 2
udisk2 1 PASS udisk2 1

Which means that udisk2 can be promoted because it pass, and systemd must be blocked because it introduced a regression in udisk2.
With the bug only the last cause in the list is considered and the result for udisk2/systemd is ignored.

This MP also:
- Adds tests to cover the bug mentioned above, and update existing tests for the new states "always fails/regression"
- makes the distinction between tests that are always failing and consider them as valid candidates and tests that regress, in which case the package is block.
- Add colouring to make excuses more readable for long list of autopkgtests
- Points jenkins URL directly lastBuild to save some clicks

To post a comment you must log in.
Revision history for this message
Martin Pitt (pitti) wrote :
Download full text (3.3 KiB)

Many thanks for this, Jean-Baptiste! Some comments:

 * My original tests (from https://code.launchpad.net/~canonical-platform-qa/britney/tests/+merge/207982) are not merged into britney2-ubuntu yet. I don't know whether this was due to lack of time, or the release team would prefer the tests to have a different form. In the latter case, I suggest merging this without the changes to tests/autopkgtest.py, and we apply the latter on top of my MP above. We shouldn't block this urgent fix on desinging a new test suite, and the existing one at least shows that it's working now.

 * Thanks for introducing the "always fails" state. This introduces the additional requirement that when opening a release we need to copy the last successful result (if any) in Jenkins to the new release, so that we don't consider packages which succeeded in trsuty and now fail in utopic as "always failed". That's a reasonable thing to do, I just want to point it out explicitly.

Code review for the new tests:

  * Checking the precise HTML formatting and colors in the tests seems a bit excessive to me, but I don't mind it much. But if you want to keep it, please don't duplicate the definition of ADT_EXCUSES_LABELS in the test but import it from autopkgtest (note, this might require renaming tests/autopkgtest.py to tests/test_autopkgtest.py to avoid a namespace clash).

  * __merge_records() could do with a docstring, as it's not quite clear what this does and why it's needed in a test. That's the sort of logic I'd expect in the actual code, but in the test suite the fake results should be pre-formatted correctly? Also, is it actually justified to assume any particular ordering in history.txt, or should autopkgtest.py not assume any and just order it by itself?

    def test_request_for_installable_fail_always(self):
        '''Requests a test for an installable package, test fail'''

I think it would make sense to split this in two: One should be the code as it is right now (i. e. with empty history), and be called "test_request_for_installable_first_fail()"; and another one with a history of just one (or perhaps two) FAILs, which is called "test_request_for_installable_fail_always" (with adjusted docstring). I think they both should count as ALWAYSFAIL, but I think it's still important to see that both cases behave as expected.

+ def test_request_for_installable_fail_regression_promoted(self):
+ '''Requests a test for an installable package, test fail, is a
+ regression.

Please no multi-line short docstrings, this causes truncated descriptions when running with -v and is also against PEP-8/looks ugly.

Some really small nitpicks:

+ ''' % {'py': sys.executable, 'path': self.data.path, 'rq':

  Please move the 'rq': to the next line so that key and value are together, for better readability.

+ '<li>autopkgtest for green 1.1~beta: {}'.format(ADT_EXCUSES_LABELS['RUNNING'])])

  We use %s macros (or similar) and the % operator instead of {} and .format() in other places, so maybe this could be made consistent?

+ In this case results for A 1didn't appear in the list of results

missing space after '1'. Also, excess ...

Read more...

review: Needs Fixing
Revision history for this message
Martin Pitt (pitti) wrote :

autopkgtest.py
==============

- logging.warning(
- "Invalid line format: '%s', skipped" % line)
+ print("W: Invalid line format: '%s', skipped" % line)

This indeed should be fixed. However, for consistency this should use self.__log(..., type='W') instead of print.

+ if not trigsrc in self.pkglist[src][ver]['causes']:
+ self.pkglist[src][ver]['causes'][trigsrc] = []
+ self.pkglist[src][ver]['causes'][trigsrc].append((trigver,
+ status))

This is a common pattern in Python which is much more succinct with a dict's setdefault() method. I. e.

                self.pkglist[src][ver]['causes'].setdefault(trigsrc, []).append(
                          (trigver, status))

It would be helpful to add a docstring to read() to say what it reads (the results file), what its format is, and what the structure of the generated self.pkgcauses and self.pkglist are.

Otherwise the new logic seems fine to me (thanks to JB for additional explanations on IRC).

britney.py
==========

+ adt_label = status
+ if status in ADT_EXCUSES_LABELS:
+ adt_label = ADT_EXCUSES_LABELS[status]

Do we realistically expect a status which isn't in the map? If not (and we want to get pointed to that error), I'd suggest to simply use the dict lookup. If we want to allow that possibility, I suggest this for simplification:

          adt_label = ADT_EXCUSES_LABELS.get(status, status)

LGTM otherwise.

414. By Jean-Baptiste Lallement

* tests/test_autopkgtest.py:
- Added docstrings
- Replaced format by %
- Import ADT_EXCUSES_LABELS from autopkgtest.py instead of redefining it
- Renamed test to avoid name conflict with autopkgtest.py from britney
- Fixed some formatting

415. By Jean-Baptiste Lallement

autopkgtest.py: Document method read() and code simplification
britney.py: code simplification

Revision history for this message
Jean-Baptiste Lallement (jibel) wrote :

> Code review for the new tests:
>
> * Checking the precise HTML formatting and colors in the tests seems a bit
> excessive to me, but I don't mind it much. But if you want to keep it, please
> don't duplicate the definition of ADT_EXCUSES_LABELS in the test but import it
> from autopkgtest (note, this might require renaming tests/autopkgtest.py to
> tests/test_autopkgtest.py to avoid a namespace clash).

I renamed tests/autopkgtest.py and now import ADT_EXCUSES_LABELS.

>
> * __merge_records() could do with a docstring, as it's not quite clear what
> this does and why it's needed in a test. That's the sort of logic I'd expect
> in the actual code, but in the test suite the fake results should be pre-
> formatted correctly? Also, is it actually justified to assume any particular
> ordering in history.txt, or should autopkgtest.py not assume any and just
> order it by itself?
The sorting logic should indeed be in the fake britney, but it is more convenient to have actual python code in the test and make the fake adt-britney just return static data.
Results are sorted by version to have latest results last in the list. There is no other justification than to the ordering than keeping results for a given package grouped. I should revisit it in adt-britney.

>
> def test_request_for_installable_fail_always(self):
> '''Requests a test for an installable package, test fail'''
>
> I think it would make sense to split this in two: One should be the code as it
> is right now (i. e. with empty history), and be called
> "test_request_for_installable_first_fail()"; and another one with a history of
> just one (or perhaps two) FAILs, which is called
> "test_request_for_installable_fail_always" (with adjusted docstring). I think
> they both should count as ALWAYSFAIL, but I think it's still important to see
> that both cases behave as expected.

I renamed the test to clarify what this test does (first result fail) and the second case (test failed and previous test failed too) is already covered by test_history_always_failed()

>
> + def test_request_for_installable_fail_regression_promoted(self):
> + '''Requests a test for an installable package, test fail, is a
> + regression.
>
> Please no multi-line short docstrings, this causes truncated descriptions when
> running with -v and is also against PEP-8/looks ugly.
Fixed.

>
>
> Some really small nitpicks:
>
> + ''' % {'py': sys.executable, 'path': self.data.path,
> 'rq':
>
> Please move the 'rq': to the next line so that key and value are together,
> for better readability.
>
Fixed

> + '<li>autopkgtest for green 1.1~beta:
> {}'.format(ADT_EXCUSES_LABELS['RUNNING'])])
>
> We use %s macros (or similar) and the % operator instead of {} and .format()
> in other places, so maybe this could be made consistent?
>
Fixed

> + In this case results for A 1didn't appear in the list of results
>
> missing space after '1'. Also, excess empty lines after the docstring.
>
Fixed

> I'll do the code review for the actual britney.py and autopkgtest.py code in a
> separate comment.

Thanks for your review

Revision history for this message
Jean-Baptiste Lallement (jibel) wrote :

> autopkgtest.py
> ==============
>
> - logging.warning(
> - "Invalid line format: '%s', skipped" % line)
> + print("W: Invalid line format: '%s', skipped" % line)
>
> This indeed should be fixed. However, for consistency this should use
> self.__log(..., type='W') instead of print.
>
Actually __log() is a method of britney and used in britney.py. In autopkgtest.py only print() is used.

>
> + if not trigsrc in self.pkglist[src][ver]['causes']:
> + self.pkglist[src][ver]['causes'][trigsrc] = []
> + self.pkglist[src][ver]['causes'][trigsrc].append((trigver,
> + status))
>
> This is a common pattern in Python which is much more succinct with a dict's
> setdefault() method. I. e.

Fixed.

>
> self.pkglist[src][ver]['causes'].setdefault(trigsrc,
> []).append(
> (trigver, status))
>
> It would be helpful to add a docstring to read() to say what it reads (the
> results file), what its format is, and what the structure of the generated
> self.pkgcauses and self.pkglist are.

Added documentation.

>
> Otherwise the new logic seems fine to me (thanks to JB for additional
> explanations on IRC).
>
> britney.py
> ==========
>
> + adt_label = status
> + if status in ADT_EXCUSES_LABELS:
> + adt_label = ADT_EXCUSES_LABELS[status]
>

Fixed.

> Do we realistically expect a status which isn't in the map? If not (and we
> want to get pointed to that error), I'd suggest to simply use the dict lookup.
> If we want to allow that possibility, I suggest this for simplification:
>
> adt_label = ADT_EXCUSES_LABELS.get(status, status)
>

because adt-britney and britney are 2 separate projects, it is possible, while unlikely, that we add new statuses in adt-britney that are not in britney and make britney crash if the status is unknown.
>
> LGTM otherwise.

Revision history for this message
Martin Pitt (pitti) wrote :

Thanks Jean-Baptiste! Looks good to me now, passing on to release team / Colin.

review: Approve
416. By Jean-Baptiste Lallement

Merged trunk

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopkgtest.py'
2--- autopkgtest.py 2013-11-15 09:20:54 +0000
3+++ autopkgtest.py 2014-05-12 13:01:57 +0000
4@@ -19,18 +19,24 @@
5
6 from collections import defaultdict
7 from contextlib import closing
8-import logging
9 import os
10 import subprocess
11 import tempfile
12 from textwrap import dedent
13 import time
14-
15 import apt_pkg
16
17
18 adt_britney = os.path.expanduser("~/auto-package-testing/jenkins/adt-britney")
19
20+ADT_PASS = ["PASS", "ALWAYSFAIL"]
21+ADT_EXCUSES_LABELS = {
22+ "PASS": '<span style="background:#87d96c">Pass</span>',
23+ "ALWAYSFAIL": '<span style="background:#e5c545">Always failed</span>',
24+ "REGRESSION": '<span style="background:#ff6666">Regression</span>',
25+ "RUNNING": '<span style="background:#99ddff">Test in progress</span>',
26+}
27+
28
29 class AutoPackageTest(object):
30 """autopkgtest integration
31@@ -62,7 +68,7 @@
32 components: main restricted universe multiverse
33 rsync_host: rsync://tachash.ubuntu-ci/adt/
34 datadir: ~/proposed-migration/autopkgtest/data""" %
35- (self.series, self.series, home)), file=rc_file)
36+ (self.series, self.series, home)), file=rc_file)
37
38 @property
39 def _request_path(self):
40@@ -85,38 +91,39 @@
41 continue
42 linebits = line.split()
43 if len(linebits) < 2:
44- logging.warning(
45- "Invalid line format: '%s', skipped" % line)
46+ print("W: Invalid line format: '%s', skipped" % line)
47 continue
48 yield linebits
49
50 def read(self):
51+ '''Loads a list of results
52+
53+ This function loads a list of results returned by __parse() and builds
54+ 2 lists:
55+ - a list of source package/version with all the causes that
56+ triggered a test and the result of the test for this trigger.
57+ - a list of packages/version that triggered a test with the source
58+ package/version and result triggered by this package.
59+ These lists will be used in result() called from britney.py to generate
60+ excuses and now which uploads passed, caused regression or which tests
61+ have always been failing
62+ '''
63 self.pkglist = defaultdict(dict)
64 self.pkgcauses = defaultdict(lambda: defaultdict(list))
65 for linebits in self._parse(self._result_path):
66- src = linebits.pop(0)
67- ver = linebits.pop(0)
68- self.pkglist[src][ver] = {
69- "status": "NEW",
70- "causes": {},
71+ (src, ver, status) = linebits[:3]
72+
73+ if not (src in self.pkglist and ver in self.pkglist[src]):
74+ self.pkglist[src][ver] = {
75+ "status": status,
76+ "causes": {}
77 }
78- try:
79- status = linebits.pop(0).upper()
80- self.pkglist[src][ver]["status"] = status
81- while True:
82- trigsrc = linebits.pop(0)
83- trigver = linebits.pop(0)
84- self.pkglist[src][ver]["causes"][trigsrc] = trigver
85- except IndexError:
86- # End of the list
87- pass
88- for src in self.pkglist:
89- all_vers = sorted(self.pkglist[src], cmp=apt_pkg.version_compare)
90- for ver in self.pkglist[src]:
91- status = self.pkglist[src][ver]["status"]
92- for trigsrc, trigver in \
93- self.pkglist[src][ver]["causes"].items():
94- self.pkgcauses[trigsrc][trigver].append((status, src, ver))
95+
96+ i = iter(linebits[3:])
97+ for trigsrc, trigver in zip(i, i):
98+ self.pkglist[src][ver]['causes'].setdefault(
99+ trigsrc, []).append((trigver, status))
100+ self.pkgcauses[trigsrc][trigver].append((status, src, ver))
101
102 def _adt_britney(self, *args):
103 command = [
104@@ -197,12 +204,29 @@
105 self.read()
106 if self.britney.options.verbose:
107 for src in sorted(self.pkglist):
108- for ver in self.pkglist[src]:
109- print("I: [%s] - Collected autopkgtest status for %s_%s: "
110- "%s" %
111- (time.asctime(), src, ver,
112- self.pkglist[src][ver]["status"]))
113+ for ver in sorted(self.pkglist[src],
114+ cmp=apt_pkg.version_compare):
115+ for trigsrc in sorted(self.pkglist[src][ver]['causes']):
116+ for trigver, status \
117+ in self.pkglist[src][ver]['causes'][trigsrc]:
118+ print("I: [%s] - Collected autopkgtest status "
119+ "for %s_%s/%s_%s: " "%s" % (
120+ time.asctime(), src, ver, trigsrc,
121+ trigver, status))
122
123 def results(self, trigsrc, trigver):
124 for status, src, ver in self.pkgcauses[trigsrc][trigver]:
125+ # Check for regresssion
126+ if status == 'FAIL':
127+ passed_once = False
128+ for ver in self.pkglist[src]:
129+ for trigsrc in self.pkglist[src][ver]['causes']:
130+ for trigver, status \
131+ in self.pkglist[src][ver]['causes'][trigsrc]:
132+ if status == 'PASS':
133+ passed_once = True
134+ if not passed_once:
135+ status = 'ALWAYSFAIL'
136+ else:
137+ status = 'REGRESSION'
138 yield status, src, ver
139
140=== modified file 'britney.py'
141--- britney.py 2014-03-05 16:14:48 +0000
142+++ britney.py 2014-05-12 13:01:57 +0000
143@@ -222,7 +222,7 @@
144 from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
145 SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
146 PROVIDES, RDEPENDS, RCONFLICTS, MULTIARCH)
147-from autopkgtest import AutoPackageTest
148+from autopkgtest import AutoPackageTest, ADT_PASS, ADT_EXCUSES_LABELS
149
150 __author__ = 'Fabio Tranchitella and the Debian Release Team'
151 __version__ = '2.0'
152@@ -1756,18 +1756,19 @@
153 adtpass = True
154 for status, adtsrc, adtver in autopkgtest.results(
155 e.name, e.ver[1]):
156- public_url = "%s/%s-adt-%s/" % (
157+ public_url = "%s/%s-adt-%s/lastBuild" % (
158 jenkins_public, self.options.adt_series,
159 adtsrc.replace("+", "-"))
160- private_url = "%s/%s-adt-%s/" % (
161+ private_url = "%s/%s-adt-%s/lastBuild" % (
162 jenkins_private, self.options.adt_series,
163 adtsrc.replace("+", "-"))
164+ adt_label = ADT_EXCUSES_LABELS.get(status, status)
165 e.addhtml(
166 "autopkgtest for %s %s: %s (Jenkins: "
167 "<a href=\"%s\">public</a>, "
168 "<a href=\"%s\">private</a>)" %
169- (adtsrc, adtver, status, public_url, private_url))
170- if status != "PASS":
171+ (adtsrc, adtver, adt_label, public_url, private_url))
172+ if status not in ADT_PASS:
173 hints = self.hints.search(
174 'force-badtest', package=adtsrc)
175 hints.extend(
176
177=== renamed file 'tests/autopkgtest.py' => 'tests/test_autopkgtest.py'
178--- tests/autopkgtest.py 2014-05-12 12:04:55 +0000
179+++ tests/test_autopkgtest.py 2014-05-12 13:01:57 +0000
180@@ -12,7 +12,10 @@
181 import sys
182 import subprocess
183 import unittest
184+import apt_pkg
185+import operator
186
187+apt_pkg.init()
188 architectures = ['amd64', 'arm64', 'armhf', 'i386', 'powerpc', 'ppc64el']
189
190 my_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
191@@ -20,6 +23,9 @@
192 NOT_CONSIDERED = False
193 VALID_CANDIDATE = True
194
195+sys.path.insert(0, my_dir)
196+from autopkgtest import ADT_EXCUSES_LABELS
197+
198
199 class TestData:
200 def __init__(self):
201@@ -157,7 +163,29 @@
202 def tearDown(self):
203 del self.data
204
205- def make_adt_britney(self, request):
206+ def __merge_records(self, results, history=""):
207+ '''Merges a list of results with records in history.
208+
209+ This function merges results from a collect with records already in
210+ history and sort records by version/name of causes and version/name of
211+ source packages with tests. This should be done in the fake
212+ adt-britney but it is more convenient to just pass a static list of
213+ records and make adt-britney just return this list.
214+ '''
215+
216+ if history is None:
217+ history = ""
218+ records = [x.split() for x in (results.strip() + '\n' +
219+ history.strip()).split('\n') if x]
220+
221+ records.sort(cmp=apt_pkg.version_compare, key=operator.itemgetter(4))
222+ records.sort(key=operator.itemgetter(3))
223+ records.sort(cmp=apt_pkg.version_compare, key=operator.itemgetter(1))
224+ records.sort()
225+
226+ return "\n".join([' '.join(x) for x in records])
227+
228+ def make_adt_britney(self, request, history=""):
229 with open(self.adt_britney, 'w') as f:
230 f.write('''#!%(py)s
231 import argparse, shutil,sys
232@@ -175,7 +203,7 @@
233
234 def collect():
235 with open(args.output, 'w') as f:
236- f.write("""%(rq)s""")
237+ f.write("""%(res)s""")
238
239 p = argparse.ArgumentParser()
240 p.add_argument('-c', '--config')
241@@ -202,7 +230,9 @@
242
243 args = p.parse_args()
244 args.func()
245-''' % {'py': sys.executable, 'path': self.data.path, 'rq': request})
246+ ''' % {'py': sys.executable, 'path': self.data.path,
247+ 'rq': request,
248+ 'res': self.__merge_records(request, history)})
249
250 def run_britney(self, args=[]):
251 '''Run britney.
252@@ -245,9 +275,19 @@
253 'green 1.1~beta RUNNING green 1.1~beta\n',
254 NOT_CONSIDERED,
255 [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
256- '<li>autopkgtest for green 1.1~beta: RUNNING'])
257-
258- def test_request_for_installable_fail(self):
259+ '<li>autopkgtest for green 1.1~beta: %s' % ADT_EXCUSES_LABELS['RUNNING']])
260+
261+ def test_request_for_installable_first_fail(self):
262+ '''Requests a test for an installable package. No history and first result is a failure'''
263+
264+ self.do_test(
265+ [('green', {'Version': '1.1~beta', 'Depends': 'libc6 (>= 0.9), libgreen1'})],
266+ 'green 1.1~beta FAIL green 1.1~beta\n',
267+ VALID_CANDIDATE,
268+ [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
269+ '<li>autopkgtest for green 1.1~beta: %s' % ADT_EXCUSES_LABELS['ALWAYSFAIL']])
270+
271+ def test_request_for_installable_fail_regression(self):
272 '''Requests a test for an installable package, test fail'''
273
274 self.do_test(
275@@ -255,7 +295,8 @@
276 'green 1.1~beta FAIL green 1.1~beta\n',
277 NOT_CONSIDERED,
278 [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
279- '<li>autopkgtest for green 1.1~beta: FAIL'])
280+ '<li>autopkgtest for green 1.1~beta: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
281+ history='green 1.0~beta PASS green 1.0~beta\n')
282
283 def test_request_for_installable_pass(self):
284 '''Requests a test for an installable package, test pass'''
285@@ -265,7 +306,7 @@
286 'green 1.1~beta PASS green 1.1~beta\n',
287 VALID_CANDIDATE,
288 [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
289- '<li>autopkgtest for green 1.1~beta: PASS'])
290+ '<li>autopkgtest for green 1.1~beta: %s' % ADT_EXCUSES_LABELS['PASS']])
291
292 def test_multi_rdepends_with_tests_running(self):
293 '''Multiple reverse dependencies with tests (still running)'''
294@@ -276,10 +317,22 @@
295 'darkgreen 1 RUNNING green 2\n',
296 NOT_CONSIDERED,
297 [r'\bgreen\b.*>1</a> to .*>2<',
298- '<li>autopkgtest for lightgreen 1: PASS',
299- '<li>autopkgtest for darkgreen 1: RUNNING'])
300-
301- def test_multi_rdepends_with_tests_fail(self):
302+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
303+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['RUNNING']])
304+
305+ def test_multi_rdepends_with_tests_fail_always(self):
306+ '''Multiple reverse dependencies with tests (fail)'''
307+
308+ self.do_test(
309+ [('libgreen1', {'Version': '2', 'Source': 'green', 'Depends': 'libc6'})],
310+ 'lightgreen 1 PASS green 2\n'
311+ 'darkgreen 1 FAIL green 2\n',
312+ VALID_CANDIDATE,
313+ [r'\bgreen\b.*>1</a> to .*>2<',
314+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
315+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['ALWAYSFAIL']])
316+
317+ def test_multi_rdepends_with_tests_fail_regression(self):
318 '''Multiple reverse dependencies with tests (fail)'''
319
320 self.do_test(
321@@ -288,8 +341,9 @@
322 'darkgreen 1 FAIL green 2\n',
323 NOT_CONSIDERED,
324 [r'\bgreen\b.*>1</a> to .*>2<',
325- '<li>autopkgtest for lightgreen 1: PASS',
326- '<li>autopkgtest for darkgreen 1: FAIL'])
327+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
328+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
329+ history='darkgreen 1 PASS green 1\n')
330
331 def test_multi_rdepends_with_tests_pass(self):
332 '''Multiple reverse dependencies with tests (pass)'''
333@@ -300,8 +354,8 @@
334 'darkgreen 1 PASS green 2\n',
335 VALID_CANDIDATE,
336 [r'\bgreen\b.*>1</a> to .*>2<',
337- '<li>autopkgtest for lightgreen 1: PASS',
338- '<li>autopkgtest for darkgreen 1: PASS'])
339+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
340+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['PASS']])
341
342 def test_multi_rdepends_with_some_tests_running(self):
343 '''Multiple reverse dependencies with some tests (running)'''
344@@ -315,10 +369,25 @@
345 'darkgreen 1 RUNNING green 2\n',
346 NOT_CONSIDERED,
347 [r'\bgreen\b.*>1</a> to .*>2<',
348- '<li>autopkgtest for lightgreen 1: RUNNING',
349- '<li>autopkgtest for darkgreen 1: RUNNING'])
350-
351- def test_multi_rdepends_with_some_tests_fail(self):
352+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['RUNNING'],
353+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['RUNNING']])
354+
355+ def test_multi_rdepends_with_some_tests_fail_always(self):
356+ '''Multiple reverse dependencies with some tests (fail)'''
357+
358+ # add a third reverse dependency to libgreen1 which does not have a test
359+ self.data.add('mint', False, {'Depends': 'libgreen1'})
360+
361+ self.do_test(
362+ [('libgreen1', {'Version': '2', 'Source': 'green', 'Depends': 'libc6'})],
363+ 'lightgreen 1 PASS green 2\n'
364+ 'darkgreen 1 FAIL green 2\n',
365+ VALID_CANDIDATE,
366+ [r'\bgreen\b.*>1</a> to .*>2<',
367+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
368+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['ALWAYSFAIL']])
369+
370+ def test_multi_rdepends_with_some_tests_fail_regression(self):
371 '''Multiple reverse dependencies with some tests (fail)'''
372
373 # add a third reverse dependency to libgreen1 which does not have a test
374@@ -330,8 +399,9 @@
375 'darkgreen 1 FAIL green 2\n',
376 NOT_CONSIDERED,
377 [r'\bgreen\b.*>1</a> to .*>2<',
378- '<li>autopkgtest for lightgreen 1: PASS',
379- '<li>autopkgtest for darkgreen 1: FAIL'])
380+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
381+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
382+ history='darkgreen 1 PASS green 1\n')
383
384 def test_multi_rdepends_with_some_tests_pass(self):
385 '''Multiple reverse dependencies with some tests (pass)'''
386@@ -345,8 +415,8 @@
387 'darkgreen 1 PASS green 2\n',
388 VALID_CANDIDATE,
389 [r'\bgreen\b.*>1</a> to .*>2<',
390- '<li>autopkgtest for lightgreen 1: PASS',
391- '<li>autopkgtest for darkgreen 1: PASS'])
392+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
393+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['PASS']])
394
395 def test_binary_from_new_source_package_running(self):
396 '''building an existing binary for a new source package (running)'''
397@@ -357,10 +427,22 @@
398 'darkgreen 1 RUNNING newgreen 2\n',
399 NOT_CONSIDERED,
400 [r'\bnewgreen\b.*\(- to .*>2<',
401- '<li>autopkgtest for lightgreen 1: PASS',
402- '<li>autopkgtest for darkgreen 1: RUNNING'])
403-
404- def test_binary_from_new_source_package_fail(self):
405+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
406+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['RUNNING']])
407+
408+ def test_binary_from_new_source_package_fail_always(self):
409+ '''building an existing binary for a new source package (fail)'''
410+
411+ self.do_test(
412+ [('libgreen1', {'Version': '2', 'Source': 'newgreen', 'Depends': 'libc6'})],
413+ 'lightgreen 1 PASS newgreen 2\n'
414+ 'darkgreen 1 FAIL newgreen 2\n',
415+ VALID_CANDIDATE,
416+ [r'\bnewgreen\b.*\(- to .*>2<',
417+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
418+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['ALWAYSFAIL']])
419+
420+ def test_binary_from_new_source_package_fail_regression(self):
421 '''building an existing binary for a new source package (fail)'''
422
423 self.do_test(
424@@ -369,8 +451,9 @@
425 'darkgreen 1 FAIL newgreen 2\n',
426 NOT_CONSIDERED,
427 [r'\bnewgreen\b.*\(- to .*>2<',
428- '<li>autopkgtest for lightgreen 1: PASS',
429- '<li>autopkgtest for darkgreen 1: FAIL'])
430+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
431+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
432+ history='darkgreen 1 PASS green 1\n')
433
434 def test_binary_from_new_source_package_pass(self):
435 '''building an existing binary for a new source package (pass)'''
436@@ -381,8 +464,8 @@
437 'darkgreen 1 PASS newgreen 2\n',
438 VALID_CANDIDATE,
439 [r'\bnewgreen\b.*\(- to .*>2<',
440- '<li>autopkgtest for lightgreen 1: PASS',
441- '<li>autopkgtest for darkgreen 1: PASS'])
442+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS'],
443+ '<li>autopkgtest for darkgreen 1: %s' % ADT_EXCUSES_LABELS['PASS']])
444
445 def test_binary_from_new_source_package_uninst(self):
446 '''building an existing binary for a new source package (uninstallable)'''
447@@ -406,14 +489,76 @@
448 NOT_CONSIDERED,
449 [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
450 # it's not entirely clear what precisely it should say here
451- '<li>autopkgtest for green 1.1~beta: RUNNING'])
452+ '<li>autopkgtest for green 1.1~beta: %s' % ADT_EXCUSES_LABELS['RUNNING']])
453+
454+ def test_request_for_installable_fail_regression_promoted(self):
455+ '''Requests a test for an installable package, test fail, is a regression.
456+
457+ This test verifies a bug in britney where a package was promoted if latest test
458+ appeared before previous result in history, only the last result in
459+ alphabetic order was taken into account. For example:
460+ A 1 FAIL B 1
461+ A 1 PASS A 1
462+ In this case results for A 1 didn't appear in the list of results
463+ triggered by the upload of B 1 and B 1 was promoted
464+ '''
465+
466+ self.do_test(
467+ [('green', {'Version': '1.1~beta', 'Depends': 'libc6 (>= 0.9), libgreen1'})],
468+ 'lightgreen 1 FAIL green 1.1~beta\n',
469+ NOT_CONSIDERED,
470+ [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
471+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
472+ history="lightgreen 1 PASS lightgreen 1"
473+ )
474+
475+ def test_history_always_passed(self):
476+ '''All the results in history are PASS, and test passed
477+
478+ '''
479+
480+ self.do_test(
481+ [('green', {'Version': '1.1~beta', 'Depends': 'libc6 (>= 0.9), libgreen1'})],
482+ 'lightgreen 1 PASS green 1.1~beta\n',
483+ VALID_CANDIDATE,
484+ [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
485+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['PASS']],
486+ history="lightgreen 1 PASS lightgreen 1"
487+ )
488+
489+ def test_history_always_failed(self):
490+ '''All the results in history are FAIL, test fails. not a regression.
491+
492+ '''
493+
494+ self.do_test(
495+ [('green', {'Version': '1.1~beta', 'Depends': 'libc6 (>= 0.9), libgreen1'})],
496+ 'lightgreen 1 FAIL green 1.1~beta\n',
497+ VALID_CANDIDATE,
498+ [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
499+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['ALWAYSFAIL']],
500+ history="lightgreen 1 FAIL lightgreen 1"
501+ )
502+
503+ def test_history_regression(self):
504+ '''All the results in history are PASS, test fails. Blocked.
505+
506+ '''
507+ self.do_test(
508+ [('green', {'Version': '1.1~beta', 'Depends': 'libc6 (>= 0.9), libgreen1'})],
509+ 'lightgreen 1 FAIL green 1.1~beta\n',
510+ NOT_CONSIDERED,
511+ [r'\bgreen\b.*>1</a> to .*>1.1~beta<',
512+ '<li>autopkgtest for lightgreen 1: %s' % ADT_EXCUSES_LABELS['REGRESSION']],
513+ history="lightgreen 1 PASS lightgreen 1"
514+ )
515
516 def do_test(self, unstable_add, adt_request, considered, expect=None,
517- no_expect=None):
518+ no_expect=None, history=""):
519 for (pkg, fields) in unstable_add:
520 self.data.add(pkg, True, fields)
521
522- self.make_adt_britney(adt_request)
523+ self.make_adt_britney(adt_request, history)
524
525 (excuses, out) = self.run_britney()
526 #print('-------\nexcuses: %s\n-----' % excuses)
527@@ -437,7 +582,8 @@
528 self.data.add('yellow', True, {'Version': '1.1~beta',
529 'Depends': 'libc6 (>= 0.9), nosuchpkg'})
530
531- self.make_adt_britney('yellow 1.1~beta RUNNING yellow 1.1~beta\n')
532+ self.make_adt_britney('yellow 1.1~beta RUNNING yellow 1.1~beta\n',
533+ 'purple 2 FAIL pink 3.0.~britney\n')
534
535 print('run:\n%s -c %s\n' % (self.britney, self.britney_conf))
536 subprocess.call(['bash', '-i'], cwd=self.data.path)

Subscribers

People subscribed via source and target branches