Merge lp:~jml/pkgme-service/actually-run-pkgme into lp:pkgme-service
- actually-run-pkgme
- Merge into trunk
Proposed by
Jonathan Lange
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | James Westby | ||||
Approved revision: | 35 | ||||
Merged at revision: | 15 | ||||
Proposed branch: | lp:~jml/pkgme-service/actually-run-pkgme | ||||
Merge into: | lp:pkgme-service | ||||
Diff against target: |
488 lines (+239/-46) 9 files modified
fabtasks/django.py (+0/-3) setup.py (+11/-10) src/djpkgme/models.py (+21/-0) src/djpkgme/tasks.py (+44/-3) src/djpkgme/tests/factory.py (+58/-5) src/djpkgme/tests/test_handlers.py (+6/-2) src/djpkgme/tests/test_models.py (+18/-2) src/djpkgme/tests/test_tasks.py (+74/-19) test-dependencies.txt (+7/-2) |
||||
To merge this branch: | bzr merge lp:~jml/pkgme-service/actually-run-pkgme | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Westby (community) | Approve | ||
Review via email: mp+83024@code.launchpad.net |
Commit message
Description of the change
At long last, actually running pkgme-binary from pkgme-service.
I'm a little too zonked to provide much more details right now, but happy to answer questions.
To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote : | # |
Merged. Filed bug 893675, bug 893674 and bug 893563.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'fabtasks/django.py' |
2 | --- fabtasks/django.py 2011-08-30 17:41:11 +0000 |
3 | +++ fabtasks/django.py 2011-11-22 14:09:24 +0000 |
4 | @@ -1,6 +1,3 @@ |
5 | -import os |
6 | -import os.path |
7 | - |
8 | from fabric.api import local |
9 | |
10 | |
11 | |
12 | === modified file 'setup.py' |
13 | --- setup.py 2011-11-09 11:14:13 +0000 |
14 | +++ setup.py 2011-11-22 14:09:24 +0000 |
15 | @@ -1,22 +1,23 @@ |
16 | from setuptools import setup, find_packages |
17 | |
18 | setup( |
19 | - name = "django-pkgme", |
20 | - version = "0.1", |
21 | - author = "Canonical Consumer Applications", |
22 | - author_email = "canonical-consumer-applications@lists.launchpad.net", |
23 | - license = "AGPL3", |
24 | - install_requires = [ |
25 | + name="django-pkgme", |
26 | + version="0.1", |
27 | + author="Canonical Consumer Applications", |
28 | + author_email="canonical-consumer-applications@lists.launchpad.net", |
29 | + license="AGPL3", |
30 | + install_requires=[ |
31 | 'django==1.3', |
32 | 'django-piston', |
33 | # XXX: Until https://github.com/ask/django-celery/pull/88 is fixed, we |
34 | # have to use the older release. Review this when django-celery |
35 | # releases the version after 2.4.1. -- jml |
36 | 'django-celery==2.4.0', |
37 | + 'fixtures', |
38 | 'south==0.7.3', |
39 | 'oauth', |
40 | - ], |
41 | - zip_safe = False, |
42 | - packages = find_packages('src'), |
43 | - package_dir = {'': 'src'}, |
44 | + ], |
45 | + zip_safe=False, |
46 | + packages=find_packages('src'), |
47 | + package_dir={'': 'src'}, |
48 | ) |
49 | |
50 | === modified file 'src/djpkgme/models.py' |
51 | --- src/djpkgme/models.py 2011-09-01 21:46:47 +0000 |
52 | +++ src/djpkgme/models.py 2011-11-22 14:09:24 +0000 |
53 | @@ -2,6 +2,11 @@ |
54 | from django.db import models |
55 | from datetime import datetime |
56 | |
57 | +from devportalbinary.binary import ( |
58 | + CATEGORIES, |
59 | + PACKAGE_NAME, |
60 | + TAGLINE, |
61 | + ) |
62 | |
63 | class AppMetadata(models.Model): |
64 | # ID of the app, according to myapps |
65 | @@ -16,8 +21,12 @@ |
66 | categories = models.CharField(max_length=128, blank=True) |
67 | department = models.CharField(max_length=64, blank=True) |
68 | keywords = models.TextField(blank=True, default='') |
69 | + # XXX: We *think* this is the URL to the tarball that contains the app |
70 | + # that we are packaging. |
71 | package_url = models.CharField(max_length=256) |
72 | icon_url = models.CharField(max_length=256) |
73 | + # XXX: This should not be required, or even sent. It's not part of the |
74 | + # package. |
75 | screenshot_url = models.CharField(max_length=256) |
76 | price = models.DecimalField(max_digits=7, decimal_places=2, null=True) |
77 | callback_url = models.CharField(max_length=256) |
78 | @@ -35,6 +44,18 @@ |
79 | # Location of the built package |
80 | packaged_app_url = models.CharField(max_length=256) |
81 | |
82 | + def as_dict(self): |
83 | + """Return this metadata as a dict. |
84 | + |
85 | + The contents of that dict should be suitable for dumping as JSON into |
86 | + devportal-metadata.json (see lp:pkgme-binary). |
87 | + """ |
88 | + return { |
89 | + CATEGORIES: self.categories, |
90 | + PACKAGE_NAME: self.package_name, |
91 | + TAGLINE: self.tagline, |
92 | + } |
93 | + |
94 | def update_guesses(self, guesses): |
95 | """Updates the guess fields based on a result from pkgme.""" |
96 | other = {} |
97 | |
98 | === modified file 'src/djpkgme/tasks.py' |
99 | --- src/djpkgme/tasks.py 2011-09-01 21:46:47 +0000 |
100 | +++ src/djpkgme/tasks.py 2011-11-22 14:09:24 +0000 |
101 | @@ -1,4 +1,8 @@ |
102 | import json |
103 | +import os |
104 | +import shutil |
105 | +import tarfile |
106 | + |
107 | from celery.registry import tasks |
108 | from celery.task import Task |
109 | from django.conf import settings |
110 | @@ -10,7 +14,13 @@ |
111 | OAuthSignatureMethod_PLAINTEXT, |
112 | ) |
113 | |
114 | -import djpkgme.mock_pkgme as pkgme |
115 | +from devportalbinary.binary import METADATA_FILE |
116 | +from devportalbinary.database import download_file |
117 | +from fixtures import TempDir |
118 | +from pkgme import write_packaging |
119 | +from pkgme.debuild import build_source_package |
120 | + |
121 | +from djpkgme import mock_pkgme |
122 | |
123 | |
124 | def oauth_sign_request(url): |
125 | @@ -34,7 +44,7 @@ |
126 | |
127 | class GuessPackagingDetailsTask(Task): |
128 | def run(self, metadata): |
129 | - result = pkgme.guess(metadata) |
130 | + result = mock_pkgme.guess(metadata) |
131 | metadata.update_guesses(result) |
132 | submit_pkgme_info(metadata) |
133 | return result |
134 | @@ -42,8 +52,39 @@ |
135 | |
136 | |
137 | class BuildPackageTask(Task): |
138 | + |
139 | + def build_package(self, metadata, overrides, output_tar_path): |
140 | + """Build packaging for 'metadata' and store it all in 'output_tar_path'. |
141 | + """ |
142 | + with TempDir() as temp_dir: |
143 | + # Download, extract and remove the tarball. |
144 | + output_dir = temp_dir.path |
145 | + path = download_file(metadata.package_url, output_dir) |
146 | + # XXX: 'package' is an arbitrary name. Maybe we should pick a |
147 | + # better arbitrary name? |
148 | + extraction_path = os.path.join(output_dir, 'package') |
149 | + with tarfile.open(path) as t: |
150 | + t.extractall(extraction_path) |
151 | + os.unlink(path) |
152 | + # Write the metadata out to a special file, where the binary |
153 | + # backend will find it. |
154 | + metadata_filename = os.path.join(extraction_path, METADATA_FILE) |
155 | + with open(metadata_filename, 'w') as metadata_file: |
156 | + json.dump(metadata.as_dict(), metadata_file) |
157 | + # Run pkgme: generate the packaging and build a source package. |
158 | + write_packaging(extraction_path, allowed_backend_names=['binary']) |
159 | + build_source_package(extraction_path, False) |
160 | + # Create a new tarball containing the source package and the |
161 | + # metadata file. |
162 | + os.rename( |
163 | + metadata_filename, os.path.join(output_dir, METADATA_FILE)) |
164 | + shutil.rmtree(extraction_path) |
165 | + with tarfile.open(output_tar_path, 'w') as t: |
166 | + for name in os.listdir(output_dir): |
167 | + t.add(os.path.join(output_dir, name), name) |
168 | + |
169 | def run(self, metadata, overrides): |
170 | - result = pkgme.build_package(metadata, overrides) |
171 | + result = self.build_package(metadata, overrides) |
172 | metadata.packaged_app_url = result |
173 | metadata.save() |
174 | submit_pkgme_info(metadata) |
175 | |
176 | === modified file 'src/djpkgme/tests/factory.py' |
177 | --- src/djpkgme/tests/factory.py 2011-09-01 20:35:22 +0000 |
178 | +++ src/djpkgme/tests/factory.py 2011-11-22 14:09:24 +0000 |
179 | @@ -1,9 +1,24 @@ |
180 | -from django.test import TestCase |
181 | from itertools import count |
182 | - |
183 | +import os |
184 | + |
185 | +from django.test import TestCase as DjangoTestCase |
186 | +from fixtures import ( |
187 | + EnvironmentVariableFixture, |
188 | + Fixture, |
189 | + TempDir, |
190 | + ) |
191 | +from testtools import ( |
192 | + RunTest, |
193 | + TestCase, |
194 | + ) |
195 | + |
196 | +from devportalbinary.database import PackageDatabase |
197 | from djpkgme.models import AppMetadata |
198 | |
199 | -__all__ = ['TestCaseWithFactory'] |
200 | +__all__ = [ |
201 | + 'EmptyBinaryPackageDatabase', |
202 | + 'TestCaseWithFactory', |
203 | + ] |
204 | |
205 | |
206 | class PkgmeObjectFactory(object): |
207 | @@ -20,7 +35,9 @@ |
208 | return prefix + str(self.get_unique_integer()) |
209 | |
210 | def make_metadata(self, myapps_id=None, callback_url=None, |
211 | - with_guesses=False): |
212 | + with_guesses=False, package_name=None): |
213 | + if package_name is None: |
214 | + package_name = self.get_unique_string('package-name') |
215 | if myapps_id is None: |
216 | myapps_id = self.get_unique_integer() |
217 | if callback_url is None: |
218 | @@ -39,7 +56,43 @@ |
219 | return AppMetadata.objects.create(myapps_id=myapps_id, |
220 | executable=executable, distroarchseries=distroarchseries, |
221 | dependencies=dependencies, other_guesses=other_guesses, |
222 | - callback_url=callback_url) |
223 | + callback_url=callback_url, package_name=package_name) |
224 | + |
225 | + |
226 | +class DjangoRunner(RunTest): |
227 | + """Use with run_tests_with to run Django tests. |
228 | + |
229 | + Django's test case has a couple of hooks that it runs before and after |
230 | + tests to isolate transactions and do other fun things |
231 | + <https://docs.djangoproject.com/en/dev/topics/testing/#django.test.TestCase>. |
232 | + |
233 | + Use this runner to get that same effect with testtools. |
234 | + """ |
235 | + |
236 | + class MyDjangoTestCase(DjangoTestCase): |
237 | + def test_foo(self): |
238 | + pass |
239 | + |
240 | + def __init__(self, case, handlers=None): |
241 | + super(DjangoRunner, self).__init__(case, handlers=handlers) |
242 | + self._django_case = self.MyDjangoTestCase('test_foo') |
243 | + |
244 | + def _run_core(self): |
245 | + self._run_user(self._django_case._pre_setup) |
246 | + super(DjangoRunner, self)._run_core() |
247 | + self._run_user(self._django_case._post_teardown) |
248 | + |
249 | + |
250 | +class EmptyBinaryPackageDatabase(Fixture): |
251 | + |
252 | + def setUp(self): |
253 | + super(EmptyBinaryPackageDatabase, self).setUp() |
254 | + db_directory = self.useFixture(TempDir()).path |
255 | + db_path = os.path.join(db_directory, 'test.db') |
256 | + self.useFixture( |
257 | + EnvironmentVariableFixture(PackageDatabase.ENV_VAR, db_path)) |
258 | + |
259 | |
260 | class TestCaseWithFactory(TestCase): |
261 | factory = PkgmeObjectFactory() |
262 | + run_tests_with = DjangoRunner |
263 | |
264 | === modified file 'src/djpkgme/tests/test_handlers.py' |
265 | --- src/djpkgme/tests/test_handlers.py 2011-09-01 20:35:22 +0000 |
266 | +++ src/djpkgme/tests/test_handlers.py 2011-11-22 14:09:24 +0000 |
267 | @@ -78,16 +78,20 @@ |
268 | |
269 | |
270 | @patch('httplib2.Http.request') |
271 | - def test_no_overrides_success(self, mock_request): |
272 | + @patch('djpkgme.tasks.BuildPackageTask.build_package') |
273 | + def test_no_overrides_success(self, mock_build_package, mock_request): |
274 | request = Mock() |
275 | + mock_build_package.return_value = 'foo' |
276 | request.data = {'metadata': test_metadata, 'overrides': {}} |
277 | handler = PackageHandler() |
278 | response = handler.create(request) |
279 | self.assertEqual({'status': 'success'}, response) |
280 | |
281 | @patch('httplib2.Http.request') |
282 | - def test_overrides_success(self, mock_request): |
283 | + @patch('djpkgme.tasks.BuildPackageTask.build_package') |
284 | + def test_overrides_success(self, mock_build_package, mock_request): |
285 | request = Mock() |
286 | + mock_build_package.return_value = 'foo' |
287 | request.data = {'metadata': test_metadata, 'overrides': { |
288 | 'executable': 'bin/foo', |
289 | 'deps': ['libfoo', 'libbar'], |
290 | |
291 | === modified file 'src/djpkgme/tests/test_models.py' |
292 | --- src/djpkgme/tests/test_models.py 2011-09-01 21:46:47 +0000 |
293 | +++ src/djpkgme/tests/test_models.py 2011-11-22 14:09:24 +0000 |
294 | @@ -1,6 +1,13 @@ |
295 | -from factory import TestCaseWithFactory |
296 | +from djpkgme.tests.factory import TestCaseWithFactory |
297 | + |
298 | +from devportalbinary.binary import ( |
299 | + CATEGORIES, |
300 | + PACKAGE_NAME, |
301 | + TAGLINE, |
302 | + ) |
303 | |
304 | class AppMetadataTestCase(TestCaseWithFactory): |
305 | + |
306 | def test_update_guesses_doesnt_mutate_argument(self): |
307 | """Calling update_guesses doesn't modify the dict we pass in.""" |
308 | metadata = self.factory.make_metadata(with_guesses=True) |
309 | @@ -50,4 +57,13 @@ |
310 | |
311 | expected = ['dependencies', 'pkgme_distro_arch_series', |
312 | 'pkgme_extra_data', 'packaged_app_url'] |
313 | - self.assertEquals(set(expected), set(info)) |
314 | \ No newline at end of file |
315 | + self.assertEquals(set(expected), set(info)) |
316 | + |
317 | + def test_as_dict(self): |
318 | + metadata = self.factory.make_metadata() |
319 | + expected = { |
320 | + CATEGORIES: metadata.categories, |
321 | + PACKAGE_NAME: metadata.package_name, |
322 | + TAGLINE: metadata.tagline, |
323 | + } |
324 | + self.assertEqual(expected, metadata.as_dict()) |
325 | |
326 | === modified file 'src/djpkgme/tests/test_tasks.py' |
327 | --- src/djpkgme/tests/test_tasks.py 2011-09-01 21:46:47 +0000 |
328 | +++ src/djpkgme/tests/test_tasks.py 2011-11-22 14:09:24 +0000 |
329 | @@ -1,15 +1,29 @@ |
330 | -# Run tasks eagerly for tests. Taken from |
331 | +# Run tasks eagerly for tests. Taken from |
332 | # http://ask.github.com/celery/cookbook/unit-testing.html |
333 | |
334 | import json |
335 | +import os |
336 | +import shutil |
337 | +import tarfile |
338 | + |
339 | +from fixtures import TempDir |
340 | from mock import patch |
341 | |
342 | -from djpkgme.tasks import GuessPackagingDetailsTask, BuildPackageTask |
343 | -from djpkgme.tests.factory import TestCaseWithFactory |
344 | +from devportalbinary.binary import METADATA_FILE |
345 | +from djpkgme import tasks |
346 | +from djpkgme.tasks import ( |
347 | + BuildPackageTask, |
348 | + GuessPackagingDetailsTask, |
349 | + ) |
350 | +from djpkgme.tests.factory import ( |
351 | + EmptyBinaryPackageDatabase, |
352 | + TestCaseWithFactory, |
353 | + ) |
354 | + |
355 | |
356 | class GuessPackagingDetailsTaskTestCase(TestCaseWithFactory): |
357 | @patch('httplib2.Http.request') |
358 | - @patch('djpkgme.tasks.pkgme') |
359 | + @patch('djpkgme.tasks.mock_pkgme') |
360 | def test_guess_details_is_called(self, mock_pkgme, mock_request): |
361 | mock_request.return_value = ({}, '') |
362 | mock_pkgme.guess.return_value = {} |
363 | @@ -23,7 +37,7 @@ |
364 | self.assertEqual(1, mock_pkgme.guess.call_count) |
365 | |
366 | @patch('httplib2.Http.request') |
367 | - @patch('djpkgme.tasks.pkgme') |
368 | + @patch('djpkgme.tasks.mock_pkgme') |
369 | def test_result_is_submitted(self, mock_pkgme, mock_request): |
370 | mock_request.return_value = ({}, '') |
371 | mock_pkgme.guess.return_value = {} |
372 | @@ -35,20 +49,21 @@ |
373 | "pkgme_extra_data": {"pkgme_id": 1, "executable": ""} |
374 | } |
375 | |
376 | - result = GuessPackagingDetailsTask.delay(metadata) |
377 | + GuessPackagingDetailsTask.delay(metadata) |
378 | |
379 | self.assertEqual(1, mock_request.call_count) |
380 | args, kwargs = mock_request.call_args |
381 | - self.assertEqual((metadata.callback_url,), args) |
382 | - self.assertEqual(json.loads(kwargs['body']), expected) |
383 | - |
384 | + self.assertEqual(args, (metadata.callback_url,)) |
385 | + self.assertEqual(expected, json.loads(kwargs['body'])) |
386 | + |
387 | |
388 | class BuildPackageTaskTestCase(TestCaseWithFactory): |
389 | + |
390 | @patch('httplib2.Http.request') |
391 | - @patch('djpkgme.tasks.pkgme') |
392 | - def test_guess_details_is_called(self, mock_pkgme, mock_request): |
393 | + @patch('djpkgme.tasks.BuildPackageTask.build_package') |
394 | + def test_build_package_is_called(self, mock_build_package, mock_request): |
395 | mock_request.return_value = ({}, '') |
396 | - mock_pkgme.build_package.return_value = "foo" |
397 | + mock_build_package.return_value = "foo" |
398 | |
399 | metadata = self.factory.make_metadata() |
400 | overrides = {} |
401 | @@ -57,18 +72,18 @@ |
402 | |
403 | self.assertEqual('foo', result.get()) |
404 | self.assertTrue(result.successful()) |
405 | - self.assertEqual(1, mock_pkgme.build_package.call_count) |
406 | + self.assertEqual(1, mock_build_package.call_count) |
407 | |
408 | @patch('httplib2.Http.request') |
409 | - @patch('djpkgme.tasks.pkgme') |
410 | - def test_result_is_submitted(self, mock_pkgme, mock_request): |
411 | + @patch('djpkgme.tasks.BuildPackageTask.build_package') |
412 | + def test_result_is_submitted(self, mock_build_package, mock_request): |
413 | mock_request.return_value = ({}, '') |
414 | - mock_pkgme.build_package.return_value = "foo" |
415 | + mock_build_package.return_value = "foo" |
416 | |
417 | metadata = self.factory.make_metadata() |
418 | overrides = {} |
419 | |
420 | - result = BuildPackageTask.delay(metadata, overrides) |
421 | + BuildPackageTask.delay(metadata, overrides) |
422 | |
423 | self.assertEqual(1, mock_request.call_count) |
424 | args, kwargs = mock_request.call_args |
425 | @@ -78,5 +93,45 @@ |
426 | "pkgme_distro_arch_series": [], |
427 | "pkgme_extra_data": {"pkgme_id": 1, "executable": ""} |
428 | } |
429 | - self.assertEqual((metadata.callback_url,), args) |
430 | - self.assertEqual(json.loads(kwargs['body']), expected) |
431 | + self.assertEqual(args, (metadata.callback_url,)) |
432 | + self.assertEqual(expected, json.loads(kwargs['body'])) |
433 | + |
434 | + def download_file(self, url, directory): |
435 | + # For the moment, the test we just want to create a valid tarball in |
436 | + # the directory. |
437 | + import acceptance |
438 | + binary_path = os.path.join( |
439 | + os.path.dirname(acceptance.__file__), 'data', 'gtk', 'test_gtk') |
440 | + temp_dir = self.useFixture(TempDir()).path |
441 | + nothing_file_path = os.path.join(temp_dir, 'binary-file') |
442 | + shutil.copyfile(binary_path, nothing_file_path) |
443 | + tarball_path = os.path.join(directory, 'tarball.tar.gz') |
444 | + tarball = tarfile.open(tarball_path, 'w') |
445 | + tarball.add(nothing_file_path, os.path.basename(nothing_file_path)) |
446 | + tarball.close() |
447 | + return tarball_path |
448 | + |
449 | + def test_writes_packaging_for_tarball(self): |
450 | + # BuildPackageTask.build_package runs pkgme to build the package and |
451 | + # outputs a tarball to the given path. Here we're testing the happy |
452 | + # case. |
453 | + metadata = self.factory.make_metadata() |
454 | + task = BuildPackageTask() |
455 | + self.patch(tasks, 'download_file', self.download_file) |
456 | + self.useFixture(EmptyBinaryPackageDatabase()) |
457 | + temp_dir = self.useFixture(TempDir()).path |
458 | + output_tar_path = os.path.join(temp_dir, 'output.tar.gz') |
459 | + task.build_package(metadata, {}, output_tar_path) |
460 | + with tarfile.open(output_tar_path) as output_tar: |
461 | + self.assertEqual( |
462 | + sorted( |
463 | + ['%s_0.dsc' % (metadata.package_name,), |
464 | + '%s_0.tar.gz' % (metadata.package_name,), |
465 | + '%s_0_source.build' % (metadata.package_name,), |
466 | + '%s_0_source.changes' % (metadata.package_name,), |
467 | + METADATA_FILE, |
468 | + ]), |
469 | + sorted(output_tar.getnames())) |
470 | + |
471 | + # XXX: Write a test directly for the contents of METADATA_FILE? |
472 | + # XXX: Check the contents of the tarball in the source package? |
473 | |
474 | === modified file 'test-dependencies.txt' |
475 | --- test-dependencies.txt 2011-11-09 11:14:13 +0000 |
476 | +++ test-dependencies.txt 2011-11-22 14:09:24 +0000 |
477 | @@ -1,4 +1,9 @@ |
478 | +coverage |
479 | django-kombu |
480 | -coverage |
481 | +mock |
482 | piston-mini-client |
483 | -mock |
484 | +testtools |
485 | +# These aren't test requirements, but we want to fetch these dependencies from |
486 | +# bzr and this is the only way I can figure out how to do this -- jml |
487 | +-e bzr+ssh://bazaar.launchpad.net/+branch/pkgme#egg=pkgme |
488 | +-e bzr+ssh://bazaar.launchpad.net/+branch/pkgme-binary#egg=pkgme-binary |
Hi,
This broadly looks good, and is certainly an improvement.
As it stands this test will only work when run from source:
437 + import acceptance
as pkgme-binary doesn't include this when installed. We could move
it in to the devportalbinary namespace?
Thanks,
James