Merge lp:~jml/pkgme-devportal/reuse-backend into lp:pkgme-devportal

Proposed by Jonathan Lange on 2012-01-10
Status: Merged
Approved by: James Westby on 2012-01-10
Approved revision: 47
Merged at revision: 30
Proposed branch: lp:~jml/pkgme-devportal/reuse-backend
Merge into: lp:pkgme-devportal
Diff against target: 1657 lines (+630/-618)
24 files modified
devportalbinary/backends/binary/all_info (+13/-0)
devportalbinary/backends/binary/build_depends (+0/-16)
devportalbinary/backends/binary/depends (+0/-13)
devportalbinary/backends/binary/description (+0/-19)
devportalbinary/backends/binary/extra_files (+0/-37)
devportalbinary/backends/binary/package_name (+0/-17)
devportalbinary/backends/binary/want (+2/-5)
devportalbinary/backends/pdf/all_info (+14/-0)
devportalbinary/backends/pdf/architecture (+0/-5)
devportalbinary/backends/pdf/build_depends (+0/-11)
devportalbinary/backends/pdf/depends (+0/-11)
devportalbinary/backends/pdf/description (+0/-26)
devportalbinary/backends/pdf/extra_files (+0/-37)
devportalbinary/backends/pdf/package_name (+0/-17)
devportalbinary/backends/pdf/want (+2/-12)
devportalbinary/binary.py (+37/-111)
devportalbinary/metadata.py (+204/-0)
devportalbinary/pdf.py (+47/-0)
devportalbinary/testing.py (+2/-2)
devportalbinary/tests/test_binary.py (+9/-170)
devportalbinary/tests/test_binary_backend.py (+62/-69)
devportalbinary/tests/test_metadata.py (+178/-0)
devportalbinary/tests/test_pdf.py (+21/-0)
devportalbinary/tests/test_pdf_backend.py (+39/-40)
To merge this branch: bzr merge lp:~jml/pkgme-devportal/reuse-backend
Reviewer Review Type Date Requested Status
James Westby 2012-01-10 Approve on 2012-01-10
Review via email: mp+88053@code.launchpad.net

Commit Message

Factor out common code from the PDF & binary backends.

Description of the Change

This is a big branch, but I think it's worth it.

 * Take the PDF & binary backends and turn them into all_info backends, rather than per-file backends
 * Create classes, BinaryBackend and PdfBackend that do all of the work
 * Create a MetadataBackend class that both of those inherit from which handles all of the common work
 * Move a bunch of constants into MetadataBackend
 * Move all of the stuff that isn't specifically about binary backend out of the binary.py module and into either metadata.py or pdf.py backends
 * Do the analogous thing for test_binary → test_metadata, test_pdf

I think that after this branch lands it would be a good idea to go over the test_binary_backend and test_pdf_backend test modules and make them exercise the BinaryBackend and PdfBackend classes directly, as well as extracting tests out of them and into test_metadata. Probably doing that would also reveal some common test fixtures that would help us reduce boiler plate.

To post a comment you must log in.
James Westby (james-w) wrote :

522 + # XXX: Technically this should be an error, but our tests exercise
523 + # this code in directories that have no binaries. Silly them.

Is this some of the tests that you are adding that are doing this?

I think that the dump_json implementation doesn't actually follow the spec,
which says it should return the values for the keys passed in, but I'm not
sure that matters, as it will only be called in the way that it would
return the data as it does now unless pkgme changes, and I don't think pkgme
cares if it gets extra info anyway.

Thanks,

James

James Westby (james-w) wrote :

Overall this looks great though, and much cleaner, so thanks for doing it.

I'm not approving yet as the XXX I highlighted seems to undo the work of
my last branch, so I'd like to understand why so we can fix that.

Thanks,

James

review: Needs Information
46. By Jonathan Lange on 2012-01-10

Fix tests to always have enough info for backend to work properly.

47. By Jonathan Lange on 2012-01-10

Make all of the binary backend tests directly exercise the BinaryBackend class.

Jonathan Lange (jml) wrote :

I've fixed the XXX by making sure that a proper executable is set up for all of the tests in test_binary_backend and then removing the try/except.

While there, I retargeted the tests to exercise BinaryBackend directly rather than by invoking pkgme machinery. We still would need a test cleanup branch to provide a swag of direct tests for MetadataBackend, to fix up the pdf tests and probably to change the binary tests to call the specific get methods rather than get_info() all the time.

Regarding dump_json not being to spec, I don't think it's relevant here. all_info doesn't get arguments, so it has no way of knowing what fields to generate values for. Thus SingleExternalHelperInfo.get_all() always returns a dict with all of the information in it.

James Westby (james-w) wrote :

On Tue, 10 Jan 2012 15:31:30 -0000, Jonathan Lange <email address hidden> wrote:
> I've fixed the XXX by making sure that a proper executable is set up
> for all of the tests in test_binary_backend and then removing the
> try/except.

Great, thanks.

> While there, I retargeted the tests to exercise BinaryBackend directly
> rather than by invoking pkgme machinery. We still would need a test
> cleanup branch to provide a swag of direct tests for MetadataBackend,
> to fix up the pdf tests and probably to change the binary tests to
> call the specific get methods rather than get_info() all the time.

Sounds good. Is there a card for this already?

> Regarding dump_json not being to spec, I don't think it's relevant
> here. all_info doesn't get arguments, so it has no way of knowing what
> fields to generate values for. Thus SingleExternalHelperInfo.get_all()
> always returns a dict with all of the information in it.

It's fugly, but...

  http://pkgme.net/doc/backends/index.html#one-script

Thanks,

James

James Westby (james-w) :
review: Approve
Jonathan Lange (jml) wrote :

