Merge lp:~cjwatson/launchpad/snap-initial-name-bzr into lp:launchpad
- snap-initial-name-bzr
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18715 |
Proposed branch: | lp:~cjwatson/launchpad/snap-initial-name-bzr |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/better-bzr-mp-diffs |
Diff against target: |
1044 lines (+564/-106) 9 files modified
lib/lp/code/interfaces/branch.py (+9/-0) lib/lp/code/model/branch.py (+63/-0) lib/lp/code/model/tests/test_branch.py (+152/-1) lib/lp/code/tests/helpers.py (+21/-2) lib/lp/snappy/browser/snap.py (+13/-30) lib/lp/snappy/browser/tests/test_snap.py (+69/-72) lib/lp/snappy/interfaces/snap.py (+26/-0) lib/lp/snappy/model/snap.py (+59/-1) lib/lp/snappy/tests/test_snap.py (+152/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/snap-initial-name-bzr |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+345757@code.launchpad.net |
Commit message
Extract initial Snap.store_name from snapcraft.yaml for Bazaar as well as Git.
Description of the change
The potential timeout issues will be at least as bad as with git, but BranchHostingClient has all the same sort of timeout management code as we put together for GitHostingClient, which should mitigate that.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Revision history for this message
Colin Watson (cjwatson) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/interfaces/branch.py' | |||
2 | --- lib/lp/code/interfaces/branch.py 2018-06-14 17:09:31 +0000 | |||
3 | +++ lib/lp/code/interfaces/branch.py 2018-06-14 17:09:31 +0000 | |||
4 | @@ -765,6 +765,15 @@ | |||
5 | 765 | :param launchbag: `ILaunchBag`. | 765 | :param launchbag: `ILaunchBag`. |
6 | 766 | """ | 766 | """ |
7 | 767 | 767 | ||
8 | 768 | def getBlob(filename, revision_id=None): | ||
9 | 769 | """Get a blob by file name from this branch. | ||
10 | 770 | |||
11 | 771 | :param filename: Relative path of a file in the branch. | ||
12 | 772 | :param revision_id: An optional revision ID. Defaults to the last | ||
13 | 773 | scanned revision ID of the branch. | ||
14 | 774 | :return: The blob content as a byte string. | ||
15 | 775 | """ | ||
16 | 776 | |||
17 | 768 | def getDiff(new, old): | 777 | def getDiff(new, old): |
18 | 769 | """Get the diff between two revisions in this branch. | 778 | """Get the diff between two revisions in this branch. |
19 | 770 | 779 | ||
20 | 771 | 780 | ||
21 | === modified file 'lib/lp/code/model/branch.py' | |||
22 | --- lib/lp/code/model/branch.py 2018-06-14 17:09:31 +0000 | |||
23 | +++ lib/lp/code/model/branch.py 2018-06-14 17:09:31 +0000 | |||
24 | @@ -10,12 +10,15 @@ | |||
25 | 10 | 10 | ||
26 | 11 | from datetime import datetime | 11 | from datetime import datetime |
27 | 12 | from functools import partial | 12 | from functools import partial |
28 | 13 | import json | ||
29 | 13 | import operator | 14 | import operator |
30 | 15 | import os.path | ||
31 | 14 | 16 | ||
32 | 15 | from bzrlib import urlutils | 17 | from bzrlib import urlutils |
33 | 16 | from bzrlib.revision import NULL_REVISION | 18 | from bzrlib.revision import NULL_REVISION |
34 | 17 | from lazr.lifecycle.event import ObjectCreatedEvent | 19 | from lazr.lifecycle.event import ObjectCreatedEvent |
35 | 18 | import pytz | 20 | import pytz |
36 | 21 | from six.moves.urllib_parse import urlsplit | ||
37 | 19 | from sqlobject import ( | 22 | from sqlobject import ( |
38 | 20 | ForeignKey, | 23 | ForeignKey, |
39 | 21 | IntCol, | 24 | IntCol, |
40 | @@ -84,6 +87,7 @@ | |||
41 | 84 | ) | 87 | ) |
42 | 85 | from lp.code.errors import ( | 88 | from lp.code.errors import ( |
43 | 86 | AlreadyLatestFormat, | 89 | AlreadyLatestFormat, |
44 | 90 | BranchFileNotFound, | ||
45 | 87 | BranchMergeProposalExists, | 91 | BranchMergeProposalExists, |
46 | 88 | BranchTargetError, | 92 | BranchTargetError, |
47 | 89 | BranchTypeError, | 93 | BranchTypeError, |
48 | @@ -172,10 +176,12 @@ | |||
49 | 172 | ArrayAgg, | 176 | ArrayAgg, |
50 | 173 | ArrayIntersects, | 177 | ArrayIntersects, |
51 | 174 | ) | 178 | ) |
52 | 179 | from lp.services.features import getFeatureFlag | ||
53 | 175 | from lp.services.helpers import shortlist | 180 | from lp.services.helpers import shortlist |
54 | 176 | from lp.services.job.interfaces.job import JobStatus | 181 | from lp.services.job.interfaces.job import JobStatus |
55 | 177 | from lp.services.job.model.job import Job | 182 | from lp.services.job.model.job import Job |
56 | 178 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet | 183 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet |
57 | 184 | from lp.services.memcache.interfaces import IMemcacheClient | ||
58 | 179 | from lp.services.propertycache import ( | 185 | from lp.services.propertycache import ( |
59 | 180 | cachedproperty, | 186 | cachedproperty, |
60 | 181 | get_property_cache, | 187 | get_property_cache, |
61 | @@ -783,6 +789,63 @@ | |||
62 | 783 | RevisionAuthor, revisions, ['revision_author_id']) | 789 | RevisionAuthor, revisions, ['revision_author_id']) |
63 | 784 | return DecoratedResultSet(result, pre_iter_hook=eager_load) | 790 | return DecoratedResultSet(result, pre_iter_hook=eager_load) |
64 | 785 | 791 | ||
65 | 792 | def getBlob(self, filename, revision_id=None, enable_memcache=None, | ||
66 | 793 | logger=None): | ||
67 | 794 | """See `IBranch`.""" | ||
68 | 795 | hosting_client = getUtility(IBranchHostingClient) | ||
69 | 796 | if enable_memcache is None: | ||
70 | 797 | enable_memcache = not getFeatureFlag( | ||
71 | 798 | u'code.bzr.blob.disable_memcache') | ||
72 | 799 | if revision_id is None: | ||
73 | 800 | revision_id = self.last_scanned_id | ||
74 | 801 | if revision_id is None: | ||
75 | 802 | # revision_id may still be None if the branch scanner hasn't | ||
76 | 803 | # scanned this branch yet. In this case, we won't be able to | ||
77 | 804 | # guarantee that subsequent calls to this method within the same | ||
78 | 805 | # transaction with revision_id=None will see the same revision, | ||
79 | 806 | # and we won't be able to cache file lists. Neither is fatal, | ||
80 | 807 | # and this should be relatively rare. | ||
81 | 808 | enable_memcache = False | ||
82 | 809 | dirname = os.path.dirname(filename) | ||
83 | 810 | unset = object() | ||
84 | 811 | file_list = unset | ||
85 | 812 | if enable_memcache: | ||
86 | 813 | memcache_client = getUtility(IMemcacheClient) | ||
87 | 814 | instance_name = urlsplit( | ||
88 | 815 | config.codehosting.internal_bzr_api_endpoint).hostname | ||
89 | 816 | memcache_key = '%s:bzr-file-list:%s:%s:%s' % ( | ||
90 | 817 | instance_name, self.id, revision_id, dirname) | ||
91 | 818 | if not isinstance(memcache_key, bytes): | ||
92 | 819 | memcache_key = memcache_key.encode('UTF-8') | ||
93 | 820 | cached_file_list = memcache_client.get(memcache_key) | ||
94 | 821 | if cached_file_list is not None: | ||
95 | 822 | try: | ||
96 | 823 | file_list = json.loads(cached_file_list) | ||
97 | 824 | except Exception: | ||
98 | 825 | logger.exception( | ||
99 | 826 | 'Cannot load cached file list for %s:%s:%s; deleting' % | ||
100 | 827 | (self.unique_name, revision_id, dirname)) | ||
101 | 828 | memcache_client.delete(memcache_key) | ||
102 | 829 | if file_list is unset: | ||
103 | 830 | try: | ||
104 | 831 | inventory = hosting_client.getInventory( | ||
105 | 832 | self.unique_name, dirname, rev=revision_id) | ||
106 | 833 | file_list = { | ||
107 | 834 | entry['filename']: entry['file_id'] | ||
108 | 835 | for entry in inventory['filelist']} | ||
109 | 836 | except BranchFileNotFound: | ||
110 | 837 | file_list = None | ||
111 | 838 | if enable_memcache: | ||
112 | 839 | # Cache the file list in case there's a request for another | ||
113 | 840 | # file in the same directory. | ||
114 | 841 | memcache_client.set(memcache_key, json.dumps(file_list)) | ||
115 | 842 | file_id = (file_list or {}).get(os.path.basename(filename)) | ||
116 | 843 | if file_id is None: | ||
117 | 844 | raise BranchFileNotFound( | ||
118 | 845 | self.unique_name, filename=filename, rev=revision_id) | ||
119 | 846 | return hosting_client.getBlob( | ||
120 | 847 | self.unique_name, file_id, rev=revision_id) | ||
121 | 848 | |||
122 | 786 | def getDiff(self, new, old=None): | 849 | def getDiff(self, new, old=None): |
123 | 787 | """See `IBranch`.""" | 850 | """See `IBranch`.""" |
124 | 788 | hosting_client = getUtility(IBranchHostingClient) | 851 | hosting_client = getUtility(IBranchHostingClient) |
125 | 789 | 852 | ||
126 | === modified file 'lib/lp/code/model/tests/test_branch.py' | |||
127 | --- lib/lp/code/model/tests/test_branch.py 2018-03-06 00:59:06 +0000 | |||
128 | +++ lib/lp/code/model/tests/test_branch.py 2018-06-14 17:09:31 +0000 | |||
129 | @@ -11,6 +11,7 @@ | |||
130 | 11 | datetime, | 11 | datetime, |
131 | 12 | timedelta, | 12 | timedelta, |
132 | 13 | ) | 13 | ) |
133 | 14 | import json | ||
134 | 14 | 15 | ||
135 | 15 | from bzrlib.branch import Branch | 16 | from bzrlib.branch import Branch |
136 | 16 | from bzrlib.bzrdir import BzrDir | 17 | from bzrlib.bzrdir import BzrDir |
137 | @@ -61,6 +62,7 @@ | |||
138 | 61 | AlreadyLatestFormat, | 62 | AlreadyLatestFormat, |
139 | 62 | BranchCreatorNotMemberOfOwnerTeam, | 63 | BranchCreatorNotMemberOfOwnerTeam, |
140 | 63 | BranchCreatorNotOwner, | 64 | BranchCreatorNotOwner, |
141 | 65 | BranchFileNotFound, | ||
142 | 64 | BranchTargetError, | 66 | BranchTargetError, |
143 | 65 | CannotDeleteBranch, | 67 | CannotDeleteBranch, |
144 | 66 | CannotUpgradeNonHosted, | 68 | CannotUpgradeNonHosted, |
145 | @@ -109,7 +111,10 @@ | |||
146 | 109 | from lp.code.model.branchrevision import BranchRevision | 111 | from lp.code.model.branchrevision import BranchRevision |
147 | 110 | from lp.code.model.codereviewcomment import CodeReviewComment | 112 | from lp.code.model.codereviewcomment import CodeReviewComment |
148 | 111 | from lp.code.model.revision import Revision | 113 | from lp.code.model.revision import Revision |
150 | 112 | from lp.code.tests.helpers import add_revision_to_branch | 114 | from lp.code.tests.helpers import ( |
151 | 115 | add_revision_to_branch, | ||
152 | 116 | BranchHostingFixture, | ||
153 | 117 | ) | ||
154 | 113 | from lp.codehosting.safe_open import BadUrl | 118 | from lp.codehosting.safe_open import BadUrl |
155 | 114 | from lp.codehosting.vfs.branchfs import get_real_branch_path | 119 | from lp.codehosting.vfs.branchfs import get_real_branch_path |
156 | 115 | from lp.registry.enums import ( | 120 | from lp.registry.enums import ( |
157 | @@ -136,6 +141,7 @@ | |||
158 | 136 | block_on_job, | 141 | block_on_job, |
159 | 137 | monitor_celery, | 142 | monitor_celery, |
160 | 138 | ) | 143 | ) |
161 | 144 | from lp.services.memcache.interfaces import IMemcacheClient | ||
162 | 139 | from lp.services.osutils import override_environ | 145 | from lp.services.osutils import override_environ |
163 | 140 | from lp.services.propertycache import clear_property_cache | 146 | from lp.services.propertycache import clear_property_cache |
164 | 141 | from lp.services.webapp.authorization import check_permission | 147 | from lp.services.webapp.authorization import check_permission |
165 | @@ -159,6 +165,7 @@ | |||
166 | 159 | ) | 165 | ) |
167 | 160 | from lp.testing.dbuser import dbuser | 166 | from lp.testing.dbuser import dbuser |
168 | 161 | from lp.testing.factory import LaunchpadObjectFactory | 167 | from lp.testing.factory import LaunchpadObjectFactory |
169 | 168 | from lp.testing.fakemethod import FakeMethod | ||
170 | 162 | from lp.testing.layers import ( | 169 | from lp.testing.layers import ( |
171 | 163 | CeleryBranchWriteJobLayer, | 170 | CeleryBranchWriteJobLayer, |
172 | 164 | CeleryBzrsyncdJobLayer, | 171 | CeleryBzrsyncdJobLayer, |
173 | @@ -3293,6 +3300,150 @@ | |||
174 | 3293 | self.assertRaises(BadUrl, db_stacked.getBzrBranch) | 3300 | self.assertRaises(BadUrl, db_stacked.getBzrBranch) |
175 | 3294 | 3301 | ||
176 | 3295 | 3302 | ||
177 | 3303 | class TestBranchGetBlob(TestCaseWithFactory): | ||
178 | 3304 | |||
179 | 3305 | layer = DatabaseFunctionalLayer | ||
180 | 3306 | |||
181 | 3307 | def test_default_rev_unscanned(self): | ||
182 | 3308 | branch = self.factory.makeBranch() | ||
183 | 3309 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
184 | 3310 | file_list={'README.txt': 'some-file-id'}, blob=b'Some text')) | ||
185 | 3311 | blob = branch.getBlob('src/README.txt') | ||
186 | 3312 | self.assertEqual('Some text', blob) | ||
187 | 3313 | self.assertEqual( | ||
188 | 3314 | [((branch.unique_name, 'src'), {'rev': None})], | ||
189 | 3315 | hosting_fixture.getInventory.calls) | ||
190 | 3316 | self.assertEqual( | ||
191 | 3317 | [((branch.unique_name, 'some-file-id'), {'rev': None})], | ||
192 | 3318 | hosting_fixture.getBlob.calls) | ||
193 | 3319 | self.assertEqual({}, getUtility(IMemcacheClient)._cache) | ||
194 | 3320 | |||
195 | 3321 | def test_default_rev_scanned(self): | ||
196 | 3322 | branch = self.factory.makeBranch() | ||
197 | 3323 | removeSecurityProxy(branch).last_scanned_id = 'scanned-id' | ||
198 | 3324 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
199 | 3325 | file_list={'README.txt': 'some-file-id'}, blob=b'Some text')) | ||
200 | 3326 | blob = branch.getBlob('src/README.txt') | ||
201 | 3327 | self.assertEqual('Some text', blob) | ||
202 | 3328 | self.assertEqual( | ||
203 | 3329 | [((branch.unique_name, 'src'), {'rev': 'scanned-id'})], | ||
204 | 3330 | hosting_fixture.getInventory.calls) | ||
205 | 3331 | self.assertEqual( | ||
206 | 3332 | [((branch.unique_name, 'some-file-id'), {'rev': 'scanned-id'})], | ||
207 | 3333 | hosting_fixture.getBlob.calls) | ||
208 | 3334 | key = ( | ||
209 | 3335 | 'bazaar.launchpad.dev:bzr-file-list:%s:scanned-id:src' % branch.id) | ||
210 | 3336 | self.assertEqual( | ||
211 | 3337 | json.dumps({'README.txt': 'some-file-id'}), | ||
212 | 3338 | getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
213 | 3339 | |||
214 | 3340 | def test_with_rev(self): | ||
215 | 3341 | branch = self.factory.makeBranch() | ||
216 | 3342 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
217 | 3343 | file_list={'README.txt': 'some-file-id'}, blob=b'Some text')) | ||
218 | 3344 | blob = branch.getBlob('src/README.txt', revision_id='some-rev') | ||
219 | 3345 | self.assertEqual('Some text', blob) | ||
220 | 3346 | self.assertEqual( | ||
221 | 3347 | [((branch.unique_name, 'src'), {'rev': 'some-rev'})], | ||
222 | 3348 | hosting_fixture.getInventory.calls) | ||
223 | 3349 | self.assertEqual( | ||
224 | 3350 | [((branch.unique_name, 'some-file-id'), {'rev': 'some-rev'})], | ||
225 | 3351 | hosting_fixture.getBlob.calls) | ||
226 | 3352 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
227 | 3353 | self.assertEqual( | ||
228 | 3354 | json.dumps({'README.txt': 'some-file-id'}), | ||
229 | 3355 | getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
230 | 3356 | |||
231 | 3357 | def test_cached_inventory(self): | ||
232 | 3358 | branch = self.factory.makeBranch() | ||
233 | 3359 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
234 | 3360 | blob=b'Some text')) | ||
235 | 3361 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
236 | 3362 | getUtility(IMemcacheClient).set( | ||
237 | 3363 | key.encode('UTF-8'), json.dumps({'README.txt': 'some-file-id'})) | ||
238 | 3364 | blob = branch.getBlob('src/README.txt', revision_id='some-rev') | ||
239 | 3365 | self.assertEqual('Some text', blob) | ||
240 | 3366 | self.assertEqual([], hosting_fixture.getInventory.calls) | ||
241 | 3367 | self.assertEqual( | ||
242 | 3368 | [((branch.unique_name, 'some-file-id'), {'rev': 'some-rev'})], | ||
243 | 3369 | hosting_fixture.getBlob.calls) | ||
244 | 3370 | |||
245 | 3371 | def test_disable_memcache(self): | ||
246 | 3372 | self.useFixture(FeatureFixture( | ||
247 | 3373 | {'code.bzr.blob.disable_memcache': 'on'})) | ||
248 | 3374 | branch = self.factory.makeBranch() | ||
249 | 3375 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
250 | 3376 | file_list={'README.txt': 'some-file-id'}, blob=b'Some text')) | ||
251 | 3377 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
252 | 3378 | getUtility(IMemcacheClient).set(key.encode('UTF-8'), '{}') | ||
253 | 3379 | blob = branch.getBlob('src/README.txt', revision_id='some-rev') | ||
254 | 3380 | self.assertEqual('Some text', blob) | ||
255 | 3381 | self.assertEqual( | ||
256 | 3382 | [((branch.unique_name, 'src'), {'rev': 'some-rev'})], | ||
257 | 3383 | hosting_fixture.getInventory.calls) | ||
258 | 3384 | self.assertEqual( | ||
259 | 3385 | '{}', getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
260 | 3386 | |||
261 | 3387 | def test_file_at_root_of_branch(self): | ||
262 | 3388 | branch = self.factory.makeBranch() | ||
263 | 3389 | hosting_fixture = self.useFixture(BranchHostingFixture( | ||
264 | 3390 | file_list={'README.txt': 'some-file-id'}, blob=b'Some text')) | ||
265 | 3391 | blob = branch.getBlob('README.txt', revision_id='some-rev') | ||
266 | 3392 | self.assertEqual('Some text', blob) | ||
267 | 3393 | self.assertEqual( | ||
268 | 3394 | [((branch.unique_name, ''), {'rev': 'some-rev'})], | ||
269 | 3395 | hosting_fixture.getInventory.calls) | ||
270 | 3396 | self.assertEqual( | ||
271 | 3397 | [((branch.unique_name, 'some-file-id'), {'rev': 'some-rev'})], | ||
272 | 3398 | hosting_fixture.getBlob.calls) | ||
273 | 3399 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:' % branch.id | ||
274 | 3400 | self.assertEqual( | ||
275 | 3401 | json.dumps({'README.txt': 'some-file-id'}), | ||
276 | 3402 | getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
277 | 3403 | |||
278 | 3404 | def test_file_not_in_directory(self): | ||
279 | 3405 | branch = self.factory.makeBranch() | ||
280 | 3406 | hosting_fixture = self.useFixture(BranchHostingFixture(file_list={})) | ||
281 | 3407 | self.assertRaises( | ||
282 | 3408 | BranchFileNotFound, branch.getBlob, | ||
283 | 3409 | 'src/README.txt', revision_id='some-rev') | ||
284 | 3410 | self.assertEqual( | ||
285 | 3411 | [((branch.unique_name, 'src'), {'rev': 'some-rev'})], | ||
286 | 3412 | hosting_fixture.getInventory.calls) | ||
287 | 3413 | self.assertEqual([], hosting_fixture.getBlob.calls) | ||
288 | 3414 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
289 | 3415 | self.assertEqual( | ||
290 | 3416 | '{}', getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
291 | 3417 | |||
292 | 3418 | def test_missing_directory(self): | ||
293 | 3419 | branch = self.factory.makeBranch() | ||
294 | 3420 | hosting_fixture = self.useFixture(BranchHostingFixture()) | ||
295 | 3421 | hosting_fixture.getInventory = FakeMethod( | ||
296 | 3422 | failure=BranchFileNotFound( | ||
297 | 3423 | branch.unique_name, filename='src', rev='some-rev')) | ||
298 | 3424 | self.assertRaises( | ||
299 | 3425 | BranchFileNotFound, branch.getBlob, | ||
300 | 3426 | 'src/README.txt', revision_id='some-rev') | ||
301 | 3427 | self.assertEqual( | ||
302 | 3428 | [((branch.unique_name, 'src'), {'rev': 'some-rev'})], | ||
303 | 3429 | hosting_fixture.getInventory.calls) | ||
304 | 3430 | self.assertEqual([], hosting_fixture.getBlob.calls) | ||
305 | 3431 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
306 | 3432 | self.assertEqual( | ||
307 | 3433 | 'null', getUtility(IMemcacheClient).get(key.encode('UTF-8'))) | ||
308 | 3434 | |||
309 | 3435 | def test_cached_missing_directory(self): | ||
310 | 3436 | branch = self.factory.makeBranch() | ||
311 | 3437 | hosting_fixture = self.useFixture(BranchHostingFixture()) | ||
312 | 3438 | key = 'bazaar.launchpad.dev:bzr-file-list:%s:some-rev:src' % branch.id | ||
313 | 3439 | getUtility(IMemcacheClient).set(key.encode('UTF-8'), 'null') | ||
314 | 3440 | self.assertRaises( | ||
315 | 3441 | BranchFileNotFound, branch.getBlob, | ||
316 | 3442 | 'src/README.txt', revision_id='some-rev') | ||
317 | 3443 | self.assertEqual([], hosting_fixture.getInventory.calls) | ||
318 | 3444 | self.assertEqual([], hosting_fixture.getBlob.calls) | ||
319 | 3445 | |||
320 | 3446 | |||
321 | 3296 | class TestBranchUnscan(TestCaseWithFactory): | 3447 | class TestBranchUnscan(TestCaseWithFactory): |
322 | 3297 | 3448 | ||
323 | 3298 | layer = DatabaseFunctionalLayer | 3449 | layer = DatabaseFunctionalLayer |
324 | 3299 | 3450 | ||
325 | === modified file 'lib/lp/code/tests/helpers.py' | |||
326 | --- lib/lp/code/tests/helpers.py 2018-06-14 17:09:31 +0000 | |||
327 | +++ lib/lp/code/tests/helpers.py 2018-06-14 17:09:31 +0000 | |||
328 | @@ -306,14 +306,33 @@ | |||
329 | 306 | class BranchHostingFixture(fixtures.Fixture): | 306 | class BranchHostingFixture(fixtures.Fixture): |
330 | 307 | """A fixture that temporarily registers a fake Bazaar hosting client.""" | 307 | """A fixture that temporarily registers a fake Bazaar hosting client.""" |
331 | 308 | 308 | ||
333 | 309 | def __init__(self, diff=None, inventory=None, blob=None): | 309 | def __init__(self, diff=None, inventory=None, file_list=None, blob=None, |
334 | 310 | disable_memcache=True): | ||
335 | 310 | self.create = FakeMethod() | 311 | self.create = FakeMethod() |
336 | 311 | self.getDiff = FakeMethod(result=diff or {}) | 312 | self.getDiff = FakeMethod(result=diff or {}) |
338 | 312 | self.getInventory = FakeMethod(result=inventory or {}) | 313 | if inventory is None: |
339 | 314 | if file_list is not None: | ||
340 | 315 | # Simple common case. | ||
341 | 316 | inventory = { | ||
342 | 317 | "filelist": [ | ||
343 | 318 | {"filename": filename, "file_id": file_id} | ||
344 | 319 | for filename, file_id in file_list.items()], | ||
345 | 320 | } | ||
346 | 321 | else: | ||
347 | 322 | inventory = {"filelist": []} | ||
348 | 323 | self.getInventory = FakeMethod(result=inventory) | ||
349 | 313 | self.getBlob = FakeMethod(result=blob) | 324 | self.getBlob = FakeMethod(result=blob) |
350 | 325 | self.disable_memcache = disable_memcache | ||
351 | 314 | 326 | ||
352 | 315 | def _setUp(self): | 327 | def _setUp(self): |
353 | 316 | self.useFixture(ZopeUtilityFixture(self, IBranchHostingClient)) | 328 | self.useFixture(ZopeUtilityFixture(self, IBranchHostingClient)) |
354 | 329 | if self.disable_memcache: | ||
355 | 330 | # Most tests that involve Branch.getBlob don't want to cache the | ||
356 | 331 | # result: doing so requires more time-consuming test setup and | ||
357 | 332 | # makes it awkward to repeat the same call with different | ||
358 | 333 | # responses. For convenience, we make it easy to disable that | ||
359 | 334 | # here. | ||
360 | 335 | self.memcache_fixture = self.useFixture(MemcacheFixture()) | ||
361 | 317 | 336 | ||
362 | 318 | 337 | ||
363 | 319 | class GitHostingFixture(fixtures.Fixture): | 338 | class GitHostingFixture(fixtures.Fixture): |
364 | 320 | 339 | ||
365 | === modified file 'lib/lp/snappy/browser/snap.py' | |||
366 | --- lib/lp/snappy/browser/snap.py 2018-04-21 10:01:22 +0000 | |||
367 | +++ lib/lp/snappy/browser/snap.py 2018-06-14 17:09:31 +0000 | |||
368 | @@ -23,7 +23,6 @@ | |||
369 | 23 | copy_field, | 23 | copy_field, |
370 | 24 | use_template, | 24 | use_template, |
371 | 25 | ) | 25 | ) |
372 | 26 | import yaml | ||
373 | 27 | from zope.component import getUtility | 26 | from zope.component import getUtility |
374 | 28 | from zope.error.interfaces import IErrorReportingUtility | 27 | from zope.error.interfaces import IErrorReportingUtility |
375 | 29 | from zope.interface import Interface | 28 | from zope.interface import Interface |
376 | @@ -44,6 +43,7 @@ | |||
377 | 44 | from lp.app.browser.lazrjs import InlinePersonEditPickerWidget | 43 | from lp.app.browser.lazrjs import InlinePersonEditPickerWidget |
378 | 45 | from lp.app.browser.tales import format_link | 44 | from lp.app.browser.tales import format_link |
379 | 46 | from lp.app.enums import PRIVATE_INFORMATION_TYPES | 45 | from lp.app.enums import PRIVATE_INFORMATION_TYPES |
380 | 46 | from lp.app.errors import NotFoundError | ||
381 | 47 | from lp.app.interfaces.informationtype import IInformationType | 47 | from lp.app.interfaces.informationtype import IInformationType |
382 | 48 | from lp.app.widgets.itemswidgets import ( | 48 | from lp.app.widgets.itemswidgets import ( |
383 | 49 | LabeledMultiCheckBoxWidget, | 49 | LabeledMultiCheckBoxWidget, |
384 | @@ -52,10 +52,6 @@ | |||
385 | 52 | ) | 52 | ) |
386 | 53 | from lp.buildmaster.interfaces.processor import IProcessorSet | 53 | from lp.buildmaster.interfaces.processor import IProcessorSet |
387 | 54 | from lp.code.browser.widgets.gitref import GitRefWidget | 54 | from lp.code.browser.widgets.gitref import GitRefWidget |
388 | 55 | from lp.code.errors import ( | ||
389 | 56 | GitRepositoryBlobNotFound, | ||
390 | 57 | GitRepositoryScanFault, | ||
391 | 58 | ) | ||
392 | 59 | from lp.code.interfaces.gitref import IGitRef | 55 | from lp.code.interfaces.gitref import IGitRef |
393 | 60 | from lp.registry.enums import VCSType | 56 | from lp.registry.enums import VCSType |
394 | 61 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 57 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
395 | @@ -83,6 +79,8 @@ | |||
396 | 83 | from lp.snappy.browser.widgets.storechannels import StoreChannelsWidget | 79 | from lp.snappy.browser.widgets.storechannels import StoreChannelsWidget |
397 | 84 | from lp.snappy.interfaces.snap import ( | 80 | from lp.snappy.interfaces.snap import ( |
398 | 85 | CannotAuthorizeStoreUploads, | 81 | CannotAuthorizeStoreUploads, |
399 | 82 | CannotFetchSnapcraftYaml, | ||
400 | 83 | CannotParseSnapcraftYaml, | ||
401 | 86 | ISnap, | 84 | ISnap, |
402 | 87 | ISnapSet, | 85 | ISnapSet, |
403 | 88 | NoSuchSnap, | 86 | NoSuchSnap, |
404 | @@ -424,34 +422,19 @@ | |||
405 | 424 | @property | 422 | @property |
406 | 425 | def initial_values(self): | 423 | def initial_values(self): |
407 | 426 | store_name = None | 424 | store_name = None |
409 | 427 | if self.has_snappy_distro_series and IGitRef.providedBy(self.context): | 425 | if self.has_snappy_distro_series: |
410 | 428 | # Try to extract Snap store name from snapcraft.yaml file. | 426 | # Try to extract Snap store name from snapcraft.yaml file. |
411 | 429 | try: | 427 | try: |
432 | 430 | paths = ( | 428 | snapcraft_data = getUtility(ISnapSet).getSnapcraftYaml( |
433 | 431 | 'snap/snapcraft.yaml', | 429 | self.context, logger=log) |
434 | 432 | 'snapcraft.yaml', | 430 | except (NotFoundError, CannotFetchSnapcraftYaml, |
435 | 433 | '.snapcraft.yaml', | 431 | CannotParseSnapcraftYaml): |
416 | 434 | ) | ||
417 | 435 | for i, path in enumerate(paths): | ||
418 | 436 | try: | ||
419 | 437 | blob = self.context.repository.getBlob( | ||
420 | 438 | path, self.context.name) | ||
421 | 439 | break | ||
422 | 440 | except GitRepositoryBlobNotFound: | ||
423 | 441 | if i == len(paths) - 1: | ||
424 | 442 | raise | ||
425 | 443 | # Beware of unsafe yaml.load()! | ||
426 | 444 | store_name = yaml.safe_load(blob).get('name') | ||
427 | 445 | except GitRepositoryScanFault: | ||
428 | 446 | log.exception("Failed to get Snap manifest from Git %s", | ||
429 | 447 | self.context.unique_name) | ||
430 | 448 | except (AttributeError, yaml.YAMLError): | ||
431 | 449 | # Ignore parsing errors from invalid, user-supplied YAML | ||
436 | 450 | pass | 432 | pass |
441 | 451 | except Exception as e: | 433 | else: |
442 | 452 | log.exception( | 434 | try: |
443 | 453 | "Failed to extract name from Snap manifest at Git %s: %s", | 435 | store_name = snapcraft_data.get('name') |
444 | 454 | self.context.unique_name, unicode(e)) | 436 | except AttributeError: |
445 | 437 | pass | ||
446 | 455 | 438 | ||
447 | 456 | store_series = getUtility(ISnappySeriesSet).getAll().first() | 439 | store_series = getUtility(ISnappySeriesSet).getAll().first() |
448 | 457 | if store_series.preferred_distro_series is not None: | 440 | if store_series.preferred_distro_series is not None: |
449 | 458 | 441 | ||
450 | === modified file 'lib/lp/snappy/browser/tests/test_snap.py' | |||
451 | --- lib/lp/snappy/browser/tests/test_snap.py 2018-05-31 10:23:03 +0000 | |||
452 | +++ lib/lp/snappy/browser/tests/test_snap.py 2018-06-14 17:09:31 +0000 | |||
453 | @@ -21,7 +21,6 @@ | |||
454 | 21 | 21 | ||
455 | 22 | from fixtures import FakeLogger | 22 | from fixtures import FakeLogger |
456 | 23 | from mechanize import LinkNotFoundError | 23 | from mechanize import LinkNotFoundError |
457 | 24 | import mock | ||
458 | 25 | from pymacaroons import Macaroon | 24 | from pymacaroons import Macaroon |
459 | 26 | import pytz | 25 | import pytz |
460 | 27 | import responses | 26 | import responses |
461 | @@ -40,10 +39,13 @@ | |||
462 | 40 | from lp.buildmaster.enums import BuildStatus | 39 | from lp.buildmaster.enums import BuildStatus |
463 | 41 | from lp.buildmaster.interfaces.processor import IProcessorSet | 40 | from lp.buildmaster.interfaces.processor import IProcessorSet |
464 | 42 | from lp.code.errors import ( | 41 | from lp.code.errors import ( |
466 | 43 | GitRepositoryBlobNotFound, | 42 | BranchHostingFault, |
467 | 44 | GitRepositoryScanFault, | 43 | GitRepositoryScanFault, |
468 | 45 | ) | 44 | ) |
470 | 46 | from lp.code.tests.helpers import GitHostingFixture | 45 | from lp.code.tests.helpers import ( |
471 | 46 | BranchHostingFixture, | ||
472 | 47 | GitHostingFixture, | ||
473 | 48 | ) | ||
474 | 47 | from lp.registry.enums import PersonVisibility | 49 | from lp.registry.enums import PersonVisibility |
475 | 48 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 50 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
476 | 49 | from lp.registry.interfaces.series import SeriesStatus | 51 | from lp.registry.interfaces.series import SeriesStatus |
477 | @@ -128,6 +130,7 @@ | |||
478 | 128 | def test_private_feature_flag_disabled(self): | 130 | def test_private_feature_flag_disabled(self): |
479 | 129 | # Without a private_snap feature flag, we will not create Snaps for | 131 | # Without a private_snap feature flag, we will not create Snaps for |
480 | 130 | # private contexts. | 132 | # private contexts. |
481 | 133 | self.useFixture(BranchHostingFixture()) | ||
482 | 131 | self.snap_store_client = FakeMethod() | 134 | self.snap_store_client = FakeMethod() |
483 | 132 | self.snap_store_client.listChannels = FakeMethod(result=[]) | 135 | self.snap_store_client.listChannels = FakeMethod(result=[]) |
484 | 133 | self.useFixture( | 136 | self.useFixture( |
485 | @@ -197,6 +200,7 @@ | |||
486 | 197 | def test_initial_store_distro_series(self): | 200 | def test_initial_store_distro_series(self): |
487 | 198 | # The initial store_distro_series uses the preferred distribution | 201 | # The initial store_distro_series uses the preferred distribution |
488 | 199 | # series for the latest snappy series. | 202 | # series for the latest snappy series. |
489 | 203 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
490 | 200 | lts = self.factory.makeUbuntuDistroSeries( | 204 | lts = self.factory.makeUbuntuDistroSeries( |
491 | 201 | version="16.04", status=SeriesStatus.CURRENT) | 205 | version="16.04", status=SeriesStatus.CURRENT) |
492 | 202 | current = self.factory.makeUbuntuDistroSeries( | 206 | current = self.factory.makeUbuntuDistroSeries( |
493 | @@ -221,6 +225,7 @@ | |||
494 | 221 | no_login=True) | 225 | no_login=True) |
495 | 222 | 226 | ||
496 | 223 | def test_create_new_snap_bzr(self): | 227 | def test_create_new_snap_bzr(self): |
497 | 228 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
498 | 224 | branch = self.factory.makeAnyBranch() | 229 | branch = self.factory.makeAnyBranch() |
499 | 225 | source_display = branch.display_name | 230 | source_display = branch.display_name |
500 | 226 | browser = self.getViewBrowser( | 231 | browser = self.getViewBrowser( |
501 | @@ -257,7 +262,7 @@ | |||
502 | 257 | MatchesTagText(content, "store_upload")) | 262 | MatchesTagText(content, "store_upload")) |
503 | 258 | 263 | ||
504 | 259 | def test_create_new_snap_git(self): | 264 | def test_create_new_snap_git(self): |
506 | 260 | self.useFixture(GitHostingFixture(blob="")) | 265 | self.useFixture(GitHostingFixture(blob=b"")) |
507 | 261 | [git_ref] = self.factory.makeGitRefs() | 266 | [git_ref] = self.factory.makeGitRefs() |
508 | 262 | source_display = git_ref.display_name | 267 | source_display = git_ref.display_name |
509 | 263 | browser = self.getViewBrowser( | 268 | browser = self.getViewBrowser( |
510 | @@ -295,6 +300,7 @@ | |||
511 | 295 | 300 | ||
512 | 296 | def test_create_new_snap_users_teams_as_owner_options(self): | 301 | def test_create_new_snap_users_teams_as_owner_options(self): |
513 | 297 | # Teams that the user is in are options for the snap package owner. | 302 | # Teams that the user is in are options for the snap package owner. |
514 | 303 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
515 | 298 | self.factory.makeTeam( | 304 | self.factory.makeTeam( |
516 | 299 | name="test-team", displayname="Test Team", members=[self.person]) | 305 | name="test-team", displayname="Test Team", members=[self.person]) |
517 | 300 | branch = self.factory.makeAnyBranch() | 306 | branch = self.factory.makeAnyBranch() |
518 | @@ -307,6 +313,7 @@ | |||
519 | 307 | 313 | ||
520 | 308 | def test_create_new_snap_public(self): | 314 | def test_create_new_snap_public(self): |
521 | 309 | # Public owner implies public snap. | 315 | # Public owner implies public snap. |
522 | 316 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
523 | 310 | branch = self.factory.makeAnyBranch() | 317 | branch = self.factory.makeAnyBranch() |
524 | 311 | 318 | ||
525 | 312 | browser = self.getViewBrowser( | 319 | browser = self.getViewBrowser( |
526 | @@ -339,6 +346,7 @@ | |||
527 | 339 | 346 | ||
528 | 340 | def test_create_new_snap_private(self): | 347 | def test_create_new_snap_private(self): |
529 | 341 | # Private teams will automatically create private snaps. | 348 | # Private teams will automatically create private snaps. |
530 | 349 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
531 | 342 | login_person(self.person) | 350 | login_person(self.person) |
532 | 343 | self.factory.makeTeam( | 351 | self.factory.makeTeam( |
533 | 344 | name='super-private', owner=self.person, | 352 | name='super-private', owner=self.person, |
534 | @@ -360,6 +368,7 @@ | |||
535 | 360 | 368 | ||
536 | 361 | def test_create_new_snap_build_source_tarball(self): | 369 | def test_create_new_snap_build_source_tarball(self): |
537 | 362 | # We can create a new snap and ask for it to build a source tarball. | 370 | # We can create a new snap and ask for it to build a source tarball. |
538 | 371 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
539 | 363 | branch = self.factory.makeAnyBranch() | 372 | branch = self.factory.makeAnyBranch() |
540 | 364 | browser = self.getViewBrowser( | 373 | browser = self.getViewBrowser( |
541 | 365 | branch, view_name="+new-snap", user=self.person) | 374 | branch, view_name="+new-snap", user=self.person) |
542 | @@ -375,6 +384,7 @@ | |||
543 | 375 | def test_create_new_snap_auto_build(self): | 384 | def test_create_new_snap_auto_build(self): |
544 | 376 | # Creating a new snap and asking for it to be automatically built | 385 | # Creating a new snap and asking for it to be automatically built |
545 | 377 | # sets all the appropriate fields. | 386 | # sets all the appropriate fields. |
546 | 387 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
547 | 378 | branch = self.factory.makeAnyBranch() | 388 | branch = self.factory.makeAnyBranch() |
548 | 379 | archive = self.factory.makeArchive() | 389 | archive = self.factory.makeArchive() |
549 | 380 | browser = self.getViewBrowser( | 390 | browser = self.getViewBrowser( |
550 | @@ -405,6 +415,7 @@ | |||
551 | 405 | # Creating a new snap and asking for it to be automatically uploaded | 415 | # Creating a new snap and asking for it to be automatically uploaded |
552 | 406 | # to the store sets all the appropriate fields and redirects to SSO | 416 | # to the store sets all the appropriate fields and redirects to SSO |
553 | 407 | # for authorization. | 417 | # for authorization. |
554 | 418 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
555 | 408 | branch = self.factory.makeAnyBranch() | 419 | branch = self.factory.makeAnyBranch() |
556 | 409 | view_url = canonical_url(branch, view_name="+new-snap") | 420 | view_url = canonical_url(branch, view_name="+new-snap") |
557 | 410 | browser = self.getNonRedirectingBrowser(url=view_url, user=self.person) | 421 | browser = self.getNonRedirectingBrowser(url=view_url, user=self.person) |
558 | @@ -460,6 +471,7 @@ | |||
559 | 460 | self.assertEqual(expected_args, parse_qs(parsed_location[3])) | 471 | self.assertEqual(expected_args, parse_qs(parsed_location[3])) |
560 | 461 | 472 | ||
561 | 462 | def test_create_new_snap_display_processors(self): | 473 | def test_create_new_snap_display_processors(self): |
562 | 474 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
563 | 463 | branch = self.factory.makeAnyBranch() | 475 | branch = self.factory.makeAnyBranch() |
564 | 464 | self.setUpDistroSeries() | 476 | self.setUpDistroSeries() |
565 | 465 | browser = self.getViewBrowser( | 477 | browser = self.getViewBrowser( |
566 | @@ -473,6 +485,7 @@ | |||
567 | 473 | 485 | ||
568 | 474 | def test_create_new_snap_display_restricted_processors(self): | 486 | def test_create_new_snap_display_restricted_processors(self): |
569 | 475 | # A restricted processor is shown disabled in the UI. | 487 | # A restricted processor is shown disabled in the UI. |
570 | 488 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
571 | 476 | branch = self.factory.makeAnyBranch() | 489 | branch = self.factory.makeAnyBranch() |
572 | 477 | distroseries = self.setUpDistroSeries() | 490 | distroseries = self.setUpDistroSeries() |
573 | 478 | proc_armhf = self.factory.makeProcessor( | 491 | proc_armhf = self.factory.makeProcessor( |
574 | @@ -487,6 +500,7 @@ | |||
575 | 487 | processors, ["386", "amd64", "hppa"], ["armhf"]) | 500 | processors, ["386", "amd64", "hppa"], ["armhf"]) |
576 | 488 | 501 | ||
577 | 489 | def test_create_new_snap_processors(self): | 502 | def test_create_new_snap_processors(self): |
578 | 503 | self.useFixture(BranchHostingFixture(blob=b"")) | ||
579 | 490 | branch = self.factory.makeAnyBranch() | 504 | branch = self.factory.makeAnyBranch() |
580 | 491 | self.setUpDistroSeries() | 505 | self.setUpDistroSeries() |
581 | 492 | browser = self.getViewBrowser( | 506 | browser = self.getViewBrowser( |
582 | @@ -500,74 +514,57 @@ | |||
583 | 500 | self.assertContentEqual( | 514 | self.assertContentEqual( |
584 | 501 | ["386", "amd64"], [proc.name for proc in snap.processors]) | 515 | ["386", "amd64"], [proc.name for proc in snap.processors]) |
585 | 502 | 516 | ||
654 | 503 | def test_initial_name_extraction_git_snap_snapcraft_yaml(self): | 517 | def test_initial_name_extraction_bzr_success(self): |
655 | 504 | def getBlob(filename, *args, **kwargs): | 518 | self.useFixture(BranchHostingFixture( |
656 | 505 | if filename == "snap/snapcraft.yaml": | 519 | file_list={"snapcraft.yaml": "file-id"}, blob=b"name: test-snap")) |
657 | 506 | return "name: test-snap" | 520 | branch = self.factory.makeBranch() |
658 | 507 | else: | 521 | view = create_initialized_view(branch, "+new-snap") |
659 | 508 | raise GitRepositoryBlobNotFound("dummy", filename) | 522 | initial_values = view.initial_values |
660 | 509 | 523 | self.assertIn('store_name', initial_values) | |
661 | 510 | [git_ref] = self.factory.makeGitRefs() | 524 | self.assertEqual('test-snap', initial_values['store_name']) |
662 | 511 | git_ref.repository.getBlob = getBlob | 525 | |
663 | 512 | view = create_initialized_view(git_ref, "+new-snap") | 526 | def test_initial_name_extraction_bzr_error(self): |
664 | 513 | initial_values = view.initial_values | 527 | self.useFixture(BranchHostingFixture()).getInventory = FakeMethod( |
665 | 514 | self.assertIn('store_name', initial_values) | 528 | failure=BranchHostingFault) |
666 | 515 | self.assertEqual('test-snap', initial_values['store_name']) | 529 | branch = self.factory.makeBranch() |
667 | 516 | 530 | view = create_initialized_view(branch, "+new-snap") | |
668 | 517 | def test_initial_name_extraction_git_plain_snapcraft_yaml(self): | 531 | initial_values = view.initial_values |
669 | 518 | def getBlob(filename, *args, **kwargs): | 532 | self.assertIn('store_name', initial_values) |
670 | 519 | if filename == "snapcraft.yaml": | 533 | self.assertIsNone(initial_values['store_name']) |
671 | 520 | return "name: test-snap" | 534 | |
672 | 521 | else: | 535 | def test_initial_name_extraction_bzr_no_name(self): |
673 | 522 | raise GitRepositoryBlobNotFound("dummy", filename) | 536 | self.useFixture(BranchHostingFixture( |
674 | 523 | 537 | file_list={"snapcraft.yaml": "file-id"}, blob=b"some: nonsense")) | |
675 | 524 | [git_ref] = self.factory.makeGitRefs() | 538 | branch = self.factory.makeBranch() |
676 | 525 | git_ref.repository.getBlob = getBlob | 539 | view = create_initialized_view(branch, "+new-snap") |
677 | 526 | view = create_initialized_view(git_ref, "+new-snap") | 540 | initial_values = view.initial_values |
678 | 527 | initial_values = view.initial_values | 541 | self.assertIn('store_name', initial_values) |
679 | 528 | self.assertIn('store_name', initial_values) | 542 | self.assertIsNone(initial_values['store_name']) |
680 | 529 | self.assertEqual('test-snap', initial_values['store_name']) | 543 | |
681 | 530 | 544 | def test_initial_name_extraction_git_success(self): | |
682 | 531 | def test_initial_name_extraction_git_dot_snapcraft_yaml(self): | 545 | self.useFixture(GitHostingFixture(blob=b"name: test-snap")) |
683 | 532 | def getBlob(filename, *args, **kwargs): | 546 | [git_ref] = self.factory.makeGitRefs() |
684 | 533 | if filename == ".snapcraft.yaml": | 547 | view = create_initialized_view(git_ref, "+new-snap") |
685 | 534 | return "name: test-snap" | 548 | initial_values = view.initial_values |
686 | 535 | else: | 549 | self.assertIn('store_name', initial_values) |
687 | 536 | raise GitRepositoryBlobNotFound("dummy", filename) | 550 | self.assertEqual('test-snap', initial_values['store_name']) |
688 | 537 | 551 | ||
689 | 538 | [git_ref] = self.factory.makeGitRefs() | 552 | def test_initial_name_extraction_git_error(self): |
690 | 539 | git_ref.repository.getBlob = getBlob | 553 | self.useFixture(GitHostingFixture()).getBlob = FakeMethod( |
691 | 540 | view = create_initialized_view(git_ref, "+new-snap") | 554 | failure=GitRepositoryScanFault) |
692 | 541 | initial_values = view.initial_values | 555 | [git_ref] = self.factory.makeGitRefs() |
693 | 542 | self.assertIn('store_name', initial_values) | 556 | view = create_initialized_view(git_ref, "+new-snap") |
694 | 543 | self.assertEqual('test-snap', initial_values['store_name']) | 557 | initial_values = view.initial_values |
695 | 544 | 558 | self.assertIn('store_name', initial_values) | |
696 | 545 | def test_initial_name_extraction_git_repo_error(self): | 559 | self.assertIsNone(initial_values['store_name']) |
697 | 546 | [git_ref] = self.factory.makeGitRefs() | 560 | |
698 | 547 | git_ref.repository.getBlob = FakeMethod(failure=GitRepositoryScanFault) | 561 | def test_initial_name_extraction_git_no_name(self): |
699 | 548 | view = create_initialized_view(git_ref, "+new-snap") | 562 | self.useFixture(GitHostingFixture(blob=b"some: nonsense")) |
700 | 549 | initial_values = view.initial_values | 563 | [git_ref] = self.factory.makeGitRefs() |
701 | 550 | self.assertIn('store_name', initial_values) | 564 | view = create_initialized_view(git_ref, "+new-snap") |
702 | 551 | self.assertIsNone(initial_values['store_name']) | 565 | initial_values = view.initial_values |
703 | 552 | 566 | self.assertIn('store_name', initial_values) | |
704 | 553 | def test_initial_name_extraction_git_invalid_data(self): | 567 | self.assertIsNone(initial_values['store_name']) |
637 | 554 | for invalid_result in (None, 123, '', '[][]', '#name:test', ']'): | ||
638 | 555 | [git_ref] = self.factory.makeGitRefs() | ||
639 | 556 | git_ref.repository.getBlob = FakeMethod(result=invalid_result) | ||
640 | 557 | view = create_initialized_view(git_ref, "+new-snap") | ||
641 | 558 | initial_values = view.initial_values | ||
642 | 559 | self.assertIn('store_name', initial_values) | ||
643 | 560 | self.assertIsNone(initial_values['store_name']) | ||
644 | 561 | |||
645 | 562 | def test_initial_name_extraction_git_safe_yaml(self): | ||
646 | 563 | [git_ref] = self.factory.makeGitRefs() | ||
647 | 564 | git_ref.repository.getBlob = FakeMethod(result='Malicious YAML!') | ||
648 | 565 | view = create_initialized_view(git_ref, "+new-snap") | ||
649 | 566 | with mock.patch('yaml.load') as unsafe_load: | ||
650 | 567 | with mock.patch('yaml.safe_load') as safe_load: | ||
651 | 568 | view.initial_values | ||
652 | 569 | self.assertEqual(0, unsafe_load.call_count) | ||
653 | 570 | self.assertEqual(1, safe_load.call_count) | ||
705 | 571 | 568 | ||
706 | 572 | 569 | ||
707 | 573 | class TestSnapAdminView(BaseTestSnapView): | 570 | class TestSnapAdminView(BaseTestSnapView): |
708 | 574 | 571 | ||
709 | === modified file 'lib/lp/snappy/interfaces/snap.py' | |||
710 | --- lib/lp/snappy/interfaces/snap.py 2018-05-07 05:25:27 +0000 | |||
711 | +++ lib/lp/snappy/interfaces/snap.py 2018-06-14 17:09:31 +0000 | |||
712 | @@ -9,7 +9,9 @@ | |||
713 | 9 | 'BadSnapSearchContext', | 9 | 'BadSnapSearchContext', |
714 | 10 | 'BadSnapSource', | 10 | 'BadSnapSource', |
715 | 11 | 'CannotAuthorizeStoreUploads', | 11 | 'CannotAuthorizeStoreUploads', |
716 | 12 | 'CannotFetchSnapcraftYaml', | ||
717 | 12 | 'CannotModifySnapProcessor', | 13 | 'CannotModifySnapProcessor', |
718 | 14 | 'CannotParseSnapcraftYaml', | ||
719 | 13 | 'CannotRequestAutoBuilds', | 15 | 'CannotRequestAutoBuilds', |
720 | 14 | 'DuplicateSnapName', | 16 | 'DuplicateSnapName', |
721 | 15 | 'ISnap', | 17 | 'ISnap', |
722 | @@ -237,6 +239,14 @@ | |||
723 | 237 | "because %s is not set." % field) | 239 | "because %s is not set." % field) |
724 | 238 | 240 | ||
725 | 239 | 241 | ||
726 | 242 | class CannotFetchSnapcraftYaml(Exception): | ||
727 | 243 | """Launchpad cannot fetch this snap package's snapcraft.yaml.""" | ||
728 | 244 | |||
729 | 245 | |||
730 | 246 | class CannotParseSnapcraftYaml(Exception): | ||
731 | 247 | """Launchpad cannot parse this snap package's snapcraft.yaml.""" | ||
732 | 248 | |||
733 | 249 | |||
734 | 240 | class ISnapView(Interface): | 250 | class ISnapView(Interface): |
735 | 241 | """`ISnap` attributes that require launchpad.View permission.""" | 251 | """`ISnap` attributes that require launchpad.View permission.""" |
736 | 242 | 252 | ||
737 | @@ -787,6 +797,22 @@ | |||
738 | 787 | def preloadDataForSnaps(snaps, user): | 797 | def preloadDataForSnaps(snaps, user): |
739 | 788 | """Load the data related to a list of snap packages.""" | 798 | """Load the data related to a list of snap packages.""" |
740 | 789 | 799 | ||
741 | 800 | def getSnapcraftYaml(context, logger=None): | ||
742 | 801 | """Fetch a package's snapcraft.yaml from code hosting, if possible. | ||
743 | 802 | |||
744 | 803 | :param context: Either an `ISnap` or the source branch for a snap | ||
745 | 804 | package. | ||
746 | 805 | :param logger: An optional logger. | ||
747 | 806 | |||
748 | 807 | :return: The package's parsed snapcraft.yaml. | ||
749 | 808 | :raises NotFoundError: if this package has no snapcraft.yaml. | ||
750 | 809 | :raises CannotFetchSnapcraftYaml: if it was not possible to fetch | ||
751 | 810 | snapcraft.yaml from the code hosting backend for some other | ||
752 | 811 | reason. | ||
753 | 812 | :raises CannotParseSnapcraftYaml: if the fetched snapcraft.yaml | ||
754 | 813 | cannot be parsed. | ||
755 | 814 | """ | ||
756 | 815 | |||
757 | 790 | def makeAutoBuilds(logger=None): | 816 | def makeAutoBuilds(logger=None): |
758 | 791 | """Create and return automatic builds for stale snap packages. | 817 | """Create and return automatic builds for stale snap packages. |
759 | 792 | 818 | ||
760 | 793 | 819 | ||
761 | === modified file 'lib/lp/snappy/model/snap.py' | |||
762 | --- lib/lp/snappy/model/snap.py 2018-04-21 10:01:22 +0000 | |||
763 | +++ lib/lp/snappy/model/snap.py 2018-06-14 17:09:31 +0000 | |||
764 | @@ -32,6 +32,7 @@ | |||
765 | 32 | Storm, | 32 | Storm, |
766 | 33 | Unicode, | 33 | Unicode, |
767 | 34 | ) | 34 | ) |
768 | 35 | import yaml | ||
769 | 35 | from zope.component import ( | 36 | from zope.component import ( |
770 | 36 | getAdapter, | 37 | getAdapter, |
771 | 37 | getUtility, | 38 | getUtility, |
772 | @@ -42,7 +43,10 @@ | |||
773 | 42 | 43 | ||
774 | 43 | from lp.app.browser.tales import DateTimeFormatterAPI | 44 | from lp.app.browser.tales import DateTimeFormatterAPI |
775 | 44 | from lp.app.enums import PRIVATE_INFORMATION_TYPES | 45 | from lp.app.enums import PRIVATE_INFORMATION_TYPES |
777 | 45 | from lp.app.errors import IncompatibleArguments | 46 | from lp.app.errors import ( |
778 | 47 | IncompatibleArguments, | ||
779 | 48 | NotFoundError, | ||
780 | 49 | ) | ||
781 | 46 | from lp.app.interfaces.security import IAuthorization | 50 | from lp.app.interfaces.security import IAuthorization |
782 | 47 | from lp.buildmaster.enums import BuildStatus | 51 | from lp.buildmaster.enums import BuildStatus |
783 | 48 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet | 52 | from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet |
784 | @@ -50,6 +54,12 @@ | |||
785 | 50 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 54 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
786 | 51 | from lp.buildmaster.model.buildqueue import BuildQueue | 55 | from lp.buildmaster.model.buildqueue import BuildQueue |
787 | 52 | from lp.buildmaster.model.processor import Processor | 56 | from lp.buildmaster.model.processor import Processor |
788 | 57 | from lp.code.errors import ( | ||
789 | 58 | BranchFileNotFound, | ||
790 | 59 | BranchHostingFault, | ||
791 | 60 | GitRepositoryBlobNotFound, | ||
792 | 61 | GitRepositoryScanFault, | ||
793 | 62 | ) | ||
794 | 53 | from lp.code.interfaces.branch import IBranch | 63 | from lp.code.interfaces.branch import IBranch |
795 | 54 | from lp.code.interfaces.branchcollection import ( | 64 | from lp.code.interfaces.branchcollection import ( |
796 | 55 | IAllBranches, | 65 | IAllBranches, |
797 | @@ -110,7 +120,9 @@ | |||
798 | 110 | BadSnapSearchContext, | 120 | BadSnapSearchContext, |
799 | 111 | BadSnapSource, | 121 | BadSnapSource, |
800 | 112 | CannotAuthorizeStoreUploads, | 122 | CannotAuthorizeStoreUploads, |
801 | 123 | CannotFetchSnapcraftYaml, | ||
802 | 113 | CannotModifySnapProcessor, | 124 | CannotModifySnapProcessor, |
803 | 125 | CannotParseSnapcraftYaml, | ||
804 | 114 | CannotRequestAutoBuilds, | 126 | CannotRequestAutoBuilds, |
805 | 115 | DuplicateSnapName, | 127 | DuplicateSnapName, |
806 | 116 | ISnap, | 128 | ISnap, |
807 | @@ -923,6 +935,52 @@ | |||
808 | 923 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( | 935 | list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
809 | 924 | person_ids, need_validity=True)) | 936 | person_ids, need_validity=True)) |
810 | 925 | 937 | ||
811 | 938 | def getSnapcraftYaml(self, context, logger=None): | ||
812 | 939 | """See `ISnapSet`.""" | ||
813 | 940 | if ISnap.providedBy(context): | ||
814 | 941 | context = context.source | ||
815 | 942 | try: | ||
816 | 943 | paths = ( | ||
817 | 944 | "snap/snapcraft.yaml", | ||
818 | 945 | "snapcraft.yaml", | ||
819 | 946 | ".snapcraft.yaml", | ||
820 | 947 | ) | ||
821 | 948 | for path in paths: | ||
822 | 949 | try: | ||
823 | 950 | if IBranch.providedBy(context): | ||
824 | 951 | blob = context.getBlob(path) | ||
825 | 952 | else: | ||
826 | 953 | blob = context.repository.getBlob(path, context.name) | ||
827 | 954 | break | ||
828 | 955 | except (BranchFileNotFound, GitRepositoryBlobNotFound): | ||
829 | 956 | pass | ||
830 | 957 | else: | ||
831 | 958 | msg = "Cannot find snapcraft.yaml in %s" | ||
832 | 959 | if logger is not None: | ||
833 | 960 | logger.exception(msg, context.unique_name) | ||
834 | 961 | raise NotFoundError(msg % context.unique_name) | ||
835 | 962 | except (BranchHostingFault, GitRepositoryScanFault) as e: | ||
836 | 963 | msg = "Failed to get snap manifest from %s" | ||
837 | 964 | if logger is not None: | ||
838 | 965 | logger.exception(msg, context.unique_name) | ||
839 | 966 | raise CannotFetchSnapcraftYaml( | ||
840 | 967 | "%s: %s" % (msg % context.unique_name, e)) | ||
841 | 968 | |||
842 | 969 | try: | ||
843 | 970 | snapcraft_data = yaml.safe_load(blob) | ||
844 | 971 | except Exception as e: | ||
845 | 972 | # Don't bother logging parsing errors from user-supplied YAML. | ||
846 | 973 | raise CannotParseSnapcraftYaml( | ||
847 | 974 | "Cannot parse snapcraft.yaml from %s: %s" % | ||
848 | 975 | (context.unique_name, e)) | ||
849 | 976 | |||
850 | 977 | if not isinstance(snapcraft_data, dict): | ||
851 | 978 | raise CannotParseSnapcraftYaml( | ||
852 | 979 | "The top level of snapcraft.yaml from %s is not a mapping" % | ||
853 | 980 | context.unique_name) | ||
854 | 981 | |||
855 | 982 | return snapcraft_data | ||
856 | 983 | |||
857 | 926 | @staticmethod | 984 | @staticmethod |
858 | 927 | def _findStaleSnaps(): | 985 | def _findStaleSnaps(): |
859 | 928 | """See `ISnapSet`.""" | 986 | """See `ISnapSet`.""" |
860 | 929 | 987 | ||
861 | === modified file 'lib/lp/snappy/tests/test_snap.py' | |||
862 | --- lib/lp/snappy/tests/test_snap.py 2018-05-31 10:23:03 +0000 | |||
863 | +++ lib/lp/snappy/tests/test_snap.py 2018-06-14 17:09:31 +0000 | |||
864 | @@ -14,6 +14,7 @@ | |||
865 | 14 | import json | 14 | import json |
866 | 15 | from urlparse import urlsplit | 15 | from urlparse import urlsplit |
867 | 16 | 16 | ||
868 | 17 | from fixtures import MockPatch | ||
869 | 17 | from lazr.lifecycle.event import ObjectModifiedEvent | 18 | from lazr.lifecycle.event import ObjectModifiedEvent |
870 | 18 | from pymacaroons import Macaroon | 19 | from pymacaroons import Macaroon |
871 | 19 | import pytz | 20 | import pytz |
872 | @@ -43,6 +44,16 @@ | |||
873 | 43 | from lp.buildmaster.interfaces.processor import IProcessorSet | 44 | from lp.buildmaster.interfaces.processor import IProcessorSet |
874 | 44 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob | 45 | from lp.buildmaster.model.buildfarmjob import BuildFarmJob |
875 | 45 | from lp.buildmaster.model.buildqueue import BuildQueue | 46 | from lp.buildmaster.model.buildqueue import BuildQueue |
876 | 47 | from lp.code.errors import ( | ||
877 | 48 | BranchFileNotFound, | ||
878 | 49 | BranchHostingFault, | ||
879 | 50 | GitRepositoryBlobNotFound, | ||
880 | 51 | GitRepositoryScanFault, | ||
881 | 52 | ) | ||
882 | 53 | from lp.code.tests.helpers import ( | ||
883 | 54 | BranchHostingFixture, | ||
884 | 55 | GitHostingFixture, | ||
885 | 56 | ) | ||
886 | 46 | from lp.registry.enums import PersonVisibility | 57 | from lp.registry.enums import PersonVisibility |
887 | 47 | from lp.registry.interfaces.distribution import IDistributionSet | 58 | from lp.registry.interfaces.distribution import IDistributionSet |
888 | 48 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 59 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
889 | @@ -62,7 +73,9 @@ | |||
890 | 62 | from lp.services.webapp.interfaces import OAuthPermission | 73 | from lp.services.webapp.interfaces import OAuthPermission |
891 | 63 | from lp.snappy.interfaces.snap import ( | 74 | from lp.snappy.interfaces.snap import ( |
892 | 64 | BadSnapSearchContext, | 75 | BadSnapSearchContext, |
893 | 76 | CannotFetchSnapcraftYaml, | ||
894 | 65 | CannotModifySnapProcessor, | 77 | CannotModifySnapProcessor, |
895 | 78 | CannotParseSnapcraftYaml, | ||
896 | 66 | ISnap, | 79 | ISnap, |
897 | 67 | ISnapSet, | 80 | ISnapSet, |
898 | 68 | ISnapView, | 81 | ISnapView, |
899 | @@ -975,6 +988,145 @@ | |||
900 | 975 | [snaps[0], snaps[2], snaps[4], snaps[6]], | 988 | [snaps[0], snaps[2], snaps[4], snaps[6]], |
901 | 976 | getUtility(ISnapSet).findByURLPrefixes(prefixes, owner=owners[0])) | 989 | getUtility(ISnapSet).findByURLPrefixes(prefixes, owner=owners[0])) |
902 | 977 | 990 | ||
903 | 991 | def test_getSnapcraftYaml_bzr_snap_snapcraft_yaml(self): | ||
904 | 992 | def getInventory(unique_name, dirname, *args, **kwargs): | ||
905 | 993 | if dirname == "snap": | ||
906 | 994 | return {"filelist": [{ | ||
907 | 995 | "filename": "snapcraft.yaml", "file_id": "some-file-id", | ||
908 | 996 | }]} | ||
909 | 997 | else: | ||
910 | 998 | raise BranchFileNotFound("dummy", dirname) | ||
911 | 999 | |||
912 | 1000 | self.useFixture(BranchHostingFixture( | ||
913 | 1001 | blob=b"name: test-snap")).getInventory = getInventory | ||
914 | 1002 | branch = self.factory.makeBranch() | ||
915 | 1003 | self.assertEqual( | ||
916 | 1004 | {"name": "test-snap"}, | ||
917 | 1005 | getUtility(ISnapSet).getSnapcraftYaml(branch)) | ||
918 | 1006 | |||
919 | 1007 | def test_getSnapcraftYaml_bzr_plain_snapcraft_yaml(self): | ||
920 | 1008 | def getInventory(unique_name, dirname, *args, **kwargs): | ||
921 | 1009 | if dirname == "": | ||
922 | 1010 | return {"filelist": [{ | ||
923 | 1011 | "filename": "snapcraft.yaml", "file_id": "some-file-id", | ||
924 | 1012 | }]} | ||
925 | 1013 | else: | ||
926 | 1014 | raise BranchFileNotFound("dummy", dirname) | ||
927 | 1015 | |||
928 | 1016 | self.useFixture(BranchHostingFixture( | ||
929 | 1017 | blob=b"name: test-snap")).getInventory = getInventory | ||
930 | 1018 | branch = self.factory.makeBranch() | ||
931 | 1019 | self.assertEqual( | ||
932 | 1020 | {"name": "test-snap"}, | ||
933 | 1021 | getUtility(ISnapSet).getSnapcraftYaml(branch)) | ||
934 | 1022 | |||
935 | 1023 | def test_getSnapcraftYaml_bzr_dot_snapcraft_yaml(self): | ||
936 | 1024 | def getInventory(unique_name, dirname, *args, **kwargs): | ||
937 | 1025 | if dirname == "": | ||
938 | 1026 | return {"filelist": [{ | ||
939 | 1027 | "filename": ".snapcraft.yaml", "file_id": "some-file-id", | ||
940 | 1028 | }]} | ||
941 | 1029 | else: | ||
942 | 1030 | raise BranchFileNotFound("dummy", dirname) | ||
943 | 1031 | |||
944 | 1032 | self.useFixture(BranchHostingFixture( | ||
945 | 1033 | blob=b"name: test-snap")).getInventory = getInventory | ||
946 | 1034 | branch = self.factory.makeBranch() | ||
947 | 1035 | self.assertEqual( | ||
948 | 1036 | {"name": "test-snap"}, | ||
949 | 1037 | getUtility(ISnapSet).getSnapcraftYaml(branch)) | ||
950 | 1038 | |||
951 | 1039 | def test_getSnapcraftYaml_bzr_error(self): | ||
952 | 1040 | self.useFixture(BranchHostingFixture()).getInventory = FakeMethod( | ||
953 | 1041 | failure=BranchHostingFault) | ||
954 | 1042 | branch = self.factory.makeBranch() | ||
955 | 1043 | self.assertRaises( | ||
956 | 1044 | CannotFetchSnapcraftYaml, | ||
957 | 1045 | getUtility(ISnapSet).getSnapcraftYaml, branch) | ||
958 | 1046 | |||
959 | 1047 | def test_getSnapcraftYaml_git_snap_snapcraft_yaml(self): | ||
960 | 1048 | def getBlob(path, filename, *args, **kwargs): | ||
961 | 1049 | if filename == "snap/snapcraft.yaml": | ||
962 | 1050 | return b"name: test-snap" | ||
963 | 1051 | else: | ||
964 | 1052 | raise GitRepositoryBlobNotFound("dummy", filename) | ||
965 | 1053 | |||
966 | 1054 | self.useFixture(GitHostingFixture()).getBlob = getBlob | ||
967 | 1055 | [git_ref] = self.factory.makeGitRefs() | ||
968 | 1056 | self.assertEqual( | ||
969 | 1057 | {"name": "test-snap"}, | ||
970 | 1058 | getUtility(ISnapSet).getSnapcraftYaml(git_ref)) | ||
971 | 1059 | |||
972 | 1060 | def test_getSnapcraftYaml_git_plain_snapcraft_yaml(self): | ||
973 | 1061 | def getBlob(path, filename, *args, **kwargs): | ||
974 | 1062 | if filename == "snapcraft.yaml": | ||
975 | 1063 | return b"name: test-snap" | ||
976 | 1064 | else: | ||
977 | 1065 | raise GitRepositoryBlobNotFound("dummy", filename) | ||
978 | 1066 | |||
979 | 1067 | self.useFixture(GitHostingFixture()).getBlob = getBlob | ||
980 | 1068 | [git_ref] = self.factory.makeGitRefs() | ||
981 | 1069 | self.assertEqual( | ||
982 | 1070 | {"name": "test-snap"}, | ||
983 | 1071 | getUtility(ISnapSet).getSnapcraftYaml(git_ref)) | ||
984 | 1072 | |||
985 | 1073 | def test_getSnapcraftYaml_git_dot_snapcraft_yaml(self): | ||
986 | 1074 | def getBlob(path, filename, *args, **kwargs): | ||
987 | 1075 | if filename == ".snapcraft.yaml": | ||
988 | 1076 | return b"name: test-snap" | ||
989 | 1077 | else: | ||
990 | 1078 | raise GitRepositoryBlobNotFound("dummy", filename) | ||
991 | 1079 | |||
992 | 1080 | self.useFixture(GitHostingFixture()).getBlob = getBlob | ||
993 | 1081 | [git_ref] = self.factory.makeGitRefs() | ||
994 | 1082 | self.assertEqual( | ||
995 | 1083 | {"name": "test-snap"}, | ||
996 | 1084 | getUtility(ISnapSet).getSnapcraftYaml(git_ref)) | ||
997 | 1085 | |||
998 | 1086 | def test_getSnapcraftYaml_git_error(self): | ||
999 | 1087 | self.useFixture(GitHostingFixture()).getBlob = FakeMethod( | ||
1000 | 1088 | failure=GitRepositoryScanFault) | ||
1001 | 1089 | [git_ref] = self.factory.makeGitRefs() | ||
1002 | 1090 | self.assertRaises( | ||
1003 | 1091 | CannotFetchSnapcraftYaml, | ||
1004 | 1092 | getUtility(ISnapSet).getSnapcraftYaml, git_ref) | ||
1005 | 1093 | |||
1006 | 1094 | def test_getSnapcraftYaml_snap_bzr(self): | ||
1007 | 1095 | self.useFixture(BranchHostingFixture( | ||
1008 | 1096 | file_list={"snapcraft.yaml": "some-file-id"}, | ||
1009 | 1097 | blob=b"name: test-snap")) | ||
1010 | 1098 | branch = self.factory.makeBranch() | ||
1011 | 1099 | snap = self.factory.makeSnap(branch=branch) | ||
1012 | 1100 | self.assertEqual( | ||
1013 | 1101 | {"name": "test-snap"}, getUtility(ISnapSet).getSnapcraftYaml(snap)) | ||
1014 | 1102 | |||
1015 | 1103 | def test_getSnapcraftYaml_snap_git(self): | ||
1016 | 1104 | self.useFixture(GitHostingFixture(blob=b"name: test-snap")) | ||
1017 | 1105 | [git_ref] = self.factory.makeGitRefs() | ||
1018 | 1106 | snap = self.factory.makeSnap(git_ref=git_ref) | ||
1019 | 1107 | self.assertEqual( | ||
1020 | 1108 | {"name": "test-snap"}, getUtility(ISnapSet).getSnapcraftYaml(snap)) | ||
1021 | 1109 | |||
1022 | 1110 | def test_getSnapcraftYaml_invalid_data(self): | ||
1023 | 1111 | hosting_fixture = self.useFixture(GitHostingFixture()) | ||
1024 | 1112 | for invalid_result in (None, 123, b"", b"[][]", b"#name:test", b"]"): | ||
1025 | 1113 | [git_ref] = self.factory.makeGitRefs() | ||
1026 | 1114 | hosting_fixture.getBlob = FakeMethod(result=invalid_result) | ||
1027 | 1115 | self.assertRaises( | ||
1028 | 1116 | CannotParseSnapcraftYaml, | ||
1029 | 1117 | getUtility(ISnapSet).getSnapcraftYaml, git_ref) | ||
1030 | 1118 | |||
1031 | 1119 | def test_getSnapcraftYaml_safe_yaml(self): | ||
1032 | 1120 | self.useFixture(GitHostingFixture(blob=b"Malicious YAML!")) | ||
1033 | 1121 | [git_ref] = self.factory.makeGitRefs() | ||
1034 | 1122 | unsafe_load = self.useFixture(MockPatch("yaml.load")) | ||
1035 | 1123 | safe_load = self.useFixture(MockPatch("yaml.safe_load")) | ||
1036 | 1124 | self.assertRaises( | ||
1037 | 1125 | CannotParseSnapcraftYaml, | ||
1038 | 1126 | getUtility(ISnapSet).getSnapcraftYaml, git_ref) | ||
1039 | 1127 | self.assertEqual(0, unsafe_load.mock.call_count) | ||
1040 | 1128 | self.assertEqual(1, safe_load.mock.call_count) | ||
1041 | 1129 | |||
1042 | 978 | def test__findStaleSnaps(self): | 1130 | def test__findStaleSnaps(self): |
1043 | 979 | # Stale; not built automatically. | 1131 | # Stale; not built automatically. |
1044 | 980 | self.factory.makeSnap(is_stale=True) | 1132 | self.factory.makeSnap(is_stale=True) |