Merge lp:~jml/pkgme-service/actually-run-pkgme into lp:pkgme-service

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
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Review via email: mp+83024@code.launchpad.net

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
James Westby (james-w) wrote :

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

review: Approve
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

Subscribers

People subscribed via source and target branches