By bad, all_info has the JSON dict written to it on stdin, but we don't acknowledge it. As you say, I think it's OK to leave as is.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'devportalbinary/backends/binary/all_info'
2--- devportalbinary/backends/binary/all_info 1970-01-01 00:00:00 +0000
3+++ devportalbinary/backends/binary/all_info 2012-01-10 15:29:28 +0000
4@@ -0,0 +1,13 @@
5+#!/usr/bin/env python
6+# Copyright 2011 Canonical Ltd. This software is licensed under the
7+# GNU Affero General Public License version 3 (see the file LICENSE).
8+
9+from devportalbinary.binary import BinaryBackend
10+
11+
12+def main():
13+ BinaryBackend().dump_json()
14+
15+
16+if __name__ == '__main__':
17+ main()
18
19=== removed file 'devportalbinary/backends/binary/build_depends'
20--- devportalbinary/backends/binary/build_depends 2011-11-21 16:16:36 +0000
21+++ devportalbinary/backends/binary/build_depends 1970-01-01 00:00:00 +0000
22@@ -1,16 +0,0 @@
23-#!/usr/bin/env python
24-# Copyright 2011 Canonical Ltd. This software is licensed under the
25-# GNU Affero General Public License version 3 (see the file LICENSE).
26-
27-import os
28-
29-from devportalbinary.binary import guess_dependencies
30-
31-
32-def main():
33- path = os.getcwd()
34- print ', '.join(guess_dependencies(path))
35-
36-
37-if __name__ == '__main__':
38- main()
39
40=== removed file 'devportalbinary/backends/binary/depends'
41--- devportalbinary/backends/binary/depends 2011-11-21 16:16:36 +0000
42+++ devportalbinary/backends/binary/depends 1970-01-01 00:00:00 +0000
43@@ -1,13 +0,0 @@
44-#!/usr/bin/env python
45-# Copyright 2011 Canonical Ltd. This software is licensed under the
46-# GNU Affero General Public License version 3 (see the file LICENSE).
47-
48-from devportalbinary.binary import DEPENDS
49-
50-
51-def main():
52- print DEPENDS
53-
54-
55-if __name__ == '__main__':
56- main()
57
58=== removed file 'devportalbinary/backends/binary/description'
59--- devportalbinary/backends/binary/description 2011-12-20 18:00:57 +0000
60+++ devportalbinary/backends/binary/description 1970-01-01 00:00:00 +0000
61@@ -1,19 +0,0 @@
62-#!/usr/bin/env python
63-# Copyright 2011 Canonical Ltd. This software is licensed under the
64-# GNU Affero General Public License version 3 (see the file LICENSE).
65-
66-import json
67-from devportalbinary.binary import (
68- DESCRIPTION,
69- get_metadata,
70- )
71-
72-
73-def main():
74- description = get_metadata(DESCRIPTION, None)
75- if description:
76- print description
77-
78-
79-if __name__ == '__main__':
80- main()
81
82=== removed file 'devportalbinary/backends/binary/extra_files'
83--- devportalbinary/backends/binary/extra_files 2011-12-20 13:51:05 +0000
84+++ devportalbinary/backends/binary/extra_files 1970-01-01 00:00:00 +0000
85@@ -1,37 +0,0 @@
86-#!/usr/bin/env python
87-# Copyright 2011 Canonical Ltd. This software is licensed under the
88-# GNU Affero General Public License version 3 (see the file LICENSE).
89-
90-import json
91-import os
92-import sys
93-
94-from devportalbinary.binary import (
95- CATEGORIES,
96- get_binary_desktop_file,
97- get_install_file,
98- get_metadata,
99- PACKAGE_NAME,
100- TAGLINE,
101- )
102-
103-
104-def main():
105- metadata = get_metadata()
106- package_name = metadata[PACKAGE_NAME]
107- path = os.getcwd()
108- install_file = get_install_file(package_name, path, True)
109- desktop_file = get_binary_desktop_file(
110- package_name, path,
111- tagline=metadata.get(TAGLINE, ''),
112- categories=metadata.get(CATEGORIES, ''))
113- json.dump(
114- {
115- # XXX: Hardcoded literal attack!
116- 'debian/install': install_file,
117- 'debian/%s.desktop' % (package_name,): desktop_file.get_contents(),
118- }, sys.stdout)
119-
120-
121-if __name__ == '__main__':
122- main()
123
124=== removed file 'devportalbinary/backends/binary/package_name'
125--- devportalbinary/backends/binary/package_name 2011-12-20 13:31:47 +0000
126+++ devportalbinary/backends/binary/package_name 1970-01-01 00:00:00 +0000
127@@ -1,17 +0,0 @@
128-#!/usr/bin/env python
129-# Copyright 2011 Canonical Ltd. This software is licensed under the
130-# GNU Affero General Public License version 3 (see the file LICENSE).
131-
132-import json
133-from devportalbinary.binary import (
134- get_metadata,
135- PACKAGE_NAME,
136- )
137-
138-
139-def main():
140- print get_metadata(PACKAGE_NAME)
141-
142-
143-if __name__ == '__main__':
144- main()
145
146=== modified file 'devportalbinary/backends/binary/want'
147--- devportalbinary/backends/binary/want 2011-11-21 16:16:36 +0000
148+++ devportalbinary/backends/binary/want 2012-01-10 15:29:28 +0000
149@@ -4,13 +4,10 @@
150
151 import os
152
153-from devportalbinary.binary import METADATA_FILE
154+from devportalbinary.binary import BinaryBackend
155
156 def main():
157- if os.path.exists(METADATA_FILE):
158- print 10
159- else:
160- print 0
161+ print BinaryBackend().want()
162
163
164 if __name__ == '__main__':
165
166=== added file 'devportalbinary/backends/pdf/all_info'
167--- devportalbinary/backends/pdf/all_info 1970-01-01 00:00:00 +0000
168+++ devportalbinary/backends/pdf/all_info 2012-01-10 15:29:28 +0000
169@@ -0,0 +1,14 @@
170+#!/usr/bin/env python
171+# Copyright 2011 Canonical Ltd. This software is licensed under the
172+# GNU Affero General Public License version 3 (see the file LICENSE).
173+
174+
175+from devportalbinary.pdf import PdfBackend
176+
177+
178+def main():
179+ PdfBackend().dump_json()
180+
181+
182+if __name__ == '__main__':
183+ main()
184
185=== removed file 'devportalbinary/backends/pdf/architecture'
186--- devportalbinary/backends/pdf/architecture 2011-12-13 04:23:22 +0000
187+++ devportalbinary/backends/pdf/architecture 1970-01-01 00:00:00 +0000
188@@ -1,5 +0,0 @@
189-#!/bin/sh
190-# Copyright 2011 Canonical Ltd. This software is licensed under the
191-# GNU Affero General Public License version 3 (see the file LICENSE).
192-
193-echo "all"
194
195=== removed file 'devportalbinary/backends/pdf/build_depends'
196--- devportalbinary/backends/pdf/build_depends 2011-12-05 22:20:24 +0000
197+++ devportalbinary/backends/pdf/build_depends 1970-01-01 00:00:00 +0000
198@@ -1,11 +0,0 @@
199-#!/usr/bin/env python
200-# Copyright 2011 Canonical Ltd. This software is licensed under the
201-# GNU Affero General Public License version 3 (see the file LICENSE).
202-
203-
204-def main():
205- print 'debhelper (>=7)'
206-
207-
208-if __name__ == '__main__':
209- main()
210
211=== removed file 'devportalbinary/backends/pdf/depends'
212--- devportalbinary/backends/pdf/depends 2011-12-20 13:31:47 +0000
213+++ devportalbinary/backends/pdf/depends 1970-01-01 00:00:00 +0000
214@@ -1,11 +0,0 @@
215-#!/usr/bin/env python
216-# Copyright 2011 Canonical Ltd. This software is licensed under the
217-# GNU Affero General Public License version 3 (see the file LICENSE).
218-
219-
220-def main():
221- print 'xdg-utils, ${misc:Depends}'
222-
223-
224-if __name__ == '__main__':
225- main()
226
227=== removed file 'devportalbinary/backends/pdf/description'
228--- devportalbinary/backends/pdf/description 2011-12-20 13:51:05 +0000
229+++ devportalbinary/backends/pdf/description 1970-01-01 00:00:00 +0000
230@@ -1,26 +0,0 @@
231-#!/usr/bin/env python
232-# Copyright 2011 Canonical Ltd. This software is licensed under the
233-# GNU Affero General Public License version 3 (see the file LICENSE).
234-
235-import json
236-from devportalbinary.binary import (
237- get_metadata,
238- TAGLINE
239- )
240-
241-
242-def main():
243- print get_metadata(TAGLINE)
244- # Not included in metadata yet.
245- #long_description = metadata["description"]
246- #lines = long_description.split("\n")
247- #new_lines = []
248- #for line in lines:
249- # if not line.strip():
250- # line = "."
251- # new_lines.append(" " + line)
252- #print "\n".join([description] + new_lines)
253-
254-
255-if __name__ == '__main__':
256- main()
257
258=== removed file 'devportalbinary/backends/pdf/extra_files'
259--- devportalbinary/backends/pdf/extra_files 2011-12-20 13:51:05 +0000
260+++ devportalbinary/backends/pdf/extra_files 1970-01-01 00:00:00 +0000
261@@ -1,37 +0,0 @@
262-#!/usr/bin/env python
263-# Copyright 2011 Canonical Ltd. This software is licensed under the
264-# GNU Affero General Public License version 3 (see the file LICENSE).
265-
266-import json
267-import os
268-import sys
269-
270-from devportalbinary.binary import (
271- CATEGORIES,
272- get_pdf_desktop_file,
273- get_install_file,
274- get_metadata,
275- PACKAGE_NAME,
276- TAGLINE,
277- )
278-
279-
280-def main():
281- metadata = get_metadata()
282- package_name = metadata[PACKAGE_NAME]
283- path = os.getcwd()
284- install_file = get_install_file(package_name, path, True)
285- desktop_file = get_pdf_desktop_file(
286- package_name, path,
287- tagline=metadata.get(TAGLINE, ''),
288- categories=metadata.get(CATEGORIES, ''))
289- extra_files = {
290- # XXX: Hardcoded literal attack!
291- 'debian/install': install_file,
292- 'debian/%s.desktop' % (package_name,): desktop_file.get_contents(),
293- }
294- json.dump(extra_files, sys.stdout)
295-
296-
297-if __name__ == '__main__':
298- main()
299
300=== removed file 'devportalbinary/backends/pdf/package_name'
301--- devportalbinary/backends/pdf/package_name 2011-12-20 13:31:47 +0000
302+++ devportalbinary/backends/pdf/package_name 1970-01-01 00:00:00 +0000
303@@ -1,17 +0,0 @@
304-#!/usr/bin/env python
305-# Copyright 2011 Canonical Ltd. This software is licensed under the
306-# GNU Affero General Public License version 3 (see the file LICENSE).
307-
308-import json
309-from devportalbinary.binary import (
310- get_metadata,
311- PACKAGE_NAME,
312- )
313-
314-
315-def main():
316- print get_metadata(PACKAGE_NAME)
317-
318-
319-if __name__ == '__main__':
320- main()
321
322=== modified file 'devportalbinary/backends/pdf/want'
323--- devportalbinary/backends/pdf/want 2011-12-13 01:52:36 +0000
324+++ devportalbinary/backends/pdf/want 2012-01-10 15:29:28 +0000
325@@ -4,20 +4,10 @@
326
327 import os
328
329-from devportalbinary.binary import METADATA_FILE
330+from devportalbinary.pdf import PdfBackend
331
332 def main():
333- want = False
334- if os.path.exists(METADATA_FILE):
335- files = os.listdir('.')
336- files = [f for f in files if f not in (METADATA_FILE, 'debian')]
337- if len(files) == 1:
338- if files[0].endswith(".pdf"):
339- want = True
340- if want:
341- print 20
342- else:
343- print 0
344+ print PdfBackend().want()
345
346
347 if __name__ == '__main__':
348
349=== modified file 'devportalbinary/binary.py'
350--- devportalbinary/binary.py 2011-12-22 22:41:37 +0000
351+++ devportalbinary/binary.py 2012-01-10 15:29:28 +0000
352@@ -20,62 +20,30 @@
353 * That the entire contents of the tarball can be copied into
354 /opt/<package-name> and run from there
355
356-* That we have a metadata file, called 'devportal-metadata.json', in JSON,
357- that specifies:
358- * package_name
359+* That we have a metadata file, called 'devportal-metadata.json', in JSON.
360+ See ``MetadataBackend``.
361
362 The expectation is that this metadata file is generated from the developer
363 portal.
364 """
365
366-# XXX: No idea about architecture
367-
368 __all__ = [
369- 'get_install_file',
370+ 'BinaryBackend',
371 'guess_executable',
372 'iter_executables',
373- 'METADATA_FILE',
374 ]
375
376
377-import json
378 import os
379 import subprocess
380
381 from pkgme.errors import PkgmeError
382-from pkgme.info_elements import (
383- ApplicationName,
384- Categories,
385- Executable,
386- PackageName,
387- TagLine,
388- )
389-from pkgme.package_files import (
390- DEBIAN_DIR,
391- Desktop,
392- )
393-from pkgme.project_info import DictInfo
394 from pkgme.run_script import run_subprocess
395
396 from devportalbinary.database import PackageDatabase
397-
398-
399-METADATA_FILE = 'devportal-metadata.json'
400-
401-# XXX: These duplicate the schema found in pkgme-service.
402-# Keys found in the metadata file.
403-CATEGORIES = 'categories'
404-DESCRIPTION = 'description'
405-# It's called package_name in the database, so this probably makes sense.
406-PACKAGE_NAME = 'package_name'
407-TAGLINE = 'tagline'
408-
409-
410-# Always depend on these.
411-#
412-# XXX: jml just doing this because iamfuzz does. Need to either experiment or
413-# consult expert.
414-DEPENDS = '${shlibs:Depends}, ${misc:Depends}'
415+from devportalbinary.metadata import MetadataBackend
416+
417+
418
419 # XXX: No idea about how icons will be there. Ignoring for now.
420
421@@ -129,79 +97,6 @@
422 yield os.path.relpath(file_path, path)
423
424
425-_no_field = object()
426-def get_metadata(field_name=None, default=_no_field, path=METADATA_FILE):
427- """Return the value of ``field_name`` in metadata.
428-
429- :param field_name: The field to look up. If None, then return all the
430- metadata as a dict.
431- :param default: If provided, then this value will be returned if
432- 'field_name' is not present in the metadata.
433- :param path: The path to the metadata file, if unspecified, look for
434- METADATA_FILE in the current working directory.
435- :return: The value of the field.
436- """
437- with open(path) as f:
438- metadata = json.load(f)
439- if field_name is None:
440- return metadata
441- value = metadata.get(field_name, default)
442- if value is _no_field:
443- raise KeyError(field_name)
444- return value
445-
446-
447-def get_install_file(package_name, path, include_desktop=False):
448- """Generate the install file for 'package_name'."""
449- lines = []
450- # Sorting not actually needed for functionality, but makes the tests more
451- # reliable.
452- for filename in sorted(os.listdir(path)):
453- if filename in (DEBIAN_DIR, METADATA_FILE):
454- # We don't want to install the 'debian/' directory or the metadata
455- # file.
456- continue
457- lines.append('%s opt/%s' % (filename, package_name))
458- if include_desktop:
459- lines.append(
460- 'debian/%s.desktop usr/share/applications' % (package_name,))
461- # Ending the file with a newline is basic good manners.
462- lines.append('')
463- return '\n'.join(lines)
464-
465-
466-def get_desktop_file(package_name, executable, tagline=None, categories=None):
467- info = {
468- PackageName.name: package_name,
469- ApplicationName.name: package_name.capitalize(),
470- Executable.name: executable,
471- TagLine.name: tagline,
472- Categories.name: categories,
473- }
474- return Desktop.from_info(DictInfo(info))
475-
476-
477-def get_binary_desktop_file(package_name, path, tagline=None,
478- categories=None):
479- executable = guess_executable(package_name, iter_executables(path))
480- return get_desktop_file(package_name,
481- '/opt/%s/%s' % (package_name, executable), tagline=tagline,
482- categories=categories)
483-
484-
485-def get_pdf_desktop_file(package_name, path, tagline=None, categories=None):
486- pdf_filename = None
487- for filename in os.listdir(path):
488- if filename.endswith('.pdf'):
489- pdf_filename = filename
490- break
491- if pdf_filename is None:
492- raise AssertionError("Couldn't find pdf file")
493- executable = '/usr/bin/xdg-open /opt/%s/%s' % (package_name, pdf_filename)
494- return get_desktop_file(package_name, executable, tagline=tagline,
495- categories=categories)
496-
497-
498 def get_file_type(path):
499 return get_file_types([path])[0]
500
501@@ -296,3 +191,34 @@
502 cwd = os.getcwd()
503 print guess_executable(os.path.dirname(cwd), iter_executables(cwd))
504 return 0
505+
506+
507+class BinaryBackend(MetadataBackend):
508+ """A backend that uses MyApps metadata to build a package for a binary."""
509+
510+ # XXX: jml just doing this because iamfuzz does. Need to either experiment
511+ # or consult expert.
512+ DEPENDS = '${shlibs:Depends}, ${misc:Depends}'
513+
514+ def get_architecture(self):
515+ # XXX: No idea about architecture
516+ return None
517+
518+ def get_build_depends(self, metadata):
519+ return ', '.join(guess_dependencies(self.path))
520+
521+ def get_depends(self, metadata):
522+ return self.DEPENDS
523+
524+ def get_description(self, metadata):
525+ return metadata.get(self.DESCRIPTION, '')
526+
527+ def get_executable(self, package_name):
528+ executable = guess_executable(package_name, iter_executables(self.path))
529+ return '/opt/%s/%s' % (package_name, executable)
530+
531+ def want(self):
532+ if os.path.exists(self.metadata_path):
533+ return 10
534+ else:
535+ return 0
536
537=== added file 'devportalbinary/metadata.py'
538--- devportalbinary/metadata.py 1970-01-01 00:00:00 +0000
539+++ devportalbinary/metadata.py 2012-01-10 15:29:28 +0000
540@@ -0,0 +1,204 @@
541+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
542+# GNU Affero General Public License version 3 (see the file LICENSE).
543+
544+"""A pkgme backend that gets much of its data from MyApps.
545+
546+The main idea behind this backend is that it looks for a file,
547+'devportal-metadata.json', created with data provided by users of MyApps. It
548+then uses this data, along with inferences from submitted files to create a
549+Debian package.
550+"""
551+
552+__all__ = [
553+ 'MetadataBackend',
554+ ]
555+
556+import json
557+import os
558+import sys
559+
560+from pkgme.info_elements import (
561+ ApplicationName,
562+ Architecture,
563+ BuildDepends,
564+ Categories,
565+ Depends,
566+ Description,
567+ Executable,
568+ ExtraFiles,
569+ PackageName,
570+ TagLine,
571+ )
572+from pkgme.package_files import (
573+ DEBIAN_DIR,
574+ Desktop,
575+ )
576+from pkgme.project_info import DictInfo
577+
578+
579+_no_field = object()
580+def get_metadata(field_name=None, default=_no_field, path=None):
581+ """Return the value of ``field_name`` in metadata.
582+
583+ :param field_name: The field to look up. If None, then return all the
584+ metadata as a dict.
585+ :param default: If provided, then this value will be returned if
586+ 'field_name' is not present in the metadata.
587+ :param path: The path to the metadata file, if unspecified, look for
588+ ``MetadataBackend.METADATA_FILE`` in the current working directory.
589+ :return: The value of the field.
590+ """
591+ if path is None:
592+ path = MetadataBackend.METADATA_FILE
593+ with open(path) as f:
594+ metadata = json.load(f)
595+ if field_name is None:
596+ return metadata
597+ value = metadata.get(field_name, default)
598+ if value is _no_field:
599+ raise KeyError(field_name)
600+ return value
601+
602+
603+def get_install_file(package_name, path, include_desktop=False):
604+ """Generate the install file for 'package_name'."""
605+ lines = []
606+ # Sorting not actually needed for functionality, but makes the tests more
607+ # reliable.
608+ for filename in sorted(os.listdir(path)):
609+ if filename in (DEBIAN_DIR, MetadataBackend.METADATA_FILE):
610+ # We don't want to install the 'debian/' directory or the metadata
611+ # file.
612+ continue
613+ lines.append('%s opt/%s' % (filename, package_name))
614+ if include_desktop:
615+ lines.append(
616+ 'debian/%s.desktop usr/share/applications' % (package_name,))
617+ # Ending the file with a newline is basic good manners.
618+ lines.append('')
619+ return '\n'.join(lines)
620+
621+
622+class MetadataBackend(object):
623+ """A backend that is mostly powered by metadata from MyApps."""
624+
625+ # Where the metadata file lives.
626+ METADATA_FILE = 'devportal-metadata.json'
627+
628+ # Keys found in the metadata file.
629+ # XXX: These duplicate the schema found in pkgme-service.
630+ CATEGORIES = 'categories'
631+ DESCRIPTION = 'description'
632+ PACKAGE_NAME = 'package_name'
633+ TAGLINE = 'tagline'
634+
635+ def __init__(self, path=None):
636+ """Construct a ``MetadataBackend``."""
637+ if path is None:
638+ path = os.getcwd()
639+ self.path = path
640+ self.metadata_path = os.path.join(path, self.METADATA_FILE)
641+
642+ def get_architecture(self):
643+ """Get the architecture for the package.
644+
645+ :return: The architecture tag, or None if no architecture is
646+ specified.
647+ """
648+ raise NotImplementedError(self.get_architecture)
649+
650+ def get_build_depends(self, metadata):
651+ """Get the build dependencies of the package."""
652+ raise NotImplementedError(self.get_build_depends)
653+
654+ def get_desktop_file(self, package_name, tagline=None, categories=None):
655+ """Get the desktop file for the package.
656+
657+ :return: A ``Desktop``.
658+ """
659+ executable = self.get_executable(package_name)
660+ info = {
661+ PackageName.name: package_name,
662+ ApplicationName.name: package_name.capitalize(),
663+ Executable.name: executable,
664+ TagLine.name: tagline,
665+ Categories.name: categories,
666+ }
667+ return Desktop.from_info(DictInfo(info))
668+
669+ def get_depends(self, metadata):
670+ """Get the dependencies for the package."""
671+ raise NotImplementedError(self.depends)
672+
673+ def get_description(self, metadata):
674+ """Get the package description."""
675+ raise NotImplementedError(self.get_description)
676+
677+ def get_executable(self, package_name):
678+ """Return the path to the executable."""
679+ raise NotImplementedError(self.get_executable)
680+
681+ def get_extra_files(self, package_name, metadata):
682+ """Get the extra files for the package.
683+
684+ Assumes that the only extra files are a desktop file and an install
685+ file. Delegates to ``MetadataBackend.get_desktop_file`` for the
686+ desktop file.
687+ """
688+ install_file = get_install_file(package_name, self.path, True)
689+ desktop_file = self.get_desktop_file(
690+ package_name,
691+ tagline=metadata.get(self.TAGLINE, ''),
692+ categories=metadata.get(self.CATEGORIES, ''))
693+ return {
694+ # XXX: Hardcoded literal attack!
695+ 'debian/install': install_file,
696+ 'debian/%s.desktop' % (package_name,): desktop_file.get_contents(),
697+ }
698+
699+ def get_metadata(self):
700+ """Get the metadata for this backend.
701+
702+ Looks for the metadata in a file called ``METADATA_FILE`` in the
703+ directory given to the constructor.
704+
705+ :return: A dict of metadata.
706+ """
707+ return get_metadata(path=self.metadata_path)
708+
709+ def get_info(self, metadata):
710+ """Return a dict of InfoElements given 'metadata'.
711+
712+ This is the work-horse method of the backend. It takes a dict of
713+ metadata, as extracted from a devportal-metadata.json file, and
714+ converts it into a dictionary mapping InfoElements to their actual
715+ values.
716+
717+ This dictionary will then be dumped as the JSON output of 'all_info',
718+ substituting the InfoElements for their names.
719+ """
720+ package_name = metadata[self.PACKAGE_NAME]
721+ info = {
722+ BuildDepends: self.get_build_depends(metadata),
723+ Depends: self.get_depends(metadata),
724+ Description: self.get_description(metadata),
725+ ExtraFiles: self.get_extra_files(package_name, metadata),
726+ PackageName: package_name,
727+ }
728+ architecture = self.get_architecture()
729+ if architecture:
730+ info[Architecture] = architecture
731+ return info
732+
733+ def dump_json(self, stream=sys.stdout):
734+ """Dump the information from this backend as JSON."""
735+ info = self.get_info(self.get_metadata())
736+ info_for_json = dict(
737+ (element.name, data) for element, data in info.items())
738+ json.dump(info_for_json, stream)
739+
740+ def want(self):
741+ """How relevant this backend is."""
742+ raise NotImplementedError(self.want)
743+
744+
745
746=== added file 'devportalbinary/pdf.py'
747--- devportalbinary/pdf.py 1970-01-01 00:00:00 +0000
748+++ devportalbinary/pdf.py 2012-01-10 15:29:28 +0000
749@@ -0,0 +1,47 @@
750+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
751+# GNU Affero General Public License version 3 (see the file LICENSE).
752+
753+import os
754+
755+from devportalbinary.metadata import MetadataBackend
756+
757+
758+class PdfBackend(MetadataBackend):
759+ """A backend that uses MyApps metadata to build a package for a PDF."""
760+
761+ def get_architecture(self):
762+ return 'all'
763+
764+ def get_build_depends(self, metadata):
765+ return 'debhelper (>=7)'
766+
767+ def get_depends(self, metadata):
768+ return 'xdg-utils, ${misc:Depends}'
769+
770+ def get_description(self, metadata):
771+ return metadata[self.TAGLINE]
772+
773+ def get_executable(self, package_name):
774+ pdf_filename = None
775+ for filename in os.listdir(self.path):
776+ if filename.endswith('.pdf'):
777+ pdf_filename = filename
778+ break
779+ if pdf_filename is None:
780+ return None
781+ return '/usr/bin/xdg-open /opt/%s/%s' % (package_name, pdf_filename)
782+
783+ def want(self):
784+ want = False
785+ if os.path.exists(self.metadata_path):
786+ files = os.listdir(self.path)
787+ files = [
788+ f for f in files
789+ if f not in (self.METADATA_FILE, 'debian')]
790+ if len(files) == 1:
791+ if files[0].endswith(".pdf"):
792+ want = True
793+ if want:
794+ return 20
795+ else:
796+ return 0
797
798=== modified file 'devportalbinary/testing.py'
799--- devportalbinary/testing.py 2011-12-22 22:41:37 +0000
800+++ devportalbinary/testing.py 2012-01-10 15:29:28 +0000
801@@ -9,7 +9,7 @@
802
803 from pkgme.testing import TempdirFixture
804
805-from devportalbinary.binary import METADATA_FILE
806+from devportalbinary.binary import MetadataBackend
807 from devportalbinary.database import PackageDatabase
808
809
810@@ -67,7 +67,7 @@
811 super(MetadataFixture, self).setUp()
812 self.tempdir = self.useFixture(TempdirFixture())
813 self.path = self.tempdir.path
814- self.metadata_path = os.path.join(self.path, METADATA_FILE)
815+ self.metadata_path = os.path.join(self.path, MetadataBackend.METADATA_FILE)
816 with open(self.metadata_path, 'w') as fp:
817 json.dump(self._metadata, fp)
818
819
820=== modified file 'devportalbinary/tests/test_binary.py'
821--- devportalbinary/tests/test_binary.py 2011-12-22 22:41:37 +0000
822+++ devportalbinary/tests/test_binary.py 2012-01-10 15:29:28 +0000
823@@ -1,4 +1,4 @@
824-# Copyright 2011 Canonical Ltd. This software is licensed under the
825+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
826 # GNU Affero General Public License version 3 (see the file LICENSE).
827
828 import os
829@@ -6,25 +6,17 @@
830 from testtools import TestCase
831 from testtools.matchers import StartsWith
832
833-from pkgme.package_files import DEBIAN_DIR
834-from pkgme.testing import (
835- AreDesktopValuesFor,
836- TempdirFixture,
837- )
838+from pkgme.testing import TempdirFixture
839
840 from devportalbinary.binary import (
841- get_binary_desktop_file,
842+ BinaryBackend,
843 get_file_type,
844 get_file_types,
845- get_install_file,
846- get_metadata,
847- get_pdf_desktop_file,
848 get_shared_library_dependencies,
849 guess_dependencies,
850 guess_executable,
851 iter_binaries,
852 iter_executables,
853- METADATA_FILE,
854 needed_libraries_from_objdump,
855 NoBinariesFound,
856 UnknownDependency,
857@@ -33,134 +25,16 @@
858 DatabaseFixture,
859 get_test_data_dir_path,
860 get_test_data_file_path,
861- MetadataFixture,
862 )
863
864
865-class MetadataTests(TestCase):
866-
867- def test_metadata_file(self):
868- self.assertEqual('devportal-metadata.json', METADATA_FILE)
869-
870- def test_get_metadata_field_present(self):
871- # get_metadata returns the metadata value of the requested field.
872- metadata = {
873- 'foo': self.getUniqueString(),
874- 'bar': self.getUniqueInteger(),
875- }
876- path = self.useFixture(MetadataFixture(metadata)).metadata_path
877- foo = get_metadata('foo', path=path)
878- self.assertEqual(metadata['foo'], foo)
879-
880- def test_get_metadata_default_file(self):
881- # By default, get_metadata looks for the METADATA_FILE in the current
882- # working directory.
883- metadata = {
884- 'foo': self.getUniqueString(),
885- 'bar': self.getUniqueInteger(),
886- }
887- path = self.useFixture(MetadataFixture(metadata)).path
888- self.addCleanup(os.chdir, os.getcwd())
889- os.chdir(path)
890- foo = get_metadata('foo')
891- self.assertEqual(metadata['foo'], foo)
892-
893- def test_get_metadata_field_not_present_default_provided(self):
894- # get_metadata returns the provided default value if the field is not
895- # present in the metadata.
896- metadata = {}
897- path = self.useFixture(MetadataFixture(metadata)).metadata_path
898- default = object()
899- foo = get_metadata('foo', default, path=path)
900- self.assertIs(default, foo)
901-
902- def test_get_metadata_field_not_present_no_default(self):
903- # get_metadata raises an exception if the field isn't there and no
904- # default was provided.
905- metadata = {}
906- field = self.getUniqueString()
907- path = self.useFixture(MetadataFixture(metadata)).metadata_path
908- e = self.assertRaises(KeyError, get_metadata, field, path=path)
909- self.assertEqual(repr(field), str(e))
910-
911- def test_get_metadata_all_fields(self):
912- # get_metadata returns the metadata value of the requested field.
913- metadata = {
914- 'foo': self.getUniqueString(),
915- 'bar': self.getUniqueInteger(),
916- }
917- path = self.useFixture(MetadataFixture(metadata)).metadata_path
918- found_metadata = get_metadata(path=path)
919- self.assertEqual(metadata, found_metadata)
920-
921-
922 class TestObjDump(TestCase):
923
924 def test_no_binaries(self):
925 self.assertRaises(NoBinariesFound, needed_libraries_from_objdump, [])
926
927
928-class InstallFileTests(TestCase):
929-
930- def test_install_file(self):
931- # The install file instructs debhelper to copy everything in the
932- # top-level to /opt/<package-name>.
933- tempdir = self.useFixture(TempdirFixture())
934- tempdir.touch('some-file')
935- install_file = get_install_file('package-name', tempdir.path)
936- self.assertEqual("some-file opt/package-name\n", install_file)
937-
938- def test_install_file_many_files_and_directories(self):
939- # The install file instructs debhelper to copy everything in the
940- # top-level to /opt/<package-name>.
941- tempdir = self.useFixture(TempdirFixture())
942- tempdir.touch('some-file')
943- tempdir.mkdir('directory')
944- install_file = get_install_file('package-name', tempdir.path)
945- self.assertEqual(
946- "directory opt/package-name\n"
947- "some-file opt/package-name\n",
948- install_file)
949-
950- def test_skip_debian(self):
951- # The install file instructs debhelper to copy everything in the
952- # top-level to /opt/<package-name>, except for the 'debian' directory.
953- tempdir = self.useFixture(TempdirFixture())
954- tempdir.touch('some-file')
955- tempdir.mkdir('directory')
956- tempdir.mkdir(DEBIAN_DIR)
957- install_file = get_install_file('package-name', tempdir.path)
958- self.assertEqual(
959- "directory opt/package-name\n"
960- "some-file opt/package-name\n",
961- install_file)
962-
963- def test_skip_metadata(self):
964- # The install file instructs debhelper to copy everything in the
965- # top-level to /opt/<package-name>, except for the 'debian' directory
966- # and the metadata file.
967- tempdir = self.useFixture(TempdirFixture())
968- tempdir.touch('some-file')
969- tempdir.mkdir('directory')
970- tempdir.touch(METADATA_FILE)
971- install_file = get_install_file('package-name', tempdir.path)
972- self.assertEqual(
973- "directory opt/package-name\n"
974- "some-file opt/package-name\n",
975- install_file)
976-
977- def test_include_desktop_file(self):
978- tempdir = self.useFixture(TempdirFixture())
979- tempdir.touch('some-file')
980- install_file = get_install_file(
981- 'package-name', tempdir.path, include_desktop=True)
982- self.assertEqual(
983- "some-file opt/package-name\n"
984- "debian/package-name.desktop usr/share/applications\n",
985- install_file)
986-
987-
988-class DesktopFileTests(TestCase):
989+class BackendTests(TestCase):
990
991 def test_executable_is_best_guess(self):
992 package_name = self.getUniqueString()
993@@ -168,45 +42,10 @@
994 tempdir.mkdir('whatever')
995 tempdir.touch('whatever/not-the-best', 0755)
996 tempdir.touch('the-best', 0755)
997- desktop_file = get_binary_desktop_file(package_name, tempdir.path)
998- self.assertThat(
999- {'Exec': '/opt/%s/the-best' % (package_name,)},
1000- AreDesktopValuesFor(desktop_file))
1001-
1002- def test_app_name_is_capitalized_package_name(self):
1003- # We don't have any information on the package name, so try to guess
1004- # the application name.
1005- tempdir = self.useFixture(TempdirFixture())
1006- package_name = self.getUniqueString()
1007- desktop_file = get_binary_desktop_file(package_name, tempdir.path)
1008- self.assertThat(
1009- {'Name': package_name.capitalize()},
1010- AreDesktopValuesFor(desktop_file))
1011-
1012- def test_category_and_tagline_are_specified(self):
1013- # We just pass the category and comment through.
1014- tempdir = self.useFixture(TempdirFixture())
1015- package_name = self.getUniqueString()
1016- tagline = self.getUniqueString()
1017- categories = self.getUniqueString()
1018- desktop_file = get_binary_desktop_file(
1019- package_name, tempdir.path, tagline=tagline, categories=categories)
1020- self.assertThat(
1021- {'Comment': tagline, 'Categories': categories},
1022- AreDesktopValuesFor(desktop_file))
1023-
1024- def test_pdf_exec_line(self):
1025- tempdir = self.useFixture(TempdirFixture())
1026- tempdir.touch('foo.pdf')
1027- package_name = self.getUniqueString()
1028- tagline = self.getUniqueString()
1029- categories = self.getUniqueString()
1030- desktop_file = get_pdf_desktop_file(
1031- package_name, tempdir.path, tagline=tagline, categories=categories)
1032- self.assertThat(
1033- {'Exec': '/usr/bin/xdg-open /opt/%s/%s'
1034- % (package_name, 'foo.pdf')},
1035- AreDesktopValuesFor(desktop_file))
1036+ backend = BinaryBackend(tempdir.path)
1037+ self.assertEqual(
1038+ '/opt/%s/the-best' % (package_name,),
1039+ backend.get_executable(package_name))
1040
1041
1042 class FindExecutableTests(TestCase):
1043@@ -380,7 +219,7 @@
1044 self.assertEqual(set(['libc6']), deps)
1045
1046 def test_guess_dependencies_error_on_unknown_dependency(self):
1047- db = self.useFixture(DatabaseFixture()).db
1048+ self.useFixture(DatabaseFixture())
1049 e = self.assertRaises(UnknownDependency,
1050 guess_dependencies, get_test_data_dir_path('hello'))
1051 self.assertEqual('Can\'t find dependency for "libc.so.6".', str(e))
1052
1053=== modified file 'devportalbinary/tests/test_binary_backend.py'
1054--- devportalbinary/tests/test_binary_backend.py 2011-12-22 22:41:37 +0000
1055+++ devportalbinary/tests/test_binary_backend.py 2012-01-10 15:29:28 +0000
1056@@ -1,8 +1,6 @@
1057 # Copyright 2011 Canonical Ltd. This software is licensed under the
1058 # GNU Affero General Public License version 3 (see the file LICENSE).
1059
1060-import json
1061-import os
1062 import shutil
1063
1064 from fixtures import TempDir
1065@@ -12,7 +10,6 @@
1066 Matcher,
1067 )
1068
1069-from pkgme.backend import ExternalHelpersBackend, get_backend_dir
1070 from pkgme.info_elements import (
1071 BuildDepends,
1072 Description,
1073@@ -22,15 +19,11 @@
1074 )
1075
1076 from devportalbinary.binary import (
1077- CATEGORIES,
1078- DESCRIPTION,
1079- DEPENDS,
1080- get_binary_desktop_file,
1081- get_install_file,
1082+ BinaryBackend,
1083 guess_dependencies,
1084- PACKAGE_NAME,
1085- TAGLINE,
1086+ MetadataBackend,
1087 )
1088+from devportalbinary.metadata import get_install_file
1089 from devportalbinary.testing import (
1090 DatabaseFixture,
1091 get_test_data_file_path,
1092@@ -38,11 +31,6 @@
1093 )
1094
1095
1096-BACKEND_NAME = 'binary'
1097-
1098-backend_dir = get_backend_dir(__file__, BACKEND_NAME)
1099-
1100-
1101 class HasField(Matcher):
1102 """Matches if 'info' has a field with the given value."""
1103
1104@@ -52,86 +40,95 @@
1105 self.field_value = field_value
1106
1107 def match(self, info):
1108- return Equals(
1109- {self.field.name: self.field_value}).match(
1110- info.get_all([self.field.name]))
1111+ return Equals(self.field_value).match(info[self.field])
1112
1113
1114 class BinaryBackendTests(TestCase):
1115
1116+ def make_metadata(self, package_name=None, description=None, tagline=None,
1117+ categories=None):
1118+ if package_name is None:
1119+ package_name = self.getUniqueString('package-name')
1120+ metadata = {MetadataBackend.PACKAGE_NAME: package_name}
1121+ if description is not None:
1122+ metadata[MetadataBackend.DESCRIPTION] = description
1123+ if tagline is not None:
1124+ metadata[MetadataBackend.TAGLINE] = tagline
1125+ if categories is not None:
1126+ metadata[MetadataBackend.CATEGORIES] = categories
1127+ return metadata
1128+
1129+ def make_backend(self, path=None):
1130+ if path is None:
1131+ path = self.useFixture(TempDir()).path
1132+ backend = BinaryBackend(path)
1133+ shutil.copy(
1134+ get_test_data_file_path('hello', 'hello'), path)
1135+ db = self.useFixture(DatabaseFixture()).db
1136+ db.update_source_package('eglibc', [[('libc.so.6', 'libc6')]])
1137+ return backend
1138+
1139 def test_want_with_metadata(self):
1140 # If we detect a binary, then we score 10. The way we determine if
1141 # something is a binary is if it has a devportal-metadata.json in its
1142 # top-level.
1143 path = self.useFixture(MetadataFixture({})).path
1144- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1145- self.assertEqual(10, backend.want(path))
1146+ backend = self.make_backend(path)
1147+ self.assertEqual(10, backend.want())
1148
1149 def test_want_without_metadata(self):
1150 # If we do *not* detect a binary, then we score 0. The way we
1151 # determine if something is a binary is if it has a
1152 # devportal-metadata.json in its top-level.
1153- tempdir = self.useFixture(TempDir())
1154- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1155- self.assertEqual(0, backend.want(tempdir.path))
1156+ backend = self.make_backend()
1157+ self.assertEqual(0, backend.want())
1158
1159 def test_package_name(self):
1160 # The binary backend gets the package name from the metadata file.
1161- path = self.useFixture(MetadataFixture({PACKAGE_NAME: 'foo'})).path
1162- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1163- info = backend.get_info(path)
1164+ backend = self.make_backend()
1165+ metadata = self.make_metadata(package_name='foo')
1166+ info = backend.get_info(metadata)
1167 self.assertThat(info, HasField(PackageName, 'foo'))
1168
1169 def test_description(self):
1170 # The binary backend uses the package description that's in the
1171 # metadata.
1172 description = self.getUniqueString()
1173- path = self.useFixture(MetadataFixture({
1174- PACKAGE_NAME: 'foo',
1175- DESCRIPTION: description,
1176- })).path
1177- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1178- info = backend.get_info(path)
1179+ metadata = self.make_metadata(description=description)
1180+ backend = self.make_backend()
1181+ info = backend.get_info(metadata)
1182 self.assertThat(info, HasField(Description, description))
1183
1184 def test_no_description(self):
1185 # If no description is provided in the metadata then the description
1186 # in the package info is just an empty string.
1187- path = self.useFixture(MetadataFixture({PACKAGE_NAME: 'foo'})).path
1188- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1189- info = backend.get_info(path)
1190+ backend = self.make_backend()
1191+ metadata = self.make_metadata()
1192+ info = backend.get_info(metadata)
1193 self.assertThat(info, HasField(Description, ''))
1194
1195 def test_build_depends(self):
1196 # Make sure there's a database.
1197- db = self.useFixture(DatabaseFixture()).db
1198- db.update_source_package('eglibc', [[('libc.so.6', 'libc6')]])
1199- path = self.useFixture(MetadataFixture({PACKAGE_NAME: 'foo'})).path
1200- shutil.copy(
1201- get_test_data_file_path('hello', 'hello'),
1202- path)
1203- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1204- info = backend.get_info(path)
1205- deps = ', '.join(guess_dependencies(path))
1206+ backend = self.make_backend()
1207+ info = backend.get_info(self.make_metadata())
1208+ deps = ', '.join(guess_dependencies(backend.path))
1209 self.assertThat(info, HasField(BuildDepends, deps))
1210
1211 def test_depends(self):
1212- path = self.useFixture(MetadataFixture({PACKAGE_NAME: 'foo'})).path
1213- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1214- info = backend.get_info(path)
1215- self.assertThat(info, HasField(Depends, DEPENDS))
1216+ backend = self.make_backend()
1217+ info = backend.get_info(self.make_metadata())
1218+ self.assertThat(info, HasField(Depends, BinaryBackend.DEPENDS))
1219
1220 def test_extra_files_install_file(self):
1221 # We create an 'install' file that tells debhelper to just copy
1222 # everything to opt.
1223 package_name = self.getUniqueString()
1224- path = self.useFixture(MetadataFixture({PACKAGE_NAME: package_name})).path
1225- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1226- info = backend.get_info(path)
1227- extra_files = info.get_all([ExtraFiles.name])[ExtraFiles.name]
1228- install_file = json.loads(extra_files)['debian/install']
1229+ metadata = self.make_metadata(package_name=package_name)
1230+ backend = self.make_backend()
1231+ info = backend.get_info(metadata)
1232+ install_file = info[ExtraFiles]['debian/install']
1233 self.assertEqual(
1234- get_install_file(package_name, path, True),
1235+ get_install_file(package_name, backend.path, True),
1236 install_file)
1237
1238 def test_extra_files_desktop_file(self):
1239@@ -139,20 +136,16 @@
1240 package_name = self.getUniqueString()
1241 tagline = self.getUniqueString()
1242 categories = self.getUniqueString()
1243- metadata = {
1244- PACKAGE_NAME: package_name,
1245- TAGLINE: tagline,
1246- CATEGORIES: categories,
1247- }
1248- tempdir = self.useFixture(MetadataFixture(metadata)).tempdir
1249- # We need to create an executable in order to be able to generate a
1250- # desktop.
1251- tempdir.touch('executable', 0755)
1252- expected_desktop_file = get_binary_desktop_file(
1253- package_name, tempdir.path, tagline=tagline,
1254+ metadata = self.make_metadata(
1255+ package_name=package_name,
1256+ tagline=tagline,
1257+ categories=categories,
1258+ )
1259+ backend = self.make_backend()
1260+ expected_desktop_file = backend.get_desktop_file(
1261+ package_name, tagline=tagline,
1262 categories=categories).get_contents()
1263- backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1264- info = backend.get_info(tempdir.path)
1265- extra_files = info.get_all([ExtraFiles.name])[ExtraFiles.name]
1266- desktop = json.loads(extra_files)['debian/%s.desktop' % (package_name,)]
1267+ info = backend.get_info(metadata)
1268+ extra_files = info[ExtraFiles]
1269+ desktop = extra_files['debian/%s.desktop' % (package_name,)]
1270 self.assertEqual(expected_desktop_file, desktop)
1271
1272=== added file 'devportalbinary/tests/test_metadata.py'
1273--- devportalbinary/tests/test_metadata.py 1970-01-01 00:00:00 +0000
1274+++ devportalbinary/tests/test_metadata.py 2012-01-10 15:29:28 +0000
1275@@ -0,0 +1,178 @@
1276+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
1277+# GNU Affero General Public License version 3 (see the file LICENSE).
1278+
1279+import os
1280+
1281+from pkgme.package_files import DEBIAN_DIR
1282+from pkgme.testing import (
1283+ AreDesktopValuesFor,
1284+ TempdirFixture,
1285+ )
1286+from testtools import TestCase
1287+
1288+from devportalbinary.metadata import (
1289+ get_install_file,
1290+ get_metadata,
1291+ MetadataBackend,
1292+ )
1293+from devportalbinary.testing import (
1294+ MetadataFixture,
1295+ )
1296+
1297+
1298+class MetadataTests(TestCase):
1299+
1300+ def test_metadata_file(self):
1301+ self.assertEqual('devportal-metadata.json', MetadataBackend.METADATA_FILE)
1302+
1303+ def test_get_metadata_field_present(self):
1304+ # get_metadata returns the metadata value of the requested field.
1305+ metadata = {
1306+ 'foo': self.getUniqueString(),
1307+ 'bar': self.getUniqueInteger(),
1308+ }
1309+ path = self.useFixture(MetadataFixture(metadata)).metadata_path
1310+ foo = get_metadata('foo', path=path)
1311+ self.assertEqual(metadata['foo'], foo)
1312+
1313+ def test_get_metadata_default_file(self):
1314+ # By default, get_metadata looks for the METADATA_FILE in the current
1315+ # working directory.
1316+ metadata = {
1317+ 'foo': self.getUniqueString(),
1318+ 'bar': self.getUniqueInteger(),
1319+ }
1320+ path = self.useFixture(MetadataFixture(metadata)).path
1321+ self.addCleanup(os.chdir, os.getcwd())
1322+ os.chdir(path)
1323+ foo = get_metadata('foo')
1324+ self.assertEqual(metadata['foo'], foo)
1325+
1326+ def test_get_metadata_field_not_present_default_provided(self):
1327+ # get_metadata returns the provided default value if the field is not
1328+ # present in the metadata.
1329+ metadata = {}
1330+ path = self.useFixture(MetadataFixture(metadata)).metadata_path
1331+ default = object()
1332+ foo = get_metadata('foo', default, path=path)
1333+ self.assertIs(default, foo)
1334+
1335+ def test_get_metadata_field_not_present_no_default(self):
1336+ # get_metadata raises an exception if the field isn't there and no
1337+ # default was provided.
1338+ metadata = {}
1339+ field = self.getUniqueString()
1340+ path = self.useFixture(MetadataFixture(metadata)).metadata_path
1341+ e = self.assertRaises(KeyError, get_metadata, field, path=path)
1342+ self.assertEqual(repr(field), str(e))
1343+
1344+ def test_get_metadata_all_fields(self):
1345+ # get_metadata returns the metadata value of the requested field.
1346+ metadata = {
1347+ 'foo': self.getUniqueString(),
1348+ 'bar': self.getUniqueInteger(),
1349+ }
1350+ path = self.useFixture(MetadataFixture(metadata)).metadata_path
1351+ found_metadata = get_metadata(path=path)
1352+ self.assertEqual(metadata, found_metadata)
1353+
1354+
1355+class InstallFileTests(TestCase):
1356+
1357+ def test_install_file(self):
1358+ # The install file instructs debhelper to copy everything in the
1359+ # top-level to /opt/<package-name>.
1360+ tempdir = self.useFixture(TempdirFixture())
1361+ tempdir.touch('some-file')
1362+ install_file = get_install_file('package-name', tempdir.path)
1363+ self.assertEqual("some-file opt/package-name\n", install_file)
1364+
1365+ def test_install_file_many_files_and_directories(self):
1366+ # The install file instructs debhelper to copy everything in the
1367+ # top-level to /opt/<package-name>.
1368+ tempdir = self.useFixture(TempdirFixture())
1369+ tempdir.touch('some-file')
1370+ tempdir.mkdir('directory')
1371+ install_file = get_install_file('package-name', tempdir.path)
1372+ self.assertEqual(
1373+ "directory opt/package-name\n"
1374+ "some-file opt/package-name\n",
1375+ install_file)
1376+
1377+ def test_skip_debian(self):
1378+ # The install file instructs debhelper to copy everything in the
1379+ # top-level to /opt/<package-name>, except for the 'debian' directory.
1380+ tempdir = self.useFixture(TempdirFixture())
1381+ tempdir.touch('some-file')
1382+ tempdir.mkdir('directory')
1383+ tempdir.mkdir(DEBIAN_DIR)
1384+ install_file = get_install_file('package-name', tempdir.path)
1385+ self.assertEqual(
1386+ "directory opt/package-name\n"
1387+ "some-file opt/package-name\n",
1388+ install_file)
1389+
1390+ def test_skip_metadata(self):
1391+ # The install file instructs debhelper to copy everything in the
1392+ # top-level to /opt/<package-name>, except for the 'debian' directory
1393+ # and the metadata file.
1394+ tempdir = self.useFixture(TempdirFixture())
1395+ tempdir.touch('some-file')
1396+ tempdir.mkdir('directory')
1397+ tempdir.touch(MetadataBackend.METADATA_FILE)
1398+ install_file = get_install_file('package-name', tempdir.path)
1399+ self.assertEqual(
1400+ "directory opt/package-name\n"
1401+ "some-file opt/package-name\n",
1402+ install_file)
1403+
1404+ def test_include_desktop_file(self):
1405+ tempdir = self.useFixture(TempdirFixture())
1406+ tempdir.touch('some-file')
1407+ install_file = get_install_file(
1408+ 'package-name', tempdir.path, include_desktop=True)
1409+ self.assertEqual(
1410+ "some-file opt/package-name\n"
1411+ "debian/package-name.desktop usr/share/applications\n",
1412+ install_file)
1413+
1414+
1415+class DesktopFileTests(TestCase):
1416+
1417+ def make_backend(self, executable=None):
1418+ tempdir = self.useFixture(TempdirFixture())
1419+ backend = MetadataBackend(tempdir.path)
1420+ backend.get_executable = lambda package_name: executable
1421+ return backend
1422+
1423+ def test_app_name_is_capitalized_package_name(self):
1424+ # We don't have any information on the package name, so try to guess
1425+ # the application name.
1426+ backend = self.make_backend()
1427+ package_name = self.getUniqueString()
1428+ desktop_file = backend.get_desktop_file(package_name)
1429+ self.assertThat(
1430+ {'Name': package_name.capitalize()},
1431+ AreDesktopValuesFor(desktop_file))
1432+
1433+ def test_category_and_tagline_are_specified(self):
1434+ # We just pass the category and comment through.
1435+ package_name = self.getUniqueString()
1436+ tagline = self.getUniqueString()
1437+ categories = self.getUniqueString()
1438+ backend = self.make_backend()
1439+ desktop_file = backend.get_desktop_file(
1440+ package_name, tagline=tagline, categories=categories)
1441+ self.assertThat(
1442+ {'Comment': tagline, 'Categories': categories},
1443+ AreDesktopValuesFor(desktop_file))
1444+
1445+ def test_executable_from_get_executable(self):
1446+ # The executable in the desktop file is just whatever get_desktop
1447+ # returns.
1448+ package_name = self.getUniqueString()
1449+ executable = self.getUniqueString()
1450+ backend = self.make_backend(executable)
1451+ desktop_file = backend.get_desktop_file(package_name)
1452+ self.assertThat(
1453+ {'Exec': executable}, AreDesktopValuesFor(desktop_file))
1454
1455=== added file 'devportalbinary/tests/test_pdf.py'
1456--- devportalbinary/tests/test_pdf.py 1970-01-01 00:00:00 +0000
1457+++ devportalbinary/tests/test_pdf.py 2012-01-10 15:29:28 +0000
1458@@ -0,0 +1,21 @@
1459+# Copyright 2012 Canonical Ltd. This software is licensed under the
1460+# GNU Affero General Public License version 3 (see the file LICENSE).
1461+
1462+from testtools import TestCase
1463+
1464+from pkgme.testing import TempdirFixture
1465+
1466+from devportalbinary.pdf import PdfBackend
1467+
1468+
1469+class BackendTests(TestCase):
1470+
1471+ def test_executable_opens_pdf(self):
1472+ tempdir = self.useFixture(TempdirFixture())
1473+ tempdir.touch('foo.pdf')
1474+ package_name = self.getUniqueString()
1475+ backend = PdfBackend(tempdir.path)
1476+ executable = backend.get_executable(package_name)
1477+ self.assertEqual(
1478+ '/usr/bin/xdg-open /opt/%s/%s' % (package_name, 'foo.pdf'),
1479+ executable)
1480
1481=== modified file 'devportalbinary/tests/test_pdf_backend.py'
1482--- devportalbinary/tests/test_pdf_backend.py 2011-12-13 04:23:22 +0000
1483+++ devportalbinary/tests/test_pdf_backend.py 2012-01-10 15:29:28 +0000
1484@@ -1,9 +1,6 @@
1485 # Copyright 2011 Canonical Ltd. This software is licensed under the
1486 # GNU Affero General Public License version 3 (see the file LICENSE).
1487
1488-import json
1489-
1490-from fixtures import TestWithFixtures
1491 from testtools import TestCase
1492
1493 from pkgme.backend import ExternalHelpersBackend, get_backend_dir
1494@@ -17,14 +14,10 @@
1495 )
1496 from pkgme.testing import TempdirFixture
1497
1498-from devportalbinary.binary import (
1499- CATEGORIES,
1500- get_install_file,
1501- get_pdf_desktop_file,
1502- METADATA_FILE,
1503- PACKAGE_NAME,
1504- TAGLINE,
1505-)
1506+from devportalbinary.binary import MetadataBackend
1507+from devportalbinary.pdf import PdfBackend
1508+from devportalbinary.metadata import get_install_file
1509+from devportalbinary.testing import MetadataFixture
1510
1511
1512 BACKEND_NAME = 'pdf'
1513@@ -32,12 +25,21 @@
1514 backend_dir = get_backend_dir(__file__, BACKEND_NAME)
1515
1516
1517-class BinaryBackendTests(TestCase, TestWithFixtures):
1518+class PdfBackendTests(TestCase):
1519+
1520+ def with_metadata(self, metadata=None):
1521+ if metadata is None:
1522+ metadata = {}
1523+ metadata.setdefault(
1524+ MetadataBackend.PACKAGE_NAME, self.getUniqueString('package_name'))
1525+ metadata.setdefault(
1526+ MetadataBackend.TAGLINE, self.getUniqueString('tagline'))
1527+ return self.useFixture(MetadataFixture(metadata))
1528
1529 def test_want_with_metadata(self):
1530 # If we detect the metadata file and a pdf, then we score 20.
1531 tempdir = self.useFixture(TempdirFixture())
1532- tempdir.touch(METADATA_FILE)
1533+ tempdir.touch(MetadataBackend.METADATA_FILE)
1534 filename = self.getUniqueString() + ".pdf"
1535 tempdir.touch(filename)
1536 backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1537@@ -46,7 +48,7 @@
1538 def test_want_with_just_metadata(self):
1539 # If we detect just the metadata file then we score 0.
1540 tempdir = self.useFixture(TempdirFixture())
1541- tempdir.touch(METADATA_FILE)
1542+ tempdir.touch(MetadataBackend.METADATA_FILE)
1543 backend = ExternalHelpersBackend(BACKEND_NAME, backend_dir)
1544 self.assertEqual(0, backend.want(tempdir.path))
1545
1546@@ -63,7 +65,7 @@
1547 # this backend doesn't trigger on something that just happens
1548 # to contain a pdf.
1549 tempdir = self.useFixture(TempdirFixture())
1550- tempdir.touch(METADATA_FILE)
1551+ tempdir.touch(MetadataBackend.METADATA_FILE)
1552 filename = self.getUniqueString() + ".pdf"
1553 tempdir.touch(filename)
1554 other_filename = self.getUniqueString() + ".foo"
1555@@ -75,7 +77,7 @@
1556 # If the other file is a debian dir then we score 20 still.
1557 # This just makes local testing of the backend easier.
1558 tempdir = self.useFixture(TempdirFixture())
1559- tempdir.touch(METADATA_FILE)
1560+ tempdir.touch(MetadataBackend.METADATA_FILE)
1561 filename = self.getUniqueString() + ".pdf"
1562 tempdir.touch(filename)
1563 other_filename = "debian"
1564@@ -85,15 +87,14 @@
1565
1566 def test_package_name(self):
1567 # The pdf backend gets the package name from the metadata file.
1568- tempdir = self.useFixture(TempdirFixture())
1569- tempdir.create_file(METADATA_FILE, json.dumps({PACKAGE_NAME: 'foo'}))
1570- package_name = self.get_info_for(BACKEND_NAME, PackageName, tempdir.path)
1571+ path = self.with_metadata({MetadataBackend.PACKAGE_NAME: 'foo'}).path
1572+ package_name = self.get_info_for(BACKEND_NAME, PackageName, path)
1573 self.assertEqual("foo", package_name)
1574
1575 def test_architecture(self):
1576 # The pdf backend set architecture to all
1577- tempdir = self.useFixture(TempdirFixture())
1578- architecture = self.get_info_for(BACKEND_NAME, Architecture, tempdir.path)
1579+ path = self.with_metadata().path
1580+ architecture = self.get_info_for(BACKEND_NAME, Architecture, path)
1581 self.assertEqual("all", architecture)
1582
1583 def get_info_for(self, backend_name, info_element, path):
1584@@ -103,33 +104,32 @@
1585
1586 def test_build_depends(self):
1587 # The pdf backend has simple build dependencies
1588- tempdir = self.useFixture(TempdirFixture())
1589- build_depends = self.get_info_for(BACKEND_NAME, BuildDepends, tempdir.path)
1590+ path = self.with_metadata().path
1591+ build_depends = self.get_info_for(BACKEND_NAME, BuildDepends, path)
1592 self.assertEqual('debhelper (>=7)', build_depends)
1593
1594 def test_depends(self):
1595 # The pdf backend depends on xdg-utils
1596- tempdir = self.useFixture(TempdirFixture())
1597- depends = self.get_info_for(BACKEND_NAME, Depends, tempdir.path)
1598+ path = self.with_metadata().path
1599+ depends = self.get_info_for(BACKEND_NAME, Depends, path)
1600 self.assertEqual('xdg-utils, ${misc:Depends}', depends)
1601
1602 def test_description(self):
1603 # The pdf backend gets the description from the metadata file.
1604- tempdir = self.useFixture(TempdirFixture())
1605 expected_description = 'best pdf evar'
1606- tempdir.create_file(METADATA_FILE, json.dumps({TAGLINE: expected_description}))
1607- description = self.get_info_for(BACKEND_NAME, Description, tempdir.path)
1608+ path = self.with_metadata(
1609+ {MetadataBackend.TAGLINE: expected_description}).path
1610+ description = self.get_info_for(BACKEND_NAME, Description, path)
1611 self.assertEqual(expected_description, description)
1612
1613 def test_extra_files_install_file(self):
1614 # We create an 'install' file that tells debhelper to copy
1615 # the pdf to opt
1616- tempdir = self.useFixture(TempdirFixture())
1617- tempdir.touch('foo.pdf')
1618 package_name = self.getUniqueString()
1619- tempdir.create_file(METADATA_FILE, json.dumps({PACKAGE_NAME: package_name}))
1620+ tempdir = self.with_metadata(
1621+ {MetadataBackend.PACKAGE_NAME: package_name}).tempdir
1622 extra_files = self.get_info_for(BACKEND_NAME, ExtraFiles, tempdir.path)
1623- install_file = json.loads(extra_files)['debian/install']
1624+ install_file = extra_files['debian/install']
1625 self.assertEqual(
1626 get_install_file(package_name, tempdir.path, True),
1627 install_file)
1628@@ -137,22 +137,21 @@
1629 def test_extra_files_desktop_file(self):
1630 # We create an 'desktop' file that uses xdg-open to open the
1631 # pdf.
1632- tempdir = self.useFixture(TempdirFixture())
1633 package_name = self.getUniqueString()
1634 tagline = self.getUniqueString()
1635 categories = self.getUniqueString()
1636 metadata = {
1637- PACKAGE_NAME: package_name,
1638- TAGLINE: tagline,
1639- CATEGORIES: categories,
1640+ MetadataBackend.PACKAGE_NAME: package_name,
1641+ MetadataBackend.TAGLINE: tagline,
1642+ MetadataBackend.CATEGORIES: categories,
1643 }
1644- tempdir.create_file(METADATA_FILE, json.dumps(metadata))
1645+ tempdir = self.with_metadata(metadata).tempdir
1646 # We need to create a pdf file in order to be able to generate a
1647 # desktop.
1648 tempdir.touch('foo.pdf')
1649- expected_desktop_file = get_pdf_desktop_file(
1650- package_name, tempdir.path, tagline=tagline,
1651+ expected_desktop_file = PdfBackend(tempdir.path).get_desktop_file(
1652+ package_name, tagline=tagline,
1653 categories=categories).get_contents()
1654 extra_files = self.get_info_for(BACKEND_NAME, ExtraFiles, tempdir.path)
1655- desktop = json.loads(extra_files)['debian/%s.desktop' % (package_name,)]
1656+ desktop = extra_files['debian/%s.desktop' % (package_name,)]
1657 self.assertEqual(expected_desktop_file, desktop)

Subscribers

People subscribed via source and target branches