Merge lp:~ursinha/ubuntu-cdimage/add-manifest-changelog-libs into lp:ubuntu-cdimage

Proposed by Ursula Junque
Status: Rejected
Rejected by: Steve Langasek
Proposed branch: lp:~ursinha/ubuntu-cdimage/add-manifest-changelog-libs
Merge into: lp:ubuntu-cdimage
Diff against target: 1242 lines (+1123/-58)
7 files modified
bin/diff-manifest (+5/-58)
bin/image-changelog (+49/-0)
lib/cdimage/changelog.py (+160/-0)
lib/cdimage/manifest.py (+318/-0)
lib/cdimage/tests/helpers.py (+14/-0)
lib/cdimage/tests/test_changelog.py (+193/-0)
lib/cdimage/tests/test_manifest.py (+384/-0)
To merge this branch: bzr merge lp:~ursinha/ubuntu-cdimage/add-manifest-changelog-libs
Reviewer Review Type Date Requested Status
Steve Langasek Needs Resubmitting
Review via email: mp+180945@code.launchpad.net

Description of the change

This branch adds two new libs to cdimage: manifest.py and changelog.py, required to generate between-images changelogs by parsing their manifests and fetching packages' changelog information. manifest.py also contains the methods previously declared in diff-manifest, that now only calls such methods. Tests were added.

To post a comment you must log in.
1341. By Ursula Junque

lib/cdimage/manifest.py: printing all the changed packages in the changed packages session, regardless they have a changelog or not

1342. By Ursula Junque

lib/cdimage/manifest.py: sometimes the changelogs are manually (?) changed and versions are removed from it, added a check to return only the newest changelog block in case that happens

1343. By Ursula Junque

lib/cdimage/tests/test_manifest.py: testing whenever the old source version
doesn't exist in the current package changelog, which may happen due to a
debian sync for example

1344. By Ursula Junque

lib/cdimage/manifest.py: making it clear what are binary versions and source
versions; also adding some docs to the methods

1345. By Ursula Junque

lib/cdimage/manifest.py: fixing the changed packages output

Revision history for this message
Steve Langasek (vorlon) wrote :

Unfortunately this MP never got reviewed, and in the meantime we are moving the source to git which means this would need to be resubmitted. It is not clear to me what the application of this new code was supposed to be so it is not a priority for me to review and land this change prior to migration to git. Please resubmit against git if it's still relevant.

review: Needs Resubmitting

Unmerged revisions

1345. By Ursula Junque

lib/cdimage/manifest.py: fixing the changed packages output

1344. By Ursula Junque

lib/cdimage/manifest.py: making it clear what are binary versions and source
versions; also adding some docs to the methods

1343. By Ursula Junque

lib/cdimage/tests/test_manifest.py: testing whenever the old source version
doesn't exist in the current package changelog, which may happen due to a
debian sync for example

1342. By Ursula Junque

lib/cdimage/manifest.py: sometimes the changelogs are manually (?) changed and versions are removed from it, added a check to return only the newest changelog block in case that happens

1341. By Ursula Junque

lib/cdimage/manifest.py: printing all the changed packages in the changed packages session, regardless they have a changelog or not

1340. By Ursula Junque

bin/image-changelog: calling manifest.parse_file; lib/cdimage/manifest.py:
adding text to differentiate packages with changelogs (found in
changelogs.ubuntu.com) from the others

1339. By Ursula Junque

tests/test_manifest.py: testing manifest_diff.get_changes()

1338. By Ursula Junque

lib/cdimage/manifest.py: using correct function to return the changes in the
changelog

1337. By Ursula Junque

Fixing pyflakes errors; closing the file when I extract the manifest content.

1336. By Ursula Junque

