Merge lp:~robru/cupstream2distro/fix-statuses into lp:cupstream2distro
- fix-statuses
- Merge into trunk
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 | ||||||||
Related bugs: |
|
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).
- 1257. By Robert Bruce Park
-
Better test isolation.
- 1258. By Robert Bruce Park
-
More publication blocker words.
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1258
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1259. By Robert Bruce Park
-
EVEN FASTER Package.
get_package_ version( ).
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1259
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1260. By Robert Bruce Park
-
Speed up BuildBase.
_check_ build_status( )
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1260
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1261. By Robert Bruce Park
-
Drop migration frequency to 30 minutes due to slowness.
- 1262. By Robert Bruce Park
-
Debug logging.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1262
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1263. By Robert Bruce Park
-
Experimental speedup for Archive.
find_archive_ arches( ). - 1264. By Robert Bruce Park
-
More experiments in speed.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1264
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1265. By Robert Bruce Park
-
Logging tweak.
- 1266. By Robert Bruce Park
-
Fix tests.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1266
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1267. By Robert Bruce Park
-
Push more slow stuff out of check-publicati
on-migration.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1267
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Robert Bruce Park (robru) wrote : | # |
Alright, I'm really happy with this & tested it well in staging. Will roll out first thing tomorrow.
Preview Diff
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') |
PASSED: Continuous integration, rev:1257 jenkins. qa.ubuntu. com/job/ cu2d-choo- choo-ci/ 901/
http://
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/cu2d- choo-choo- ci/901/ rebuild
http://