Merge lp:~robru/cupstream2distro/fix-statuses into lp:cupstream2distro

Proposed by Robert Bruce Park
Status: Merged
Approved by: Robert Bruce Park
Approved revision: 1267
Merged at revision: 1204
Proposed branch: lp:~robru/cupstream2distro/fix-statuses
Merge into: lp:cupstream2distro
Diff against target: 2206 lines (+524/-619)
29 files modified
citrain/autopkgtests.py (+3/-1)
citrain/build.py (+12/-22)
citrain/jenkins-templates/check-publication-migration.xml.tmpl (+1/-1)
citrain/merge_clean.py (+3/-3)
citrain/migration.py (+10/-18)
citrain/prepare_silo.py (+1/-1)
citrain/publisher.py (+2/-2)
citrain/recipes/base.py (+141/-111)
citrain/recipes/manager.py (+13/-1)
citrain/recipes/merge.py (+12/-5)
citrain/revert.py (+3/-7)
cupstream2distro/archive.py (+23/-15)
cupstream2distro/errors.py (+2/-20)
cupstream2distro/packagemanager.py (+12/-12)
cupstream2distro/silomanager.py (+5/-59)
cupstream2distro/version.py (+1/-1)
tests/unit/test_archive.py (+9/-26)
tests/unit/test_packagemanager.py (+1/-10)
tests/unit/test_recipe_base.py (+135/-110)
tests/unit/test_recipe_manager.py (+29/-0)
tests/unit/test_recipe_merge.py (+32/-3)
tests/unit/test_script_autopkgtests.py (+2/-1)
tests/unit/test_script_build.py (+24/-49)
tests/unit/test_script_merge_clean.py (+3/-1)
tests/unit/test_script_migration.py (+34/-30)
tests/unit/test_script_prepare_silo.py (+1/-1)
tests/unit/test_script_publisher.py (+4/-4)
tests/unit/test_script_revert.py (+0/-8)
tests/unit/test_silomanager.py (+6/-97)
To merge this branch: bzr merge lp:~robru/cupstream2distro/fix-statuses
Reviewer Review Type Date Requested Status
Robert Bruce Park (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+277302@code.launchpad.net

Commit message

Improve reliability of status reporting for individual packages.

Description of the change

This branch is a little experimental, I'm trying to make packages a little more self-aware about what state they're in, rather than relying on local train state which often becomes inconsistent (for example the current issue with not being able to un-dirty silos after deleting commits that made them dirty in the first place).

To post a comment you must log in.
1257. By Robert Bruce Park

Better test isolation.

1258. By Robert Bruce Park

More publication blocker words.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1257
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/901/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/901/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1258
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/902/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/902/rebuild

review: Approve (continuous-integration)
1259. By Robert Bruce Park

EVEN FASTER Package.get_package_version().

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1259
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/903/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/903/rebuild

review: Approve (continuous-integration)
1260. By Robert Bruce Park

Speed up BuildBase._check_build_status()

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1260
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/904/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/904/rebuild

review: Approve (continuous-integration)
1261. By Robert Bruce Park

Drop migration frequency to 30 minutes due to slowness.

1262. By Robert Bruce Park

Debug logging.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1262
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/905/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/905/rebuild

review: Approve (continuous-integration)
1263. By Robert Bruce Park

Experimental speedup for Archive.find_archive_arches().

1264. By Robert Bruce Park

More experiments in speed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1264
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/906/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/906/rebuild

review: Needs Fixing (continuous-integration)
1265. By Robert Bruce Park

Logging tweak.

1266. By Robert Bruce Park

Fix tests.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1266
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/907/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/907/rebuild

review: Approve (continuous-integration)
1267. By Robert Bruce Park

Push more slow stuff out of check-publication-migration.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1267
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/908/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/908/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

Alright, I'm really happy with this & tested it well in staging. Will roll out first thing tomorrow.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'citrain/autopkgtests.py'
2--- citrain/autopkgtests.py 2015-11-10 17:50:43 +0000
3+++ citrain/autopkgtests.py 2015-11-18 06:50:36 +0000
4@@ -36,7 +36,9 @@
5
6 def autopkgtest(silo_state):
7 """Submit $SILONAME to debci for testing."""
8- Manager(silo_state).do('validate', 'amqp')
9+ manager = Manager(silo_state)
10+ manager.do('validate', 'amqp')
11+ silo_state.status = manager.get_states()
12
13
14 run_script(__name__, __doc__, lambda: stock_main(autopkgtest))
15
16=== modified file 'citrain/build.py'
17--- citrain/build.py 2015-11-06 21:45:41 +0000
18+++ citrain/build.py 2015-11-18 06:50:36 +0000
19@@ -46,7 +46,7 @@
20 from citrain.recipes.base import missing
21 from citrain.recipes.merge import check_for_unbuilt_revids
22 from citrain.recipes.manager import Manager
23-from cupstream2distro.silomanager import SiloState, get_dirty, splitter
24+from cupstream2distro.silomanager import splitter, stock_main
25 from cupstream2distro.errors import BuildError, CITrainError
26 from cupstream2distro.utils import env, run_script
27
28@@ -67,7 +67,6 @@
29 if check_for_unbuilt_revids(source, merges) == []:
30 logging.info(source + ' has no new commits, skipping.')
31 self.names.discard(source)
32- self.names.update(set(get_dirty(env.SILONAME)) & all_projects)
33 self.names.update(missing(self.names))
34 if not self.names:
35 raise BuildError(
36@@ -75,27 +74,18 @@
37 'PACKAGES_TO_REBUILD, FORCE_REBUILD, or WATCH_ONLY.')
38
39
40-def main(buildmanager=BuildManager):
41- """Execute the build, logging & saving any errors.
42-
43- :returns: 0 on success, 1 on any failure.
44- """
45- try:
46- silo_state = SiloState(env.SILONAME, primary=True)
47+def buildfactory(buildmanager=BuildManager):
48+ """Create a build function."""
49+ def build(silo_state):
50+ """Execute the build, logging & saving any errors."""
51 manager = buildmanager(silo_state)
52 manager.do('validate')
53 if env.WATCH_ONLY != 'true':
54 manager.do('clean', 'collect', 'build', 'upload', 'expose')
55- Manager(silo_state).do('watch', 'diff')
56- silo_state.status = 'Packages built.'
57- except CITrainError as err:
58- silo_state.status = err
59- silo_state.mark_dirty()
60- silo_state.save_config()
61- return 1
62- silo_state.mark_dirty()
63- silo_state.save_config()
64- return 0
65-
66-
67-run_script(__name__, __doc__, main)
68+ manager = Manager(silo_state)
69+ manager.do('watch', 'diff')
70+ silo_state.status = manager.get_states()
71+ return build
72+
73+
74+run_script(__name__, __doc__, lambda: stock_main(buildfactory(BuildManager)))
75
76=== modified file 'citrain/jenkins-templates/check-publication-migration.xml.tmpl'
77--- citrain/jenkins-templates/check-publication-migration.xml.tmpl 2015-10-27 19:46:01 +0000
78+++ citrain/jenkins-templates/check-publication-migration.xml.tmpl 2015-11-18 06:50:36 +0000
79@@ -38,7 +38,7 @@
80 <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
81 <triggers>
82 <hudson.triggers.TimerTrigger>
83- <spec>H/15 * * * *</spec>
84+ <spec>H/30 * * * *</spec>
85 </hudson.triggers.TimerTrigger>
86 </triggers>
87 <concurrentBuild>false</concurrentBuild>
88
89=== modified file 'citrain/merge_clean.py'
90--- citrain/merge_clean.py 2015-11-06 21:45:41 +0000
91+++ citrain/merge_clean.py 2015-11-18 06:50:36 +0000
92@@ -42,9 +42,9 @@
93
94 def merge(silo_state):
95 """Do the steps specific to merging & cleaning."""
96- Manager(silo_state).do(
97- 'validate', 'enumeration', 'merge', 'push', 'nuke')
98+ manager = Manager(silo_state)
99+ manager.do('validate', 'enumeration', 'merge', 'push', 'nuke')
100 silo_state.status = 'Landed'
101
102
103-run_script(__name__, __doc__, lambda: stock_main(merge, mark_dirty=True))
104+run_script(__name__, __doc__, lambda: stock_main(merge))
105
106=== modified file 'citrain/migration.py'
107--- citrain/migration.py 2015-11-09 17:06:20 +0000
108+++ citrain/migration.py 2015-11-18 06:50:36 +0000
109@@ -25,17 +25,16 @@
110 Enable debug infos
111 """
112
113+import re
114 import logging
115
116 from citrain.recipes.base import BuildBase
117 from citrain.recipes.manager import Manager
118-from cupstream2distro.errors import CITrainError
119 from cupstream2distro.utils import env, run_script
120 from cupstream2distro.silomanager import SiloState, stock_main
121 from citrain.merge_clean import merge
122
123-
124-CITrainError.prefix = 'Migration: '
125+SUCCESS = re.compile(r'^((Release|Updates) pocket \([^()]+\). ?)+$')
126
127
128 def main():
129@@ -46,27 +45,20 @@
130 for silo_state in SiloState.iterate():
131 env.SILONAME = silo_state
132 BuildBase.failures.clear()
133+ logging.info('\n\nInspecting silo %s:', silo_state.ppa.web_link)
134 try:
135 silo_state.enforce_lock()
136 silo_state.lock_fd.close()
137 except BlockingIOError:
138- logging.info('Silo %s is in use, skipping.\n', env.SILONAME)
139+ logging.info('Silo %s is in use, skipping.', env.SILONAME)
140 continue
141
142- if 'dirty' not in silo_state.tokenize():
143- logging.info('Inspecting silo %s:', silo_state.ppa.web_link)
144- manager = Manager(silo_state)
145- try:
146- manager.do('unbuilt')
147- if silo_state.is_autopkgtesting:
148- manager.do('autopkgtest')
149- if silo_state.is_published:
150- manager.do('migration')
151- stock_main(merge, mark_dirty=True)
152- except CITrainError as err:
153- silo_state.status = err
154- silo_state.mark_dirty()
155- silo_state.save_config()
156+ status = silo_state.status = Manager(silo_state).get_states()
157+ if SUCCESS.match(status):
158+ logging.info('Looks good, proceeding with merge & clean.')
159+ stock_main(merge)
160+ continue
161+ silo_state.save_config()
162 return 0
163
164
165
166=== modified file 'citrain/prepare_silo.py'
167--- citrain/prepare_silo.py 2015-11-06 21:45:41 +0000
168+++ citrain/prepare_silo.py 2015-11-18 06:50:36 +0000
169@@ -48,7 +48,7 @@
170 SiloState().assign(env.REQUEST_ID)
171 except CITrainError as err:
172 logging.error(str(err))
173- return 1
174+ return err.code
175 return 0
176
177
178
179=== modified file 'citrain/publisher.py'
180--- citrain/publisher.py 2015-11-09 18:09:39 +0000
181+++ citrain/publisher.py 2015-11-18 06:50:36 +0000
182@@ -51,9 +51,9 @@
183 'This silo must be transitioned to xenial before publishing.')
184 mgr = Manager(silo_state)
185 mgr.do('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging')
186- silo_state.status = 'Publishing.'
187+ silo_state.status = 'Beginning publication...'
188 silo_state.save_config()
189 mgr.do('dest_version_check', 'unapproved', 'unbuilt', 'publish')
190
191
192-run_script(__name__, __doc__, lambda: stock_main(publish, mark_dirty=True))
193+run_script(__name__, __doc__, lambda: stock_main(publish))
194
195=== modified file 'citrain/recipes/base.py'
196--- citrain/recipes/base.py 2015-11-10 18:16:52 +0000
197+++ citrain/recipes/base.py 2015-11-18 06:50:36 +0000
198@@ -25,6 +25,7 @@
199 import time
200
201 from urllib.request import urlretrieve
202+from collections import defaultdict
203 from os.path import exists, join
204 from os import remove, mkdir
205 from glob import glob
206@@ -41,7 +42,6 @@
207 AutopkgError,
208 BuildError,
209 PublishError,
210- MigrationError,
211 NoStatusError,
212 )
213 from cupstream2distro.settings import (
214@@ -100,21 +100,22 @@
215 return [TWINS[n] for n in names if n in TWINS and not TWINS[n] in names]
216
217
218-DO_NOT_PUBLISH = {'failed', 'error', 'dirty'}
219-BUILD_FAILURES = (
220+DO_NOT_PUBLISH = {'failed', 'error', 'rebuild', 'problem'}
221+BUILD_FAILURES = {
222 'Build for superseded Source',
223 'Cancelled build',
224 'Chroot problem',
225 'Failed to build',
226 'Failed to upload',
227-)
228-
229-
230+}
231+
232+
233+ARCH_FMT = '{} [{}]'
234 MISSING_VERSION = """\
235 {} {} is missing from the changelog, which has up to {}. \
236 Please sync destination version back to trunk."""
237-VERSION_MISMATCH = """\
238-Version of {} at dest changed from {} to {} since packages built."""
239+DEST_MISMATCH = """\
240+Destination version changed from {} to {} since packages built"""
241
242
243 AUTOPKGTEST_ARCHES = {'armhf', 'amd64', 'i386', 'ppc64el'}
244@@ -158,6 +159,13 @@
245 return response
246
247
248+def pretty_print_states(states, fmt='{} ({}).'):
249+ """Print a dict of lists in a nice way."""
250+ return ' '.join(
251+ fmt.format(stat, ', '.join(arch for arch in sorted(arches)))
252+ for stat, arches in sorted(states.items()))
253+
254+
255 class BuildBase(Package, Branch, Archive):
256 """Outlines build steps common to all types of package builds."""
257 blame = lp.people[env.BUILD_USER_ID] if env.BUILD_USER_ID else None
258@@ -165,7 +173,6 @@
259 packagelist = None
260 authorized = None
261 failures = set()
262- states = dict()
263
264 def __init__(self, source_name, series, dest, silo_ppa):
265 """Set instance variables needed for building a debian package.
266@@ -183,6 +190,107 @@
267 self.target = None
268 self.dest = dest
269
270+ def get_version(self):
271+ """Identify what version this package is."""
272+ for get_ver in (self.get_package_version, self.get_archive_version):
273+ version = get_ver()
274+ if version:
275+ return version
276+
277+ def check_status(self):
278+ """Identify this package's progress through the train."""
279+ checks = (
280+ self.check_silo_version,
281+ self.check_new_commits,
282+ self.check_destination_version,
283+ self.archive_pocket,
284+ self.archive_queue,
285+ self.check_autopkgtest_status,
286+ self.check_build_status,
287+ )
288+ version = self.get_version()
289+ for check in checks:
290+ logging.debug('Checking %s for %s', check, self.name)
291+ message = check(version)
292+ if message:
293+ return message
294+ return 'Unknown (this is a bug)'
295+
296+ def check_silo_version(self, version):
297+ """Check that local version matches silo version."""
298+ version = self.get_package_version()
299+ silo_version = self.get_archive_version()
300+ if not silo_version:
301+ return 'Not in PPA'
302+ if version and version != silo_version:
303+ return '{} not in PPA'.format(version)
304+
305+ def check_new_commits(self, version):
306+ """Implemented by subclasses."""
307+ pass
308+
309+ def check_destination_version(self, version=None):
310+ """Check that destination version unchanged since package built."""
311+ dest_version = self.get_archive_version(self.dest, self.series)
312+ supposed = self.dest_current_version
313+ log_value_of.dest_version('Dest archive has')
314+ log_value_of.supposed('At build time, dest had')
315+ if supposed and dest_version != supposed:
316+ return DEST_MISMATCH.format(supposed, dest_version)
317+
318+ def check_autopkgtest_status(self, version=None):
319+ """Check current status of autopkgtests, if any."""
320+ found = set()
321+ for arch in AUTOPKGTEST_ARCHES:
322+ stamp_key = self.timestamp_key(arch)
323+ our_stamp = self.get_file(stamp_key)
324+ if our_stamp is None:
325+ return
326+ local_path = self.result_tar_path(arch)
327+ log_value_of.local_path('Checking')
328+ response = check_swift(
329+ self.silo_ppa, self.series.name, arch, self.name, our_stamp)
330+ latest = our_stamp
331+ if response.status_code == 200:
332+ for line in reversed(sorted(response.iter_lines())):
333+ line = line.decode('utf-8')
334+ stamp = line.split('@')[0].split('/')[-1]
335+ latest = max(stamp, our_stamp, latest)
336+ if stamp < latest:
337+ break
338+ full_url = response.url.split('?')[0] + line
339+ logging.info(full_url)
340+ if line.endswith('result.tar') and stamp > our_stamp:
341+ self.set_file(stamp_key, latest)
342+ urlretrieve(full_url, local_path)
343+ log_value_of.local_path('Downloaded')
344+ if exists(local_path):
345+ found.add(arch)
346+ if AUTOPKGTEST_ARCHES - found:
347+ return 'Autopkgtests running'
348+ else:
349+ return self.inspect_tar_files()
350+
351+ def _check_build_status(self, version=None):
352+ """Report the build pass/fail status in the PPA."""
353+ watcher = ArchWatcher(self.arches)
354+ source = self.get_source_from_archive(
355+ version=version or self.get_version())
356+ states = defaultdict(list)
357+ if source:
358+ for build in source.getBuilds():
359+ if watcher(build):
360+ state = build.buildstate
361+ if state in BUILD_FAILURES:
362+ logging.error(build.build_log_url)
363+ states[state].append(build.arch_tag)
364+ return states
365+
366+ def check_build_status(self, version):
367+ """Check that build in PPA has succeeded."""
368+ return pretty_print_states(
369+ self._check_build_status(version), fmt=ARCH_FMT)
370+
371 def find_file(self, key):
372 """Identify where keyfiles are located."""
373 return '{}_{}'.format(self.path, key)
374@@ -217,6 +325,7 @@
375
376 def __getattr__(self, attr):
377 """Fetch some special properties."""
378+ log_value_of.attr('Getattr')
379 if attr in self.magic:
380 return self.get_file(attr)
381 raise AttributeError
382@@ -293,7 +402,7 @@
383 """Upload this locally built package to the silo PPA."""
384 logging.info('Uploading %s.', self.name)
385 ours = self.get_package_version()
386- silo = self.get_archive_version(self.silo_ppa)
387+ silo = self.get_archive_version()
388 log_value_of.ours()
389 log_value_of.silo()
390 if V(ours) > V(silo):
391@@ -312,7 +421,7 @@
392 silo_state.status = 'Building.'
393 silo_state.save_config()
394
395- def timed_out(self, started, timeout, message, condition=True):
396+ def timed_out(self, started, timeout):
397 """Determine whether or not a timeout has been reached.
398
399 :param started: The start time as returned by time.time().
400@@ -321,8 +430,8 @@
401 :param condition: A boolean that can disable the timeout if False.
402 :returns: True if the timeout has been reached, otherwise None.
403 """
404- if (time.time() - started) > timeout and condition:
405- self.failures.add(message)
406+ if (time.time() - started) > timeout:
407+ self.failures.add(self.name + ' timed out')
408 return True
409
410 def watch_phase(self):
411@@ -330,41 +439,23 @@
412
413 :returns: One of 'build failed', 'depwait', 'success', or 'timeout'
414 """
415- logging.info('Watching %s...', self.name)
416- arches = set(self.find_archive_arches(self.main_archive))
417- watcher = ArchWatcher(arches)
418- version = self.get_package_version()
419- kwargs = dict(source_name=self.name, exact_match=True)
420- if version:
421- # Expected to be '' for manual sources and binary syncs.
422- kwargs['version'] = version
423- log_value_of.kwargs('self.silo_ppa.getPublishedSources')
424+ version = self.get_version() or ''
425+ logging.info('Watching %s %s...', self.name, version)
426 started = time.time()
427 while True:
428- source = newest(self.silo_ppa, **kwargs)
429- if source is not None:
430- logging.info('Checking %s silo:', source.display_name)
431- builds = [b for b in source.getBuilds() if watcher(b)]
432- success = 0
433- for build in builds:
434- state = build.buildstate
435- logging.info('%s: %s', build.arch_tag, state)
436- if state in BUILD_FAILURES:
437- self.failures.add(
438- '{} failed to build.'.format(self.name))
439- logging.error(build.build_log_url)
440- return 'build failed'
441- if state == 'Successfully built':
442- success += 1
443- if state == 'Dependency wait':
444- message = '{} timeout for {}.'.format(state, self.name)
445- if self.timed_out(started, DEPWAIT_TIMEOUT, message):
446- return 'depwait'
447- if success == len(builds):
448- return 'success'
449- message = '{} {} not found in Silo PPA.'.format(self.name, version)
450- if self.timed_out(started, PPA_TIMEOUT, message, not source):
451+ states = self._check_build_status()
452+ if not states and self.timed_out(started, PPA_TIMEOUT):
453 return 'timeout'
454+ state_keys = states.keys()
455+ logging.info(pretty_print_states(states, fmt=ARCH_FMT))
456+ if 'Dependency wait' in states:
457+ if self.timed_out(started, DEPWAIT_TIMEOUT):
458+ return 'depwait'
459+ if state_keys & BUILD_FAILURES:
460+ self.failures.add('{} failed to build.'.format(self.name))
461+ return 'build failed'
462+ if state_keys == {'Successfully built'}:
463+ return 'success'
464 time.sleep(TIME_BETWEEN_PPA_CHECKS)
465
466 @staticmethod
467@@ -441,37 +532,6 @@
468 if process.returncode != 0:
469 raise AutopkgError('amqp-publish failed.')
470
471- def autopkgtest_phase(self):
472- """Poll swift for autopkgtest results."""
473- found = set()
474- for arch in AUTOPKGTEST_ARCHES:
475- stamp_key = self.timestamp_key(arch)
476- our_stamp = self.get_file(stamp_key)
477- local_path = self.result_tar_path(arch)
478- log_value_of.local_path('Checking')
479- response = check_swift(
480- self.silo_ppa, self.series.name, arch, self.name, our_stamp)
481- latest = our_stamp
482- if response.status_code == 200:
483- for line in reversed(sorted(response.iter_lines())):
484- line = line.decode('utf-8')
485- stamp = line.split('@')[0].split('/')[-1]
486- latest = max(stamp, our_stamp, latest)
487- if stamp < latest:
488- break
489- full_url = response.url.split('?')[0] + line
490- logging.info(full_url)
491- if line.endswith('result.tar') and stamp > our_stamp:
492- self.set_file(stamp_key, latest)
493- urlretrieve(full_url, local_path)
494- log_value_of.local_path('Downloaded')
495- if exists(local_path):
496- found.add(arch)
497- if AUTOPKGTEST_ARCHES - found:
498- raise AutopkgError('In progress.')
499- else:
500- self.inspect_tar_files()
501-
502 def inspect_tar_files(self):
503 """Check exitcodes in all tar files."""
504 codes = set()
505@@ -481,8 +541,9 @@
506 with tarfile.open(None, 'r', data) as tar:
507 codes.add(tar.extractfile('exitcode').read().strip())
508 if codes - {b'0', b'2'}:
509- raise AutopkgError('Failed (see log).')
510- raise AutopkgError('Success!')
511+ return 'Autopkgtests failed'
512+ else:
513+ return 'Autopkgtests succeeded'
514
515 @staticmethod
516 def post_checkstatus_phase(silo_state):
517@@ -490,9 +551,6 @@
518 if 'Publish failed' not in silo_state.status:
519 if DO_NOT_PUBLISH & silo_state.tokenize():
520 raise NoStatusError('Silo has bad status.')
521- if glob(SILO_DIR('*_is_dirty')):
522- silo_state.mark_dirty()
523- raise NoStatusError(silo_state.status)
524
525 def checkupload_phase(self):
526 """Confirm that the silo publisher has upload rights."""
527@@ -544,13 +602,8 @@
528 if env.IGNORE_VERSIONDESTINATION != 'true':
529 if lp.is_distro_archive(self.dest):
530 logging.info('Checking for new %s uploads at dest.', self.name)
531- version = self.get_archive_version(self.dest, self.series)
532- supposed = self.dest_current_version
533- log_value_of.version('Dest archive has')
534- log_value_of.supposed('At build time, dest had')
535- if supposed and version != supposed:
536- self.failures.add(
537- VERSION_MISMATCH.format(self.name, supposed, version))
538+ if self.check_destination_version():
539+ raise PublishError('Unexpected upload at destination.')
540
541 @classmethod
542 def pre_publish_phase(cls, silo_state):
543@@ -580,29 +633,6 @@
544 if cls.packagelist.lines:
545 cls.packagelist.write()
546
547- def migration_phase(self):
548- """Identify the location of this package in its migration."""
549- version = self.published_version
550- log_value_of.version('Checking for')
551- self.states[self.name] = self.check_archive_migration(version)
552- logging.info('%s: %s', self.name, self.states[self.name])
553-
554- @staticmethod
555- def post_migration_phase(silo_state):
556- """Prevent migration from proceeding to merge if not all migrated.
557-
558- :raises: MigrationError if not everything has fully migrated.
559- """
560- success = set(['Release pocket', 'Updates pocket'])
561- migrating = []
562- for source, location in BuildBase.states.items():
563- if location not in success:
564- migrating.append('{} is in the {}.'.format(source, location))
565- BuildBase.states.clear()
566- if migrating:
567- raise MigrationError(' '.join(sorted(migrating)))
568- logging.info('Looks good, proceeding with merge & clean.')
569-
570 def enumeration_phase(self):
571 """Halt merge&clean job if packages not in destination.
572
573
574=== modified file 'citrain/recipes/manager.py'
575--- citrain/recipes/manager.py 2015-11-04 15:43:33 +0000
576+++ citrain/recipes/manager.py 2015-11-18 06:50:36 +0000
577@@ -20,7 +20,9 @@
578
579 import logging
580
581-from citrain.recipes.base import BuildBase
582+from collections import defaultdict
583+
584+from citrain.recipes.base import BuildBase, pretty_print_states
585 from citrain.recipes.merge import Merge
586 from citrain.recipes.manual import Manual
587 from citrain.recipes.secondary import Secondary
588@@ -105,3 +107,13 @@
589 raise CITrainError('. '.join(
590 fail.strip('. ') for fail in sorted(BuildBase.failures)))
591 getattr(BuildBase, 'post_{}_phase'.format(phase), nop)(self.silo_state)
592+
593+ def get_states(self):
594+ """Report the statuses of all packages in this silo."""
595+ self.do() # Instantiates build objects if necessary.
596+ states = defaultdict(list)
597+ for name, build in sorted(self.builds.items()):
598+ states[build.check_status()].append(
599+ '{}/{}'.format(build.name, build.series.name))
600+ log_value_of.states()
601+ return pretty_print_states(states)
602
603=== modified file 'citrain/recipes/merge.py'
604--- citrain/recipes/merge.py 2015-11-04 15:43:33 +0000
605+++ citrain/recipes/merge.py 2015-11-18 06:50:36 +0000
606@@ -24,7 +24,6 @@
607 from cupstream2distro.version import V
608 from cupstream2distro.errors import BuildError, PrepError
609 from cupstream2distro.settings import BOT_DEBFULLNAME
610-from cupstream2distro.silomanager import mark_packages_dirty
611 from cupstream2distro.utils import (
612 SILO_DIR,
613 env,
614@@ -95,14 +94,18 @@
615 """Return a list of MPs that have new commits since last build."""
616 unbuilt = []
617 logging.info('Checking %s for new commits...', source)
618- # TODO: check also the merge target revids, which only just started being
619- # recorded and need time to appear in production.
620+ target = merges[0].target_branch
621+ url = target.web_link
622+ revid = read_revid(source, url)
623+ if revid and revid != target.last_scanned_id:
624+ unbuilt.append(url)
625 for merge in merges:
626 url = merge.web_link
627 revid = read_revid(source, url)
628 if merge.source_branch.last_scanned_id != revid:
629 unbuilt.append(url)
630- logging.info('New commits: ' + url)
631+ for url in unbuilt:
632+ logging.info('New commits: ' + url)
633 return unbuilt
634
635
636@@ -275,10 +278,14 @@
637 if env.ALLOW_UNAPPROVED != 'true':
638 self.enforce_merge_states(PUBLISHABLE_STATES)
639
640+ def check_new_commits(self, version=None):
641+ """Report if this package needs a rebuild, or not."""
642+ if check_for_unbuilt_revids(self.name, self.merges):
643+ return 'Needs rebuild due to new commits'
644+
645 def unbuilt_phase(self):
646 """Block publication of merges that have new commits."""
647 if check_for_unbuilt_revids(self.name, self.merges):
648- mark_packages_dirty(env.SILONAME, [self.name])
649 self.failures.add(self.name + ' has new, unbuilt commits.')
650
651 def merge_phase(self):
652
653=== modified file 'citrain/revert.py'
654--- citrain/revert.py 2015-11-06 21:45:41 +0000
655+++ citrain/revert.py 2015-11-18 06:50:36 +0000
656@@ -44,7 +44,8 @@
657 from citrain.recipes.sourcesync import SourceSync
658 from cupstream2distro.version import V
659 from cupstream2distro.archive import sort_by_date
660-from citrain.build import main as build_main
661+from citrain.build import buildfactory
662+from cupstream2distro.silomanager import stock_main
663 from cupstream2distro.errors import CITrainError, RevertError
664 from cupstream2distro.utils import (
665 SILO_DIR,
666@@ -110,9 +111,4 @@
667 Revert.versions[source_name] = version
668
669
670-def main():
671- """Execute the revert."""
672- return build_main(RevertManager)
673-
674-
675-run_script(__name__, __doc__, main)
676+run_script(__name__, __doc__, lambda: stock_main(buildfactory(RevertManager)))
677
678=== modified file 'cupstream2distro/archive.py'
679--- cupstream2distro/archive.py 2015-11-04 15:43:33 +0000
680+++ cupstream2distro/archive.py 2015-11-18 06:50:36 +0000
681@@ -30,6 +30,7 @@
682 from cupstream2distro.utils import (
683 log_call,
684 log_value_of,
685+ memoize,
686 suppress,
687 )
688
689@@ -61,6 +62,12 @@
690 """A mixin providing launchpad-archive related functions."""
691 dest = None
692
693+ @property
694+ @memoize
695+ def arches(self):
696+ """Return the set of arches that we care to watch."""
697+ return sorted(self.find_archive_arches(self.main_archive))
698+
699 def find_archive_arches(self, archive):
700 """Determine what arches we should watch by checking the destination.
701
702@@ -78,15 +85,15 @@
703 status='Published',
704 distro_series=self.series,
705 source_name=self.name)
706- if not source:
707- return
708- for binary in source.getPublishedBinaries():
709- # architecture_specific field filters out Arch:all packages showing
710- # up even on arches for which builds have failed.
711- if binary.architecture_specific:
712- yield binary.distro_arch_series.architecture_tag
713+ log_value_of.source('Inspecting this for arches')
714+ if source:
715+ return {
716+ binary.distro_arch_series_link.split('/')[-1]
717+ for binary in source.getPublishedBinaries()
718+ if binary.architecture_specific}
719+ return set()
720
721- def get_archive_version(self, archive, series=None):
722+ def get_archive_version(self, archive=None, series=None):
723 """Get current version for a package name in that series.
724
725 :param archive: An lp api archive object.
726@@ -94,20 +101,21 @@
727 :returns: The version of this package in the archive, or '0'.
728 """
729 series = series or self.series
730+ archive = archive or self.silo_ppa
731 assert hasattr(series, 'previous_series_link')
732 source = newest(archive, source_name=self.name, distro_series=series)
733- return source.source_package_version if source else '0'
734+ return source.source_package_version if source else None
735
736- def get_source_from_archive(self, archive=None):
737+ def get_source_from_archive(self, archive=None, version=None):
738 """Return the LP source package object representing this package.
739
740 :param archive: What archive to get from, defaults to self.silo_ppa.
741+ :param version: What version of the package to request.
742 """
743- return newest(
744- archive or self.silo_ppa,
745- status='Published',
746- distro_series=self.series,
747- source_name=self.name)
748+ kwargs = dict(distro_series=self.series, source_name=self.name)
749+ if version:
750+ kwargs['version'] = version
751+ return newest(archive or self.silo_ppa, **kwargs)
752
753 def copy_to_archive(self, source, archive=None):
754 """Copy this source package to a given archive.
755
756=== modified file 'cupstream2distro/errors.py'
757--- cupstream2distro/errors.py 2015-11-09 23:47:01 +0000
758+++ cupstream2distro/errors.py 2015-11-18 06:50:36 +0000
759@@ -22,7 +22,7 @@
760
761 class CITrainError(Exception):
762 """Base Exception class for all CI Train error conditions."""
763- prefix = 'Unknown error: '
764+ prefix = 'Failed: '
765 code = next(COUNTER)
766 loud = True
767 error = True
768@@ -36,66 +36,48 @@
769 """Special Exception that when raised won't set status."""
770 code = next(COUNTER)
771 loud = False
772- prefix = ''
773
774
775 class ArchiveError(CITrainError):
776 """Exception raised when there's a problem with a Launchpad archive."""
777- prefix = 'Launchpad error: '
778 code = next(COUNTER)
779
780
781 class BranchError(CITrainError):
782 """Exception raised when there's a problem with a Bzr branch."""
783- prefix = 'Branch error: '
784 code = next(COUNTER)
785
786
787 class PackageError(CITrainError):
788 """Exception raised when there's a problem with a debian package."""
789- prefix = 'Package error: '
790 code = next(COUNTER)
791
792
793 class PrepError(CITrainError):
794 """Exception raised when there's a problem during preparation."""
795- prefix = 'Prepare failed: '
796 code = next(COUNTER)
797
798
799 class PublishError(CITrainError):
800 """Exception raised when there's a problem during publishing."""
801- prefix = 'Publish failed: '
802 code = next(COUNTER)
803
804
805 class BuildError(CITrainError):
806 """Exception raised when there's a problem with building."""
807- prefix = 'Build failed: '
808 code = next(COUNTER)
809
810
811 class MergeError(CITrainError):
812 """Exception raised when there's a problem with merging."""
813- prefix = 'Merging failed: '
814- code = next(COUNTER)
815-
816-
817-class MigrationError(CITrainError):
818- """Exception raised to report on the silo migration status."""
819- prefix = 'Migration: '
820- code = next(COUNTER)
821- error = False
822+ code = next(COUNTER)
823
824
825 class RevertError(CITrainError):
826 """Exception raised when there's a problem with reverting."""
827- prefix = 'Revert failed: '
828 code = next(COUNTER)
829
830
831 class AutopkgError(CITrainError):
832 """Exception raised when there's a problem with autopkgtests."""
833- prefix = 'Autopkgtest: '
834 code = next(COUNTER)
835- error = False
836
837=== modified file 'cupstream2distro/packagemanager.py'
838--- cupstream2distro/packagemanager.py 2015-11-04 16:04:14 +0000
839+++ cupstream2distro/packagemanager.py 2015-11-18 06:50:36 +0000
840@@ -38,6 +38,7 @@
841 env,
842 log_call,
843 log_value_of,
844+ suppress,
845 utf8_inplace,
846 utf8_open,
847 )
848@@ -109,18 +110,17 @@
849
850 def get_package_version(self):
851 """Get newest version from debian changelog."""
852- key = 'Version: '
853- try:
854- inst, stdout, stderr = call(['dpkg-parsechangelog'], cwd=self.path)
855- except FileNotFoundError:
856- return None
857- if inst.returncode != 0:
858- raise PackageError(stderr)
859- for line in stdout.splitlines():
860- if line.startswith(key):
861- return line.partition(key)[-1]
862- raise PackageError(
863- 'dpkg-parsechangelog failure in {}.'.format(self.path))
864+ # Performance summary of `python3 -mtimeit`:
865+ # -s 'import re; x=re.compile("[()]")' 'x.split("foo (bar) baz")[1]'
866+ # 1000000 loops, best of 3: 0.811 usec per loop
867+ # '"foo (bar) baz".split("(")[-1].split(")")[0]'
868+ # 1000000 loops, best of 3: 0.446 usec per loop
869+ # 'x="foo (bar) baz"; x[x.find("(") + 1:x.find(")")]'
870+ # 1000000 loops, best of 3: 0.382 usec per loop
871+ with suppress(FileNotFoundError):
872+ with utf8_open(self.debian_changelog) as change:
873+ line = next(change)
874+ return line[line.index('(') + 1:line.index(')')]
875
876 def get_package_component(self):
877 """Get the selected package component (main, universe) for series"""
878
879=== modified file 'cupstream2distro/silomanager.py'
880--- cupstream2distro/silomanager.py 2015-11-06 15:40:26 +0000
881+++ cupstream2distro/silomanager.py 2015-11-18 06:50:36 +0000
882@@ -30,7 +30,7 @@
883
884 from glob import glob
885 from random import shuffle
886-from os.path import basename, dirname, join
887+from os.path import dirname, join
888 from collections import defaultdict
889
890 from lazr.restfulclient.errors import PreconditionFailed, NotFound
891@@ -91,7 +91,6 @@
892
893
894 DEFAULT_EXCEPTHOOK = sys.excepthook
895-DIRTY_FILE = join(SILOS_DIR, '{siloname}', '{package}_is_dirty')
896 JSON = {'content-type': 'application/json'}
897 MANDATORY = (
898 'description',
899@@ -108,19 +107,6 @@
900 return string.replace(',', ' ').strip().split()
901
902
903-def mark_packages_dirty(siloname, packages):
904- """Create {package}_is_dirty files in the silo dir."""
905- for package in packages:
906- with utf8_open(DIRTY_FILE.format(**locals()), 'w') as dirty_file:
907- dirty_file.write('We can rebuild it. We have the technology.')
908-
909-
910-def get_dirty(siloname, package='*'):
911- """Identify all dirty packages in a given silo."""
912- for path in glob(DIRTY_FILE.format(**locals())):
913- yield basename(path).split('_')[0]
914-
915-
916 def _bileto(api, **kwargs):
917 """Send details to bileto."""
918 try:
919@@ -144,29 +130,16 @@
920 _bileto(COMMENT_API, **kwargs)
921
922
923-def stock_main(body, mark_dirty=False):
924+def stock_main(body, returncode=0):
925 """Generic error handling for CI Train scripts."""
926 silo_state = SiloState(env.SILONAME, primary=True)
927 try:
928 body(silo_state)
929 except CITrainError as err:
930 silo_state.status = err
931- silo_state.mark_dirty()
932- silo_state.save_config()
933- return err.code
934- silo_state.mark_dirty()
935+ returncode = err.code
936 silo_state.save_config()
937- # This bit needs to be in a separate block because it's so slow that it
938- # makes the last call to save_config() end up losing races with
939- # check-publication-migration. Then you get logs that say
940- # 'Publishing. Migration:... Publishing. Migration:...'
941- if mark_dirty:
942- try:
943- silo_state.mark_others_dirty()
944- except Exception as err:
945- logging.warning('Had trouble marking other silos dirty:')
946- logging.warning(err)
947- return 0
948+ return returncode
949
950
951 class SiloState(object):
952@@ -312,33 +285,6 @@
953 with utf8_open(self.request_id_file.format(value), 'w') as rid:
954 rid.write(value)
955
956- def mark_others_dirty(self):
957- """Indicate that other silos need to be rebuilt."""
958- ours = set(self.all_projects)
959- log_value_of.ours('Looking for conflicts with')
960- for other in SiloState.iterate(ignore=self.siloname):
961- if self.series == other.series:
962- theirs = other.ppa.getPublishedSources(
963- distro_series=self.series, status='Published')
964- theirs = [pack.source_package_name for pack in theirs]
965- log_value_of.theirs('Checking conflicts')
966- conflicting = sorted(ours & set(theirs))
967- if conflicting:
968- self.info('Silo {} is now dirty.'.format(other))
969- other.mark_dirty(conflicting)
970-
971- def mark_dirty(self, packages=None):
972- """Indicate that this silo has packages needing a rebuild."""
973- msg = 'SILO DIRTY: You must rebuild: {}'
974- if packages:
975- mark_packages_dirty(self.siloname, packages)
976- # There may already be dirty packages from before, so we check that.
977- packages = sorted(get_dirty(self.siloname))
978- if packages:
979- self.status = msg.format(', '.join(packages))
980- self.push_to_bileto()
981- return packages
982-
983 def load_bileto(self):
984 """Fetch request from Bileto."""
985 rid = self.requestid
986@@ -414,7 +360,7 @@
987 siloname=self.siloname or self._siloname,
988 )
989 if self.strict:
990- # This is slow so don't do it when marking the entire world dirty.
991+ # This is slow so don't do it when iterating over all silos.
992 kwargs['sources'] = ' '.join(sorted(self.all_projects))
993 update_bileto(**kwargs)
994 bileto_comment(
995
996=== modified file 'cupstream2distro/version.py'
997--- cupstream2distro/version.py 2015-11-04 02:23:10 +0000
998+++ cupstream2distro/version.py 2015-11-18 06:50:36 +0000
999@@ -53,7 +53,7 @@
1000 self.v = version
1001
1002 def __str__(self):
1003- return self.v
1004+ return self.v or ''
1005
1006 def get_upstream(self, epoch=False):
1007 """Remove ubuntu & epoch data from source package version.
1008
1009=== modified file 'tests/unit/test_archive.py'
1010--- tests/unit/test_archive.py 2015-11-04 03:23:41 +0000
1011+++ tests/unit/test_archive.py 2015-11-18 06:50:36 +0000
1012@@ -21,38 +21,24 @@
1013 from mock import Mock, patch, call
1014
1015 from tests.unit import BaseTestCase
1016-from tests.unit.test_recipe_base import BuildFake
1017+from tests.unit.test_recipe_base import BuildFake, Listish
1018
1019 from cupstream2distro import archive
1020 from cupstream2distro.archive import Archive, ArchWatcher
1021 from cupstream2distro.errors import ArchiveError
1022
1023
1024-class Listish(list):
1025- """Pretend to be a lazr collection."""
1026- @property
1027- def total_size(self):
1028- """Report the length of this object."""
1029- return len(self)
1030-
1031-
1032-class DistroArchSeries(object):
1033- """Mimic a launchpadlib distro_arch_series object."""
1034- def __init__(self):
1035- self._arches = ['arm64', 'ppc64el', 'i386', 'amd64', 'powerpc']
1036-
1037- @property
1038- def architecture_tag(self):
1039- """Return and remove an arch from the list of arches."""
1040- return self._arches.pop()
1041-
1042-
1043 class BinaryFake(object):
1044 """Mimic a launchpadlib binary object."""
1045 def __init__(self, specific=True):
1046- self.distro_arch_series = DistroArchSeries()
1047+ self._arches = ['arm64', 'ppc64el', 'i386', 'amd64', 'powerpc']
1048 self.architecture_specific = specific
1049
1050+ @property
1051+ def distro_arch_series_link(self):
1052+ """Fake a URL pointing at a distro_arch_series."""
1053+ return 'http://example.com/long/url/pointing/to/' + self._arches.pop()
1054+
1055
1056 class ArchiveTests(BaseTestCase):
1057 """Test that we can handle a Branch!"""
1058@@ -163,12 +149,10 @@
1059
1060 @patch('cupstream2distro.archive.newest')
1061 def test_get_archive_version_none(self, new):
1062- """Return '0' when there is no archive version."""
1063+ """Return None when there is no archive version."""
1064 new.return_value = None
1065 dest = Mock()
1066- self.assertEqual(
1067- self.archive.get_archive_version(dest),
1068- '0')
1069+ self.assertIsNone(self.archive.get_archive_version(dest))
1070 new.assert_called_once_with(
1071 dest, source_name=self.archive.name,
1072 distro_series=self.archive.series)
1073@@ -201,7 +185,6 @@
1074 self.archive.silo_ppa.getPublishedSources.assert_called_once_with(
1075 distro_series=self.archive.series,
1076 source_name='archie',
1077- status='Published',
1078 exact_match=True)
1079
1080 def test_copy_to_archive(self):
1081
1082=== modified file 'tests/unit/test_packagemanager.py'
1083--- tests/unit/test_packagemanager.py 2015-10-05 22:42:23 +0000
1084+++ tests/unit/test_packagemanager.py 2015-11-18 06:50:36 +0000
1085@@ -92,16 +92,7 @@
1086 @patch('cupstream2distro.packagemanager.call')
1087 def test_get_package_version_failed(self, call_mock):
1088 """Raise PackageError when dpkg-parsechangelog errors out."""
1089- call_mock.return_value = (Mock(returncode=1), '', 'No such file')
1090- with self.assertRaisesRegexp(PackageError, 'No such file'):
1091- self.package.get_package_version()
1092-
1093- @patch('cupstream2distro.packagemanager.call')
1094- def test_get_package_version_failed2(self, call_mock):
1095- """Raise PackageError when no Version: field found."""
1096- call_mock.return_value = (Mock(returncode=0), '', '')
1097- with self.assertRaisesRegexp(PackageError, 'parsechangelog failure'):
1098- self.package.get_package_version()
1099+ self.assertIsNone(self.package.get_package_version())
1100
1101 def test_preserve_package_version(self):
1102 """Check debian/control for no-rewrite-version flag."""
1103
1104=== modified file 'tests/unit/test_recipe_base.py'
1105--- tests/unit/test_recipe_base.py 2015-11-10 18:16:52 +0000
1106+++ tests/unit/test_recipe_base.py 2015-11-18 06:50:36 +0000
1107@@ -29,8 +29,8 @@
1108 from citrain.recipes.base import (
1109 BuildBase,
1110 ClientError,
1111- DEPWAIT_TIMEOUT as DW,
1112- PPA_TIMEOUT as PT,
1113+ DEPWAIT_TIMEOUT,
1114+ PPA_TIMEOUT,
1115 check_swift,
1116 )
1117 from cupstream2distro.utils import env, utf8_open
1118@@ -38,7 +38,6 @@
1119 CITrainError,
1120 AutopkgError,
1121 PublishError,
1122- MigrationError,
1123 NoStatusError,
1124 )
1125
1126@@ -46,6 +45,14 @@
1127 N = '\n'
1128
1129
1130+class Listish(list):
1131+ """Pretend to be a lazr collection."""
1132+ @property
1133+ def total_size(self):
1134+ """Report the length of this object."""
1135+ return len(self)
1136+
1137+
1138 class BuildFake(object):
1139 """Mimic a launchpadlib build object."""
1140 def __init__(self, arch, status=''):
1141@@ -104,17 +111,88 @@
1142 for bit in bits:
1143 self.assertIn(bit, arg)
1144
1145+ @patch('cupstream2distro.archive.newest')
1146+ def test_check_status_missing(self, new):
1147+ """Correctly identify when package missing from PPA."""
1148+ new.return_value = None
1149+ build = BuildBase('state', self.series, self.dest, self.ppa)
1150+ build.get_package_version = Mock(return_value='42-0ubuntu1')
1151+ self.assertEqual(build.check_status(), 'Not in PPA')
1152+
1153+ @patch('cupstream2distro.archive.newest')
1154+ def test_check_status_mismatch(self, new):
1155+ """Correctly identify when PPA has wrong version."""
1156+ new.return_value.source_package_version = '41-0ubuntu1'
1157+ build = BuildBase('state', self.series, self.dest, self.ppa)
1158+ build.get_package_version = Mock(return_value='42-0ubuntu1')
1159+ self.assertEqual(build.check_status(), '42-0ubuntu1 not in PPA')
1160+
1161+ @patch('cupstream2distro.archive.newest')
1162+ def test_check_status_pocket(self, new):
1163+ """Correctly identify when PPA has wrong version."""
1164+ new.return_value.source_package_version = '42-0ubuntu1'
1165+ new.return_value.pocket = 'Proposed'
1166+ build = BuildBase('state', self.series, self.dest, self.ppa)
1167+ build.get_package_version = Mock(return_value='42-0ubuntu1')
1168+ self.assertEqual(build.check_status(), 'Proposed pocket')
1169+
1170+ @patch('cupstream2distro.archive.newest')
1171+ def test_check_status_queue(self, new):
1172+ """Correctly identify when PPA has wrong version."""
1173+ sources = {
1174+ self.dest: None,
1175+ self.ppa: Mock(source_package_version='42-0ubuntu1'),
1176+ }
1177+ new.side_effect = lambda archive, *ign, **ignore: sources.get(archive)
1178+ self.series.getPackageUploads.return_value = Listish(
1179+ [Mock(status='new')])
1180+ build = BuildBase('state', self.series, self.dest, self.ppa)
1181+ build.get_package_version = Mock(return_value='42-0ubuntu1')
1182+ self.assertEqual(build.check_status(), 'NEW queue')
1183+
1184+ @patch('cupstream2distro.archive.newest')
1185+ def test_check_status_build_state(self, new):
1186+ """Correctly identify when PPA has wrong version."""
1187+ build = Mock(buildstate='Successfully built', arch_tag='i386')
1188+ source = Mock(
1189+ source_package_version='42-0ubuntu1',
1190+ getBuilds=Mock(return_value=[build])
1191+ )
1192+ sources = {
1193+ self.dest: None,
1194+ self.ppa: source,
1195+ }
1196+ new.side_effect = lambda archive, *ign, **ignore: sources.get(archive)
1197+ self.series.getPackageUploads.return_value = Listish()
1198+ build = BuildBase('state', self.series, self.dest, self.ppa)
1199+ build.get_package_version = Mock(return_value='42-0ubuntu1')
1200+ self.assertEqual(build.check_status(), 'Successfully built [i386]')
1201+
1202+ @patch('cupstream2distro.archive.newest')
1203+ def test_check_status_unknown(self, new):
1204+ """Just give up"""
1205+ new.return_value = None
1206+ build = BuildBase('state', self.series, self.dest, self.ppa)
1207+ build.check_silo_version = Mock(return_value=None)
1208+ build.check_new_commits = Mock(return_value=None)
1209+ build.check_destination_version = Mock(return_value=None)
1210+ build.archive_pocket = Mock(return_value=None)
1211+ build.archive_queue = Mock(return_value=None)
1212+ build.check_autopkgtest_status = Mock(return_value=None)
1213+ build.check_build_status = Mock(return_value=None)
1214+ self.assertEqual(build.check_status(), 'Unknown (this is a bug)')
1215+
1216 def test_getattr(self):
1217 """Ensure we can read files from disk."""
1218 path = join(self.tempdir, 'getattr_published_version')
1219 with utf8_open(path, 'w') as data:
1220 data.write('hello')
1221- build = BuildBase('getattr', self.dest, self.series, self.ppa)
1222+ build = BuildBase('getattr', self.series, self.dest, self.ppa)
1223 self.assertEqual(build.published_version, 'hello')
1224
1225 def test_getattr_failed(self):
1226 """Raise AttributeError for wrong attributes."""
1227- build = BuildBase('wrong', self.dest, self.series, self.ppa)
1228+ build = BuildBase('wrong', self.series, self.dest, self.ppa)
1229 with self.assertRaises(AttributeError):
1230 build.zipblorp()
1231
1232@@ -125,7 +203,7 @@
1233 self.assertFalse(isfile(new_path))
1234 with utf8_open(path, 'w') as data:
1235 data.write('watever\nglorp\npublished_version = hiya\n')
1236- build = BuildBase('transition', self.dest, self.series, self.ppa)
1237+ build = BuildBase('transition', self.series, self.dest, self.ppa)
1238 self.assertEqual(build.published_version, 'hiya')
1239 with utf8_open(new_path) as data:
1240 self.assertEqual(data.read().strip(), 'hiya')
1241@@ -135,7 +213,7 @@
1242 path = join(self.tempdir, 'transition.project')
1243 with utf8_open(path, 'w') as data:
1244 data.write('published_version =\nha\n')
1245- build = BuildBase('transition', self.dest, self.series, self.ppa)
1246+ build = BuildBase('transition', self.series, self.dest, self.ppa)
1247 self.assertEqual(build.published_version, '')
1248
1249 def test_pre_validate(self):
1250@@ -265,21 +343,12 @@
1251 time_mock.time.return_value = 100
1252 BuildBase.failures = set()
1253 build = BuildBase('foo', self.series, self.dest, self.ppa)
1254- self.assertTrue(
1255- build.timed_out(started=50, timeout=10, message='failed at 10'))
1256- self.assertTrue(
1257- build.timed_out(started=0, timeout=99, message='failed at 99'))
1258- self.assertIsNone(
1259- build.timed_out(started=90, timeout=110, message='passed'))
1260- self.assertIsNone(
1261- build.timed_out(started=0, timeout=9, message='', condition=False))
1262- self.assertEqual(BuildBase.failures, set([
1263- 'failed at 10',
1264- 'failed at 99',
1265- ]))
1266+ self.assertTrue(build.timed_out(started=50, timeout=10))
1267+ self.assertTrue(build.timed_out(started=0, timeout=99))
1268+ self.assertIsNone(build.timed_out(started=90, timeout=110))
1269
1270 @patch('citrain.recipes.base.time')
1271- @patch('citrain.recipes.base.newest')
1272+ @patch('cupstream2distro.archive.newest')
1273 def test_buildbase_watch_phase(self, new_mock, time_mock):
1274 """A single build success is discovered immediately."""
1275 source = SourceFake('foo', '1.0-0ubuntu1')
1276@@ -294,15 +363,15 @@
1277 self.assertEqual(build.watch_phase(), 'success')
1278 new_mock.assert_called_once_with(
1279 self.ppa, source_name='foo', version='1.0-0ubuntu1',
1280- exact_match=True)
1281+ distro_series=build.series)
1282 build.find_archive_arches.assert_called_once_with(build.main_archive)
1283- build.get_package_version.assert_called_once_with()
1284+ self.assertEqual(build.get_package_version.mock_calls, [call()] * 2)
1285 time_mock.time.assert_called_once_with()
1286 self.assertEqual(time_mock.sleep.mock_calls, [])
1287 self.assertEqual(BuildBase.failures, set())
1288
1289 @patch('citrain.recipes.base.time')
1290- @patch('citrain.recipes.base.newest')
1291+ @patch('cupstream2distro.archive.newest')
1292 def test_buildbase_watch_phase_build_failure(self, new_mock, time_mock):
1293 """A single build success is discovered immediately."""
1294 source = SourceFake('foo', '1.0-0ubuntu1')
1295@@ -316,23 +385,23 @@
1296 build.timed_out = Mock(return_value=False)
1297 self.assertEqual(build.watch_phase(), 'build failed')
1298 new_mock.assert_called_once_with(
1299- self.ppa, source_name='foo', exact_match=True,
1300+ self.ppa, source_name='foo', distro_series=build.series,
1301 version='1.0-0ubuntu1')
1302 build.find_archive_arches.assert_called_once_with(build.main_archive)
1303- build.get_package_version.assert_called_once_with()
1304+ self.assertEqual(build.get_package_version.mock_calls, [call()] * 2)
1305 time_mock.time.assert_called_once_with()
1306 self.assertEqual(time_mock.sleep.mock_calls, [])
1307 self.assertEqual(
1308 BuildBase.failures, set(['foo failed to build.']))
1309
1310 @patch('citrain.recipes.base.time')
1311- @patch('citrain.recipes.base.newest')
1312+ @patch('cupstream2distro.archive.newest')
1313 def test_buildbase_watch_phase_dependency_wait(self, new_mock, time_mock):
1314 """Dependency wait times out after 1.5 hours."""
1315 t = time_mock.time.return_value
1316- timeouts = [True, False, False, False, False, False, False]
1317+ timeouts = [True, False, False, False]
1318 source = SourceFake('foo', '1.0-0ubuntu1')
1319- source._builds = [[BuildFake('amd64', 'Dependency wait')]] * 4
1320+ source._builds = [[BuildFake('amd64', 'Dependency wait')]] * 40
1321 self.ppa.getPublishedSources.return_value = source
1322 new_mock.return_value = (
1323 self.ppa.getPublishedSources.return_value)
1324@@ -341,30 +410,26 @@
1325 build.find_archive_arches = Mock(return_value=['amd64'])
1326 build.timed_out = Mock(side_effect=lambda *ignore: timeouts.pop())
1327 self.assertEqual(build.watch_phase(), 'depwait')
1328- pprint(new_mock.mock_calls)
1329 self.assertEqual(new_mock.mock_calls, [
1330- call(self.ppa, exact_match=True, source_name='foo',
1331- version='1.0-0ubuntu1'),
1332+ call(self.ppa, source_name='foo',
1333+ version='1.0-0ubuntu1', distro_series=build.series),
1334 ] * 4)
1335 build.find_archive_arches.assert_called_once_with(build.main_archive)
1336- build.get_package_version.assert_called_once_with()
1337+ self.assertEqual(build.get_package_version.mock_calls, [call()] * 5)
1338 time_mock.time.assert_called_once_with()
1339 self.assertEqual(time_mock.sleep.mock_calls, [
1340 call(300), call(300), call(300),
1341 ])
1342 self.assertEqual(
1343 build.timed_out.mock_calls, [
1344- call(t, DW, 'Dependency wait timeout for foo.'),
1345- call(t, PT, 'foo 1.0-0ubuntu1 not found in Silo PPA.', False),
1346- call(t, DW, 'Dependency wait timeout for foo.'),
1347- call(t, PT, 'foo 1.0-0ubuntu1 not found in Silo PPA.', False),
1348- call(t, DW, 'Dependency wait timeout for foo.'),
1349- call(t, PT, 'foo 1.0-0ubuntu1 not found in Silo PPA.', False),
1350- call(t, DW, 'Dependency wait timeout for foo.'),
1351+ call(t, DEPWAIT_TIMEOUT),
1352+ call(t, DEPWAIT_TIMEOUT),
1353+ call(t, DEPWAIT_TIMEOUT),
1354+ call(t, DEPWAIT_TIMEOUT),
1355 ])
1356
1357 @patch('citrain.recipes.base.time')
1358- @patch('citrain.recipes.base.newest')
1359+ @patch('cupstream2distro.archive.newest')
1360 def test_buildbase_watch_phase_upload_failed(self, new_mock, time_mock):
1361 """Sometimes dput claims success but the PPA does not accept it."""
1362 t = time_mock.time.return_value
1363@@ -380,18 +445,18 @@
1364 pprint(new_mock.mock_calls)
1365 self.assertEqual(new_mock.mock_calls, [
1366 call(self.ppa, version='1.0-0ubuntu1',
1367- source_name='foo', exact_match=True),
1368+ source_name='foo', distro_series=build.series),
1369 ] * 4)
1370 self.assertEqual(time_mock.sleep.mock_calls, [
1371 call(300), call(300), call(300),
1372 ])
1373 self.assertEqual(
1374 build.timed_out.mock_calls, [
1375- call(t, PT, 'foo 1.0-0ubuntu1 not found in Silo PPA.', True),
1376+ call(t, PPA_TIMEOUT),
1377 ] * 4)
1378
1379 @patch('citrain.recipes.base.time')
1380- @patch('citrain.recipes.base.newest')
1381+ @patch('cupstream2distro.archive.newest')
1382 def test_buildbase_watch_phase_upload_failed_2(self, new_mock, time_mock):
1383 """PPA contains old, Deleted builds, but nothing current."""
1384 t = time_mock.time.return_value
1385@@ -399,20 +464,21 @@
1386 self.ppa.getPublishedSources.return_value = 'Deleted'
1387 new_mock.return_value = None
1388 build = BuildBase('foo', self.series, self.dest, self.ppa)
1389+ build.get_version = Mock(return_value='')
1390 build.get_package_version = Mock(return_value='')
1391 build.find_archive_arches = Mock(return_value=['amd64'])
1392 build.timed_out = Mock(side_effect=lambda *ignore: timeouts.pop())
1393 self.assertEqual(build.watch_phase(), 'timeout')
1394 pprint(new_mock.mock_calls)
1395 self.assertEqual(new_mock.mock_calls, [
1396- call(self.ppa, source_name='foo', exact_match=True),
1397+ call(self.ppa, source_name='foo', distro_series=build.series),
1398 ] * 4)
1399 self.assertEqual(time_mock.sleep.mock_calls, [
1400 call(300), call(300), call(300),
1401 ])
1402 self.assertEqual(
1403 build.timed_out.mock_calls, [
1404- call(t, PT, 'foo not found in Silo PPA.', True),
1405+ call(t, PPA_TIMEOUT),
1406 ] * 4)
1407
1408 def test_buildbase_pre_diff_phase(self):
1409@@ -626,7 +692,7 @@
1410 @patch('citrain.recipes.base.requests')
1411 @patch('citrain.recipes.base.urlretrieve')
1412 @patch('citrain.recipes.base.AUTOPKGTEST_ARCHES', {'armhf'})
1413- def test_buildbase_autopkgtest_phase(self, url_mock, req_mock):
1414+ def test_buildbase_check_autopkgtest_status(self, url_mock, req_mock):
1415 """Ensure autopkgtests are polled correctly."""
1416 req_mock.get.return_value.status_code = 200
1417 req_mock.get.return_value.iter_lines.return_value = [
1418@@ -641,8 +707,8 @@
1419 build.silo_ppa.owner.name = 'polling'
1420 build.inspect_tar_files = Mock()
1421 build.get_file = Mock(return_value='20151106_141604')
1422- with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest: In prog'):
1423- build.autopkgtest_phase()
1424+ self.assertEqual(
1425+ build.check_autopkgtest_status(), 'Autopkgtests running')
1426 url = req_mock.mock_calls[0][1][0]
1427 self.assertIn('autopkgtest-wily-polling-landing-543/', url)
1428 self.assertIn('marker=wily%2Farmhf%2Fw%2Fwhip%2F20151106_141604', url)
1429@@ -660,9 +726,9 @@
1430 @patch('citrain.recipes.base.requests')
1431 @patch('citrain.recipes.base.urlretrieve')
1432 @patch('citrain.recipes.base.AUTOPKGTEST_ARCHES', {'armhf'})
1433- def test_buildbase_autopkgtest_phase_success(self, url_mock, req, exists):
1434+ def test_buildbase_check_autopkgtest_status_success(self, url, req, exist):
1435 """Inspect tarballs if all are found."""
1436- exists.return_value = True
1437+ exist.return_value = True
1438 req.get.return_value.status_code = 200
1439 req.get.return_value.iter_lines.return_value = [
1440 b'foo/20151106_100104@log.gz',
1441@@ -676,35 +742,37 @@
1442 build.silo_ppa.owner.name = 'polling'
1443 build.inspect_tar_files = Mock()
1444 build.get_file = Mock(return_value='20151106_141604')
1445- build.autopkgtest_phase()
1446+ self.assertEqual(
1447+ build.check_autopkgtest_status(),
1448+ build.inspect_tar_files.return_value)
1449 req.get.assert_called_once_with(req.mock_calls[0][1][0])
1450 req.get.return_value.iter_lines.assert_called_once_with()
1451- self.assertEqual(url_mock.mock_calls, [])
1452+ self.assertEqual(url.mock_calls, [])
1453 self.assertEqual(build.inspect_tar_files.mock_calls, [call()])
1454
1455 @patch('citrain.recipes.base.requests')
1456 @patch('citrain.recipes.base.urlretrieve')
1457 @patch('citrain.recipes.base.AUTOPKGTEST_ARCHES', {'armhf'})
1458- def test_buildbase_autopkgtest_phase_stale(self, url_mock, req_mock):
1459+ def test_buildbase_check_autopkgtest_status_stale(self, url, req):
1460 """Skip over stale results from swift."""
1461- req_mock.get.return_value.status_code = 200
1462- req_mock.get.return_value.iter_lines.return_value = [
1463+ req.get.return_value.status_code = 200
1464+ req.get.return_value.iter_lines.return_value = [
1465 b'foo/20151106_100104@log.gz',
1466 b'foo/20151106_100104@result.tar',
1467 b'foo/20151106_100104@whatever',
1468 ]
1469- req_mock.get.return_value.url = 'http://example.com/?bar'
1470+ req.get.return_value.url = 'http://example.com/?bar'
1471 build = BuildBase('whip', self.series, self.dest, self.ppa)
1472 build.series.name = 'wily'
1473 build.silo_ppa.name = 'landing-543'
1474 build.silo_ppa.owner.name = 'polling'
1475 build.inspect_tar_files = Mock()
1476 build.get_file = Mock(return_value='20151106_141604')
1477- with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest: In prog'):
1478- build.autopkgtest_phase()
1479- req_mock.get.assert_called_once_with(req_mock.mock_calls[0][1][0])
1480- req_mock.get.return_value.iter_lines.assert_called_once_with()
1481- self.assertEqual(url_mock.mock_calls, [])
1482+ self.assertEqual(
1483+ build.check_autopkgtest_status(), 'Autopkgtests running')
1484+ req.get.assert_called_once_with(req.mock_calls[0][1][0])
1485+ req.get.return_value.iter_lines.assert_called_once_with()
1486+ self.assertEqual(url.mock_calls, [])
1487 self.assertEqual(build.inspect_tar_files.mock_calls, [])
1488
1489 def test_buildbase_inspect_tar_files_pass(self):
1490@@ -718,8 +786,7 @@
1491 for inn, out in cases:
1492 shutil.copy(join(self.tarballs_dir, inn), join(self.tempdir, out))
1493 build = BuildBase('pass', self.series, self.dest, self.ppa)
1494- with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest: Success!'):
1495- build.inspect_tar_files()
1496+ self.assertEqual(build.inspect_tar_files(), 'Autopkgtests succeeded')
1497
1498 def test_buildbase_inspect_tar_files_fail(self):
1499 """Ensure failing tar files are reported correctly."""
1500@@ -732,8 +799,7 @@
1501 for inn, out in cases:
1502 shutil.copy(join(self.tarballs_dir, inn), join(self.tempdir, out))
1503 build = BuildBase('fail', self.series, self.dest, self.ppa)
1504- with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest: Failed'):
1505- build.inspect_tar_files()
1506+ self.assertEqual(build.inspect_tar_files(), 'Autopkgtests failed')
1507
1508 def test_buildbase_post_checkstatus_phase(self):
1509 """Silently allow publication when no problems found."""
1510@@ -752,23 +818,12 @@
1511 def test_buildbase_post_checkstatus_phase_fail(self):
1512 """Ensure that publish is halted if silo in bad state."""
1513 silo_state = Mock()
1514- for problem in ('failed', 'dirty', 'error'):
1515+ for problem in ('failed', 'error'):
1516 silo_state.tokenize.return_value = {'everything', 'is', problem}
1517 silo_state.status = 'Everything is ' + problem
1518 with self.assertRaisesRegexp(NoStatusError, 'Silo has bad status'):
1519 BuildBase.post_checkstatus_phase(silo_state)
1520
1521- @patch('citrain.recipes.base.glob')
1522- def test_buildbase_post_checkstatus_phase_dirty(self, glob_mock):
1523- """Ensure that publish is halted if silo in bad state."""
1524- silo_state = Mock()
1525- silo_state.tokenize.return_value = {'everything', 'is', 'awesome'}
1526- silo_state.status = 'Soooo dirty'
1527- glob_mock.return_value = ['foo_is_dirty', 'bar_is_dirty']
1528- with self.assertRaisesRegexp(NoStatusError, 'Soooo dirty'):
1529- BuildBase.post_checkstatus_phase(silo_state)
1530- silo_state.mark_dirty.assert_called_once_with()
1531-
1532 @patch('citrain.recipes.base.lp')
1533 def test_buildbase_checkupload_phase(self, lp_mock):
1534 """Ensure that we call checkupload correctly."""
1535@@ -882,11 +937,9 @@
1536 build = BuildBase('grue', self.series, self.dest, self.ppa)
1537 build.dest_current_version = '1.0'
1538 build.get_archive_version.return_value = '1.1'
1539- build.dest_version_check_phase()
1540- self.assertEqual(build.failures, {
1541- 'Version of grue at dest changed from 1.0 to 1.1 '
1542- 'since packages built.',
1543- })
1544+ with self.assertRaisesRegexp(PublishError, 'Unexpected upload'):
1545+ build.dest_version_check_phase()
1546+ self.assertEqual(build.failures, set())
1547
1548 @patch('citrain.recipes.base.lp')
1549 @patch('citrain.recipes.base.BuildBase.get_archive_version', Mock())
1550@@ -979,34 +1032,6 @@
1551 self.assertEqual(silo_state.mock_calls, [])
1552 BuildBase.packagelist.write.assert_called_once_with() # ONCE
1553
1554- def test_buildbase_migration_phase(self):
1555- """Report the location status of this package."""
1556- build = BuildBase('foo', self.series, self.dest, self.ppa)
1557- build.published_version = '2.0'
1558- build.check_archive_migration = Mock(return_value='Release pocket')
1559- build.migration_phase()
1560- build.check_archive_migration.assert_called_once_with('2.0')
1561- self.assertEqual(
1562- BuildBase.states,
1563- dict(foo='Release pocket'))
1564-
1565- def test_buildbase_post_migration_phase(self):
1566- """Silently allow merging to proceed if everything is migrated."""
1567- silo_state = Mock()
1568- BuildBase.states = dict(
1569- foo='Release pocket', bar='Updates pocket')
1570- BuildBase.post_migration_phase(silo_state)
1571- self.assertEqual(BuildBase.states, {})
1572-
1573- def test_buildbase_post_migration_phase_incomplete(self):
1574- """Raise exception if not everything has migrated."""
1575- silo_state = Mock()
1576- BuildBase.states = dict(
1577- foo='UNAPPROVED queue', bar='void')
1578- with self.assertRaisesRegexp(MigrationError, 'bar is in the void'):
1579- BuildBase.post_migration_phase(silo_state)
1580- self.assertEqual(BuildBase.states, {})
1581-
1582 def test_buildbase_enumeration_phase(self):
1583 """Don't raise an exception when versions match."""
1584 build = BuildBase('foo', self.series, self.dest, self.ppa)
1585
1586=== modified file 'tests/unit/test_recipe_manager.py'
1587--- tests/unit/test_recipe_manager.py 2015-10-07 20:47:51 +0000
1588+++ tests/unit/test_recipe_manager.py 2015-11-18 06:50:36 +0000
1589@@ -159,3 +159,32 @@
1590 merge.liquid_phase.assert_called_once_with()
1591 sync.liquid_phase.assert_called_once_with()
1592 self.assertEqual(bbase.post_liquid_phase.mock_calls, [])
1593+
1594+ def test_manager_get_states(self):
1595+ """Fetch all the states from all the builds."""
1596+ silo_state = Mock()
1597+ one = Mock(check_status=Mock(return_value='Successfully built'))
1598+ one.name = 'one'
1599+ two = Mock(check_status=Mock(return_value='Successfully built'))
1600+ two.name = 'two'
1601+ one.series.name = two.series.name = 'xenial'
1602+ bman = Manager(silo_state)
1603+ bman.types = True
1604+ bman.builds = dict(one=one, two=two)
1605+ self.assertEqual(
1606+ bman.get_states(), 'Successfully built (one/xenial, two/xenial).')
1607+
1608+ def test_manager_get_states_multi(self):
1609+ """Fetch all the states from all the builds."""
1610+ silo_state = Mock()
1611+ one = Mock(check_status=Mock(return_value='Successfully built'))
1612+ one.name = 'one'
1613+ two = Mock(check_status=Mock(return_value='Failed to build'))
1614+ two.name = 'two'
1615+ one.series.name = two.series.name = 'xenial'
1616+ bman = Manager(silo_state)
1617+ bman.types = True
1618+ bman.builds = dict(one=one, two=two)
1619+ self.assertEqual(
1620+ bman.get_states(),
1621+ 'Failed to build (two/xenial). Successfully built (one/xenial).')
1622
1623=== modified file 'tests/unit/test_recipe_merge.py'
1624--- tests/unit/test_recipe_merge.py 2015-11-04 15:43:33 +0000
1625+++ tests/unit/test_recipe_merge.py 2015-11-18 06:50:36 +0000
1626@@ -442,28 +442,57 @@
1627 self.assertFalse(merge.failures)
1628
1629 @patch('citrain.recipes.merge.read_revid')
1630- @patch('citrain.recipes.merge.mark_packages_dirty')
1631- def test_merge_unbuilt_phase(self, dirty, rev_mock):
1632+ def test_merge_check_new_commits(self, rev_mock):
1633+ """Report if this package has new commits."""
1634+ rev_mock.return_value = None
1635+ Merge.all_mps = dict(checkered=[Mock(
1636+ web_link='foo',
1637+ source_branch=Mock(last_scanned_id='zip'),
1638+ target_branch=Mock(last_scanned_id='zip', web_link='trunk'),
1639+ )])
1640+ merge = Merge('checkered', self.series, self.dest, self.ppa)
1641+ self.assertIn('Needs rebuild', merge.check_new_commits())
1642+ rev_mock.return_value = 'zip'
1643+ self.assertIsNone(merge.check_new_commits())
1644+
1645+ @patch('citrain.recipes.merge.read_revid')
1646+ def test_merge_unbuilt_phase(self, rev_mock):
1647 """Block publication if merges have new commits."""
1648 rev_mock.return_value = None
1649 Merge.all_mps = dict(foobaz=[Mock(
1650 web_link='foo',
1651 source_branch=Mock(last_scanned_id='zip'),
1652+ target_branch=Mock(last_scanned_id='zip', web_link='trunk'),
1653 )])
1654 merge = Merge('foobaz', self.series, self.dest, self.ppa)
1655 merge.unbuilt_phase()
1656- dirty.assert_called_once_with(self.tempdir, ['foobaz'])
1657 self.assertEqual(merge.failures, {
1658 'foobaz has new, unbuilt commits.'
1659 })
1660
1661 @patch('citrain.recipes.merge.read_revid')
1662+ def test_merge_unbuilt_phase_trunk(self, rev_mock):
1663+ """Block publication if trunk has new commits."""
1664+ rev_mock.return_value = 'match'
1665+ Merge.all_mps = dict(trunky=[Mock(
1666+ web_link='foo',
1667+ source_branch=Mock(last_scanned_id='match'),
1668+ target_branch=Mock(last_scanned_id='newness', web_link='trunk'),
1669+ )])
1670+ merge = Merge('trunky', self.series, self.dest, self.ppa)
1671+ merge.unbuilt_phase()
1672+ self.assertEqual(merge.failures, {
1673+ 'trunky has new, unbuilt commits.'
1674+ })
1675+
1676+ @patch('citrain.recipes.merge.read_revid')
1677 def test_merge_unbuilt_phase_none(self, rev_mock):
1678 """Allow publication if there are no new commits."""
1679 rev_mock.return_value = 'zip'
1680 Merge.all_mps = dict(foobaz=[Mock(
1681 web_link='foo',
1682 source_branch=Mock(last_scanned_id='zip'),
1683+ target_branch=Mock(last_scanned_id='zip', web_link='trunk'),
1684 )])
1685 merge = Merge('foobaz', self.series, self.dest, self.ppa)
1686 merge.unbuilt_phase()
1687
1688=== modified file 'tests/unit/test_script_autopkgtests.py'
1689--- tests/unit/test_script_autopkgtests.py 2015-11-06 18:20:52 +0000
1690+++ tests/unit/test_script_autopkgtests.py 2015-11-18 06:50:36 +0000
1691@@ -33,13 +33,14 @@
1692 self.assertEqual(self.script.Manager.mock_calls, [
1693 call(silo_state),
1694 call().do('validate', 'amqp'),
1695+ call().get_states(),
1696 ])
1697
1698 def test_main_fail(self):
1699 """Ensure main() does error reporting correctly."""
1700 silo_state = Mock()
1701 self.script.Manager.return_value.do.side_effect = AutopkgError('no')
1702- with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest: no'):
1703+ with self.assertRaisesRegexp(AutopkgError, 'Autopkgtest failed: no'):
1704 self.script.autopkgtest(silo_state)
1705 self.assertEqual(self.script.Manager.mock_calls, [
1706 call(silo_state),
1707
1708=== modified file 'tests/unit/test_script_build.py'
1709--- tests/unit/test_script_build.py 2015-11-06 06:35:12 +0000
1710+++ tests/unit/test_script_build.py 2015-11-18 06:50:36 +0000
1711@@ -46,34 +46,22 @@
1712 mps=dict(
1713 c=[Mock(
1714 source_branch=Mock(last_scanned_id=None),
1715+ target_branch=Mock(
1716+ last_scanned_id='foo', web_link='foo/trunk'),
1717 web_link='foo/123')],
1718 d=[Mock(
1719 source_branch=Mock(last_scanned_id='stale'),
1720+ target_branch=Mock(
1721+ last_scanned_id='foo', web_link='foo/trunk'),
1722 web_link='foo/789')]),
1723 sources=list('abcd'))
1724 self.script.env.PACKAGES_TO_REBUILD = ''
1725 self.script.env.FORCE_REBUILD = 'false'
1726+ self.script.env.WATCH_ONLY = 'false'
1727 bman = self.script.BuildManager(silo_state)
1728 bman.choose_packages()
1729 self.assertEqual(bman.names, {'a', 'b', 'd'})
1730
1731- def test_buildmanager_choose_packages_dirty(self):
1732- """Include dirty packages by default."""
1733- self.script.get_dirty = Mock(return_value=list('abcde'))
1734- silo_state = Mock(
1735- all_projects=list('abcd'),
1736- mps=dict(
1737- c=[Mock(
1738- source_branch=Mock(last_scanned_id=None),
1739- web_link='foo/123')]),
1740- sources=list('abcd'))
1741- self.script.env.PACKAGES_TO_REBUILD = ''
1742- self.script.env.FORCE_REBUILD = 'false'
1743- bman = self.script.BuildManager(silo_state)
1744- bman.choose_packages()
1745- self.script.get_dirty.assert_called_once_with(self.tempdir)
1746- self.assertEqual(bman.names, set('abcd'))
1747-
1748 def test_buildmanager_choose_packages_twins(self):
1749 """Always add twins for all builds."""
1750 sources = ['qtmir', 'qtmir-gles', 'foo']
1751@@ -91,9 +79,13 @@
1752 mps={
1753 'qtmir': [Mock(
1754 source_branch=Mock(last_scanned_id=None),
1755+ target_branch=Mock(
1756+ last_scanned_id='foo', web_link='foo/trunk'),
1757 web_link='qtmir/123')],
1758 'qtmir-gles': [Mock(
1759 source_branch=Mock(last_scanned_id='stale'),
1760+ target_branch=Mock(
1761+ last_scanned_id='foo', web_link='foo/trunk'),
1762 web_link='qtmir-gles/789')]},
1763 sources=['qtmir', 'qtmir-gles'])
1764 self.script.env.PACKAGES_TO_REBUILD = ''
1765@@ -108,6 +100,8 @@
1766 all_projects=['a'],
1767 mps=dict(a=[Mock(
1768 source_branch=Mock(last_scanned_id=None),
1769+ target_branch=Mock(
1770+ last_scanned_id='foo', web_link='foo/trunk'),
1771 web_link='foo/123')]),
1772 sources=[])
1773 self.script.env.PACKAGES_TO_REBUILD = ''
1774@@ -118,55 +112,36 @@
1775 bman.choose_packages()
1776 self.assertEqual(bman.names, set())
1777
1778- def test_main(self):
1779+ def test_build(self):
1780 """Run things without issue."""
1781 self.script.env.WATCH_ONLY = 'false'
1782- silo_state = self.script.SiloState.return_value
1783+ silo_state = Mock()
1784 bman = self.script.BuildManager = Mock()
1785- self.assertEqual(self.script.main(bman), 0)
1786+ self.assertIsNone(self.script.buildfactory(bman)(silo_state))
1787 bman.assert_called_once_with(silo_state)
1788 self.assertEqual(bman.return_value.mock_calls, [
1789 call.do('validate'),
1790 call.do('clean', 'collect', 'build', 'upload', 'expose'),
1791 ])
1792- self.assertEqual(self.script.Manager.return_value.mock_calls, [
1793+ manager = self.script.Manager.return_value
1794+ self.assertEqual(manager.mock_calls, [
1795 call.do('watch', 'diff'),
1796+ call.get_states(),
1797 ])
1798- self.assertEqual(silo_state.status, 'Packages built.')
1799- silo_state.mark_dirty.assert_called_once_with()
1800- silo_state.save_config.assert_called_once_with()
1801+ self.assertEqual(silo_state.status, manager.get_states())
1802
1803- def test_main_watch_only(self):
1804+ def test_build_watch_only(self):
1805 """Fewer phases during WATCH_ONLY."""
1806 self.script.env.WATCH_ONLY = 'true'
1807- silo_state = self.script.SiloState.return_value
1808+ silo_state = Mock()
1809 bman = self.script.BuildManager = Mock()
1810- self.assertEqual(self.script.main(bman), 0)
1811+ self.assertIsNone(self.script.buildfactory(bman)(silo_state))
1812 bman.assert_called_once_with(silo_state)
1813 self.assertEqual(bman.return_value.mock_calls, [
1814 call.do('validate'),
1815 ])
1816- self.assertEqual(self.script.Manager.return_value.mock_calls, [
1817+ manager = self.script.Manager.return_value
1818+ self.assertEqual(manager.mock_calls, [
1819 call.do('watch', 'diff'),
1820+ call.get_states(),
1821 ])
1822- silo_state.save_config.assert_called_once_with()
1823-
1824- def test_main_error(self):
1825- """Log raised exceptions."""
1826- errs = [BuildError('It asploded!')]
1827-
1828- def raise_errs(*ignore):
1829- """Raise an error if there's one to be raised."""
1830- if errs:
1831- raise errs.pop()
1832- silo_state = self.script.SiloState.return_value
1833- bman = self.script.BuildManager = Mock()
1834- bman.return_value.do = Mock(side_effect=raise_errs)
1835- self.assertEqual(self.script.main(bman), 1)
1836- bman.assert_called_once_with(silo_state)
1837- self.assertEqual(
1838- bman.return_value.mock_calls, [
1839- call.do('validate'),
1840- ])
1841- self.assertEqual(str(silo_state.status), 'Build failed: It asploded!')
1842- silo_state.save_config.assert_called_once_with()
1843
1844=== modified file 'tests/unit/test_script_merge_clean.py'
1845--- tests/unit/test_script_merge_clean.py 2015-11-10 17:50:43 +0000
1846+++ tests/unit/test_script_merge_clean.py 2015-11-18 06:50:36 +0000
1847@@ -40,6 +40,7 @@
1848 self.script.Manager.assert_called_once_with(silo_state)
1849 mergemanager.do.assert_called_once_with(
1850 'validate', 'enumeration', 'merge', 'push', 'nuke')
1851+ self.assertEqual(silo_state.status, 'Landed')
1852
1853 def test_merge_failed(self):
1854 """merge() doesn't catch exceptions."""
1855@@ -48,8 +49,9 @@
1856 mergemanager = self.script.Manager.return_value
1857 mergemanager.do.side_effect = MergeError('whoa buddy!')
1858 silo_state = self.script.SiloState.return_value
1859- with self.assertRaisesRegexp(MergeError, 'Merging failed: whoa'):
1860+ with self.assertRaisesRegexp(MergeError, 'Merge&Clean failed: whoa'):
1861 self.script.merge(silo_state)
1862 self.script.Manager.assert_called_once_with(silo_state)
1863 mergemanager.do.assert_called_once_with(
1864 'validate', 'enumeration', 'merge', 'push', 'nuke')
1865+ assert silo_state.status != 'Landed'
1866
1867=== modified file 'tests/unit/test_script_migration.py'
1868--- tests/unit/test_script_migration.py 2015-11-06 15:40:26 +0000
1869+++ tests/unit/test_script_migration.py 2015-11-18 06:50:36 +0000
1870@@ -22,7 +22,6 @@
1871 from tests.unit import CITrainScriptTestCase
1872
1873 from cupstream2distro.settings import SILOS_DIR
1874-from cupstream2distro.errors import MigrationError
1875
1876 # Unused import necessary for code coverage reporting
1877 from citrain import migration
1878@@ -41,6 +40,24 @@
1879 join(SILOS_DIR, 'ubuntu', 'landing-00{}'.format(x))
1880 for x in range(3)]
1881
1882+ def test_success_regex(self):
1883+ """Ensure we can properly recognize silos ready to auto-merge."""
1884+ good = (
1885+ 'Release pocket (foo).',
1886+ 'Updates pocket (foo, bar).',
1887+ 'Release pocket (foo, bar). Updates pocket (baz, qux).',
1888+ 'Updates pocket (foo, bar). Release pocket (baz, qux).',
1889+ )
1890+ results = [bool(self.script.SUCCESS.match(stat)) for stat in good]
1891+ self.assertEqual(results, [True] * len(good))
1892+ bad = (
1893+ 'Proposed pocket (foo).',
1894+ 'NEW queue (foo, bar).',
1895+ 'Dependency wait (foo, bar). Successfully built (baz, qux).',
1896+ )
1897+ results = [bool(self.script.SUCCESS.match(stat)) for stat in bad]
1898+ self.assertEqual(results, [False] * len(bad))
1899+
1900 def test_main(self):
1901 """Trigger merges after successful migrations."""
1902 tokenize = lambda: Mock(return_value={'hi'})
1903@@ -48,26 +65,24 @@
1904 tokenize=tokenize(),
1905 is_published=True,
1906 is_autopkgtesting=False) for rid in range(3)]
1907+ mgr = self.script.Manager.return_value
1908+ mgr.get_states.return_value = 'Release pocket (foo).'
1909 self.assertEqual(self.script.main(), 0)
1910 for state in states:
1911 self.assertEqual(state.mock_calls, [
1912 call.enforce_lock(),
1913 call.lock_fd.close(),
1914- call.tokenize(),
1915 ])
1916 self.assertEqual(self.script.Manager.mock_calls, [
1917 call(states[0]),
1918- call().do('unbuilt'),
1919- call().do('migration'),
1920+ call().get_states(),
1921 call(states[1]),
1922- call().do('unbuilt'),
1923- call().do('migration'),
1924+ call().get_states(),
1925 call(states[2]),
1926- call().do('unbuilt'),
1927- call().do('migration'),
1928+ call().get_states(),
1929 ])
1930 self.assertEqual(self.script.stock_main.mock_calls, [
1931- call(self.script.merge, mark_dirty=True)
1932+ call(self.script.merge)
1933 ] * 3)
1934
1935 def test_main_skip_empty_silos(self):
1936@@ -91,22 +106,21 @@
1937 tokenize = Mock(return_value={'hi'})
1938 states = self.script.SiloState.iterate.return_value = [
1939 Mock(tokenize=tokenize, is_published=False) for rid in range(3)]
1940+ mgr = self.script.Manager.return_value
1941+ mgr.get_states.return_value = 'Proposed pocket (foo).'
1942 self.assertEqual(self.script.main(), 0)
1943 self.assertEqual(self.script.Manager.mock_calls, [
1944 call(states[0]),
1945- call().do('unbuilt'),
1946- call().do('autopkgtest'),
1947+ call().get_states(),
1948 call(states[1]),
1949- call().do('unbuilt'),
1950- call().do('autopkgtest'),
1951+ call().get_states(),
1952 call(states[2]),
1953- call().do('unbuilt'),
1954- call().do('autopkgtest'),
1955+ call().get_states(),
1956 ])
1957 self.assertEqual(self.script.stock_main.mock_calls, [])
1958 for state in states:
1959 self.assertEqual(state.set_migrating.mock_calls, [])
1960- self.assertEqual(state.save_config.mock_calls, [])
1961+ self.assertEqual(state.save_config.mock_calls, [call()])
1962
1963 def test_main_dont_merge_migrating_silos(self):
1964 """Don't merge silos that are still migrating."""
1965@@ -114,29 +128,19 @@
1966 states = self.script.SiloState.iterate.return_value = [Mock(
1967 tokenize=tokenize,
1968 is_published=True,
1969- is_autopkgtesting=False,
1970 requestid=str(1),
1971 )]
1972- states[0].mark_dirty.return_value = False
1973-
1974- def set_side_effect(*ignore):
1975- """Raise an exception second time mock is called."""
1976- self.script.Manager.return_value.do.side_effect = MigrationError(
1977- 'foo is in the Proposed pocket.')
1978- self.script.Manager.return_value.do.side_effect = set_side_effect
1979+ mgr = self.script.Manager.return_value
1980+ mgr.get_states.return_value = 'Proposed pocket (foo).'
1981 self.assertEqual(self.script.main(), 0)
1982- self.assertEqual(
1983- str(states[0].status), 'Migration: foo is in the Proposed pocket.')
1984+ self.assertEqual(states[0].status, mgr.get_states.return_value)
1985 self.assertEqual(states[0].mock_calls, [
1986 call.enforce_lock(),
1987 call.lock_fd.close(),
1988- call.tokenize(),
1989- call.mark_dirty(),
1990 call.save_config(),
1991 ])
1992 self.assertEqual(self.script.Manager.mock_calls, [
1993 call(states[0]),
1994- call().do('unbuilt'),
1995- call().do('migration'),
1996+ call().get_states(),
1997 ])
1998 self.assertEqual(self.script.stock_main.mock_calls, [])
1999
2000=== modified file 'tests/unit/test_script_prepare_silo.py'
2001--- tests/unit/test_script_prepare_silo.py 2015-11-04 02:19:56 +0000
2002+++ tests/unit/test_script_prepare_silo.py 2015-11-18 06:50:36 +0000
2003@@ -48,4 +48,4 @@
2004 def test_main_fail(self):
2005 """Explode."""
2006 self.script.SiloState.return_value.assign.side_effect = PrepError
2007- self.assertEqual(self.script.main(), 1)
2008+ self.assertEqual(self.script.main(), 6)
2009
2010=== modified file 'tests/unit/test_script_publisher.py'
2011--- tests/unit/test_script_publisher.py 2015-11-09 18:09:39 +0000
2012+++ tests/unit/test_script_publisher.py 2015-11-18 06:50:36 +0000
2013@@ -37,14 +37,14 @@
2014 def test_publish_success(self):
2015 """publisher.publish() should call the right functions."""
2016 silo_state = Mock()
2017- self.script.Manager = Mock()
2018+ mgr = self.script.Manager = Mock()
2019 self.assertIsNone(self.script.publish(silo_state))
2020- self.script.Manager.assert_called_once_with(silo_state)
2021- self.assertEqual(self.script.Manager.return_value.do.mock_calls, [
2022+ mgr.assert_called_once_with(silo_state)
2023+ self.assertEqual(mgr.return_value.do.mock_calls, [
2024 call('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging'),
2025 call('dest_version_check', 'unapproved', 'unbuilt', 'publish'),
2026 ])
2027- self.assertEqual(silo_state.status, 'Publishing.')
2028+ self.assertEqual(silo_state.status, 'Beginning publication...')
2029 self.assertEqual(silo_state.mock_calls, [
2030 call.save_config(),
2031 ])
2032
2033=== modified file 'tests/unit/test_script_revert.py'
2034--- tests/unit/test_script_revert.py 2015-11-04 02:19:56 +0000
2035+++ tests/unit/test_script_revert.py 2015-11-18 06:50:36 +0000
2036@@ -126,11 +126,3 @@
2037 'foo': '1.0',
2038 'bar': '2.0',
2039 })
2040-
2041- def test_main(self):
2042- """Ensure main() triggers everything."""
2043- self.script.build_main = Mock()
2044- self.assertEqual(
2045- self.script.main(), self.script.build_main.return_value)
2046- self.script.build_main.assert_called_once_with(
2047- self.script.RevertManager)
2048
2049=== modified file 'tests/unit/test_silomanager.py'
2050--- tests/unit/test_silomanager.py 2015-11-06 15:40:26 +0000
2051+++ tests/unit/test_silomanager.py 2015-11-18 06:50:36 +0000
2052@@ -23,7 +23,7 @@
2053 import logging
2054
2055 from mock import Mock, patch, call
2056-from os.path import join, exists
2057+from os.path import join
2058
2059 from tests.unit import DirectoryAwareTestCase
2060
2061@@ -63,7 +63,6 @@
2062 class Other:
2063 """Fake SiloState instance."""
2064 series = None
2065- mark_dirty = Mock()
2066 ppa = Mock()
2067 self.other = Other
2068 super().setUp()
2069@@ -89,7 +88,6 @@
2070 silomanager.requests = Mock()
2071 silomanager.lp = Mock()
2072 silomanager.lp.load.side_effect = lambda x: Mock(self_link=x)
2073- silomanager.DIRTY_FILE = join(self.tempdir, '{siloname}', '{package}')
2074 silomanager.SILO_NAME_LIST = dict(
2075 ubuntu=['ubuntu/landing-00{}'.format(x) for x in range(0, 10)])
2076
2077@@ -117,12 +115,10 @@
2078 @patch('cupstream2distro.silomanager.SiloState')
2079 def test_stock_main(self, state_mock):
2080 """Ensure that the stock main() function works."""
2081- self.assertEqual(stock_main(lambda state: state, True), 0)
2082+ self.assertEqual(stock_main(lambda state: state), 0)
2083 self.assertEqual(state_mock.mock_calls, [
2084 call(self.tempdir, primary=True),
2085- call().mark_dirty(),
2086 call().save_config(),
2087- call().mark_others_dirty(),
2088 ])
2089
2090 @patch('cupstream2distro.silomanager.SiloState')
2091@@ -131,23 +127,10 @@
2092 def body(silo_state):
2093 """Raise a little hell."""
2094 raise PrepError('Boo!')
2095- self.assertEqual(stock_main(body, True), PrepError.code)
2096- self.assertEqual(state_mock.mock_calls, [
2097- call(self.tempdir, primary=True),
2098- call().mark_dirty(),
2099- call().save_config(),
2100- ])
2101-
2102- @patch('cupstream2distro.silomanager.SiloState')
2103- def test_stock_main_dirty_fail(self, state_mock):
2104- """Ensure that the stock main() function handles dirty exceptions."""
2105- state_mock.return_value.mark_others_dirty.side_effect = Exception('Oh')
2106- self.assertEqual(stock_main(lambda state: state, True), 0)
2107- self.assertEqual(state_mock.mock_calls, [
2108- call(self.tempdir, primary=True),
2109- call().mark_dirty(),
2110- call().save_config(),
2111- call().mark_others_dirty(),
2112+ self.assertEqual(stock_main(body), PrepError.code)
2113+ self.assertEqual(state_mock.mock_calls, [
2114+ call(self.tempdir, primary=True),
2115+ call().save_config(),
2116 ])
2117
2118 def test_silostate_init(self):
2119@@ -278,24 +261,6 @@
2120 self.assertIsNone(self.state.source_archive)
2121 self.assertIsNone(self.state.source_series)
2122
2123- def test_mark_packages_dirty(self):
2124- """Create dirty package marker files."""
2125- names = ['bleep', 'blorp']
2126- os.makedirs(join(self.tempdir, 'foo'))
2127- silomanager.mark_packages_dirty('foo', names)
2128- for name in names:
2129- self.assertTrue(exists(join(self.tempdir, 'foo', name)))
2130-
2131- def test_get_dirty(self):
2132- """List packages that have been marked dirty."""
2133- names = ['whiskey', 'tango', 'foxtrot']
2134- os.makedirs(join(self.tempdir, 'mud'))
2135- for name in names:
2136- path = join(self.tempdir, 'mud', name)
2137- with utf8_open(path, 'w') as dirt:
2138- dirt.write('boo')
2139- self.assertEqual(sorted(silomanager.get_dirty('mud')), sorted(names))
2140-
2141 def test_scrub(self):
2142 """Scrub API tokens out of messages."""
2143 cases = [
2144@@ -418,62 +383,6 @@
2145 self.state.status = NoStatusError('No')
2146 self.assertEquals(self.state.status, 'Preserved')
2147
2148- @patch('cupstream2distro.silomanager.SiloState.series')
2149- @patch('cupstream2distro.silomanager.SiloState.iterate')
2150- @patch('cupstream2distro.silomanager.SiloState.all_projects', ['qtmir'])
2151- def test_silostate_mark_others_dirty(self, iterate, series):
2152- """SiloState marks other silos dirty when told to."""
2153- self.other.series = self.state.series
2154- self.other.ppa.getPublishedSources.return_value = [
2155- Mock(source_package_name='qtmir'),
2156- ]
2157- iterate.return_value = [self.other]
2158- self.state.mark_others_dirty()
2159- iterate.assert_called_once_with(ignore='ubuntu/landing-123')
2160- self.other.mark_dirty.assert_called_once_with(['qtmir'])
2161-
2162- @patch('cupstream2distro.silomanager.SiloState.series')
2163- @patch('cupstream2distro.silomanager.SiloState.iterate')
2164- @patch('cupstream2distro.silomanager.SiloState.all_projects', ['qtmir'])
2165- def test_silostate_mark_others_dirty_none(self, iterate, series):
2166- """SiloState skips non-conflicting silos."""
2167- self.other.series = self.state.series
2168- self.other.ppa.getPublishedSources.return_value = [
2169- Mock(source_package_name='foobar'),
2170- ]
2171- iterate.return_value = [self.other]
2172- self.state.mark_others_dirty()
2173- iterate.assert_called_once_with(ignore='ubuntu/landing-123')
2174- self.assertEqual(self.other.mark_dirty.mock_calls, [])
2175-
2176- @patch('cupstream2distro.silomanager.SiloState.series')
2177- @patch('cupstream2distro.silomanager.SiloState.iterate')
2178- @patch('cupstream2distro.silomanager.SiloState.all_projects', ['qtmir'])
2179- def test_silostate_mark_others_dirty_series(self, iterate, series):
2180- """SiloState only marks same-series dirty."""
2181- self.other.series = 'some other series'
2182- self.other.ppa.getPublishedSources.return_value = [
2183- Mock(source_package_name='qtmir'),
2184- ]
2185- iterate.return_value = [self.other]
2186- self.state.mark_others_dirty()
2187- iterate.assert_called_once_with(ignore='ubuntu/landing-123')
2188- self.assertEqual(self.other.mark_dirty.mock_calls, [])
2189-
2190- @patch('cupstream2distro.silomanager.mark_packages_dirty')
2191- @patch('cupstream2distro.silomanager.get_dirty')
2192- def test_silostate_mark_dirty(self, get, mark):
2193- """SiloState correctly marks itself dirty."""
2194- self.state.set_build_failed = Mock()
2195- self.state.push_to_bileto = Mock()
2196- get.return_value = list('dbca')
2197- self.state.mark_dirty(list('abc'))
2198- mark.assert_called_once_with(self.state.siloname, ['a', 'b', 'c'])
2199- get.assert_called_once_with(self.state.siloname)
2200- self.assertEqual(
2201- self.state.status, 'SILO DIRTY: You must rebuild: a, b, c, d')
2202- self.state.push_to_bileto.assert_called_once_with()
2203-
2204 @patch('cupstream2distro.silomanager.lp')
2205 @patch('cupstream2distro.silomanager.requests')
2206 @patch('cupstream2distro.silomanager.SiloState.requestid', '121')

Subscribers

People subscribed via source and target branches