diff -Nru python-debian-0.1.20ubuntu3/debian/changelog python-debian-0.1.21ubuntu1/debian/changelog --- python-debian-0.1.20ubuntu3/debian/changelog 2011-12-31 02:09:27.000000000 +0000 +++ python-debian-0.1.21ubuntu1/debian/changelog 2012-01-25 16:03:14.000000000 +0000 @@ -1,3 +1,41 @@ +python-debian (0.1.21ubuntu1) precise; urgency=low + + * Merge from Debian testing. Remaining changes: + - Add .lzma as a possible extenstion for data.tar (LP: #407198) + Note that this does not mean that reading from the .lzma part + is supported. This is blocked on http://bugs.python.org/issue5689 + + -- Michael Vogt Wed, 25 Jan 2012 16:59:16 +0100 + +python-debian (0.1.21) unstable; urgency=low + + [ Tshepang Lekhonkhobe ] + * test_changelog.py: Close open files. (Closes: #625672) + + [ John Wright ] + * deb822: Allow ':' as the first character of a value. + (Closes: #597249) + * deb822: Avoid dumping unparseable data. (Closes: #597120) + * Clean up deb822.GpgInfo implementation: + - Change several @staticmethod decorated methods to @classmethod, + since they call the class constructor. + - from_sequence now can accept both sequences of newline-terminated + strings and the output of str.splitlines(). + - from_file now actually calls the from_sequence method. + (Closes: #627058) + - from_sequence now makes a copy of the initial args list before + extending it. (Closes: #627060) + - Keyrings are no longer checked for accessibility, since gpgv can + accept keyring arguments that are under the GnuPG home directory, + regardless of the current directory. (Closes: #627063) + * deb822.Deb822.gpg_info takes an optional keyrings argument. + * deb822: Don't interpret lines starting with '#'. (Closes: #632306) + + [ Colin Watson ] + * Use dh_python2 instead of python-support. (Closes: #631392) + + -- John Wright Wed, 03 Aug 2011 23:07:17 -0700 + python-debian (0.1.20ubuntu3) precise; urgency=low * Rebuild to drop python2.6 dependencies. diff -Nru python-debian-0.1.20ubuntu3/debian/control python-debian-0.1.21ubuntu1/debian/control --- python-debian-0.1.20ubuntu3/debian/control 2011-09-14 18:17:41.000000000 +0000 +++ python-debian-0.1.21ubuntu1/debian/control 2012-01-25 16:00:36.000000000 +0000 @@ -11,9 +11,9 @@ John Wright Build-Depends: debhelper (>= 5.0.37.2), python (>= 2.6.6-3~), python-setuptools, python-chardet Standards-Version: 3.8.4 -X-Python-Version: >= 2.5 Vcs-Browser: http://git.debian.org/?p=pkg-python-debian/python-debian.git Vcs-Git: git://git.debian.org/git/pkg-python-debian/python-debian.git +X-Python-Version: >= 2.5 Package: python-debian Architecture: all diff -Nru python-debian-0.1.20ubuntu3/debian/rules python-debian-0.1.21ubuntu1/debian/rules --- python-debian-0.1.20ubuntu3/debian/rules 2011-09-14 18:20:39.000000000 +0000 +++ python-debian-0.1.21ubuntu1/debian/rules 2012-01-25 16:02:32.000000000 +0000 @@ -18,7 +18,7 @@ # run the tests cd tests && ./test_deb822.py - cd tests && python ./test_debfile.py + cd tests && ./test_debfile.py cd tests && ./test_debtags.py cd tests && ./test_changelog.py cd tests && ./test_debian_support.py diff -Nru python-debian-0.1.20ubuntu3/lib/debian/deb822.py python-debian-0.1.21ubuntu1/lib/debian/deb822.py --- python-debian-0.1.20ubuntu3/lib/debian/deb822.py 2011-07-18 22:21:37.000000000 +0000 +++ python-debian-0.1.21ubuntu1/lib/debian/deb822.py 2011-12-31 06:49:33.000000000 +0000 @@ -33,15 +33,21 @@ _have_apt_pkg = False import chardet -import new +import os import re import string +import subprocess import sys import warnings import StringIO import UserDict + +GPGV_DEFAULT_KEYRINGS = frozenset(['/usr/share/keyrings/debian-keyring.gpg']) +GPGV_EXECUTABLE = '/usr/bin/gpgv' + + class TagSectionWrapper(object, UserDict.DictMixin): """Wrap a TagSection object, using its find_raw method to get field values @@ -53,7 +59,8 @@ self.__section = section def keys(self): - return self.__section.keys() + return [key for key in self.__section.keys() + if not key.startswith('#')] def __getitem__(self, key): s = self.__section.find_raw(key) @@ -69,6 +76,7 @@ # off any newline at the end of the data. return data.lstrip(' \t').rstrip('\n') + class OrderedSet(object): """A set-like object that preserves order when iterating over it @@ -112,6 +120,7 @@ self.add(item) ### + class Deb822Dict(object, UserDict.DictMixin): # Subclassing UserDict.DictMixin because we're overriding so much dict # functionality that subclassing dict requires overriding many more than @@ -302,8 +311,11 @@ if _have_apt_pkg and use_apt_pkg and isinstance(sequence, file): parser = apt_pkg.TagFile(sequence) for section in parser: - yield cls(fields=fields, _parsed=TagSectionWrapper(section), - encoding=encoding) + paragraph = cls(fields=fields, + _parsed=TagSectionWrapper(section), + encoding=encoding) + if paragraph: + yield paragraph else: iterable = iter(sequence) @@ -316,10 +328,28 @@ ### + @staticmethod + def _skip_useless_lines(sequence): + """Yields only lines that do not begin with '#'. + + Also skips any blank lines at the beginning of the input. + """ + at_beginning = True + for line in sequence: + if line.startswith('#'): + continue + if at_beginning: + if not line.rstrip('\r\n'): + continue + at_beginning = False + yield line + def _internal_parser(self, sequence, fields=None): - single = re.compile("^(?P\S+)\s*:\s*(?P\S.*?)\s*$") - multi = re.compile("^(?P\S+)\s*:\s*$") - multidata = re.compile("^\s(?P.+?)\s*$") + # The key is non-whitespace, non-colon characters before any colon. + key_part = r"^(?P[^: \t\n\r\f\v]+)\s*:\s*" + single = re.compile(key_part + r"(?P\S.*?)\s*$") + multi = re.compile(key_part + r"$") + multidata = re.compile(r"^\s(?P.+?)\s*$") wanted_field = lambda f: fields is None or f in fields @@ -328,7 +358,9 @@ curkey = None content = "" - for line in self.gpg_stripped_paragraph(sequence): + + for line in self.gpg_stripped_paragraph( + self._skip_useless_lines(sequence)): m = single.match(line) if m: if curkey: @@ -406,8 +438,9 @@ value = self.get_as_string(key) if not value or value[0] == '\n': # Avoid trailing whitespace after "Field:" if it's on its own - # line or the value is empty - # XXX Uh, really print value if value == '\n'? + # line or the value is empty. We don't have to worry about the + # case where value == '\n', since we ensure that is not the + # case in __setitem__. entry = '%s:%s\n' % (key, value) else: entry = '%s: %s\n' % (key, value) @@ -571,11 +604,14 @@ gpg_stripped_paragraph = classmethod(gpg_stripped_paragraph) - def get_gpg_info(self): + def get_gpg_info(self, keyrings=None): """Return a GpgInfo object with GPG signature information This method will raise ValueError if the signature is not available - (e.g. the original text cannot be found)""" + (e.g. the original text cannot be found). + + :param keyrings: list of keyrings to use (see GpgInfo.from_sequence) + """ # raw_text is saved (as a string) only for Changes and Dsc (see # _gpg_multivalued.__init__) which is small compared to Packages or @@ -584,11 +620,36 @@ raise ValueError, "original text cannot be found" if self.gpg_info is None: - self.gpg_info = GpgInfo.from_sequence(self.raw_text) + self.gpg_info = GpgInfo.from_sequence(self.raw_text, + keyrings=keyrings) return self.gpg_info -### + def validate_input(self, key, value): + """Raise ValueError if value is not a valid value for key + + Subclasses that do interesting things for different keys may wish to + override this method. + """ + + # The value cannot end in a newline (if it did, dumping the object + # would result in multiple stanzas) + if value.endswith('\n'): + raise ValueError("value must not end in '\\n'") + + # Make sure there are no blank lines (actually, the first one is + # allowed to be blank, but no others), and each subsequent line starts + # with whitespace + for line in value.splitlines()[1:]: + if not line: + raise ValueError("value must not have blank lines") + if not line[0].isspace(): + raise ValueError("each line must start with whitespace") + + def __setitem__(self, key, value): + self.validate_input(key, value) + Deb822Dict.__setitem__(self, key, value) + # XXX check what happens if input contains more that one signature class GpgInfo(dict): @@ -612,14 +673,14 @@ """Return the primary ID of the signee key, None is not available""" pass - @staticmethod - def from_output(out, err=None): + @classmethod + def from_output(cls, out, err=None): """Create a new GpgInfo object from gpg(v) --status-fd output (out) and optionally collect stderr as well (err). Both out and err can be lines in newline-terminated sequence or regular strings.""" - n = GpgInfo() + n = cls() if isinstance(out, basestring): out = out.split('\n') @@ -640,7 +701,7 @@ # str.partition() would be better, 2.5 only though s = l.find(' ') key = l[:s] - if key in GpgInfo.uidkeys: + if key in cls.uidkeys: # value is "keyid UID", don't split UID value = l[s+1:].split(' ', 1) else: @@ -649,42 +710,69 @@ n[key] = value return n -# XXX how to handle sequences of lines? file() returns \n-terminated - @staticmethod - def from_sequence(sequence, keyrings=['/usr/share/keyrings/debian-keyring.gpg'], - executable=["/usr/bin/gpgv"]): + @classmethod + def from_sequence(cls, sequence, keyrings=None, executable=None): """Create a new GpgInfo object from the given sequence. - Sequence is a sequence of lines or a string - executable is a list of args for subprocess.Popen, the first element being the gpg executable""" + :param sequence: sequence of lines or a string + + :param keyrings: list of keyrings to use (default: + ['/usr/share/keyrings/debian-keyring.gpg']) + + :param executable: list of args for subprocess.Popen, the first element + being the gpgv executable (default: ['/usr/bin/gpgv']) + """ + + keyrings = keyrings or GPGV_DEFAULT_KEYRINGS + executable = executable or [GPGV_EXECUTABLE] # XXX check for gpg as well and use --verify accordingly? - args = executable + args = list(executable) #args.extend(["--status-fd", "1", "--no-default-keyring"]) args.extend(["--status-fd", "1"]) - import os - [args.extend(["--keyring", k]) for k in keyrings if os.path.isfile(k) and os.access(k, os.R_OK)] + for k in keyrings: + args.extend(["--keyring", k]) if "--keyring" not in args: raise IOError, "cannot access any of the given keyrings" - import subprocess - p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(args, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) # XXX what to do with exit code? if isinstance(sequence, basestring): (out, err) = p.communicate(sequence) else: - (out, err) = p.communicate("\n".join(sequence)) + (out, err) = p.communicate(cls._get_full_string(sequence)) - return GpgInfo.from_output(out, err) + return cls.from_output(out, err) @staticmethod - def from_file(target, *args): - """Create a new GpgInfo object from the given file, calls from_sequence(file(target), *args)""" - return from_sequence(file(target), *args) - -### + def _get_full_string(sequence): + """Return a string from a sequence of lines. + + This method detects if the sequence's lines are newline-terminated, and + constructs the string appropriately. + """ + # Peek at the first line to see if it's newline-terminated. + sequence_iter = iter(sequence) + try: + first_line = sequence_iter.next() + except StopIteration: + return "" + join_str = '\n' + if first_line.endswith('\n'): + join_str = '' + return first_line + join_str + join_str.join(sequence_iter) + + @classmethod + def from_file(cls, target, *args, **kwargs): + """Create a new GpgInfo object from the given file. + + See GpgInfo.from_sequence. + """ + return cls.from_sequence(file(target), *args, **kwargs) + class PkgRelation(object): """Inter-package relationships @@ -864,6 +952,7 @@ self.__parsed_relations = True return self.__relations + class _multivalued(Deb822): """A class with (R/W) support for multivalued fields. @@ -892,6 +981,16 @@ for line in filter(None, contents.splitlines()): updater_method(Deb822Dict(zip(fields, line.split()))) + def validate_input(self, key, value): + if key.lower() in self._multivalued_fields: + # It's difficult to write a validator for multivalued fields, and + # basically futile, since we allow mutable lists. In any case, + # with sanity checking in get_as_string, we shouldn't ever output + # unparseable data. + pass + else: + Deb822.validate_input(self, key, value) + def get_as_string(self, key): keyl = key.lower() if keyl in self._multivalued_fields: @@ -909,21 +1008,22 @@ field_lengths = {} for item in array: for x in order: - raw_value = str(item[x]) + raw_value = unicode(item[x]) try: length = field_lengths[keyl][x] except KeyError: value = raw_value else: value = (length - len(raw_value)) * " " + raw_value + if "\n" in value: + raise ValueError("'\\n' not allowed in component of " + "multivalued field %s" % key) fd.write(" %s" % value) fd.write("\n") return fd.getvalue().rstrip("\n") else: return Deb822.get_as_string(self, key) -### - class _gpg_multivalued(_multivalued): """A _multivalued class that can support gpg signed objects @@ -1101,7 +1201,6 @@ Deb822.__init__(self, *args, **kwargs) _PkgRelationMixin.__init__(self, *args, **kwargs) -### class _CaseInsensitiveString(str): """Case insensitive string. @@ -1122,4 +1221,5 @@ def lower(self): return self.str_lower + _strI = _CaseInsensitiveString diff -Nru python-debian-0.1.20ubuntu3/tests/test_changelog.py python-debian-0.1.21ubuntu1/tests/test_changelog.py --- python-debian-0.1.20ubuntu3/tests/test_changelog.py 2011-07-18 22:21:37.000000000 +0000 +++ python-debian-0.1.21ubuntu1/tests/test_changelog.py 2011-12-31 06:49:33.000000000 +0000 @@ -34,7 +34,9 @@ class ChangelogTests(unittest.TestCase): def test_create_changelog(self): - c = open('test_changelog').read() + f = open('test_changelog') + c = f.read() + f.close() cl = changelog.Changelog(c) cs = str(cl) clines = c.split('\n') @@ -44,7 +46,9 @@ self.assertEqual(len(clines), len(cslines), "Different lengths") def test_create_changelog_single_block(self): - c = open('test_changelog').read() + f = open('test_changelog') + c = f.read() + f.close() cl = changelog.Changelog(c, max_blocks=1) cs = str(cl) self.assertEqual(cs, @@ -64,7 +68,9 @@ """) def test_modify_changelog(self): - c = open('test_modify_changelog1').read() + f = open('test_modify_changelog1') + c = f.read() + f.close() cl = changelog.Changelog(c) cl.package = 'gnutls14' cl.version = '1:1.4.1-2' @@ -73,7 +79,9 @@ cl.add_change(' * Add magic foo') cl.author = 'James Westby ' cl.date = 'Sat, 16 Jul 2008 11:11:08 -0200' - c = open('test_modify_changelog2').read() + f = open('test_modify_changelog2') + c = f.read() + f.close() clines = c.split('\n') cslines = str(cl).split('\n') for i in range(len(clines)): @@ -81,7 +89,9 @@ self.assertEqual(len(clines), len(cslines), "Different lengths") def test_add_changelog_section(self): - c = open('test_modify_changelog2').read() + f = open('test_modify_changelog2') + c = f.read() + f.close() cl = changelog.Changelog(c) cl.new_block(package='gnutls14', version=changelog.Version('1:1.4.1-3'), @@ -96,7 +106,9 @@ cl.add_change(' * Foo did not work, let us try bar') cl.add_change('') - c = open('test_modify_changelog3').read() + f = open('test_modify_changelog3') + c = f.read() + f.close() clines = c.split('\n') cslines = str(cl).split('\n') for i in range(len(clines)): @@ -105,13 +117,17 @@ def test_strange_changelogs(self): """ Just opens and parses a strange changelog """ - c = open('test_strange_changelog').read() + f = open('test_strange_changelog') + c = f.read() + f.close() cl = changelog.Changelog(c) def test_set_version_with_string(self): - c1 = changelog.Changelog(open('test_modify_changelog1').read()) - c2 = changelog.Changelog(open('test_modify_changelog1').read()) - + f = open('test_modify_changelog1') + c1 = changelog.Changelog(f.read()) + f.seek(0) + c2 = changelog.Changelog(f.read()) + f.close() c1.version = '1:2.3.5-2' c2.version = changelog.Version('1:2.3.5-2') self.assertEqual(c1.version, c2.version) @@ -136,7 +152,9 @@ self.assertRaises(changelog.ChangelogParseError, c2.parse_changelog, cl_no_author) def test_magic_version_properties(self): - c = changelog.Changelog(open('test_changelog')) + f = open('test_changelog') + c = changelog.Changelog(f) + f.close() self.assertEqual(c.debian_version, '1') self.assertEqual(c.full_version, '1:1.4.1-1') self.assertEqual(c.upstream_version, '1.4.1') @@ -144,7 +162,9 @@ self.assertEqual(str(c.version), c.full_version) def test_allow_full_stops_in_distribution(self): - c = changelog.Changelog(open('test_changelog_full_stops')) + f = open('test_changelog_full_stops') + c = changelog.Changelog(f) + f.close() self.assertEqual(c.debian_version, None) self.assertEqual(c.full_version, '1.2.3') self.assertEqual(str(c.version), c.full_version) @@ -153,15 +173,20 @@ # The parsing of the changelog (including the string representation) # should be consistent whether we give a single string, a list of # lines, or a file object to the Changelog initializer - cl_data = open('test_changelog').read() - c1 = changelog.Changelog(open('test_changelog')) + f = open('test_changelog') + cl_data = f.read() + f.seek(0) + c1 = changelog.Changelog(f) + f.close() c2 = changelog.Changelog(cl_data) c3 = changelog.Changelog(cl_data.splitlines()) for c in (c1, c2, c3): self.assertEqual(str(c), cl_data) def test_utf8_encoded_file_input(self): - c = changelog.Changelog(open('test_changelog_unicode')) + f = open('test_changelog_unicode') + c = changelog.Changelog(f) + f.close() u = unicode(c) expected_u = u"""haskell-src-exts (1.8.2-3) unstable; urgency=low @@ -179,14 +204,18 @@ self.assertEquals(str(c), u.encode('utf-8')) def test_unicode_object_input(self): - c_str = open('test_changelog_unicode').read() + f = open('test_changelog_unicode') + c_str = f.read() + f.close() c_unicode = c_str.decode('utf-8') c = changelog.Changelog(c_unicode) self.assertEqual(unicode(c), c_unicode) self.assertEqual(str(c), c_str) def test_non_utf8_encoding(self): - c_str = open('test_changelog_unicode').read() + f = open('test_changelog_unicode') + c_str = f.read() + f.close() c_unicode = c_str.decode('utf-8') c_latin1_str = c_unicode.encode('latin1') c = changelog.Changelog(c_latin1_str, encoding='latin1') @@ -196,11 +225,15 @@ self.assertEqual(str(block), unicode(block).encode('latin1')) def test_block_iterator(self): - c = changelog.Changelog(open('test_changelog')) + f = open('test_changelog') + c = changelog.Changelog(f) + f.close() self.assertEqual(map(str, c._blocks), map(str, c)) def test_len(self): - c = changelog.Changelog(open('test_changelog')) + f = open('test_changelog') + c = changelog.Changelog(f) + f.close() self.assertEqual(len(c._blocks), len(c)) class VersionTests(unittest.TestCase): diff -Nru python-debian-0.1.20ubuntu3/tests/test_deb822.py python-debian-0.1.21ubuntu1/tests/test_deb822.py --- python-debian-0.1.20ubuntu3/tests/test_deb822.py 2011-07-18 22:21:37.000000000 +0000 +++ python-debian-0.1.21ubuntu1/tests/test_deb822.py 2011-12-31 06:49:33.000000000 +0000 @@ -20,6 +20,7 @@ import os import re import sys +import tempfile import unittest import warnings from StringIO import StringIO @@ -236,6 +237,47 @@ -----END PGP SIGNATURE----- ''' +UNPARSED_PARAGRAPHS_WITH_COMMENTS = '''\ +# Leading comments should be ignored. + +Source: foo +Section: bar +# An inline comment in the middle of a paragraph should be ignored. +Priority: optional +Homepage: http://www.debian.org/ + +# Comments in the middle shouldn't result in extra blank paragraphs either. + +# Ditto. + +# A comment at the top of a paragraph should be ignored. +Package: foo +Architecture: any +Description: An awesome package + # This should still appear in the result. + Blah, blah, blah. # So should this. +# A comment at the end of a paragraph should be ignored. + +# Trailing comments shouldn't cause extra blank paragraphs. +''' + +PARSED_PARAGRAPHS_WITH_COMMENTS = [ + deb822.Deb822Dict([ + ('Source', 'foo'), + ('Section', 'bar'), + ('Priority', 'optional'), + ('Homepage', 'http://www.debian.org/'), + ]), + deb822.Deb822Dict([ + ('Package', 'foo'), + ('Architecture', 'any'), + ('Description', 'An awesome package\n' + ' # This should still appear in the result.\n' + ' Blah, blah, blah. # So should this.'), + ]), +] + + class TestDeb822Dict(unittest.TestCase): def make_dict(self): d = deb822.Deb822Dict() @@ -396,10 +438,10 @@ self.assertWellParsed(d, PARSED_PACKAGE) self.assertEqual(count, 2) - def _test_iter_paragraphs(self, file, cls, **kwargs): + def _test_iter_paragraphs(self, filename, cls, **kwargs): """Ensure iter_paragraphs consistency""" - f = open(file) + f = open(filename) packages_content = f.read() f.close() # XXX: The way multivalued fields parsing works, we can't guarantee @@ -409,10 +451,12 @@ s = StringIO() l = [] - for p in cls.iter_paragraphs(open(file), **kwargs): + f = open(filename) + for p in cls.iter_paragraphs(f, **kwargs): p.dump(s) s.write("\n") l.append(p) + f.close() self.assertEqual(s.getvalue(), packages_content) if kwargs["shared_storage"] is False: # If shared_storage is False, data should be consistent across @@ -726,6 +770,58 @@ self.assertEqual(p2['uploaders'], u'Frank Küster ') + def test_bug597249_colon_as_first_value_character(self): + """Colon should be allowed as the first value character. See #597249. + """ + + data = 'Foo: : bar' + parsed = {'Foo': ': bar'} + self.assertWellParsed(deb822.Deb822(data), parsed) + + @staticmethod + def _dictset(d, key, value): + d[key] = value + + def test_field_value_ends_in_newline(self): + """Field values are not allowed to end with newlines""" + + d = deb822.Deb822() + self.assertRaises(ValueError, self._dictset, d, 'foo', 'bar\n') + self.assertRaises(ValueError, self._dictset, d, 'foo', 'bar\nbaz\n') + + def test_field_value_contains_blank_line(self): + """Field values are not allowed to contain blank lines""" + + d = deb822.Deb822() + self.assertRaises(ValueError, self._dictset, d, 'foo', 'bar\n\nbaz') + self.assertRaises(ValueError, self._dictset, d, 'foo', '\n\nbaz') + + def test_multivalued_field_contains_newline(self): + """Multivalued field components are not allowed to contain newlines""" + + d = deb822.Dsc() + # We don't check at set time, since one could easily modify the list + # without deb822 knowing. We instead check at get time. + d['Files'] = [{'md5sum': 'deadbeef', 'size': '9605', 'name': 'bad\n'}] + self.assertRaises(ValueError, d.get_as_string, 'files') + + def _test_iter_paragraphs_comments(self, paragraphs): + self.assertEqual(len(paragraphs), len(PARSED_PARAGRAPHS_WITH_COMMENTS)) + for i in range(len(paragraphs)): + self.assertWellParsed(paragraphs[i], + PARSED_PARAGRAPHS_WITH_COMMENTS[i]) + + def test_iter_paragraphs_comments_use_apt_pkg(self): + paragraphs = list(deb822.Deb822.iter_paragraphs( + UNPARSED_PARAGRAPHS_WITH_COMMENTS.splitlines(), use_apt_pkg=True)) + self._test_iter_paragraphs_comments(paragraphs) + + def test_iter_paragraphs_comments_native(self): + paragraphs = list(deb822.Deb822.iter_paragraphs( + UNPARSED_PARAGRAPHS_WITH_COMMENTS.splitlines(), use_apt_pkg=False)) + self._test_iter_paragraphs_comments(paragraphs) + + class TestPkgRelations(unittest.TestCase): def test_packages(self): @@ -844,5 +940,78 @@ [{'name': 'binutils-source', 'version': None, 'arch': None}]]} self.assertEqual(rel2, pkg2.relations) + +class TestGpgInfo(unittest.TestCase): + + def setUp(self): + # These tests can only run with gpgv and a keyring available. When we + # can use Python >= 2.7, we can use the skip decorator; for now just + # check in each test method whether we should actually run. + self.should_run = ( + os.path.exists('/usr/bin/gpgv') and + os.path.exists('/usr/share/keyrings/debian-keyring.gpg')) + + self.data = SIGNED_CHECKSUM_CHANGES_FILE % CHECKSUM_CHANGES_FILE + self.valid = { + 'GOODSIG': + ['D14219877A786561', 'John Wright '], + 'VALIDSIG': + ['8FEFE900783CF175827C2F65D14219877A786561', '2008-05-01', + '1209623566', '0', '3', '0', '17', '2', '01', + '8FEFE900783CF175827C2F65D14219877A786561'], + 'SIG_ID': + ['j3UjSpdky92fcQISbm8W5PlwC/g', '2008-05-01', '1209623566'], + } + + def _validate_gpg_info(self, gpg_info): + # The second part of the GOODSIG field could change if the primary + # uid changes, so avoid checking that. Also, the first part of the + # SIG_ID field has undergone at least one algorithm changein gpg, + # so don't bother testing that either. + self.assertEqual(set(gpg_info.keys()), set(self.valid.keys())) + self.assertEqual(gpg_info['GOODSIG'][0], self.valid['GOODSIG'][0]) + self.assertEqual(gpg_info['VALIDSIG'], self.valid['VALIDSIG']) + self.assertEqual(gpg_info['SIG_ID'][1:], self.valid['SIG_ID'][1:]) + + def test_from_sequence_string(self): + if not self.should_run: + return + + gpg_info = deb822.GpgInfo.from_sequence(self.data) + self._validate_gpg_info(gpg_info) + + def test_from_sequence_newline_terminated(self): + if not self.should_run: + return + + sequence = StringIO(self.data) + gpg_info = deb822.GpgInfo.from_sequence(sequence) + self._validate_gpg_info(gpg_info) + + def test_from_sequence_no_newlines(self): + if not self.should_run: + return + + sequence = self.data.splitlines() + gpg_info = deb822.GpgInfo.from_sequence(sequence) + self._validate_gpg_info(gpg_info) + + def test_from_file(self): + if not self.should_run: + return + + fd, filename = tempfile.mkstemp() + fp = os.fdopen(fd, 'w') + fp.write(self.data) + fp.close() + + try: + gpg_info = deb822.GpgInfo.from_file(filename) + finally: + os.remove(filename) + + self._validate_gpg_info(gpg_info) + + if __name__ == '__main__': unittest.main()