tests/*: fixing pep8 and flakes problems

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/diff-manifest'
2--- bin/diff-manifest 2013-03-21 08:41:44 +0000
3+++ bin/diff-manifest 2013-08-30 23:19:27 +0000
4@@ -1,6 +1,6 @@
5 #! /usr/bin/python
6 #
7-# Copyright (C) 2011, 2012 Canonical Ltd.
8+# Copyright (C) 2011, 2013 Canonical Ltd.
9 # Author: Kate Stewart <kate.stewart@canonical.com>
10 # Author: Colin Watson <cjwatson@canonical.com>
11
12@@ -14,48 +14,11 @@
13 from __future__ import print_function
14
15 from optparse import OptionParser
16+import os
17 import sys
18
19-
20-def next_package(manifest):
21- """Get the next package name and version information from a manifest."""
22- while manifest:
23- line = manifest.pop(0)
24- if not line:
25- # silently skip empty lines
26- continue
27- bits = line.split()
28- if len(bits) < 2:
29- print("WARNING: '%s' not in format 'package version'" % line,
30- file=sys.stderr)
31- else:
32- return bits
33- return None
34-
35-
36-def manifest_pairs(ma, mb):
37- """Generate a current pair of packages, one from each manifest to compare.
38- """
39- left = next_package(ma)
40- right = next_package(mb)
41- while left and right:
42- if left[0] == right[0]:
43- yield left, right
44- left = next_package(ma)
45- right = next_package(mb)
46- elif left[0] < right[0]:
47- yield left, None
48- left = next_package(ma)
49- else: # left[0] > right[0]
50- yield None, right
51- right = next_package(mb)
52- # drain off whichever file still has packages in it
53- while left:
54- yield left, None
55- left = next_package(ma)
56- while right:
57- yield None, right
58- right = next_package(mb)
59+sys.path.insert(0, os.path.join(sys.path[0], os.pardir, "lib"))
60+from cdimage.manifest import compare_manifests
61
62
63 def main():
64@@ -64,23 +27,7 @@
65 if len(args) < 2:
66 parser.error("must provide two manifest files to diff")
67
68- # create similarly ordered lists from each manifest file
69- with open(args[0]) as mfa:
70- ma = sorted(mfa.readlines())
71- with open(args[1]) as mfb:
72- mb = sorted(mfb.readlines())
73-
74- # analyze relation between pairs of packages from manifest lists
75- for a, b in manifest_pairs(ma, mb):
76- if a and b:
77- if a[1] == b[1]:
78- print("= %s\t%s\t%s" % (a[0], a[1], b[1]))
79- else:
80- print("? %s\t%s\t%s" % (a[0], a[1], b[1]))
81- elif a:
82- print("< %s\t%s\t--" % (a[0], a[1]))
83- else:
84- print("> %s\t--\t%s" % (b[0], b[1]))
85+ compare_manifests(args[0], args[1])
86
87
88 if __name__ == '__main__':
89
90=== added file 'bin/image-changelog'
91--- bin/image-changelog 1970-01-01 00:00:00 +0000
92+++ bin/image-changelog 2013-08-30 23:19:27 +0000
93@@ -0,0 +1,49 @@
94+#! /usr/bin/python
95+
96+# Copyright (C) 2013 Canonical Ltd.
97+# Author: Ursula Junque <ursula.junque@canonical.com>
98+
99+# This program is free software: you can redistribute it and/or modify
100+# it under the terms of the GNU General Public License as published by
101+# the Free Software Foundation; version 3 of the License.
102+#
103+# This program is distributed in the hope that it will be useful,
104+# but WITHOUT ANY WARRANTY; without even the implied warranty of
105+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106+# GNU General Public License for more details.
107+#
108+# You should have received a copy of the GNU General Public License
109+# along with this program. If not, see <http://www.gnu.org/licenses/>.
110+
111+"""Generates a changelog of an image comparing its manifest with the previous
112+one."""
113+
114+import os
115+import sys
116+from optparse import OptionParser
117+
118+sys.path.insert(0, os.path.join(sys.path[0], os.pardir, "lib"))
119+from cdimage.manifest import Manifest, ManifestDiff
120+
121+
122+def main():
123+ parser = OptionParser(usage="Usage: %prog manifest1 manifest2")
124+ _, args = parser.parse_args()
125+ if len(args) < 2:
126+ parser.error("must provide two manifest files to diff")
127+
128+ # TODO: look for the manifest paths and get them directly instead of
129+ # passing as an argument
130+
131+ old_manifest = Manifest(args[0])
132+ old_manifest.parse_file()
133+ new_manifest = Manifest(args[1])
134+ new_manifest.parse_file()
135+
136+ manifest_diff = ManifestDiff()
137+ manifest_diff.compare_manifests(old_manifest, new_manifest)
138+ manifest_diff.output_changes()
139+
140+
141+if __name__ == '__main__':
142+ main()
143
144=== added file 'lib/cdimage/changelog.py'
145--- lib/cdimage/changelog.py 1970-01-01 00:00:00 +0000
146+++ lib/cdimage/changelog.py 2013-08-30 23:19:27 +0000
147@@ -0,0 +1,160 @@
148+# Copyright (C) 2013 Canonical Ltd.
149+# Author: Ursula Junque <ursula.junque@canonical.com>
150+
151+# Utilities to parse and handle ubuntu changelogs.
152+#
153+# This program is free software: you can redistribute it and/or modify it
154+# under the terms of the the GNU General Public License version 3, as
155+# published by the Free Software Foundation.
156+#
157+# This program is distributed in the hope that it will be useful, but
158+# WITHOUT ANY WARRANTY; without even the implied warranties of
159+# MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
160+# PURPOSE. See the applicable version of the GNU Lesser General Public
161+# License for more details.
162+#
163+# You should have received a copy of the GNU General Public License
164+# along with this program. If not, see <http://www.gnu.org/licenses/>.
165+
166+
167+import urllib
168+from debian import changelog
169+
170+
171+CHANGELOG_WEBSITE = "http://changelogs.ubuntu.com/changelogs"
172+# Currently only most recent series
173+
174+
175+class BinaryToSourceLinkNotFound(Exception):
176+ """Raised in case the "binary to source" url returns a 404."""
177+
178+
179+class ChangelogNotFound(Exception):
180+ """Raised in case the changelog url returns a 404."""
181+ def __init__(self, name, version, url):
182+ self.name = name
183+ self.version = version
184+ self.url = url
185+
186+ def __str__(self):
187+ return "Changelog not found: %s_%s" % (self.name, self.version)
188+
189+
190+class EmptyUrlError(Exception):
191+ """Raised in case the url is None."""
192+
193+
194+class VersionNotFound(Exception):
195+ """An exception to signal a version isn't found in the changelog."""
196+
197+ def __init__(self, version):
198+ self.version = str(version)
199+
200+ def __str__(self):
201+ return "Version not found in changelog: %s" % self.version
202+
203+
204+def get_source_changelog_url(binary_name, binary_version):
205+ """Returns source changelog url given a binary name and version."""
206+ if binary_name is None or binary_version is None:
207+ return None
208+ if binary_name.startswith("lib"):
209+ prefix = binary_name[0:4]
210+ else:
211+ prefix = binary_name[0]
212+ url = "%s/binary/%s/%s/%s/changelog" % (CHANGELOG_WEBSITE, prefix,
213+ binary_name, binary_version)
214+ return url
215+
216+
217+def get_changelog_url(source_name, source_version, component):
218+ """Returns source changelog url given source name and version."""
219+ if source_name is None or source_version is None or component is None:
220+ return None
221+ if source_name.startswith("lib"):
222+ prefix = source_name[0:4]
223+ else:
224+ prefix = source_name[0]
225+ url = ("%s/pool/%s/%s/%s-%s/changelog" % (CHANGELOG_WEBSITE, component,
226+ prefix, source_name, source_version))
227+ return url
228+
229+
230+def get_changelog(binary_name, binary_version):
231+ """Returns a source Changelog object of a given binary and version.
232+
233+ Looks up the source name and version that originated the binary of version
234+ provided. Retrieves the source url and parses it, returning a Changelog
235+ object.
236+ """
237+ url = get_source_changelog_url(binary_name, binary_version)
238+ if url is None:
239+ raise EmptyUrlError()
240+ try:
241+ content = urllib.urlopen(url)
242+ if content.code == 404:
243+ raise ChangelogNotFound(binary_name, binary_version, url)
244+ if content.code == 200:
245+ return Changelog(content)
246+ return None
247+ except ChangelogNotFound:
248+ raise
249+
250+
251+class Changelog(changelog.Changelog):
252+ """Extends deb822.changelog.Changelog.
253+
254+ Add ability to get a slice of changelog given the versions in between,
255+ also get a block given a version.
256+ """
257+
258+ def __init__(self, file_):
259+ super(Changelog, self).__init__(file_)
260+
261+ @property
262+ def source_name(self):
263+ return self._blocks[0].package
264+
265+ @property
266+ def raw_version(self):
267+ """Return the last version string"""
268+ return self._blocks[0]._raw_version
269+
270+ def has_version(self, version):
271+ """Returns True if version is found in parsed changelog."""
272+ return str(version) in self._raw_versions()
273+
274+ def get_changes_interval(self, start_version, end_version=None,
275+ include_start_version=True):
276+ """Get all changelog blocks on an interval of versions.
277+
278+ Default is returning all changelog blocks in the given interval,
279+ including the start version. If you want to get changes excluding the
280+ start version, set include_start_version to False.
281+ """
282+ if end_version is None:
283+ end_version = self.get_version()
284+ elif not self.has_version(end_version):
285+ raise VersionNotFound(end_version)
286+ if not self.has_version(start_version):
287+ raise VersionNotFound(start_version)
288+
289+ start_index = self._raw_versions().index(str(start_version))
290+ end_index = self._raw_versions().index(str(end_version))
291+
292+ if include_start_version:
293+ start_index += 1
294+
295+ return self._blocks[end_index:start_index]
296+
297+ def get_changes_after_version(self, start_version, up_to=None):
298+ """Returns all changelog blocks after given version."""
299+ return self.get_changes_interval(start_version, up_to,
300+ include_start_version=False)
301+
302+ def get_change_for_version(self, version):
303+ """Returns changeblock of a version."""
304+ if self.has_version(version):
305+ index = self._raw_versions().index(str(version))
306+ return self._blocks[index]
307+ raise VersionNotFound(version)
308
309=== added file 'lib/cdimage/manifest.py'
310--- lib/cdimage/manifest.py 1970-01-01 00:00:00 +0000
311+++ lib/cdimage/manifest.py 2013-08-30 23:19:27 +0000
312@@ -0,0 +1,318 @@
313+# Copyright (C) 2011, 2013 Canonical Ltd.
314+# Author: Kate Stewart <kate.stewart@canonical.com>
315+# Author: Colin Watson <cjwatson@canonical.com>
316+# Author: Ursula Junque <ursula.junque@canonical.com>
317+
318+# This program is free software: you can redistribute it and/or modify
319+# it under the terms of the GNU General Public License as published by
320+# the Free Software Foundation; version 3 of the License.
321+#
322+# This program is distributed in the hope that it will be useful,
323+# but WITHOUT ANY WARRANTY; without even the implied warranty of
324+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
325+# GNU General Public License for more details.
326+#
327+# You should have received a copy of the GNU General Public License
328+# along with this program. If not, see <http://www.gnu.org/licenses/>.
329+
330+"""Manifest handler."""
331+
332+from __future__ import print_function
333+
334+__metaclass__ = type
335+
336+
337+from cdimage.log import logger
338+from cdimage.changelog import (
339+ ChangelogNotFound,
340+ get_changelog,
341+ VersionNotFound,
342+)
343+
344+
345+def compare_manifests(manifest1, manifest2):
346+ # create similarly ordered lists from each manifest file
347+ with open(manifest1) as mfa:
348+ ma = sorted(mfa.readlines())
349+ with open(manifest2) as mfb:
350+ mb = sorted(mfb.readlines())
351+
352+ # analyze relation between pairs of packages from manifest lists
353+ for a, b in _manifest_pairs(ma, mb):
354+ if a and b:
355+ if a[1] == b[1]:
356+ print("= %s\t%s\t%s" % (a[0], a[1], b[1]))
357+ else:
358+ print("? %s\t%s\t%s" % (a[0], a[1], b[1]))
359+ elif a:
360+ print("< %s\t%s\t--" % (a[0], a[1]))
361+ else:
362+ print("> %s\t--\t%s" % (b[0], b[1]))
363+
364+
365+def _next_package(manifest):
366+ """Get the next package name and version information from a manifest."""
367+ while manifest:
368+ line = manifest.pop(0)
369+ if not line:
370+ # silently skip empty lines
371+ continue
372+ bits = line.split()
373+ if len(bits) < 2:
374+ logger.warning("'%s' not in format 'package version'" % line)
375+ else:
376+ return bits
377+ return None
378+
379+
380+def _manifest_pairs(ma, mb):
381+ """Generate a current pair of packages, one from each manifest to compare.
382+ """
383+ left = _next_package(ma)
384+ right = _next_package(mb)
385+ while left and right:
386+ if left[0] == right[0]:
387+ yield left, right
388+ left = _next_package(ma)
389+ right = _next_package(mb)
390+ elif left[0] < right[0]:
391+ yield left, None
392+ left = _next_package(ma)
393+ else: # left[0] > right[0]
394+ yield None, right
395+ right = _next_package(mb)
396+ # drain off whichever file still has packages in it
397+ while left:
398+ yield left, None
399+ left = _next_package(ma)
400+ while right:
401+ yield None, right
402+ right = _next_package(mb)
403+
404+
405+class Manifest:
406+
407+ def __init__(self, manifest_name=None):
408+ self.filename = manifest_name
409+ self.content = {}
410+
411+ def parse_file(self, manifest_name=None):
412+ if manifest_name is None and self.filename is not None:
413+ filename = self.filename
414+ file_ = open(filename)
415+ content = file_.readlines()
416+ file_.close()
417+ return self.parse(content)
418+ return None
419+
420+ def parse(self, content):
421+ if content is None:
422+ return None
423+ for line in content:
424+ parsed_line = line.strip().split()
425+ if not parsed_line:
426+ continue
427+ self.content[parsed_line[0]] = parsed_line[1]
428+ return self.content
429+
430+ @property
431+ def packages(self):
432+ return set(self.content.keys())
433+
434+ def get_version(self, package_name):
435+ try:
436+ return self.content[package_name]
437+ except KeyError:
438+ return None
439+
440+
441+class PackageDiff:
442+
443+ def __init__(self, binary_name, old_binary_version, new_binary_version):
444+ self.binary_name = binary_name
445+ self.old_binary_version = old_binary_version
446+ self.new_binary_version = new_binary_version
447+ self.old_changelog = self.get_changelog_info(old_binary_version)
448+ self.new_changelog = self.get_changelog_info(new_binary_version)
449+ self.old_source_info = self._get_source_info(self.old_changelog)
450+ self.new_source_info = self._get_source_info(self.new_changelog)
451+
452+ @property
453+ def old_source_name(self):
454+ return self.old_source_info[0]
455+
456+ @property
457+ def new_source_name(self):
458+ return self.new_source_info[0]
459+
460+ @property
461+ def old_source_version(self):
462+ return self.old_source_info[1]
463+
464+ @property
465+ def new_source_version(self):
466+ return self.new_source_info[1]
467+
468+ @property
469+ def source_has_changed(self):
470+ return (self.old_source_name != self.new_source_name)
471+
472+ def get_changelog_info(self, binary_version):
473+ """Fetches the changelog information for the given binary version.
474+
475+ This method fetches the changelog specific to this binary
476+ version, so the latest source name and version in the changelog are
477+ guaranteed to refer to this binary name and version.
478+ """
479+ return get_changelog(self.binary_name, binary_version)
480+
481+ @property
482+ def source_name(self):
483+ return self.new_source_name
484+
485+ @property
486+ def source_info(self):
487+ return (self.source_name, self.old_source_version,
488+ self.new_source_version)
489+
490+ def _get_source_info(self, changelog):
491+ """Return the latest source name version given a changelog."""
492+ if changelog is not None:
493+ return changelog.source_name, changelog.raw_version
494+ else:
495+ return None, None
496+
497+ @property
498+ def changes(self):
499+ """List of changelog blocks between two binary versions."""
500+ try:
501+ if self.source_has_changed:
502+ # Return only the latest change of the recent changelog.
503+ return [self.new_changelog.get_change_for_version(
504+ self.new_source_version)]
505+ changes = self.new_changelog.get_changes_after_version(
506+ self.old_source_version, self.new_source_version)
507+ except VersionNotFound as e:
508+ # Sometimes the old versions disappear from the changelog due to
509+ # debian syncs. Whenever that happens, retrieve only the latest
510+ # change.
511+ if e.version == self.old_source_version:
512+ changes = [self.new_changelog.get_change_for_version(
513+ self.new_source_version)]
514+ else:
515+ # If the problem is the newest version, there's something
516+ # really wrong, this is not supposed to happen.
517+ raise
518+ return changes
519+
520+
521+class ManifestDiff:
522+
523+ def __init__(self):
524+ self.added_pkgs = {}
525+ self.removed_pkgs = {}
526+ self._changed_pkgs = {}
527+ self.changelogs = {}
528+ self.missing_pkgs = {}
529+
530+ def compare_manifests(self, manifest1, manifest2):
531+ """Compares two Manifest objects generating the differences."""
532+ self.added_pkgs = {pkg: manifest2.get_version(pkg) for pkg in (
533+ manifest2.packages - manifest1.packages)}
534+ self.removed_pkgs = {pkg: manifest1.get_version(pkg) for pkg in (
535+ manifest1.packages - manifest2.packages)}
536+
537+ for pkg in manifest1.packages.intersection(manifest2.packages):
538+ version1 = manifest1.get_version(pkg)
539+ version2 = manifest2.get_version(pkg)
540+ if version1 != version2:
541+ self._changed_pkgs[pkg] = (version1, version2)
542+
543+ def compare(self, dict1, dict2):
544+ """Compares two {pkg: version} dicts generating the differences."""
545+ self.added_pkgs = {pkg: dict2[pkg] for pkg in (
546+ set(dict2.keys()) - set(dict1.keys()))}
547+ self.removed_pkgs = {pkg: dict1[pkg] for pkg in (
548+ set(dict1.keys()) - set(dict2.keys()))}
549+
550+ for pkg in set(dict1.keys()).intersection(set(dict2.keys())):
551+ version1 = dict1[pkg]
552+ version2 = dict2[pkg]
553+ if version1 != version2:
554+ self._changed_pkgs[pkg] = (version1, version2)
555+
556+ def print_new_pkgs(self):
557+ for pkg in sorted(list(self.added_pkgs.keys())):
558+ print("%s - %s" % (pkg, self.added_pkgs[pkg]))
559+
560+ def print_removed_pkgs(self):
561+ for pkg in sorted(list(self.removed_pkgs.keys())):
562+ print("%s - %s" % (pkg, self.removed_pkgs[pkg]))
563+
564+ def get_changes(self):
565+ for pkg, versions in list(self._changed_pkgs.items()):
566+ old_binary_version, new_binary_version = versions
567+ try:
568+ pkg_name = pkg.split(":")[0]
569+ pkg_changes = PackageDiff(pkg_name, old_binary_version,
570+ new_binary_version)
571+ except ChangelogNotFound as e:
572+ self.missing_pkgs[pkg] = (versions, e)
573+ continue
574+ if pkg_changes.source_name in self.changelogs:
575+ continue
576+ self.changelogs[pkg_changes.source_name] = pkg_changes
577+ return self.changelogs
578+
579+ def print_changed_pkgs(self):
580+ changed_pkgs = ""
581+ changelogs = ""
582+ for pkg in sorted(list(self.changed_pkgs.keys())):
583+ packagediff = self.changed_pkgs[pkg]
584+ changed_pkgs += "%s - %s -> %s\n" % (
585+ pkg, packagediff.old_source_version,
586+ packagediff.new_source_version)
587+ for change in packagediff.changes:
588+ changelogs += " %s" % str(change)
589+ for pkg in sorted(list(self.missing_pkgs.keys())):
590+ versions, error = self.missing_pkgs[pkg]
591+ changed_pkgs += "%s (binary) - %s -> %s\n" % (pkg, versions[0],
592+ versions[1])
593+ print(changed_pkgs)
594+ print("== Changelogs ==\n")
595+ print(changelogs)
596+
597+ def print_changed_pkgs_changelogs(self):
598+ for pkg in sorted(list(self.changed_pkgs.keys())):
599+ for change in self.changed_pkgs[pkg]:
600+ print(" %s" % str(change))
601+
602+ def print_missing_pkgs(self, hide_errors=True):
603+ for pkg in sorted(list(self.missing_pkgs.keys())):
604+ if hide_errors:
605+ print("%s" % pkg)
606+ else:
607+ print("%s - %s" % (pkg, self.missing_pkgs[pkg][1].version))
608+
609+ @property
610+ def changed_pkgs(self):
611+ if self.changelogs:
612+ return self.changelogs
613+ return self.get_changes()
614+
615+ def output_changes(self):
616+ if self.added_pkgs:
617+ print("== Added packages ==")
618+ self.print_new_pkgs()
619+ if self.removed_pkgs:
620+ print("\n== Removed packages ==")
621+ self.print_removed_pkgs()
622+ if self.changed_pkgs:
623+ print("\n== Changed packages ==")
624+ self.print_changed_pkgs()
625+
626+ if self.missing_pkgs:
627+ missing = len(self.missing_pkgs)
628+ print("== Changed packages not found in the archive (%s) ==" %
629+ missing)
630+ self.print_missing_pkgs(hide_errors=False)
631
632=== modified file 'lib/cdimage/tests/helpers.py'
633--- lib/cdimage/tests/helpers.py 2013-03-27 17:31:02 +0000
634+++ lib/cdimage/tests/helpers.py 2013-08-30 23:19:27 +0000
635@@ -130,6 +130,20 @@
636 assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
637
638
639+class FakeUrlOpenReturn:
640+ """Object to mock the return of urllib.urlopen."""
641+
642+ def __init__(self, code, content):
643+ self.code = code
644+ self.content = content
645+
646+ def __repr__(self):
647+ return self.content
648+
649+ def __iter__(self):
650+ return (line for line in self.content.split("\n"))
651+
652+
653 @contextlib.contextmanager
654 def mkfile(path, mode="w"):
655 osextras.ensuredir(os.path.dirname(path))
656
657=== added file 'lib/cdimage/tests/test_changelog.py'
658--- lib/cdimage/tests/test_changelog.py 1970-01-01 00:00:00 +0000
659+++ lib/cdimage/tests/test_changelog.py 2013-08-30 23:19:27 +0000
660@@ -0,0 +1,193 @@
661+#! /usr/bin/python
662+
663+# Copyright (C) 2013 Canonical Ltd.
664+# Author: Ursula Junque <ursula.junque@canonical.com>
665+
666+# This program is free software: you can redistribute it and/or modify
667+# it under the terms of the GNU General Public License as published by
668+# the Free Software Foundation; version 3 of the License.
669+#
670+# This program is distributed in the hope that it will be useful,
671+# but WITHOUT ANY WARRANTY; without even the implied warranty of
672+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
673+# GNU General Public License for more details.
674+#
675+# You should have received a copy of the GNU General Public License
676+# along with this program. If not, see <http://www.gnu.org/licenses/>.
677+
678+"""Unit tests for cdimage.changelog."""
679+
680+from __future__ import print_function
681+
682+__metaclass__ = type
683+
684+import os
685+
686+from cdimage.changelog import (Changelog, ChangelogNotFound, EmptyUrlError,
687+ get_source_changelog_url, get_changelog_url,
688+ get_changelog, VersionNotFound)
689+from cdimage.tests.helpers import FakeUrlOpenReturn, mkfile, TestCase
690+
691+try:
692+ from unittest import mock
693+except ImportError:
694+ import mock
695+
696+
697+CHANGELOG_CONTENT = ("""unzip (5.51-1) unstable; urgency=low
698+ * New upstream release, improves error message when a zipfile is not
699+ readable (Closes: #139331).
700+ * Added a newline character to the CannotOpenZipfile string for the
701+ previous fix to be really complete.
702+
703+ -- Santiago Vila <sanvila@debian.org> Sun, 6 Jun 2004 17:57:46 +0200
704+
705+unzip (5.51a.0-2) unstable; urgency=low
706+
707+ * Added unshrinking support (Closes: #252563).
708+
709+ -- Santiago Vila <sanvila@debian.org> Tue, 25 May 2004 14:38:26 +0200
710+
711+unzip (5.50-4) unstable; urgency=low
712+
713+ * Changed __GNU__ to __GLIBC__ in unix/unxcfg.h to support glibc-based
714+ systems not being GNU itself, like GNU/KFreeBSD and GNU/KNetBSD.
715+
716+ -- Santiago Vila <sanvila@debian.org> Sun, 16 Nov 2003 14:45:28 +0100
717+
718+unzip (5.50-3) unstable; urgency=high
719+
720+ * Fixed "unzip directory traversal revisited" again (Bug #206439).
721+ There was still a missing case that the previous patch didn't catch.
722+ Patch borrowed from unzip-5.50-33.src.rpm.
723+ * For reference, this is (still) CAN-2003-0282.
724+
725+ -- Santiago Vila <sanvila@debian.org> Wed, 20 Aug 2003 23:00:42 +0200
726+
727+unzip (5.50-2) unstable; urgency=high
728+
729+ * Fixed "unzip directory traversal revisited" problem (Bug #199648).
730+ A filename containing ".somenonprintablechar." will not unpack
731+ into .. anymore. Patch borrowed from unzip-5.50-11.src.rpm.
732+ * For reference, this is CAN-2003-0282.
733+ * No more doc symlinks.
734+
735+ -- Santiago Vila <sanvila@debian.org> Mon, 7 Jul 2003 20:25:20 +0200
736+""")
737+
738+
739+class ChangelogTests(TestCase):
740+
741+ def setUp(self):
742+ super(ChangelogTests, self).setUp()
743+ temp_dir = self.use_temp_dir()
744+ changelog_path = os.path.join(temp_dir, "changelog")
745+ with mkfile(changelog_path) as f:
746+ print(CHANGELOG_CONTENT, file=f)
747+ file_ = open(changelog_path)
748+ self.changelog = Changelog(file_)
749+ file_.close()
750+
751+ def test_changelog_has_version(self):
752+ version = "5.51-1"
753+ self.assertTrue(self.changelog.has_version(version))
754+ version = "foobar"
755+ self.assertFalse(self.changelog.has_version(version))
756+
757+ def test_changelog_raw_version(self):
758+ version = self.changelog.get_version()
759+ self.assertEqual(str(version), self.changelog.raw_version)
760+
761+ def test_changelog_get_changes_interval(self):
762+ start_version = "5.50-2"
763+ end_version = "5.51-1"
764+ # 5.50-2 5.50-3 5.50-4 5.51a.0-2 5.51-1
765+
766+ self.assertEqual(len(self.changelog.get_changes_interval(start_version,
767+ end_version, include_start_version=False)), 4)
768+ self.assertEqual(len(self.changelog.get_changes_interval(start_version,
769+ end_version)), 5)
770+
771+ def test_changelog_get_changes_interval_no_endversion(self):
772+ start_version = "5.50-4"
773+ end_version = None
774+
775+ changes = self.changelog.get_changes_interval(
776+ start_version, end_version, include_start_version=False)
777+ self.assertEqual(len(changes), 2)
778+ changes = self.changelog.get_changes_interval(start_version)
779+ self.assertEqual(len(changes), 3)
780+
781+ changes_versions = [change._raw_version for change in changes]
782+ self.assertTrue("5.51-1" in changes_versions)
783+ self.assertTrue("5.51a.0-2" in changes_versions)
784+ self.assertTrue("5.50-4" in changes_versions)
785+
786+ def test_changelog_get_changes_after_version(self):
787+ start_version = "5.50-3"
788+
789+ changes = self.changelog.get_changes_after_version(start_version)
790+ self.assertEqual(len(changes), 3)
791+ changes_versions = [change._raw_version for change in changes]
792+ self.assertTrue("5.50-4" in changes_versions)
793+ self.assertTrue("5.51a.0-2" in changes_versions)
794+ self.assertTrue("5.51-1" in changes_versions)
795+ self.assertTrue("5.50-3" not in changes_versions)
796+
797+ def test_changelog_get_changes_interval_version_doesnt_exist(self):
798+ start_version = "foobar"
799+ self.assertRaises(VersionNotFound,
800+ self.changelog.get_changes_interval,
801+ start_version)
802+
803+
804+class TestGetUrls(TestCase):
805+
806+ def setUp(self):
807+ super(TestGetUrls, self).setUp()
808+
809+ def test_get_source_changelog_url(self):
810+ url = ("http://changelogs.ubuntu.com/changelogs/binary/f/"
811+ "foobar/1.0/changelog")
812+ self.assertEqual(get_source_changelog_url("foobar", "1.0"), url)
813+
814+ def test_get_source_changelog_url_missing_argument(self):
815+ self.assertEqual(get_source_changelog_url(None, "1.0"), None)
816+ self.assertEqual(get_source_changelog_url("foobar", None), None)
817+ self.assertEqual(get_source_changelog_url(None, None), None)
818+
819+ def test_get_changelog_url(self):
820+ url = ("http://changelogs.ubuntu.com/changelogs/pool/main/f/"
821+ "foobar-1.0/changelog")
822+ self.assertEqual(get_changelog_url("foobar", "1.0", "main"), url)
823+
824+ def test_get_changelog_url_missing_argument(self):
825+ self.assertEqual(get_changelog_url("foobar", "1.0", None), None)
826+ self.assertEqual(get_changelog_url("foobar", None, "main"), None)
827+ self.assertEqual(get_changelog_url(None, "1.0", "main"), None)
828+
829+
830+class TestGetChangelog(TestCase):
831+
832+ def setUp(self):
833+ super(TestGetChangelog, self).setUp()
834+
835+ @mock.patch("urllib.urlopen",
836+ return_value=FakeUrlOpenReturn(200, CHANGELOG_CONTENT))
837+ def test_get_changelog(self, mock_urlopen):
838+ changelog = get_changelog("foobar", "1.0")
839+ self.assertIsNotNone(changelog)
840+
841+ @mock.patch("urllib.urlopen",
842+ return_value=FakeUrlOpenReturn(404, "Not found"))
843+ def test_get_changelog_url_not_found(self, mock_urlopen):
844+ self.assertRaises(ChangelogNotFound, get_changelog, "foobar", "1.0")
845+
846+ @mock.patch("urllib.urlopen",
847+ return_value=FakeUrlOpenReturn(500, "Forbidden"))
848+ def test_get_changelog_othererror(self, mock_urlopen):
849+ changelog = get_changelog("foobar", "1.0")
850+ self.assertIsNone(changelog)
851+
852+ def test_get_changelog_urlisNone(self):
853+ self.assertRaises(EmptyUrlError, get_changelog, None, None)
854
855=== added file 'lib/cdimage/tests/test_manifest.py'
856--- lib/cdimage/tests/test_manifest.py 1970-01-01 00:00:00 +0000
857+++ lib/cdimage/tests/test_manifest.py 2013-08-30 23:19:27 +0000
858@@ -0,0 +1,384 @@
859+#! /usr/bin/python
860+
861+# Copyright (C) 2013 Canonical Ltd.
862+# Author: Ursula Junque <ursinha@ubuntu.com>
863+
864+# This program is free software: you can redistribute it and/or modify
865+# it under the terms of the GNU General Public License as published by
866+# the Free Software Foundation; version 3 of the License.
867+#
868+# This program is distributed in the hope that it will be useful,
869+# but WITHOUT ANY WARRANTY; without even the implied warranty of
870+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
871+# GNU General Public License for more details.
872+#
873+# You should have received a copy of the GNU General Public License
874+# along with this program. If not, see <http://www.gnu.org/licenses/>.
875+
876+"""Unit tests for cdimage.manifest."""
877+
878+from __future__ import print_function
879+import os
880+
881+__metaclass__ = type
882+
883+try:
884+ from unittest import mock
885+except ImportError:
886+ import mock
887+
888+import urllib
889+urllib.urlopen = mock.Mock()
890+
891+from cdimage.manifest import Manifest, ManifestDiff, PackageDiff
892+from cdimage.tests.helpers import dedent, FakeUrlOpenReturn, mkfile, TestCase
893+
894+
895+def generate_manifest_file(content, temp_dir, filename=None):
896+ if filename is None:
897+ filename = "manifest"
898+ manifest_path = os.path.join(temp_dir, filename)
899+ with mkfile(manifest_path) as f:
900+ print(content, file=f)
901+ return manifest_path
902+
903+
904+class TestManifest(TestCase):
905+
906+ CONTENT = dedent("""\
907+ apparmor 2.8.0-0ubuntu19.1
908+ at-spi2-core 2.9.4-1ubuntu1
909+ libatk-bridge2.0-0:armhf 2.9.3-1""")
910+
911+ def setUp(self):
912+ super(TestManifest, self).setUp()
913+ self.temp_dir = self.use_temp_dir()
914+ self.parsed_content = {"apparmor": "2.8.0-0ubuntu19.1",
915+ "at-spi2-core": "2.9.4-1ubuntu1",
916+ "libatk-bridge2.0-0:armhf": "2.9.3-1"}
917+
918+ def _generate_manifest_from_file(self, content=None):
919+ if content is None:
920+ content = self.CONTENT
921+ return Manifest(generate_manifest_file(content, self.temp_dir))
922+
923+ def test_parse_regular_file(self):
924+ manifest = self._generate_manifest_from_file()
925+ self.assertEqual(self.parsed_content, manifest.parse_file())
926+
927+ def test_parse_empty_file(self):
928+ manifest = self._generate_manifest_from_file(content="")
929+ self.assertEqual({}, manifest.parse_file())
930+
931+ def test_parse(self):
932+ manifest = Manifest()
933+ content = self.CONTENT.split("\n")
934+ self.assertEqual(manifest.parse(content), self.parsed_content)
935+
936+ def test_packages(self):
937+ manifest = Manifest()
938+ content = self.CONTENT.split("\n")
939+ parsed_content = manifest.parse(content)
940+ self.assertEqual(set(parsed_content.keys()), manifest.packages)
941+
942+ def test_get_version(self):
943+ manifest = Manifest()
944+ content = self.CONTENT.split("\n")
945+ manifest.parse(content)
946+ package = "apparmor"
947+ self.assertEqual(manifest.get_version(package), "2.8.0-0ubuntu19.1")
948+
949+ def test_get_version_package_not_found(self):
950+ manifest = Manifest()
951+ content = self.CONTENT.split("\n")
952+ manifest.parse(content)
953+ package = "foobar"
954+ self.assertEqual(manifest.get_version(package), None)
955+
956+
957+OLD_CHANGELOG_CONTENT = ("""\
958+unzip (5.51a.0-2) unstable; urgency=low
959+
960+ * Added unshrinking support (Closes: #252563).
961+
962+ -- Santiago Vila <sanvila@debian.org> Tue, 25 May 2004 14:38:26 +0200""")
963+
964+NEW_CHANGELOG_CONTENT = ("""\
965+unzip (5.51-1) unstable; urgency=low
966+ * New upstream release, improves error message when a zipfile is not
967+ readable (Closes: #139331).
968+ * Added a newline character to the CannotOpenZipfile string for the
969+ previous fix to be really complete.
970+
971+ -- Santiago Vila <sanvila@debian.org> Sun, 6 Jun 2004 17:57:46 +0200
972+
973+unzip (5.51a.0-3) unstable; urgency=low
974+ * One release in the middle to get two changes between manifests.
975+
976+ -- Santiago Vila <sanvila@debian.org> Wed, 26 Jun 2004 17:57:46 +0200
977+
978+unzip (5.51a.0-2) unstable; urgency=low
979+
980+ * Added unshrinking support (Closes: #252563).
981+
982+ -- Santiago Vila <sanvila@debian.org> Tue, 25 May 2004 14:38:26 +0200""")
983+
984+OLD_CHANGELOG_NO_VERSION_CONTENT = ("""\
985+unzip (5.51a.0-1) unstable; urgency=low
986+
987+ * Added unshrinking support (Closes: #252563).
988+
989+ -- Santiago Vila <sanvila@debian.org> Tue, 25 May 2004 14:38:26 +0200""")
990+
991+NEW_CHANGELOG_CONTENT_NEWSOURCE = ("""\
992+unzippo (6.0) unstable; urgency=low
993+
994+ * Added unshrinking support (Closes: #252563).
995+
996+ -- Santiago Vila <sanvila@debian.org> Tue, 25 May 2004 14:38:26 +0200""")
997+
998+
999+class TestPackageDiffSameSource(TestCase):
1000+
1001+ def setUp(self):
1002+ super(TestPackageDiffSameSource, self).setUp()
1003+ urllib.urlopen.side_effect = [
1004+ FakeUrlOpenReturn(200, OLD_CHANGELOG_CONTENT),
1005+ FakeUrlOpenReturn(200, NEW_CHANGELOG_CONTENT)]
1006+ self.old_version = "5.51a.0-2"
1007+ self.new_version = "5.51-1"
1008+ self.packagediff = PackageDiff("unzip", self.old_version,
1009+ self.new_version)
1010+
1011+ def test_packagediff_old_source_name(self):
1012+ self.assertEqual(self.packagediff.old_source_name, "unzip")
1013+
1014+ def test_packagediff_new_source_name(self):
1015+ self.assertEqual(self.packagediff.new_source_name, "unzip")
1016+
1017+ def test_packagediff_old_source_version(self):
1018+ self.assertEqual(self.packagediff.old_source_version, "5.51a.0-2")
1019+
1020+ def test_packagediff_new_source_version(self):
1021+ self.assertEqual(self.packagediff.new_source_version, "5.51-1")
1022+
1023+ def test_packagediff_source_hasnt_changed(self):
1024+ self.assertFalse(self.packagediff.source_has_changed)
1025+ self.assertEqual(self.packagediff.source_name, "unzip")
1026+
1027+ def test_packagediff_source_name_is_new_source_name(self):
1028+ self.assertEqual(self.packagediff.new_source_name,
1029+ self.packagediff.source_name)
1030+
1031+ def test_changes(self):
1032+ self.assertEqual(len(self.packagediff.changes), 2)
1033+
1034+
1035+class TestPackageDiffSameSourceMissingVersion(TestCase):
1036+ def setUp(self):
1037+ super(TestPackageDiffSameSourceMissingVersion, self).setUp()
1038+
1039+ def test_changes_old_version_is_gone(self):
1040+ urllib.urlopen.side_effect = [
1041+ FakeUrlOpenReturn(200, OLD_CHANGELOG_NO_VERSION_CONTENT),
1042+ FakeUrlOpenReturn(200, NEW_CHANGELOG_CONTENT)]
1043+ old_version = "5.51a.0-1"
1044+ new_version = "5.51-1"
1045+ packagediff = PackageDiff("unzip", old_version, new_version)
1046+ self.assertEqual(len(packagediff.changes), 1)
1047+ self.assertEqual(packagediff.changes[0].version, new_version)
1048+
1049+
1050+class TestPackageDiffDifferentSource(TestCase):
1051+
1052+ def setUp(self):
1053+ super(TestPackageDiffDifferentSource, self).setUp()
1054+ urllib.urlopen.side_effect = [
1055+ FakeUrlOpenReturn(200, OLD_CHANGELOG_CONTENT),
1056+ FakeUrlOpenReturn(200, NEW_CHANGELOG_CONTENT_NEWSOURCE)]
1057+ self.packagediff = PackageDiff("unzip", "5.51a.0-2", "6.0")
1058+
1059+ def test_packagediff_source_has_changed(self):
1060+ self.assertTrue(self.packagediff.source_has_changed)
1061+ self.assertEqual(self.packagediff.new_source_name, "unzippo")
1062+
1063+ def test_packagediff_source_name_is_new_source_name(self):
1064+ self.assertEqual(self.packagediff.new_source_name,
1065+ self.packagediff.source_name)
1066+ self.assertNotEqual(self.packagediff.old_source_name,
1067+ self.packagediff.source_name)
1068+
1069+ def test_changes(self):
1070+ # Check if it's returning only the latest change of the new changelog.
1071+ self.assertEqual(len(self.packagediff.changes), 1)
1072+
1073+
1074+FOO_NEW_CHANGELOG = """\
1075+foo (0.1-0ubuntu3) unstable; urgency=low
1076+ * More foo stuff
1077+
1078+ -- John Doe <johndoe@example.com> Sun, 6 Jun 2004 17:57:46 +0200
1079+
1080+foo (0.1-0ubuntu2) unstable; urgency=low
1081+ * Initial release
1082+
1083+ -- John Doe <johndoe@example.com> Tue, 25 May 2004 14:38:26 +0200"""
1084+
1085+FOO_OLD_CHANGELOG = """\
1086+foo (0.1-0ubuntu2) unstable; urgency=low
1087+ * Initial release
1088+
1089+ -- John Doe <johndoe@example.com> Tue, 25 May 2004 14:38:26 +0200"""
1090+
1091+BAR_NEW_CHANGELOG = """\
1092+bar (0.2-1ubuntu4) unstable; urgency=low
1093+ * Baring the thing
1094+
1095+ -- John Doe <johndoe@example.com> Mon, 7 Jun 2004 17:57:46 +0200
1096+
1097+bar (0.2-1ubuntu3) unstable; urgency=low
1098+ * Release in the middle
1099+
1100+ -- John Doe <johndoe@example.com> Wed, 26 Jun 2004 17:57:46 +0200
1101+
1102+bar (0.2-1ubuntu2) unstable; urgency=low
1103+ * Initial release
1104+
1105+ -- John Doe <johndoe@example.com> Mon, 24 May 2004 14:38:26 +0200"""
1106+
1107+BAR_OLD_CHANGELOG = """\
1108+bar (0.2-1ubuntu2) unstable; urgency=low
1109+ * Initial release
1110+
1111+ -- John Doe <johndoe@example.com> Mon, 24 May 2004 14:38:26 +0200"""
1112+
1113+
1114+class TestManifestDiff(TestCase):
1115+
1116+ # Old manifest: foo, bar, baz, libbaz1, foo5, libbar6
1117+ OLD_MANIFEST = dedent("""\
1118+ foo 0.1-0ubuntu2
1119+ bar 0.2-1ubuntu2
1120+ libbaz1 0.3-0ubuntu1
1121+ libbar6 1.3-2+13.10.20130520-0ubuntu1""")
1122+
1123+ # New manifest: foo, bar, baz, libbaz2, foo5, libbar7
1124+ # - Added packages: libbaz2, libbar7
1125+ # - Removed packages: libbaz1, libbar6
1126+ # - Updated packages: foo, bar, baz, foo5
1127+ NEW_MANIFEST = dedent("""\
1128+ foo 0.1-0ubuntu3
1129+ bar 0.2-1ubuntu4
1130+ libbaz2 0.4-0ubuntu1
1131+ libbar7 1.5""")
1132+
1133+ def setUp(self):
1134+ super(TestManifestDiff, self).setUp()
1135+ self.manifest_diff = ManifestDiff()
1136+
1137+ def _generate_manifest_files(self):
1138+ temp_dir = self.use_temp_dir()
1139+ self.manifest1_file = generate_manifest_file(self.OLD_MANIFEST,
1140+ temp_dir, "manifest1")
1141+ self.manifest2_file = generate_manifest_file(self.NEW_MANIFEST,
1142+ temp_dir, "manifest2")
1143+
1144+ def test_compare_manifests(self):
1145+ self._generate_manifest_files()
1146+ manifest1 = Manifest(self.manifest1_file)
1147+ manifest1.parse_file()
1148+ manifest2 = Manifest(self.manifest2_file)
1149+ manifest2.parse_file()
1150+ manifest_diff = self.manifest_diff
1151+ manifest_diff.compare_manifests(manifest1, manifest2)
1152+
1153+ # - Added packages: libbaz2, libbar7
1154+ self.assertEqual(len(manifest_diff.added_pkgs), 2)
1155+ self.assertTrue("libbaz2" in manifest_diff.added_pkgs)
1156+ self.assertTrue("libbar7" in manifest_diff.added_pkgs)
1157+ self.assertTrue("libbaz2" not in manifest_diff._changed_pkgs)
1158+
1159+ # - Removed packages: libbaz1, libbar6
1160+ self.assertEqual(len(manifest_diff.removed_pkgs), 2)
1161+ self.assertTrue("libbaz1" in manifest_diff.removed_pkgs)
1162+ self.assertTrue("libbar6" in manifest_diff.removed_pkgs)
1163+ self.assertTrue("libbaz1" not in manifest_diff._changed_pkgs)
1164+
1165+ # - Updated packages: foo, bar
1166+ self.assertEqual(len(manifest_diff._changed_pkgs), 2)
1167+ self.assertTrue("foo" in manifest_diff._changed_pkgs)
1168+ self.assertTrue("bar" in manifest_diff._changed_pkgs)
1169+
1170+ # All packages in both manifests (ignoring duplicates)
1171+ all_packages = manifest1.packages.union(manifest2.packages)
1172+ self.assertEqual((len(manifest_diff.added_pkgs) +
1173+ len(manifest_diff.removed_pkgs) +
1174+ len(manifest_diff._changed_pkgs)),
1175+ len(all_packages))
1176+
1177+ def test_compare_dicts(self):
1178+ manifest1 = Manifest()
1179+ content1 = self.OLD_MANIFEST.split("\n")
1180+ manifest1.parse(content1)
1181+ manifest2 = Manifest()
1182+ content2 = self.NEW_MANIFEST.split("\n")
1183+ manifest2.parse(content2)
1184+
1185+ manifest_diff = self.manifest_diff
1186+
1187+ dict1 = manifest1.content
1188+ dict2 = manifest2.content
1189+
1190+ manifest_diff.compare(dict1, dict2)
1191+ # - Added packages: libbaz2, libbar7
1192+ self.assertEqual(len(manifest_diff.added_pkgs), 2)
1193+ self.assertTrue("libbaz2" in manifest_diff.added_pkgs)
1194+ self.assertTrue("libbar7" in manifest_diff.added_pkgs)
1195+ self.assertTrue("libbaz2" not in manifest_diff._changed_pkgs)
1196+
1197+ # - Removed packages: libbaz1, libbar6
1198+ self.assertEqual(len(manifest_diff.removed_pkgs), 2)
1199+ self.assertTrue("libbaz1" in manifest_diff.removed_pkgs)
1200+ self.assertTrue("libbar6" in manifest_diff.removed_pkgs)
1201+ self.assertTrue("libbaz1" not in manifest_diff._changed_pkgs)
1202+
1203+ # - Updated packages: foo, bar
1204+ self.assertEqual(len(manifest_diff._changed_pkgs), 2)
1205+ self.assertTrue("foo" in manifest_diff._changed_pkgs)
1206+ self.assertTrue("bar" in manifest_diff._changed_pkgs)
1207+
1208+ # All packages in both manifests (ignoring duplicates)
1209+ all_packages = manifest1.packages.union(manifest2.packages)
1210+ self.assertEqual((len(manifest_diff.added_pkgs) +
1211+ len(manifest_diff.removed_pkgs) +
1212+ len(manifest_diff._changed_pkgs)),
1213+ len(all_packages))
1214+
1215+ def test_get_changes(self):
1216+ manifest1 = Manifest()
1217+ content1 = self.OLD_MANIFEST.split("\n")
1218+ manifest1.parse(content1)
1219+ manifest2 = Manifest()
1220+ content2 = self.NEW_MANIFEST.split("\n")
1221+ manifest2.parse(content2)
1222+
1223+ manifest_diff = self.manifest_diff
1224+
1225+ dict1 = manifest1.content
1226+ dict2 = manifest2.content
1227+
1228+ manifest_diff.compare(dict1, dict2)
1229+
1230+ # Two packages changed, foo and bar. bar had two new versions since old
1231+ # manifest, foo had one.
1232+
1233+ urllib.urlopen.side_effect = [
1234+ FakeUrlOpenReturn(200, FOO_OLD_CHANGELOG),
1235+ FakeUrlOpenReturn(200, FOO_NEW_CHANGELOG),
1236+ FakeUrlOpenReturn(200, BAR_OLD_CHANGELOG),
1237+ FakeUrlOpenReturn(200, BAR_NEW_CHANGELOG)]
1238+ changes = self.manifest_diff.get_changes()
1239+ # One entry for each changed package
1240+ self.assertIsNotNone(changes)
1241+ self.assertEqual(len(changes["foo"].changes), 1)
1242+ self.assertEqual(len(changes["bar"].changes), 2)

Subscribers

People subscribed via source and target branches