Merge lp:~robru/cupstream2distro/exception-tweaks into lp:cupstream2distro
- exception-tweaks
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Robert Bruce Park |
Approved revision: | 1202 |
Merged at revision: | 1195 |
Proposed branch: | lp:~robru/cupstream2distro/exception-tweaks |
Merge into: | lp:cupstream2distro |
Diff against target: |
667 lines (+198/-195) 14 files modified
citrain/build.py (+1/-2) citrain/merge_clean.py (+12/-27) citrain/migration.py (+5/-6) citrain/publisher.py (+18/-39) citrain/recipes/base.py (+0/-5) cupstream2distro/errors.py (+36/-14) cupstream2distro/silomanager.py (+32/-2) tests/unit/__init__.py (+0/-5) tests/unit/test_recipe_base.py (+0/-6) tests/unit/test_script_build.py (+1/-1) tests/unit/test_script_merge_clean.py (+11/-17) tests/unit/test_script_migration.py (+9/-7) tests/unit/test_script_publisher.py (+29/-62) tests/unit/test_silomanager.py (+44/-2) |
To merge this branch: | bzr merge lp:~robru/cupstream2distro/exception-tweaks |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
CU2D maintainers | Pending | ||
Review via email: mp+276842@code.launchpad.net |
Commit message
Improve consistency of error handling & logging.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
- 1196. By Robert Bruce Park
-
Support NoStatusError everywhere.
- 1197. By Robert Bruce Park
-
New test.
- 1198. By Robert Bruce Park
-
Pass the err to status.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1198
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1199. By Robert Bruce Park
-
Logging tweaks.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1199
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1200. By Robert Bruce Park
-
Unify merge_clean.main and publisher.main.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1200
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1201. By Robert Bruce Park
-
Stop hard-coding return codes.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1201
http://
Executed test runs:
Click here to trigger a rebuild:
http://
- 1202. By Robert Bruce Park
-
Func name/docstring cleanup.
Preview Diff
1 | === modified file 'citrain/build.py' | |||
2 | --- citrain/build.py 2015-10-28 19:46:24 +0000 | |||
3 | +++ citrain/build.py 2015-11-06 15:09:47 +0000 | |||
4 | @@ -86,8 +86,7 @@ | |||
5 | 86 | Manager(silo_state).do('watch', 'diff') | 86 | Manager(silo_state).do('watch', 'diff') |
6 | 87 | silo_state.status = 'Packages built.' | 87 | silo_state.status = 'Packages built.' |
7 | 88 | except CITrainError as err: | 88 | except CITrainError as err: |
10 | 89 | logging.error(str(err)) | 89 | silo_state.status = err |
9 | 90 | silo_state.status = 'Build failed: ' + str(err) | ||
11 | 91 | silo_state.mark_dirty() | 90 | silo_state.mark_dirty() |
12 | 92 | silo_state.save_config() | 91 | silo_state.save_config() |
13 | 93 | return 1 | 92 | return 1 |
14 | 94 | 93 | ||
15 | === modified file 'citrain/merge_clean.py' | |||
16 | --- citrain/merge_clean.py 2015-10-28 19:46:24 +0000 | |||
17 | +++ citrain/merge_clean.py 2015-11-06 15:09:47 +0000 | |||
18 | @@ -31,31 +31,16 @@ | |||
19 | 31 | Enable debug infos | 31 | Enable debug infos |
20 | 32 | """ | 32 | """ |
21 | 33 | 33 | ||
22 | 34 | import logging | ||
23 | 35 | |||
24 | 36 | from citrain.recipes.manager import Manager | 34 | from citrain.recipes.manager import Manager |
50 | 37 | from cupstream2distro.errors import CITrainError | 35 | from cupstream2distro.utils import run_script |
51 | 38 | from cupstream2distro.utils import env, run_script | 36 | from cupstream2distro.silomanager import stock_main |
52 | 39 | from cupstream2distro.silomanager import SiloState | 37 | |
53 | 40 | 38 | ||
54 | 41 | 39 | def merge(silo_state): | |
55 | 42 | def main(): | 40 | """Do the steps specific to merging & cleaning.""" |
56 | 43 | """Execute the merge & clean phases, logging & saving any errors. | 41 | Manager(silo_state).do( |
57 | 44 | 42 | 'validate', 'enumeration', 'merge', 'push', 'nuke') | |
58 | 45 | :returns: 0 on success, 1 on any failure. | 43 | silo_state.status = 'Landed' |
59 | 46 | """ | 44 | |
60 | 47 | try: | 45 | |
61 | 48 | silo_state = SiloState(env.SILONAME, primary=True) | 46 | run_script(__name__, __doc__, lambda: stock_main(merge, mark_dirty=True)) |
37 | 49 | Manager(silo_state).do( | ||
38 | 50 | 'validate', 'enumeration', 'merge', 'push', 'nuke') | ||
39 | 51 | silo_state.status = 'Landed' | ||
40 | 52 | except CITrainError as err: | ||
41 | 53 | logging.error(str(err)) | ||
42 | 54 | silo_state.status = 'Merge&Clean failed: ' + str(err) | ||
43 | 55 | silo_state.save_config() | ||
44 | 56 | return 1 | ||
45 | 57 | silo_state.save_config() | ||
46 | 58 | return 0 | ||
47 | 59 | |||
48 | 60 | |||
49 | 61 | run_script(__name__, __doc__, main) | ||
62 | 62 | 47 | ||
63 | === modified file 'citrain/migration.py' | |||
64 | --- citrain/migration.py 2015-11-04 15:43:33 +0000 | |||
65 | +++ citrain/migration.py 2015-11-06 15:09:47 +0000 | |||
66 | @@ -31,8 +31,8 @@ | |||
67 | 31 | from citrain.recipes.manager import Manager | 31 | from citrain.recipes.manager import Manager |
68 | 32 | from cupstream2distro.errors import CITrainError | 32 | from cupstream2distro.errors import CITrainError |
69 | 33 | from cupstream2distro.utils import env, run_script | 33 | from cupstream2distro.utils import env, run_script |
72 | 34 | from cupstream2distro.silomanager import SiloState | 34 | from cupstream2distro.silomanager import SiloState, stock_main |
73 | 35 | from citrain.merge_clean import main as merge_main | 35 | from citrain.merge_clean import merge |
74 | 36 | 36 | ||
75 | 37 | 37 | ||
76 | 38 | def main(): | 38 | def main(): |
77 | @@ -57,11 +57,10 @@ | |||
78 | 57 | manager.do('unbuilt') | 57 | manager.do('unbuilt') |
79 | 58 | if silo_state.is_published: | 58 | if silo_state.is_published: |
80 | 59 | manager.do('migration') | 59 | manager.do('migration') |
82 | 60 | merge_main() | 60 | stock_main(merge, mark_dirty=True) |
83 | 61 | except CITrainError as err: | 61 | except CITrainError as err: |
87 | 62 | logging.info(str(err) + '\n') | 62 | silo_state.status = err |
88 | 63 | if not silo_state.mark_dirty(): | 63 | silo_state.mark_dirty() |
86 | 64 | silo_state.status = 'Migration: ' + str(err) | ||
89 | 65 | silo_state.save_config() | 64 | silo_state.save_config() |
90 | 66 | return 0 | 65 | return 0 |
91 | 67 | 66 | ||
92 | 68 | 67 | ||
93 | === modified file 'citrain/publisher.py' | |||
94 | --- citrain/publisher.py 2015-11-04 15:04:26 +0000 | |||
95 | +++ citrain/publisher.py 2015-11-06 15:09:47 +0000 | |||
96 | @@ -35,44 +35,23 @@ | |||
97 | 35 | Enable debug infos | 35 | Enable debug infos |
98 | 36 | """ | 36 | """ |
99 | 37 | 37 | ||
100 | 38 | import logging | ||
101 | 39 | |||
102 | 40 | from citrain.recipes.manager import Manager | 38 | from citrain.recipes.manager import Manager |
127 | 41 | from cupstream2distro.errors import CITrainError, PublishError | 39 | from cupstream2distro.errors import PublishError |
128 | 42 | from cupstream2distro.utils import env, run_script | 40 | from cupstream2distro.utils import run_script |
129 | 43 | from cupstream2distro.silomanager import SiloState | 41 | from cupstream2distro.silomanager import stock_main |
130 | 44 | 42 | ||
131 | 45 | 43 | ||
132 | 46 | def main(): | 44 | def publish(silo_state): |
133 | 47 | """Conduct a beautiful orchestra of publication.""" | 45 | """Do the actual specific steps required to publish.""" |
134 | 48 | try: | 46 | if silo_state._series in ('wily+vivid', 'dual'): |
135 | 49 | silo_state = SiloState(env.SILONAME, primary=True) | 47 | raise PublishError( |
136 | 50 | if silo_state._series in ('wily+vivid', 'dual'): | 48 | 'This silo must be transitioned to xenial before publishing.') |
137 | 51 | raise PublishError( | 49 | mgr = Manager(silo_state) |
138 | 52 | 'This silo must be transitioned to xenial before publishing.') | 50 | mgr.do('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging') |
139 | 53 | mgr = Manager(silo_state) | 51 | silo_state.status = 'Publishing.' |
116 | 54 | mgr.do('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging') | ||
117 | 55 | silo_state.status = 'Publishing.' | ||
118 | 56 | silo_state.save_config() | ||
119 | 57 | mgr.do('dest_version_check', 'unapproved', 'unbuilt', 'publish') | ||
120 | 58 | silo_state.set_published() | ||
121 | 59 | except CITrainError as err: | ||
122 | 60 | logging.error(str(err)) | ||
123 | 61 | if err.loud: | ||
124 | 62 | silo_state.status = 'Publish failed: ' + str(err) | ||
125 | 63 | silo_state.save_config() | ||
126 | 64 | return 1 | ||
140 | 65 | silo_state.save_config() | 52 | silo_state.save_config() |
154 | 66 | # This bit needs to be in a separate block because it's so slow that it | 53 | mgr.do('dest_version_check', 'unapproved', 'unbuilt', 'publish') |
155 | 67 | # makes the last call to save_config() end up losing races with | 54 | silo_state.set_published() |
156 | 68 | # check-publication-migration. Then you get logs that say | 55 | |
157 | 69 | # 'Publishing. Migration:... Publishing. Migration:...' | 56 | |
158 | 70 | try: | 57 | run_script(__name__, __doc__, lambda: stock_main(publish, mark_dirty=True)) |
146 | 71 | silo_state.mark_others_dirty() | ||
147 | 72 | except Exception as err: | ||
148 | 73 | logging.warning('Had trouble marking other silos dirty:') | ||
149 | 74 | logging.warning(err) | ||
150 | 75 | return 0 | ||
151 | 76 | |||
152 | 77 | |||
153 | 78 | run_script(__name__, __doc__, main) | ||
159 | 79 | 58 | ||
160 | === modified file 'citrain/recipes/base.py' | |||
161 | --- citrain/recipes/base.py 2015-11-04 15:43:33 +0000 | |||
162 | +++ citrain/recipes/base.py 2015-11-06 15:09:47 +0000 | |||
163 | @@ -507,11 +507,6 @@ | |||
164 | 507 | silo_state.save_config() | 507 | silo_state.save_config() |
165 | 508 | 508 | ||
166 | 509 | @staticmethod | 509 | @staticmethod |
167 | 510 | def post_push_phase(silo_state): | ||
168 | 511 | """Mark other silos dirty once pushing is successful.""" | ||
169 | 512 | silo_state.mark_others_dirty() | ||
170 | 513 | |||
171 | 514 | @staticmethod | ||
172 | 515 | def post_nuke_phase(silo_state): | 510 | def post_nuke_phase(silo_state): |
173 | 516 | """Delete all packages from PPA and remove local silo dir.""" | 511 | """Delete all packages from PPA and remove local silo dir.""" |
174 | 517 | logging.info('Cleaning %s ppa.', silo_state.ppa.web_link) | 512 | logging.info('Cleaning %s ppa.', silo_state.ppa.web_link) |
175 | 518 | 513 | ||
176 | === modified file 'cupstream2distro/errors.py' | |||
177 | --- cupstream2distro/errors.py 2015-11-04 15:04:26 +0000 | |||
178 | +++ cupstream2distro/errors.py 2015-11-06 15:09:47 +0000 | |||
179 | @@ -17,56 +17,78 @@ | |||
180 | 17 | """Exceptions raised by CI Train in various situations.""" | 17 | """Exceptions raised by CI Train in various situations.""" |
181 | 18 | 18 | ||
182 | 19 | 19 | ||
183 | 20 | COUNTER = iter(range(1, 100)) | ||
184 | 21 | |||
185 | 22 | |||
186 | 20 | class CITrainError(Exception): | 23 | class CITrainError(Exception): |
187 | 21 | """Base Exception class for all CI Train error conditions.""" | 24 | """Base Exception class for all CI Train error conditions.""" |
188 | 25 | prefix = 'Unknown error: ' | ||
189 | 26 | code = next(COUNTER) | ||
190 | 22 | loud = True | 27 | loud = True |
191 | 28 | error = True | ||
192 | 29 | |||
193 | 30 | def __str__(self): | ||
194 | 31 | """Stringify this exception.""" | ||
195 | 32 | return self.prefix + super().__str__() | ||
196 | 33 | |||
197 | 34 | |||
198 | 35 | class NoStatusError(CITrainError): | ||
199 | 36 | """Special Exception that when raised won't set status.""" | ||
200 | 37 | code = next(COUNTER) | ||
201 | 38 | loud = False | ||
202 | 39 | prefix = '' | ||
203 | 23 | 40 | ||
204 | 24 | 41 | ||
205 | 25 | class ArchiveError(CITrainError): | 42 | class ArchiveError(CITrainError): |
206 | 26 | """Exception raised when there's a problem with a Launchpad archive.""" | 43 | """Exception raised when there's a problem with a Launchpad archive.""" |
208 | 27 | pass | 44 | prefix = 'Launchpad error: ' |
209 | 45 | code = next(COUNTER) | ||
210 | 28 | 46 | ||
211 | 29 | 47 | ||
212 | 30 | class BranchError(CITrainError): | 48 | class BranchError(CITrainError): |
213 | 31 | """Exception raised when there's a problem with a Bzr branch.""" | 49 | """Exception raised when there's a problem with a Bzr branch.""" |
215 | 32 | pass | 50 | prefix = 'Branch error: ' |
216 | 51 | code = next(COUNTER) | ||
217 | 33 | 52 | ||
218 | 34 | 53 | ||
219 | 35 | class PackageError(CITrainError): | 54 | class PackageError(CITrainError): |
220 | 36 | """Exception raised when there's a problem with a debian package.""" | 55 | """Exception raised when there's a problem with a debian package.""" |
222 | 37 | pass | 56 | prefix = 'Package error: ' |
223 | 57 | code = next(COUNTER) | ||
224 | 38 | 58 | ||
225 | 39 | 59 | ||
226 | 40 | class PrepError(CITrainError): | 60 | class PrepError(CITrainError): |
227 | 41 | """Exception raised when there's a problem during preparation.""" | 61 | """Exception raised when there's a problem during preparation.""" |
229 | 42 | pass | 62 | prefix = 'Prepare failed: ' |
230 | 63 | code = next(COUNTER) | ||
231 | 43 | 64 | ||
232 | 44 | 65 | ||
233 | 45 | class PublishError(CITrainError): | 66 | class PublishError(CITrainError): |
234 | 46 | """Exception raised when there's a problem during publishing.""" | 67 | """Exception raised when there's a problem during publishing.""" |
236 | 47 | pass | 68 | prefix = 'Publish failed: ' |
237 | 69 | code = next(COUNTER) | ||
238 | 48 | 70 | ||
239 | 49 | 71 | ||
240 | 50 | class BuildError(CITrainError): | 72 | class BuildError(CITrainError): |
241 | 51 | """Exception raised when there's a problem with building.""" | 73 | """Exception raised when there's a problem with building.""" |
243 | 52 | pass | 74 | prefix = 'Build failed: ' |
244 | 75 | code = next(COUNTER) | ||
245 | 53 | 76 | ||
246 | 54 | 77 | ||
247 | 55 | class MergeError(CITrainError): | 78 | class MergeError(CITrainError): |
248 | 56 | """Exception raised when there's a problem with merging.""" | 79 | """Exception raised when there's a problem with merging.""" |
250 | 57 | pass | 80 | prefix = 'Merge&Clean failed: ' |
251 | 81 | code = next(COUNTER) | ||
252 | 58 | 82 | ||
253 | 59 | 83 | ||
254 | 60 | class MigrationError(CITrainError): | 84 | class MigrationError(CITrainError): |
255 | 61 | """Exception raised to report on the silo migration status.""" | 85 | """Exception raised to report on the silo migration status.""" |
257 | 62 | pass | 86 | prefix = 'Migration: ' |
258 | 87 | code = next(COUNTER) | ||
259 | 88 | error = False | ||
260 | 63 | 89 | ||
261 | 64 | 90 | ||
262 | 65 | class RevertError(CITrainError): | 91 | class RevertError(CITrainError): |
263 | 66 | """Exception raised when there's a problem with reverting.""" | 92 | """Exception raised when there's a problem with reverting.""" |
270 | 67 | pass | 93 | prefix = 'Revert failed: ' |
271 | 68 | 94 | code = next(COUNTER) | |
266 | 69 | |||
267 | 70 | class NoStatusError(PublishError): | ||
268 | 71 | """Special Exception that when raised won't set status.""" | ||
269 | 72 | loud = False | ||
272 | 73 | 95 | ||
273 | === modified file 'cupstream2distro/silomanager.py' | |||
274 | --- cupstream2distro/silomanager.py 2015-11-05 16:23:09 +0000 | |||
275 | +++ cupstream2distro/silomanager.py 2015-11-06 15:09:47 +0000 | |||
276 | @@ -36,7 +36,7 @@ | |||
277 | 36 | from lazr.restfulclient.errors import PreconditionFailed, NotFound | 36 | from lazr.restfulclient.errors import PreconditionFailed, NotFound |
278 | 37 | 37 | ||
279 | 38 | from cupstream2distro.branchhandling import Branch | 38 | from cupstream2distro.branchhandling import Branch |
281 | 39 | from cupstream2distro.errors import PrepError | 39 | from cupstream2distro.errors import CITrainError, PrepError |
282 | 40 | from cupstream2distro.launchpadmanager import lp | 40 | from cupstream2distro.launchpadmanager import lp |
283 | 41 | from cupstream2distro.settings import ( | 41 | from cupstream2distro.settings import ( |
284 | 42 | BILETO_API, | 42 | BILETO_API, |
285 | @@ -152,6 +152,31 @@ | |||
286 | 152 | _bileto(COMMENT_API, **kwargs) | 152 | _bileto(COMMENT_API, **kwargs) |
287 | 153 | 153 | ||
288 | 154 | 154 | ||
289 | 155 | def stock_main(body, mark_dirty=False): | ||
290 | 156 | """Generic error handling for CI Train scripts.""" | ||
291 | 157 | silo_state = SiloState(env.SILONAME, primary=True) | ||
292 | 158 | try: | ||
293 | 159 | body(silo_state) | ||
294 | 160 | except CITrainError as err: | ||
295 | 161 | silo_state.status = err | ||
296 | 162 | silo_state.mark_dirty() | ||
297 | 163 | silo_state.save_config() | ||
298 | 164 | return err.code | ||
299 | 165 | silo_state.mark_dirty() | ||
300 | 166 | silo_state.save_config() | ||
301 | 167 | # This bit needs to be in a separate block because it's so slow that it | ||
302 | 168 | # makes the last call to save_config() end up losing races with | ||
303 | 169 | # check-publication-migration. Then you get logs that say | ||
304 | 170 | # 'Publishing. Migration:... Publishing. Migration:...' | ||
305 | 171 | if mark_dirty: | ||
306 | 172 | try: | ||
307 | 173 | silo_state.mark_others_dirty() | ||
308 | 174 | except Exception as err: | ||
309 | 175 | logging.warning('Had trouble marking other silos dirty:') | ||
310 | 176 | logging.warning(err) | ||
311 | 177 | return 0 | ||
312 | 178 | |||
313 | 179 | |||
314 | 155 | class SiloState(object): | 180 | class SiloState(object): |
315 | 156 | """This class stores and manipulates the silo config json blob.""" | 181 | """This class stores and manipulates the silo config json blob.""" |
316 | 157 | REQUEST_ID_FILE = 'request_id_{}' | 182 | REQUEST_ID_FILE = 'request_id_{}' |
317 | @@ -212,7 +237,12 @@ | |||
318 | 212 | def __setattr__(self, key, value): | 237 | def __setattr__(self, key, value): |
319 | 213 | """Set Bileto attributes in Bileto.""" | 238 | """Set Bileto attributes in Bileto.""" |
320 | 214 | if key in ('status', 'job_log', 'published_versions'): | 239 | if key in ('status', 'job_log', 'published_versions'): |
322 | 215 | self._bileto[key] = scrub(value) | 240 | scrubbed = scrub(str(value)) |
323 | 241 | if key == 'status': | ||
324 | 242 | err = getattr(value, 'error', False) | ||
325 | 243 | (logging.error if err else logging.info)(scrubbed) | ||
326 | 244 | if getattr(value, 'loud', True): | ||
327 | 245 | self._bileto[key] = scrubbed | ||
328 | 216 | else: | 246 | else: |
329 | 217 | super().__setattr__(key, value) | 247 | super().__setattr__(key, value) |
330 | 218 | 248 | ||
331 | 219 | 249 | ||
332 | === modified file 'tests/unit/__init__.py' | |||
333 | --- tests/unit/__init__.py 2015-11-04 01:46:45 +0000 | |||
334 | +++ tests/unit/__init__.py 2015-11-06 15:09:47 +0000 | |||
335 | @@ -174,8 +174,3 @@ | |||
336 | 174 | 'ubuntu/landing-{:03d}'.format(x) for x in range(0, 10)] | 174 | 'ubuntu/landing-{:03d}'.format(x) for x in range(0, 10)] |
337 | 175 | self.script.SILO_NAME_LIST = dict( | 175 | self.script.SILO_NAME_LIST = dict( |
338 | 176 | ubuntu=self.script.ALL_SILO_NAMES) | 176 | ubuntu=self.script.ALL_SILO_NAMES) |
339 | 177 | |||
340 | 178 | def test_existence(self): | ||
341 | 179 | """CI Train script has a main() func without SyntaxErrors.""" | ||
342 | 180 | if self.script: | ||
343 | 181 | self.assertTrue(self.script.main) | ||
344 | 182 | 177 | ||
345 | === modified file 'tests/unit/test_recipe_base.py' | |||
346 | --- tests/unit/test_recipe_base.py 2015-11-04 01:46:45 +0000 | |||
347 | +++ tests/unit/test_recipe_base.py 2015-11-06 15:09:47 +0000 | |||
348 | @@ -772,12 +772,6 @@ | |||
349 | 772 | self.assertEqual(silo_state.status, 'Merging.') | 772 | self.assertEqual(silo_state.status, 'Merging.') |
350 | 773 | silo_state.save_config.assert_called_once_with() | 773 | silo_state.save_config.assert_called_once_with() |
351 | 774 | 774 | ||
352 | 775 | def test_buildbase_post_push_phase(self): | ||
353 | 776 | """Mark other silos dirty after silo has merged.""" | ||
354 | 777 | silo_state = Mock() | ||
355 | 778 | BuildBase.post_push_phase(silo_state) | ||
356 | 779 | silo_state.mark_others_dirty.assert_called_once_with() | ||
357 | 780 | |||
358 | 781 | @patch('citrain.recipes.base.mkdir') | 775 | @patch('citrain.recipes.base.mkdir') |
359 | 782 | @patch('citrain.recipes.base.shutil') | 776 | @patch('citrain.recipes.base.shutil') |
360 | 783 | @patch('citrain.recipes.base.SILO_DIR') | 777 | @patch('citrain.recipes.base.SILO_DIR') |
361 | 784 | 778 | ||
362 | === modified file 'tests/unit/test_script_build.py' | |||
363 | --- tests/unit/test_script_build.py 2015-11-04 02:19:56 +0000 | |||
364 | +++ tests/unit/test_script_build.py 2015-11-06 15:09:47 +0000 | |||
365 | @@ -168,5 +168,5 @@ | |||
366 | 168 | bman.return_value.mock_calls, [ | 168 | bman.return_value.mock_calls, [ |
367 | 169 | call.do('validate'), | 169 | call.do('validate'), |
368 | 170 | ]) | 170 | ]) |
370 | 171 | self.assertEqual(silo_state.status, 'Build failed: It asploded!') | 171 | self.assertEqual(str(silo_state.status), 'Build failed: It asploded!') |
371 | 172 | silo_state.save_config.assert_called_once_with() | 172 | silo_state.save_config.assert_called_once_with() |
372 | 173 | 173 | ||
373 | === modified file 'tests/unit/test_script_merge_clean.py' | |||
374 | --- tests/unit/test_script_merge_clean.py 2015-11-04 02:19:56 +0000 | |||
375 | +++ tests/unit/test_script_merge_clean.py 2015-11-06 15:09:47 +0000 | |||
376 | @@ -31,31 +31,25 @@ | |||
377 | 31 | """Test CI Train Merge & Clean script.""" | 31 | """Test CI Train Merge & Clean script.""" |
378 | 32 | scriptname = 'merge_clean.py' | 32 | scriptname = 'merge_clean.py' |
379 | 33 | 33 | ||
383 | 34 | def test_main(self): | 34 | def test_merge(self): |
384 | 35 | """main() succeeds in the primary case.""" | 35 | """merge() succeeds in the primary case.""" |
385 | 36 | self.script.env.SILONAME = 'fred' | 36 | silo_state = Mock() |
386 | 37 | self.script.Manager = Mock() | 37 | self.script.Manager = Mock() |
387 | 38 | mergemanager = self.script.Manager.return_value | 38 | mergemanager = self.script.Manager.return_value |
392 | 39 | self.assertEqual(self.script.main(), 0) | 39 | self.assertIsNone(self.script.merge(silo_state)) |
393 | 40 | self.script.SiloState.assert_called_once_with('fred', primary=True) | 40 | self.script.Manager.assert_called_once_with(silo_state) |
390 | 41 | self.script.Manager.assert_called_once_with( | ||
391 | 42 | self.script.SiloState.return_value) | ||
394 | 43 | mergemanager.do.assert_called_once_with( | 41 | mergemanager.do.assert_called_once_with( |
395 | 44 | 'validate', 'enumeration', 'merge', 'push', 'nuke') | 42 | 'validate', 'enumeration', 'merge', 'push', 'nuke') |
396 | 45 | 43 | ||
400 | 46 | def test_main_failed(self): | 44 | def test_merge_failed(self): |
401 | 47 | """main() returns 1 when a phase raises an exception.""" | 45 | """merge() doesn't catch exceptions.""" |
402 | 48 | self.script.env.SILONAME = 'fred' | 46 | silo_state = Mock() |
403 | 49 | self.script.Manager = Mock() | 47 | self.script.Manager = Mock() |
404 | 50 | mergemanager = self.script.Manager.return_value | 48 | mergemanager = self.script.Manager.return_value |
405 | 51 | mergemanager.do.side_effect = MergeError('whoa buddy!') | 49 | mergemanager.do.side_effect = MergeError('whoa buddy!') |
406 | 52 | silo_state = self.script.SiloState.return_value | 50 | silo_state = self.script.SiloState.return_value |
411 | 53 | self.assertEqual(self.script.main(), 1) | 51 | with self.assertRaisesRegexp(MergeError, 'Merge&Clean failed: whoa'): |
412 | 54 | self.script.SiloState.assert_called_once_with('fred', primary=True) | 52 | self.script.merge(silo_state) |
413 | 55 | self.script.Manager.assert_called_once_with( | 53 | self.script.Manager.assert_called_once_with(silo_state) |
410 | 56 | self.script.SiloState.return_value) | ||
414 | 57 | mergemanager.do.assert_called_once_with( | 54 | mergemanager.do.assert_called_once_with( |
415 | 58 | 'validate', 'enumeration', 'merge', 'push', 'nuke') | 55 | 'validate', 'enumeration', 'merge', 'push', 'nuke') |
416 | 59 | self.script.logging.error.assert_called_once_with('whoa buddy!') | ||
417 | 60 | self.assertEqual(silo_state.status, 'Merge&Clean failed: whoa buddy!') | ||
418 | 61 | silo_state.save_config.assert_called_once_with() | ||
419 | 62 | 56 | ||
420 | === modified file 'tests/unit/test_script_migration.py' | |||
421 | --- tests/unit/test_script_migration.py 2015-11-04 02:19:56 +0000 | |||
422 | +++ tests/unit/test_script_migration.py 2015-11-06 15:09:47 +0000 | |||
423 | @@ -36,7 +36,7 @@ | |||
424 | 36 | def setUp(self): | 36 | def setUp(self): |
425 | 37 | super().setUp() | 37 | super().setUp() |
426 | 38 | self.script.Manager = Mock() | 38 | self.script.Manager = Mock() |
428 | 39 | self.script.merge_main = Mock() | 39 | self.script.stock_main = Mock() |
429 | 40 | self.script.glob.return_value = [ | 40 | self.script.glob.return_value = [ |
430 | 41 | join(SILOS_DIR, 'ubuntu', 'landing-00{}'.format(x)) | 41 | join(SILOS_DIR, 'ubuntu', 'landing-00{}'.format(x)) |
431 | 42 | for x in range(3)] | 42 | for x in range(3)] |
432 | @@ -64,14 +64,16 @@ | |||
433 | 64 | call().do('unbuilt'), | 64 | call().do('unbuilt'), |
434 | 65 | call().do('migration'), | 65 | call().do('migration'), |
435 | 66 | ]) | 66 | ]) |
437 | 67 | self.assertEqual(self.script.merge_main.mock_calls, [call()] * 3) | 67 | self.assertEqual(self.script.stock_main.mock_calls, [ |
438 | 68 | call(self.script.merge, mark_dirty=True) | ||
439 | 69 | ] * 3) | ||
440 | 68 | 70 | ||
441 | 69 | def test_main_skip_empty_silos(self): | 71 | def test_main_skip_empty_silos(self): |
442 | 70 | """Don't check silos that are empty.""" | 72 | """Don't check silos that are empty.""" |
443 | 71 | self.script.SiloState.iterate.return_value = [] | 73 | self.script.SiloState.iterate.return_value = [] |
444 | 72 | self.assertEqual(self.script.main(), 0) | 74 | self.assertEqual(self.script.main(), 0) |
445 | 73 | self.assertEqual(self.script.Manager.mock_calls, []) | 75 | self.assertEqual(self.script.Manager.mock_calls, []) |
447 | 74 | self.assertEqual(self.script.merge_main.mock_calls, []) | 76 | self.assertEqual(self.script.stock_main.mock_calls, []) |
448 | 75 | 77 | ||
449 | 76 | def test_main_skip_locked_silos(self): | 78 | def test_main_skip_locked_silos(self): |
450 | 77 | """Skip over silos that are currently used by other processes.""" | 79 | """Skip over silos that are currently used by other processes.""" |
451 | @@ -79,7 +81,7 @@ | |||
452 | 79 | states[0].enforce_lock.side_effect = BlockingIOError | 81 | states[0].enforce_lock.side_effect = BlockingIOError |
453 | 80 | self.assertEqual(self.script.main(), 0) | 82 | self.assertEqual(self.script.main(), 0) |
454 | 81 | self.assertEqual(self.script.Manager.mock_calls, []) | 83 | self.assertEqual(self.script.Manager.mock_calls, []) |
456 | 82 | self.assertEqual(self.script.merge_main.mock_calls, []) | 84 | self.assertEqual(self.script.stock_main.mock_calls, []) |
457 | 83 | self.assertEqual(states[0].mock_calls, [call.enforce_lock()]) | 85 | self.assertEqual(states[0].mock_calls, [call.enforce_lock()]) |
458 | 84 | 86 | ||
459 | 85 | def test_main_skip_unpublished_silos(self): | 87 | def test_main_skip_unpublished_silos(self): |
460 | @@ -96,7 +98,7 @@ | |||
461 | 96 | call(states[2]), | 98 | call(states[2]), |
462 | 97 | call().do('unbuilt'), | 99 | call().do('unbuilt'), |
463 | 98 | ]) | 100 | ]) |
465 | 99 | self.assertEqual(self.script.merge_main.mock_calls, []) | 101 | self.assertEqual(self.script.stock_main.mock_calls, []) |
466 | 100 | for state in states: | 102 | for state in states: |
467 | 101 | self.assertEqual(state.set_migrating.mock_calls, []) | 103 | self.assertEqual(state.set_migrating.mock_calls, []) |
468 | 102 | self.assertEqual(state.save_config.mock_calls, []) | 104 | self.assertEqual(state.save_config.mock_calls, []) |
469 | @@ -115,7 +117,7 @@ | |||
470 | 115 | self.script.Manager.return_value.do.side_effect = set_side_effect | 117 | self.script.Manager.return_value.do.side_effect = set_side_effect |
471 | 116 | self.assertEqual(self.script.main(), 0) | 118 | self.assertEqual(self.script.main(), 0) |
472 | 117 | self.assertEqual( | 119 | self.assertEqual( |
474 | 118 | states[0].status, 'Migration: foo is in the Proposed pocket.') | 120 | str(states[0].status), 'Migration: foo is in the Proposed pocket.') |
475 | 119 | self.assertEqual(states[0].mock_calls, [ | 121 | self.assertEqual(states[0].mock_calls, [ |
476 | 120 | call.enforce_lock(), | 122 | call.enforce_lock(), |
477 | 121 | call.lock_fd.close(), | 123 | call.lock_fd.close(), |
478 | @@ -128,4 +130,4 @@ | |||
479 | 128 | call().do('unbuilt'), | 130 | call().do('unbuilt'), |
480 | 129 | call().do('migration'), | 131 | call().do('migration'), |
481 | 130 | ]) | 132 | ]) |
483 | 131 | self.assertEqual(self.script.merge_main.mock_calls, []) | 133 | self.assertEqual(self.script.stock_main.mock_calls, []) |
484 | 132 | 134 | ||
485 | === modified file 'tests/unit/test_script_publisher.py' | |||
486 | --- tests/unit/test_script_publisher.py 2015-10-28 19:40:12 +0000 | |||
487 | +++ tests/unit/test_script_publisher.py 2015-11-06 15:09:47 +0000 | |||
488 | @@ -21,10 +21,7 @@ | |||
489 | 21 | from tests.unit import CITrainScriptTestCase | 21 | from tests.unit import CITrainScriptTestCase |
490 | 22 | 22 | ||
491 | 23 | from cupstream2distro.utils import env | 23 | from cupstream2distro.utils import env |
496 | 24 | from cupstream2distro.errors import PublishError, NoStatusError | 24 | from cupstream2distro.errors import PublishError |
493 | 25 | |||
494 | 26 | |||
495 | 27 | N = '\n' | ||
497 | 28 | 25 | ||
498 | 29 | 26 | ||
499 | 30 | class PublisherTestCase(CITrainScriptTestCase): | 27 | class PublisherTestCase(CITrainScriptTestCase): |
500 | @@ -37,67 +34,37 @@ | |||
501 | 37 | env.DISTRO = 'ubuntu' | 34 | env.DISTRO = 'ubuntu' |
502 | 38 | env.SERIES = 'vivid' | 35 | env.SERIES = 'vivid' |
503 | 39 | 36 | ||
506 | 40 | def mock_main(self): | 37 | def test_publish_success(self): |
507 | 41 | """Ensure enough of the publisher is mocked out for testing main().""" | 38 | """publisher.publish() should call the right functions.""" |
508 | 39 | silo_state = Mock() | ||
509 | 42 | self.script.Manager = Mock() | 40 | self.script.Manager = Mock() |
519 | 43 | self.script.SiloState.return_value.series = \ | 41 | self.assertIsNone(self.script.publish(silo_state)) |
520 | 44 | 'https://api.launchpad.net/devel/ubuntu/vivid' | 42 | self.script.Manager.assert_called_once_with(silo_state) |
512 | 45 | |||
513 | 46 | def test_main_success(self): | ||
514 | 47 | """publisher.main() should call the right functions.""" | ||
515 | 48 | self.mock_main() | ||
516 | 49 | state = self.script.SiloState() | ||
517 | 50 | self.assertEqual(self.script.main(), 0) | ||
518 | 51 | self.script.Manager.assert_called_once_with(state) | ||
521 | 52 | self.assertEqual(self.script.Manager.return_value.do.mock_calls, [ | 43 | self.assertEqual(self.script.Manager.return_value.do.mock_calls, [ |
522 | 53 | call('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging'), | 44 | call('checkstatus', 'validate', 'diff', 'checkupload', 'ackaging'), |
523 | 54 | call('dest_version_check', 'unapproved', 'unbuilt', 'publish'), | 45 | call('dest_version_check', 'unapproved', 'unbuilt', 'publish'), |
524 | 55 | ]) | 46 | ]) |
563 | 56 | self.assertEqual(state.status, 'Publishing.') | 47 | self.assertEqual(silo_state.status, 'Publishing.') |
564 | 57 | state.set_published.assert_called_once_with() | 48 | self.assertEqual(silo_state.mock_calls, [ |
565 | 58 | self.assertEqual(state.save_config.mock_calls, [call(), call()]) | 49 | call.save_config(), |
566 | 59 | state.mark_others_dirty.assert_called_once_with() | 50 | call.set_published(), |
567 | 60 | 51 | ]) | |
568 | 61 | def test_main_failure(self): | 52 | |
569 | 62 | """publisher.main() should handle errors.""" | 53 | def test_publish_failure(self): |
570 | 63 | self.mock_main() | 54 | """publisher.publish() should raise errors.""" |
571 | 64 | state = self.script.SiloState() | 55 | silo_state = Mock() |
572 | 65 | self.script.Manager.return_value.do.side_effect = PublishError('Oops') | 56 | self.script.Manager = Mock() |
573 | 66 | self.assertEqual(self.script.main(), 1) | 57 | err = PublishError('Oops') |
574 | 67 | self.assertEqual(state.status, "Publish failed: Oops") | 58 | self.script.Manager.return_value.do.side_effect = err |
575 | 68 | self.assertEqual(state.set_published.call_count, 0) | 59 | with self.assertRaisesRegexp(PublishError, 'Publish failed: Oops'): |
576 | 69 | self.script.logging.error.assert_called_once_with('Oops') | 60 | self.script.publish(silo_state) |
577 | 70 | self.assertEqual(state.save_config.mock_calls, [call()]) | 61 | self.assertEqual(silo_state.mock_calls, []) |
578 | 71 | self.assertEqual(state.mark_others_dirty.mock_calls, []) | 62 | |
579 | 72 | 63 | def test_publish_failure_wily(self): | |
542 | 73 | def test_main_failure_nostatus(self): | ||
543 | 74 | """publisher.main() shouldn't set status inappropriately.""" | ||
544 | 75 | self.mock_main() | ||
545 | 76 | state = self.script.SiloState() | ||
546 | 77 | state.status = 'Preserved' | ||
547 | 78 | self.script.Manager.return_value.do = Mock( | ||
548 | 79 | side_effect=NoStatusError('Whoops')) | ||
549 | 80 | self.assertEqual(self.script.main(), 1) | ||
550 | 81 | self.assertEqual(state.status, 'Preserved') | ||
551 | 82 | |||
552 | 83 | def test_main_failure_dirty(self): | ||
553 | 84 | """publisher.main() should call save_config sooner.""" | ||
554 | 85 | self.mock_main() | ||
555 | 86 | state = self.script.SiloState() | ||
556 | 87 | state.status = 'Packages built.' | ||
557 | 88 | self.script.SiloState.return_value.mark_others_dirty = Mock( | ||
558 | 89 | side_effect=Exception('Whoops')) | ||
559 | 90 | self.assertEqual(self.script.main(), 0) | ||
560 | 91 | self.assertEqual(state.status, 'Publishing.') | ||
561 | 92 | |||
562 | 93 | def test_main_failure_wily(self): | ||
580 | 94 | """Prevent publishing dual silos to wily.""" | 64 | """Prevent publishing dual silos to wily.""" |
590 | 95 | self.mock_main() | 65 | silo_state = Mock() |
591 | 96 | state = self.script.SiloState() | 66 | silo_state.status = 'Packages built.' |
592 | 97 | state.status = 'Packages built.' | 67 | silo_state._series = 'dual' |
593 | 98 | state._series = 'dual' | 68 | self.script.Manager = Mock() |
594 | 99 | self.assertEqual(self.script.main(), 1) | 69 | with self.assertRaisesRegexp(PublishError, 'must be transitioned'): |
595 | 100 | self.assertEqual( | 70 | self.script.publish(silo_state) |
587 | 101 | state.status, | ||
588 | 102 | 'Publish failed: This silo must be transitioned to xenial ' | ||
589 | 103 | 'before publishing.') | ||
596 | 104 | 71 | ||
597 | === modified file 'tests/unit/test_silomanager.py' | |||
598 | --- tests/unit/test_silomanager.py 2015-11-05 16:32:14 +0000 | |||
599 | +++ tests/unit/test_silomanager.py 2015-11-06 15:09:47 +0000 | |||
600 | @@ -28,9 +28,9 @@ | |||
601 | 28 | from tests.unit import DirectoryAwareTestCase | 28 | from tests.unit import DirectoryAwareTestCase |
602 | 29 | 29 | ||
603 | 30 | from cupstream2distro import silomanager | 30 | from cupstream2distro import silomanager |
605 | 31 | from cupstream2distro.silomanager import SiloState, splitter | 31 | from cupstream2distro.silomanager import SiloState, splitter, stock_main |
606 | 32 | from cupstream2distro.utils import env, utf8_open | 32 | from cupstream2distro.utils import env, utf8_open |
608 | 33 | from cupstream2distro.errors import PrepError | 33 | from cupstream2distro.errors import PrepError, NoStatusError |
609 | 34 | 34 | ||
610 | 35 | 35 | ||
611 | 36 | SOURCES = 'bar bat foo zing'.split() | 36 | SOURCES = 'bar bat foo zing'.split() |
612 | @@ -114,6 +114,42 @@ | |||
613 | 114 | expected = [['robru', 'sil2100']] * 7 + [[]] | 114 | expected = [['robru', 'sil2100']] * 7 + [[]] |
614 | 115 | self.assertEqual([splitter(case) for case in cases], expected) | 115 | self.assertEqual([splitter(case) for case in cases], expected) |
615 | 116 | 116 | ||
616 | 117 | @patch('cupstream2distro.silomanager.SiloState') | ||
617 | 118 | def test_stock_main(self, state_mock): | ||
618 | 119 | """Ensure that the stock main() function works.""" | ||
619 | 120 | self.assertEqual(stock_main(lambda state: state, True), 0) | ||
620 | 121 | self.assertEqual(state_mock.mock_calls, [ | ||
621 | 122 | call(self.tempdir, primary=True), | ||
622 | 123 | call().mark_dirty(), | ||
623 | 124 | call().save_config(), | ||
624 | 125 | call().mark_others_dirty(), | ||
625 | 126 | ]) | ||
626 | 127 | |||
627 | 128 | @patch('cupstream2distro.silomanager.SiloState') | ||
628 | 129 | def test_stock_main_fail(self, state_mock): | ||
629 | 130 | """Ensure that the stock main() function handles exceptions.""" | ||
630 | 131 | def body(silo_state): | ||
631 | 132 | """Raise a little hell.""" | ||
632 | 133 | raise PrepError('Boo!') | ||
633 | 134 | self.assertEqual(stock_main(body, True), PrepError.code) | ||
634 | 135 | self.assertEqual(state_mock.mock_calls, [ | ||
635 | 136 | call(self.tempdir, primary=True), | ||
636 | 137 | call().mark_dirty(), | ||
637 | 138 | call().save_config(), | ||
638 | 139 | ]) | ||
639 | 140 | |||
640 | 141 | @patch('cupstream2distro.silomanager.SiloState') | ||
641 | 142 | def test_stock_main_dirty_fail(self, state_mock): | ||
642 | 143 | """Ensure that the stock main() function handles dirty exceptions.""" | ||
643 | 144 | state_mock.return_value.mark_others_dirty.side_effect = Exception('Oh') | ||
644 | 145 | self.assertEqual(stock_main(lambda state: state, True), 0) | ||
645 | 146 | self.assertEqual(state_mock.mock_calls, [ | ||
646 | 147 | call(self.tempdir, primary=True), | ||
647 | 148 | call().mark_dirty(), | ||
648 | 149 | call().save_config(), | ||
649 | 150 | call().mark_others_dirty(), | ||
650 | 151 | ]) | ||
651 | 152 | |||
652 | 117 | def test_silostate_init(self): | 153 | def test_silostate_init(self): |
653 | 118 | """Ensure that init is sensible.""" | 154 | """Ensure that init is sensible.""" |
654 | 119 | state = SiloState('ubuntu/landing-123') | 155 | state = SiloState('ubuntu/landing-123') |
655 | @@ -375,6 +411,12 @@ | |||
656 | 375 | self.assertEqual(self.state.landers, 'm') | 411 | self.assertEqual(self.state.landers, 'm') |
657 | 376 | self.state.load_bileto.assert_called_once_with() | 412 | self.state.load_bileto.assert_called_once_with() |
658 | 377 | 413 | ||
659 | 414 | def test_silostate_setattr_nostateerror(self): | ||
660 | 415 | """Don't set status with NoStatusError.""" | ||
661 | 416 | self.state.status = 'Preserved' | ||
662 | 417 | self.state.status = NoStatusError('No') | ||
663 | 418 | self.assertEquals(self.state.status, 'Preserved') | ||
664 | 419 | |||
665 | 378 | @patch('cupstream2distro.silomanager.SiloState.series') | 420 | @patch('cupstream2distro.silomanager.SiloState.series') |
666 | 379 | @patch('cupstream2distro.silomanager.SiloState.iterate') | 421 | @patch('cupstream2distro.silomanager.SiloState.iterate') |
667 | 380 | @patch('cupstream2distro.silomanager.SiloState.all_projects', ['qtmir']) | 422 | @patch('cupstream2distro.silomanager.SiloState.all_projects', ['qtmir']) |
PASSED: Continuous integration, rev:1195 jenkins. qa.ubuntu. com/job/ cu2d-choo- choo-ci/ 889/
http://
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/cu2d- choo-choo- ci/889/ rebuild
http://