[Reproducible-commits] [debbindiff] 02/03: Massive rearchitecturing: make each file type have their own class
Jérémy Bobbio
lunar at moszumanska.debian.org
Wed Jul 29 13:10:37 UTC 2015
This is an automated email from the git hooks/post-receive script.
lunar pushed a commit to branch master
in repository debbindiff.
commit 5c02e00001e46706a7bf7c8faad353d17178a4d6
Author: Jérémy Bobbio <lunar at debian.org>
Date: Mon Jul 27 15:09:40 2015 +0000
Massive rearchitecturing: make each file type have their own class
A good amount of the code for comparators is now based on classes
instead of methods. Each file type gets its own classs.
The base class, File, is an abstract class that can represent files
on the filesystem but also files that can be extracted from an archive.
This design makes room for future implementation of fuzzy-matching.
Each file type class implements a class method recognizes() that will
receives an unspecialized File instance. This is way more flexible than
the old constrained regex table approach. The new identification method
used for Haskell interfaces is a good illustration. Appropriate caching
for calls to libmagic methods is there as they are still frequently used
and tend to be rather slow.
An unspecialized File object will then be typecasted into the class that
recognized it. If that does not happen, binary comparison is implemented
by the File class.
Instead of redefining the compare() method which returns a single
Difference or None, file type classes can implement compare_details()
which returns an array of “inside” differences. An empty array means no
differences were found.
This new approach makes room to handle special file types better. As an
example, device files can now be compared directly as their extraction
from archives is problematic without root access.
To reduce a good amount of boilerplate code, the Container and its
subclass Archive has been introduced to represent anything that
“contains” more file to be compared. While the API might still be
improved, this already helped a good amount of code become more
consistent. This will also make it pretty straightforward to implement
parallel processing in a near future.
Some archive formats (at least cpio and iso9660) were pretty annoying
to work with. To get rid of some painful code, we now use
libarchive—through the ctypes based wrapper libarchive-c—to handle these
archives in a generic manner. One downside is that libarchive is very
stream-oriented which is not really suited to our random-access model.
We'll see how this impacts performance in the future.
Other less crucial changes:
- `find` is now used to compare directory listings.
- The fallback code in case the `rpm` module cannot be found has been
isolated to a `comparators.rpm_fallback` module.
- Symlinks and devices are now compared in a consistent manner.
- `md5sums` files in Debian packages are now only recognized when
they are part of a Debian package.
- Files in squashfs are now extracted one by one.
- Text files with different encodings can be compared and this difference
is recorded as well.
- Test coverage is now at 92% for comparators.
Sincere apologies for this unreviewable commit.
---
debbindiff.py | 2 +-
debbindiff/__init__.py | 3 +-
debbindiff/comparators/__init__.py | 216 ++++++++-------------
debbindiff/comparators/binary.py | 157 ++++++++++++++-
debbindiff/comparators/bzip2.py | 72 ++++---
debbindiff/comparators/cpio.py | 63 ++----
debbindiff/comparators/deb.py | 115 ++++++-----
debbindiff/comparators/debian.py | 135 ++++++++-----
debbindiff/comparators/device.py | 54 ++++++
debbindiff/comparators/directory.py | 137 +++++++++----
debbindiff/comparators/elf.py | 50 +++--
debbindiff/comparators/fonts.py | 21 +-
debbindiff/comparators/gettext.py | 19 +-
debbindiff/comparators/gzip.py | 77 ++++----
debbindiff/comparators/haskell.py | 51 ++++-
debbindiff/comparators/ipk.py | 28 +--
debbindiff/comparators/iso9660.py | 51 ++---
debbindiff/comparators/java.py | 19 +-
debbindiff/comparators/libarchive.py | 149 ++++++++++++++
debbindiff/comparators/{pe.py => mono.py} | 19 +-
debbindiff/comparators/pdf.py | 26 ++-
debbindiff/comparators/png.py | 19 +-
debbindiff/comparators/rpm.py | 73 ++++---
.../comparators/{haskell.py => rpm_fallback.py} | 29 +--
debbindiff/comparators/squashfs.py | 206 +++++++++++++++-----
debbindiff/comparators/symlink.py | 52 +++++
debbindiff/comparators/tar.py | 150 ++++++++++----
debbindiff/comparators/text.py | 46 +++--
debbindiff/comparators/utils.py | 190 +++++++++++++-----
debbindiff/comparators/xz.py | 71 ++++---
debbindiff/comparators/zip.py | 101 ++++++----
debian/control | 1 +
setup.py | 1 +
tests/comparators/test_binary.py | 111 +++++++++--
tests/comparators/test_bzip2.py | 35 +++-
tests/comparators/test_cpio.py | 35 +++-
tests/comparators/test_deb.py | 54 +++++-
tests/comparators/test_debian.py | 38 ++--
tests/comparators/test_directory.py | 11 +-
tests/comparators/test_elf.py | 50 +++--
tests/comparators/test_fonts.py | 27 ++-
tests/comparators/test_generic.py | 51 -----
tests/comparators/test_gettext.py | 40 +++-
tests/comparators/test_gzip.py | 33 +++-
tests/comparators/test_ipk.py | 28 ++-
tests/comparators/test_iso9660.py | 38 +++-
tests/comparators/test_java.py | 27 ++-
tests/comparators/{test_pe.py => test_mono.py} | 23 ++-
tests/comparators/test_pdf.py | 27 ++-
tests/comparators/test_png.py | 27 ++-
tests/comparators/test_rpm.py | 33 +++-
tests/comparators/test_squashfs.py | 39 ++--
tests/comparators/test_tar.py | 41 ++--
tests/comparators/test_text.py | 57 +++---
tests/comparators/test_utils.py | 94 ---------
tests/comparators/test_xz.py | 33 +++-
tests/comparators/test_zip.py | 45 +++--
tests/data/pe_expected_diff | 7 +-
tests/data/rpm_listing_expected_diff | 3 +
tests/data/symlink_expected_diff | 3 +
tests/data/text_iso8859_expected_diff | 2 -
61 files changed, 2293 insertions(+), 1122 deletions(-)
diff --git a/debbindiff.py b/debbindiff.py
index 01b14f5..4c912e5 100755
--- a/debbindiff.py
+++ b/debbindiff.py
@@ -104,7 +104,7 @@ def main():
if parsed_args.debug:
logger.setLevel(logging.DEBUG)
set_locale()
- difference = debbindiff.comparators.compare_files(
+ difference = debbindiff.comparators.compare_root_paths(
parsed_args.file1, parsed_args.file2)
if difference:
if parsed_args.html_output:
diff --git a/debbindiff/__init__.py b/debbindiff/__init__.py
index be5c81b..d25b195 100644
--- a/debbindiff/__init__.py
+++ b/debbindiff/__init__.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -38,6 +38,7 @@ class RequiredToolNotFound(Exception):
, 'cpio': { 'debian': 'cpio' }
, 'diff': { 'debian': 'diffutils' }
, 'file': { 'debian': 'file' }
+ , 'find': { 'debian': 'findutils' }
, 'getfacl': { 'debian': 'acl' }
, 'ghc': { 'debian': 'ghc' }
, 'gpg': { 'debian': 'gnupg' }
diff --git a/debbindiff/comparators/__init__.py b/debbindiff/comparators/__init__.py
index d45c273..cd489e8 100644
--- a/debbindiff/comparators/__init__.py
+++ b/debbindiff/comparators/__init__.py
@@ -25,149 +25,97 @@ import sys
from debbindiff import logger, tool_required
from debbindiff.difference import Difference
from debbindiff.comparators.binary import \
- compare_binary_files, are_same_binaries
-from debbindiff.comparators.bzip2 import compare_bzip2_files
-from debbindiff.comparators.java import compare_class_files
-from debbindiff.comparators.cpio import compare_cpio_files
-from debbindiff.comparators.deb import compare_deb_files, compare_md5sums_files
-from debbindiff.comparators.debian import compare_dot_changes_files
-from debbindiff.comparators.directory import compare_directories
-from debbindiff.comparators.elf import \
- compare_elf_files, compare_static_lib_files
-from debbindiff.comparators.fonts import compare_ttf_files
-from debbindiff.comparators.gettext import compare_mo_files
-from debbindiff.comparators.gzip import compare_gzip_files
-from debbindiff.comparators.haskell import compare_hi_files
-from debbindiff.comparators.ipk import compare_ipk_files
-from debbindiff.comparators.iso9660 import compare_iso9660_files
-from debbindiff.comparators.pdf import compare_pdf_files
-from debbindiff.comparators.pe import compare_pe_files
-from debbindiff.comparators.png import compare_png_files
+ File, FilesystemFile, compare_binary_files
+from debbindiff.comparators.bzip2 import Bzip2File
+from debbindiff.comparators.java import ClassFile
+from debbindiff.comparators.cpio import CpioFile
+from debbindiff.comparators.deb import DebFile, Md5sumsFile
+from debbindiff.comparators.debian import DotChangesFile
+from debbindiff.comparators.device import Device
+from debbindiff.comparators.directory import Directory, compare_directories
+from debbindiff.comparators.elf import ElfFile, StaticLibFile
+from debbindiff.comparators.fonts import TtfFile
+from debbindiff.comparators.gettext import MoFile
+from debbindiff.comparators.gzip import GzipFile
+from debbindiff.comparators.haskell import HiFile
+from debbindiff.comparators.ipk import IpkFile
+from debbindiff.comparators.iso9660 import Iso9660File
+from debbindiff.comparators.mono import MonoExeFile
+from debbindiff.comparators.pdf import PdfFile
+from debbindiff.comparators.png import PngFile
try:
- from debbindiff.comparators.rpm import compare_rpm_files
+ from debbindiff.comparators.rpm import RpmFile
except ImportError as ex:
if ex.message != 'No module named rpm':
raise
- def compare_rpm_files(path1, path2, source=None):
- logger.info("Python rpm module not found.")
- difference = compare_binary_files(path1, path2, source)
- if difference:
- difference.comment = (difference.comment or '') + \
- '\nUnable to find Python rpm module. Falling back to binary comparison.'
- return difference
-from debbindiff.comparators.squashfs import compare_squashfs_files
-from debbindiff.comparators.text import compare_text_files
-from debbindiff.comparators.tar import compare_tar_files
-from debbindiff.comparators.xz import compare_xz_files
-from debbindiff.comparators.zip import compare_zip_files
+ from debbindiff.comparators.rpm_fallback import RpmFile
+from debbindiff.comparators.squashfs import SquashfsFile
+from debbindiff.comparators.symlink import Symlink
+from debbindiff.comparators.text import TextFile
+from debbindiff.comparators.tar import TarFile
+from debbindiff.comparators.xz import XzFile
+from debbindiff.comparators.zip import ZipFile
-def guess_mime_type(path):
- if not hasattr(guess_mime_type, 'mimedb'):
- guess_mime_type.mimedb = magic.open(magic.MIME)
- guess_mime_type.mimedb.load()
- return guess_mime_type.mimedb.file(path)
-
-
-def compare_unknown(path1, path2, source=None):
- logger.debug("compare unknown path: %s and %s", path1, path2)
- if are_same_binaries(path1, path2):
- return None
- mime_type1 = guess_mime_type(path1)
- mime_type2 = guess_mime_type(path2)
- logger.debug("mime_type1: %s | mime_type2: %s", mime_type1, mime_type2)
- if mime_type1.startswith('text/') and mime_type2.startswith('text/'):
- encodings1 = re.findall(r'; charset=([^ ]+)', mime_type1)
- encodings2 = re.findall(r'; charset=([^ ]+)', mime_type2)
- encoding = None
- encoding2 = None
- if len(encodings1) > 0:
- encoding = encodings1[0]
- if len(encodings2) > 0 and encodings1 != encodings2:
- encoding2 = encodings2[0]
- return compare_text_files(path1, path2, encoding, source, encoding2)
- return compare_binary_files(path1, path2, source)
-
-
-COMPARATORS = [
- (None, r'\.changes$', compare_dot_changes_files),
- (None, r'\.(p_)?hi$', compare_hi_files),
- (None, r'\/\./md5sums$', compare_md5sums_files),
- (None, r'\.mo$', compare_mo_files),
- (None, r'(\.cpio|/initrd)$', compare_cpio_files),
- (r'^application/x-xz(;|$)', r'\.xz$', compare_xz_files),
- (r'^application/x-tar(;|$)', r'\.tar$', compare_tar_files),
- (r'^application/zip(;|$)', r'\.(zip|jar|pk3)$', compare_zip_files),
- (r'^application/java-archive(;|$)', r'\.(jar|war)$', compare_zip_files),
- (r'^application/epub+zip(;|$)', r'\.epub$', compare_zip_files),
- (r'^application/(x-debian-package|vnd.debian.binary-package)(;|$)', r'\.u?deb$', compare_deb_files),
- (r'^application/x-rpm(;|$)', r'\.rpm$', compare_rpm_files),
- (r'^application/x-gzip(;|$)', r'\.(dz|t?gz|svgz)$', compare_gzip_files),
- (r'^application/x-gzip(;|$)', r'\.ipk$', compare_ipk_files),
- (r'^application/x-bzip2(;|$)', r'\.bzip2$', compare_bzip2_files),
- (r'^application/x-executable(;|$)', None, compare_elf_files),
- (r'^application/x-sharedlib(;|$)', r'\.so($|\.[0-9.]+$)',
- compare_elf_files),
- (r'^application/(x-font-ttf|vnd.ms-opentype)(;|$)', r'\.(ttf|otf)$', compare_ttf_files),
- (r'^image/png(;|$)', r'\.png$', compare_png_files),
- (r'^application/pdf(;|$)', r'\.pdf$', compare_pdf_files),
- (r'^application/x-dosexec(;|$)', r'\.(exe|dll)$', compare_pe_files),
- (r'^text/plain; charset=(?P<encoding>[a-z0-9-]+)$', None, compare_text_files),
- (r'^application/xml; charset=(?P<encoding>[a-z0-9-]+)$', None, compare_text_files),
- (r'^application/postscript; charset=(?P<encoding>[a-z0-9-]+)$', None, compare_text_files),
- (r'^application/x-java-applet(;|$)', r'\.class$', compare_class_files),
- (None, r'\.info(-\d+)?$', compare_text_files),
- (None, r'\.squashfs$', compare_squashfs_files),
- (None, r'\.a$', compare_static_lib_files),
- (r'^application/x-iso9660-image(;|$)', None, compare_iso9660_files)
- ]
+def compare_root_paths(path1, path2):
+ if os.path.isdir(path1) and os.path.isdir(path2):
+ return compare_directories(path1, path2)
+ return compare_files(FilesystemFile(path1), FilesystemFile(path2))
-SMALL_FILE_THRESHOLD = 65536 # 64 kiB
+def compare_files(file1, file2, source=None):
+ logger.debug('compare files %s and %s' % (file1, file2))
+ with file1.get_content(), file2.get_content():
+ if file1.has_same_content_as(file2):
+ logger.debug('same content, skipping')
+ return None
+ specialize(file1)
+ specialize(file2)
+ if file1.__class__.__name__ != file2.__class__.__name__:
+ return file1.compare_bytes(file2, source)
+ return file1.compare(file2, source)
-def compare_files(path1, path2, source=None):
- if os.path.islink(path1) or os.path.islink(path2):
- dest1, dest2 = None, None
- try:
- dest1 = os.readlink(path1)
- text1 = "%s -> %s" % (path1, dest1)
- except OSError:
- text1 = "[ No symlink ]"
- try:
- dest2 = os.readlink(path2)
- text2 = "%s -> %s" % (path2, dest2)
- except OSError:
- text2 = "[ No symlink ]"
+# The order matters! They will be tried in turns.
+FILE_CLASSES = (
+ Directory,
+ Symlink,
+ Device,
+ DotChangesFile,
+ Md5sumsFile,
+ TextFile,
+ Bzip2File,
+ CpioFile,
+ DebFile,
+ ElfFile,
+ StaticLibFile,
+ TtfFile,
+ MoFile,
+ IpkFile,
+ GzipFile,
+ HiFile,
+ Iso9660File,
+ ClassFile,
+ MonoExeFile,
+ PdfFile,
+ PngFile,
+ RpmFile,
+ SquashfsFile,
+ TarFile,
+ XzFile,
+ ZipFile
+ )
- if dest1 and dest2 and dest1 == dest2:
- return None
- return Difference.from_unicode(text1, text2, path1, path2, source=source, comment="symlink")
- if os.path.isdir(path1) and os.path.isdir(path2):
- return compare_directories(path1, path2, source)
- if not os.path.isfile(path1):
- logger.critical("%s is not a file", path1)
- sys.exit(2)
- if not os.path.isfile(path2):
- logger.critical("%s is not a file", path2)
- sys.exit(2)
- # try comparing small files directly first
- size1 = os.path.getsize(path1)
- size2 = os.path.getsize(path2)
- if size1 == size2 and size1 <= SMALL_FILE_THRESHOLD:
- if file(path1).read() == file(path2).read():
- return None
- # ok, let's do the full thing
- mime_type1 = guess_mime_type(path1)
- mime_type2 = guess_mime_type(path2)
- for mime_type_regex, filename_regex, comparator in COMPARATORS:
- if filename_regex and re.search(filename_regex, path1) \
- and re.search(filename_regex, path2):
- return comparator(path1, path2, source=source)
- if mime_type_regex:
- match1 = re.search(mime_type_regex, mime_type1)
- match2 = re.search(mime_type_regex, mime_type2)
- if match1 and match2 and match1.groupdict() == match2.groupdict():
- return comparator(path1, path2, source=source, **match1.groupdict())
- return compare_unknown(path1, path2, source)
+def specialize(file):
+ for cls in FILE_CLASSES:
+ if isinstance(file, cls):
+ logger.debug("%s is already specialized", file.name)
+ return file
+ if cls.recognizes(file):
+ logger.debug("Using %s for %s", cls.__name__, file.name)
+ new_cls = type(cls.__name__, (cls, type(file)), {})
+ file.__class__ = new_cls
+ return file
+ logger.debug('Unidentified file. Magic says: %s' % file.magic_file_type)
+ return file
diff --git a/debbindiff/comparators/binary.py b/debbindiff/comparators/binary.py
index c085f9e..e28fac8 100644
--- a/debbindiff/comparators/binary.py
+++ b/debbindiff/comparators/binary.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,11 +17,17 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+from abc import ABCMeta, abstractproperty, abstractmethod
from binascii import hexlify
from contextlib import contextmanager
+import os
+import os.path
+import re
+from stat import S_ISCHR, S_ISBLK
import subprocess
+import magic
from debbindiff.difference import Difference
-from debbindiff import tool_required, RequiredToolNotFound
+from debbindiff import tool_required, RequiredToolNotFound, logger
@contextmanager
@@ -56,8 +62,147 @@ def compare_binary_files(path1, path2, source=None):
comment = 'xxd not available in path. Falling back to Python hexlify.\n'
return Difference.from_unicode(hexdump1, hexdump2, path1, path2, source, comment)
+SMALL_FILE_THRESHOLD = 65536 # 64 kiB
- at tool_required('cmp')
-def are_same_binaries(path1, path2):
- return 0 == subprocess.call(['cmp', '--silent', path1, path2],
- shell=False, close_fds=True)
+# decorator for functions which needs to access the file content
+# (and so requires a path to be set)
+def needs_content(original_method):
+ def wrapper(self, other, *args, **kwargs):
+ with self.get_content(), other.get_content():
+ return original_method(self, other, *args, **kwargs)
+ return wrapper
+
+class File(object):
+ __metaclass__ = ABCMeta
+
+ @classmethod
+ def guess_file_type(self, path):
+ if not hasattr(self, '_mimedb'):
+ self._mimedb = magic.open(magic.NONE)
+ self._mimedb.load()
+ return self._mimedb.file(path)
+
+ @classmethod
+ def guess_encoding(self, path):
+ if not hasattr(self, '_mimedb_encoding'):
+ self._mimedb_encoding = magic.open(magic.MAGIC_MIME_ENCODING)
+ self._mimedb_encoding.load()
+ return self._mimedb_encoding.file(path)
+
+ def __repr__(self):
+ return '<%s %s %s>' % (self.__class__, self.name, self.path)
+
+ # Path should only be used when accessing the file content (through get_content())
+ @property
+ def path(self):
+ return self._path
+
+ # This might be different from path and is used to do file extension matching
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def magic_file_type(self):
+ if not hasattr(self, '_magic_file_type'):
+ with self.get_content():
+ self._magic_file_type = File.guess_file_type(self.path)
+ return self._magic_file_type
+
+ @abstractmethod
+ @contextmanager
+ def get_content(self):
+ raise NotImplemented
+
+ @abstractmethod
+ def is_directory():
+ raise NotImplemented
+
+ @abstractmethod
+ def is_symlink():
+ raise NotImplemented
+
+ @abstractmethod
+ def is_device():
+ raise NotImplemented
+
+ @needs_content
+ def compare_bytes(self, other, source=None):
+ return compare_binary_files(self.path, other.path, source)
+
+ def _compare_using_details(self, other, source):
+ details = [d for d in self.compare_details(other, source) if d is not None]
+ if len(details) == 0:
+ return None
+ difference = Difference(None, self.name, other.name, source=source)
+ difference.add_details(details)
+ return difference
+
+ @tool_required('cmp')
+ @needs_content
+ def has_same_content_as(self, other):
+ logger.debug('%s has_same_content %s', self, other)
+ # try comparing small files directly first
+ my_size = os.path.getsize(self.path)
+ other_size = os.path.getsize(other.path)
+ if my_size == other_size and my_size <= SMALL_FILE_THRESHOLD:
+ if file(self.path).read() == file(other.path).read():
+ return True
+
+ return 0 == subprocess.call(['cmp', '--silent', self.path, other.path],
+ shell=False, close_fds=True)
+
+
+ # To be specialized directly, or by implementing compare_details
+ @needs_content
+ def compare(self, other, source=None):
+ if hasattr(self, 'compare_details'):
+ try:
+ difference = self._compare_using_details(other, source)
+ # no differences detected inside? let's at least do a binary diff
+ if difference is None:
+ difference = self.compare_bytes(other, source=source)
+ if difference is None:
+ return None
+ difference.comment = (difference.comment or '') + \
+ "No differences found inside, yet data differs"
+ except subprocess.CalledProcessError as e:
+ difference = self.compare_bytes(other, source=source)
+ output = re.sub(r'^', ' ', e.output, flags=re.MULTILINE)
+ cmd = ' '.join(e.cmd)
+ difference.comment = (difference.comment or '') + \
+ "Command `%s` exited with %d. Output:\n%s" \
+ % (cmd, e.returncode, output)
+ except RequiredToolNotFound as e:
+ difference = self.compare_bytes(other, source=source)
+ difference.comment = (difference.comment or '') + \
+ "'%s' not available in path. Falling back to binary comparison." % e.command
+ package = e.get_package()
+ if package:
+ difference.comment += "\nInstall '%s' to get a better output." % package
+ return difference
+ return self.compare_bytes(other, source)
+
+class FilesystemFile(File):
+ def __init__(self, path):
+ self._path = None
+ self._name = path
+
+ @contextmanager
+ def get_content(self):
+ if self._path is not None:
+ yield
+ else:
+ self._path = self._name
+ yield
+ self._path = None
+
+ def is_directory(self):
+ return not os.path.islink(self._name) and os.path.isdir(self._name)
+
+ def is_symlink(self):
+ return os.path.islink(self._name)
+
+ def is_device(self):
+ mode = os.lstat(self._name).st_mode
+ return S_ISCHR(mode) or S_ISBLK(mode)
diff --git a/debbindiff/comparators/bzip2.py b/debbindiff/comparators/bzip2.py
index d77a408..7ec9714 100644
--- a/debbindiff/comparators/bzip2.py
+++ b/debbindiff/comparators/bzip2.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,33 +19,49 @@
from contextlib import contextmanager
import os.path
+import re
import subprocess
import debbindiff.comparators
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory
-from debbindiff.difference import get_source
-from debbindiff import tool_required
-
-
- at contextmanager
- at tool_required('bzip2')
-def decompress_bzip2(path):
- with make_temp_directory() as temp_dir:
- if path.endswith('.bz2'):
- temp_path = os.path.join(temp_dir, os.path.basename(path[:-4]))
- else:
- temp_path = os.path.join(temp_dir, "%s-content" % path)
- with open(temp_path, 'wb') as temp_file:
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Archive, get_compressed_content_name
+from debbindiff import logger, tool_required
+
+
+class Bzip2Container(Archive):
+ @property
+ def path(self):
+ return self._path
+
+ def open_archive(self, path):
+ self._path = path
+ return self
+
+ def close_archive(self):
+ self._path = None
+
+ def get_member_names(self):
+ return [get_compressed_content_name(self.path, '.bz2')]
+
+ @tool_required('bzip2')
+ def extract(self, member_name, dest_dir):
+ dest_path = os.path.join(dest_dir, member_name)
+ logger.debug('bzip2 extracting to %s' % dest_path)
+ with open(dest_path, 'wb') as fp:
subprocess.check_call(
- ["bzip2", "--decompress", "--stdout", path],
- shell=False, stdout=temp_file, stderr=None)
- yield temp_path
-
-
- at binary_fallback
- at returns_details
-def compare_bzip2_files(path1, path2, source=None):
- with decompress_bzip2(path1) as new_path1:
- with decompress_bzip2(path2) as new_path2:
- return [debbindiff.comparators.compare_files(
- new_path1, new_path2,
- source=[os.path.basename(new_path1), os.path.basename(new_path2)])]
+ ["bzip2", "--decompress", "--stdout", self.path],
+ shell=False, stdout=fp, stderr=None)
+ return dest_path
+
+
+class Bzip2File(File):
+ RE_FILE_TYPE = re.compile(r'^bzip2 compressed data\b')
+
+ @staticmethod
+ def recognizes(file):
+ return Bzip2File.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ with Bzip2Container(self).open() as my_container, \
+ Bzip2Container(other).open() as other_container:
+ return my_container.compare(other_container, source)
diff --git a/debbindiff/comparators/cpio.py b/debbindiff/comparators/cpio.py
index ccf187c..46bc196 100644
--- a/debbindiff/comparators/cpio.py
+++ b/debbindiff/comparators/cpio.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,58 +18,36 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+import re
import subprocess
import os.path
import debbindiff.comparators
from debbindiff import logger, tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.libarchive import LibarchiveContainer
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
+
class CpioContent(Command):
@tool_required('cpio')
def cmdline(self):
- return ['cpio', '-tvF', self.path]
-
-
- at tool_required('cpio')
-def get_cpio_names(path):
- cmd = ['cpio', '--quiet', '-tF', path]
- return subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=False).splitlines(False)
-
-
- at tool_required('cpio')
-def extract_cpio_archive(path, destdir):
- cmd = ['cpio', '--no-absolute-filenames', '--quiet', '-idF',
- os.path.abspath(path.encode('utf-8'))]
- logger.debug("extracting %s into %s", path.encode('utf-8'), destdir)
- p = subprocess.Popen(cmd, shell=False, cwd=destdir)
- p.communicate()
- p.wait()
- if p.returncode != 0:
- logger.error('cpio exited with error code %d', p.returncode)
-
+ return ['cpio', '--quiet', '-tvF', self.path]
- at binary_fallback
- at returns_details
-def compare_cpio_files(path1, path2, source=None):
- differences = []
- differences.append(Difference.from_command(
- CpioContent, path1, path2, source="file list"))
+class CpioFile(File):
+ RE_FILE_TYPE = re.compile(r'\bcpio archive\b')
- # compare files contained in archive
- content1 = get_cpio_names(path1)
- content2 = get_cpio_names(path2)
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- extract_cpio_archive(path1, temp_dir1)
- extract_cpio_archive(path2, temp_dir2)
- for member in sorted(set(content1).intersection(set(content2))):
- in_path1 = os.path.join(temp_dir1, member)
- in_path2 = os.path.join(temp_dir2, member)
- if not os.path.isfile(in_path1) or not os.path.isfile(in_path2):
- continue
- differences.append(debbindiff.comparators.compare_files(
- in_path1, in_path2, source=member))
+ @staticmethod
+ def recognizes(file):
+ return CpioFile.RE_FILE_TYPE.search(file.magic_file_type)
- return differences
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(Difference.from_command(
+ CpioContent, self.path, other.path, source="file list"))
+ with LibarchiveContainer(self).open() as my_container, \
+ LibarchiveContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/deb.py b/debbindiff/comparators/deb.py
index 3130827..4d975af 100644
--- a/debbindiff/comparators/deb.py
+++ b/debbindiff/comparators/deb.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,54 +19,75 @@
from __future__ import absolute_import
+import re
import os.path
from debian.arfile import ArFile
from debbindiff import logger
-from debbindiff.difference import Difference, get_source
+from debbindiff.difference import Difference
import debbindiff.comparators
-from debbindiff.comparators.binary import are_same_binaries
+from debbindiff.comparators.binary import File, needs_content
from debbindiff.comparators.utils import \
- binary_fallback, returns_details, make_temp_directory, get_ar_content
-
-
- at binary_fallback
- at returns_details
-def compare_deb_files(path1, path2, source=None):
- differences = []
- # look up differences in content
- ar1 = ArFile(filename=path1)
- ar2 = ArFile(filename=path2)
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- logger.debug('content1 %s', ar1.getnames())
- logger.debug('content2 %s', ar2.getnames())
- for name in sorted(set(ar1.getnames())
- .intersection(ar2.getnames())):
- logger.debug('extract member %s', name)
- member1 = ar1.getmember(name)
- member2 = ar2.getmember(name)
- in_path1 = os.path.join(temp_dir1, name)
- in_path2 = os.path.join(temp_dir2, name)
- with open(in_path1, 'w') as f1:
- f1.write(member1.read())
- with open(in_path2, 'w') as f2:
- f2.write(member2.read())
- differences.append(
- debbindiff.comparators.compare_files(
- in_path1, in_path2, source=name))
- os.unlink(in_path1)
- os.unlink(in_path2)
- # look up differences in file list and file metadata
- content1 = get_ar_content(path1)
- content2 = get_ar_content(path2)
- differences.append(Difference.from_unicode(
- content1, content2, path1, path2, source="metadata"))
- return differences
-
-
-def compare_md5sums_files(path1, path2, source=None):
- if are_same_binaries(path1, path2):
- return None
- return Difference(None, path1, path2,
- source=get_source(path1, path2),
- comment="Files in package differs")
+ Archive, ArchiveMember, get_ar_content
+
+AR_EXTRACTION_BUFFER_SIZE = 32768
+
+class ArContainer(Archive):
+ def open_archive(self, path):
+ return ArFile(filename=path)
+
+ def close_archive(self):
+ # ArFile don't have to be closed
+ pass
+
+ def get_member_names(self):
+ return self.archive.getnames()
+
+ def extract(self, member_name, dest_dir):
+ logger.debug('ar extracting %s to %s', member_name, dest_dir)
+ member = self.archive.getmember(member_name)
+ dest_path = os.path.join(dest_dir, os.path.basename(member_name))
+ member.seek(0)
+ with open(dest_path, 'w') as fp:
+ for buf in iter(lambda: member.read(AR_EXTRACTION_BUFFER_SIZE), b''):
+ fp.write(buf)
+ return dest_path
+
+
+class DebContainer(ArContainer):
+ pass
+
+
+class DebFile(File):
+ RE_FILE_TYPE = re.compile(r'^Debian binary package')
+
+ @staticmethod
+ def recognizes(file):
+ return DebFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ my_content = get_ar_content(self.path)
+ other_content = get_ar_content(other.path)
+ differences.append(Difference.from_unicode(
+ my_content, other_content, self.path, other.path, source="metadata"))
+ with DebContainer(self).open() as my_container, \
+ DebContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
+
+
+class Md5sumsFile(File):
+ @staticmethod
+ def recognizes(file):
+ return isinstance(file, ArchiveMember) and \
+ file.name == './md5sums' and \
+ isinstance(file.container.source, ArchiveMember) and \
+ isinstance(file.container.source.container.source, ArchiveMember) and \
+ file.container.source.container.source.name.startswith('control.tar.')
+
+ def compare(self, other, source=None):
+ if self.has_same_content_as(other):
+ return None
+ return Difference(None, self.path, other.path, source='md5sums',
+ comment="Files in package differs")
diff --git a/debbindiff/comparators/debian.py b/debbindiff/comparators/debian.py
index 9975267..e15b47f 100644
--- a/debbindiff/comparators/debian.py
+++ b/debbindiff/comparators/debian.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,11 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+from contextlib import contextmanager
+import os.path
+import re
import sys
from debbindiff import logger
from debbindiff.changes import Changes
import debbindiff.comparators
-from debbindiff.comparators.utils import binary_fallback, returns_details
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Container
from debbindiff.difference import Difference, get_source
@@ -33,51 +37,84 @@ DOT_CHANGES_FIELDS = [
]
- at binary_fallback
- at returns_details
-def compare_dot_changes_files(path1, path2, source=None):
- try:
- dot_changes1 = Changes(filename=path1)
- dot_changes1.validate(check_signature=False)
- dot_changes2 = Changes(filename=path2)
- dot_changes2.validate(check_signature=False)
- except IOError, e:
- logger.critical(e)
- sys.exit(2)
-
- differences = []
- for field in DOT_CHANGES_FIELDS:
- differences.append(Difference.from_unicode(
- dot_changes1[field].lstrip(),
- dot_changes2[field].lstrip(),
- path1, path2, source=field))
-
- files_difference = Difference.from_unicode(
- dot_changes1.get_as_string('Files'),
- dot_changes2.get_as_string('Files'),
- path1, path2,
- source='Files')
-
- if not files_difference:
- return differences
+class DotChangesMember(File):
+ def __init__(self, container, member_name):
+ self._container = container
+ self._name = member_name
+ self._path = None
+
+ @property
+ def container(self):
+ return self._container
+
+ @property
+ def name(self):
+ return self._name
+
+ @contextmanager
+ def get_content(self):
+ if self._path is not None:
+ yield
+ else:
+ with self.container.source.get_content():
+ self._path = os.path.join(os.path.dirname(self.container.source.path), self.name)
+ yield
+ self._path = None
+
+ def is_directory(self):
+ return False
+
+ def is_symlink(self):
+ return False
+
+ def is_device(self):
+ return False
+
+
+class DotChangesContainer(Container):
+ @contextmanager
+ def open(self):
+ yield self
- differences.append(files_difference)
-
- # we are only interested in file names
- files1 = dict([(d['name'], d) for d in dot_changes1.get('Files')])
- files2 = dict([(d['name'], d) for d in dot_changes2.get('Files')])
-
- for filename in sorted(set(files1.keys()).intersection(files2.keys())):
- d1 = files1[filename]
- d2 = files2[filename]
- if d1['md5sum'] != d2['md5sum']:
- logger.debug("%s mentioned in .changes have "
- "differences", filename)
- differences.append(
- debbindiff.comparators.compare_files(
- dot_changes1.get_path(filename),
- dot_changes2.get_path(filename),
- source=get_source(dot_changes1.get_path(filename),
- dot_changes2.get_path(filename))))
-
- return differences
+ def get_member_names(self):
+ return [d['name'] for d in self.source.changes.get('Files')]
+
+ def get_member(self, member_name):
+ return DotChangesMember(self, member_name)
+
+
+class DotChangesFile(File):
+ RE_FILE_EXTENSION = re.compile(r'\.changes$')
+
+ @staticmethod
+ def recognizes(file):
+ if not DotChangesFile.RE_FILE_EXTENSION.search(file.name):
+ return False
+ with file.get_content():
+ changes = Changes(filename=file.path)
+ changes.validate(check_signature=False)
+ file._changes = changes
+ return True
+
+ @property
+ def changes(self):
+ return self._changes
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+
+ for field in DOT_CHANGES_FIELDS:
+ differences.append(Difference.from_unicode(
+ self.changes[field].lstrip(),
+ other.changes[field].lstrip(),
+ self.path, other.path, source=field))
+ # compare Files as string
+ differences.append(Difference.from_unicode(self.changes.get_as_string('Files'),
+ other.changes.get_as_string('Files'),
+ self.path, other.path, source=field))
+ with DotChangesContainer(self).open() as my_container, \
+ DotChangesContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+
+ return differences
diff --git a/debbindiff/comparators/device.py b/debbindiff/comparators/device.py
new file mode 100644
index 0000000..5b22da8
--- /dev/null
+++ b/debbindiff/comparators/device.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+#
+# debbindiff: highlight differences between two builds of Debian packages
+#
+# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
+#
+# debbindiff is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# debbindiff is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+
+from contextlib import contextmanager
+import os
+from stat import S_ISCHR, S_ISBLK
+import tempfile
+from debbindiff.comparators.binary import File, FilesystemFile, needs_content
+from debbindiff.comparators.utils import format_device
+from debbindiff.difference import Difference
+from debbindiff import logger
+
+
+class Device(File):
+ @staticmethod
+ def recognizes(file):
+ return file.is_device()
+
+ def get_device(self):
+ assert self is FilesystemFile
+ st = os.lstat(self.name)
+ return st.st_mode, os.major(st.st_dev), os.minor(st.st_dev)
+
+ @contextmanager
+ def get_content(self):
+ with tempfile.NamedTemporaryFile(suffix='debbindiff') as f:
+ f.write(format_device(*self.get_device()))
+ f.flush()
+ self._path = f.name
+ yield
+ self._path = None
+
+ @needs_content
+ def compare(self, other, source=None):
+ logger.debug('my_content %s' % self.path)
+ with open(self.path) as my_content, \
+ open(other.path) as other_content:
+ return Difference.from_file(my_content, other_content, self.name, other.name, source=source, comment="symlink")
diff --git a/debbindiff/comparators/directory.py b/debbindiff/comparators/directory.py
index 93a41aa..c23cf58 100644
--- a/debbindiff/comparators/directory.py
+++ b/debbindiff/comparators/directory.py
@@ -17,17 +17,21 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+from contextlib import contextmanager
import os.path
import re
import subprocess
from debbindiff import logger, tool_required, RequiredToolNotFound
from debbindiff.difference import Difference
import debbindiff.comparators
-from debbindiff.comparators.utils import returns_details, Command
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.utils import Container, Command
-def ls(path):
- return '\n'.join(sorted(subprocess.check_output(['ls', path], shell=False).decode('utf-8').splitlines()))
+class FindAll(Command):
+ @tool_required('find')
+ def cmdline(self):
+ return ['find', self.path, '-printf', '%P\n']
class Stat(Command):
@@ -68,57 +72,106 @@ class Getfacl(Command):
def compare_meta(path1, path2):
logger.debug('compare_meta(%s, %s)' % (path1, path2))
differences = []
-
try:
- difference = Difference.from_command(Stat, path1, path2)
- if difference:
- differences.append(difference)
+ differences.append(Difference.from_command(Stat, path1, path2))
except RequiredToolNotFound:
logger.warn("'stat' not found! Is PATH wrong?")
-
try:
lsattr1 = lsattr(path1)
lsattr2 = lsattr(path2)
- difference = Difference.from_unicode(
- lsattr1, lsattr2, path1, path2, source="lattr")
- if difference:
- differences.append(difference)
+ differences.append(Difference.from_unicode(
+ lsattr1, lsattr2, path1, path2, source="lattr"))
except RequiredToolNotFound:
logger.info("Unable to find 'lsattr'.")
-
try:
- difference = Difference.from_command(Getfacl, path1, path2)
- if difference:
- differences.append(difference)
+ differences.append(Difference.from_command(Getfacl, path1, path2))
except RequiredToolNotFound:
logger.info("Unable to find 'getfacl'.")
- return differences
+ return [d for d in differences if d is not None]
- at tool_required('ls')
- at returns_details
def compare_directories(path1, path2, source=None):
- differences = []
- logger.debug('path1 files: %s' % sorted(set(os.listdir(path1))))
- logger.debug('path2 files: %s' % sorted(set(os.listdir(path2))))
- for name in sorted(set(os.listdir(path1)).intersection(set(os.listdir(path2)))):
- logger.debug('compare %s' % name)
- in_path1 = os.path.join(path1, name)
- in_path2 = os.path.join(path2, name)
- in_difference = debbindiff.comparators.compare_files(
- in_path1, in_path2, source=name)
- if not os.path.isdir(in_path1):
- if in_difference:
- in_difference.add_details(compare_meta(in_path1, in_path2))
+ return FilesystemDirectory(path1).compare(FilesystemDirectory(path2))
+
+
+class Directory(object):
+ @staticmethod
+ def recognizes(file):
+ return file.is_directory()
+
+
+class FilesystemDirectory(object):
+ def __init__(self, path):
+ self._path = path
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def name(self):
+ return self._path
+
+ @contextmanager
+ def get_content(self):
+ yield
+
+ def is_directory(self):
+ return True
+
+ def has_same_content_as(self, other):
+ # no shortcut
+ return False
+
+ def compare(self, other, source=None):
+ differences = []
+ try:
+ find_diff = Difference.from_command(FindAll, self.path, other.path)
+ if find_diff:
+ differences.append(find_diff)
+ except RequiredToolNotFound:
+ logger.info("Unable to find 'getfacl'.")
+ differences.extend(compare_meta(self.path, other.path))
+ with DirectoryContainer(self).open() as my_container, \
+ DirectoryContainer(other).open() as other_container:
+ my_names = my_container.get_member_names()
+ other_names = other_container.get_member_names()
+ for name in sorted(set(my_names).intersection(other_names)):
+ my_file = my_container.get_member(name)
+ other_file = other_container.get_member(name)
+ with my_file.get_content(), other_file.get_content():
+ inner_difference = debbindiff.comparators.compare_files(
+ my_file, other_file, source=name)
+ meta_differences = compare_meta(my_file.path, other_file.path)
+ if meta_differences and not inner_difference:
+ inner_difference = Difference(None, my_file.path, other_file.path)
+ if inner_difference:
+ inner_difference.add_details(meta_differences)
+ differences.append(inner_difference)
+ if not differences:
+ return None
+ difference = Difference(None, self.path, other.path, source)
+ difference.add_details(differences)
+ return difference
+
+
+class DirectoryContainer(Container):
+ @contextmanager
+ def open(self):
+ with self.source.get_content():
+ self._path = self.source.path
+ yield self
+ self._path = None
+
+ def get_member_names(self):
+ names = []
+ for root, _, files in os.walk(self._path):
+ if root == self._path:
+ root = ''
else:
- details = compare_meta(in_path1, in_path2)
- if details:
- d = Difference(None, path1, path2, source=name)
- d.add_details(details)
- in_difference = d
- differences.append(in_difference)
- ls1 = ls(path1)
- ls2 = ls(path2)
- differences.append(Difference.from_unicode(ls1, ls2, path1, path2, source="ls"))
- differences.extend(compare_meta(path1, path2))
- return differences
+ root = root[len(self._path) + 1:]
+ names.extend([os.path.join(root, f) for f in files])
+ return names
+
+ def get_member(self, member_name):
+ return FilesystemFile(os.path.join(self._path, member_name))
diff --git a/debbindiff/comparators/elf.py b/debbindiff/comparators/elf.py
index bff635d..ec4cf82 100644
--- a/debbindiff/comparators/elf.py
+++ b/debbindiff/comparators/elf.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,7 +21,8 @@ import os.path
import re
import subprocess
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, get_ar_content, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import get_ar_content, Command
from debbindiff.difference import Difference
@@ -58,30 +59,39 @@ class ObjdumpDisassemble(Command):
# the full path can appear in the output, we need to remove it
return line.replace(self.path, os.path.basename(self.path))
-# this one is not wrapped with binary_fallback and is used
-# by both compare_elf_files and compare_static_lib_files
-def _compare_elf_data(path1, path2, source=None):
+def _compare_elf_data(path1, path2):
differences = []
differences.append(Difference.from_command(ReadelfAll, path1, path2))
differences.append(Difference.from_command(ReadelfDebugDump, path1, path2))
differences.append(Difference.from_command(ObjdumpDisassemble, path1, path2))
return differences
+class ElfFile(File):
+ RE_FILE_TYE = re.compile(r'^ELF ')
- at binary_fallback
- at returns_details
-def compare_elf_files(path1, path2, source=None):
- return _compare_elf_data(path1, path2, source=None)
+ @staticmethod
+ def recognizes(file):
+ return ElfFile.RE_FILE_TYE.match(file.magic_file_type)
+ @needs_content
+ def compare_details(self, other, source=None):
+ return _compare_elf_data(self.path, other.path)
- at binary_fallback
- at returns_details
-def compare_static_lib_files(path1, path2, source=None):
- differences = []
- # look up differences in metadata
- content1 = get_ar_content(path1)
- content2 = get_ar_content(path2)
- differences.append(Difference.from_unicode(
- content1, content2, path1, path2, source="metadata"))
- differences.extend(_compare_elf_data(path1, path2, source))
- return differences
+class StaticLibFile(File):
+ RE_FILE_TYPE = re.compile(r'\bar archive\b')
+ RE_FILE_EXTENSION = re.compile(r'\.a$')
+
+ @staticmethod
+ def recognizes(file):
+ return StaticLibFile.RE_FILE_TYPE.search(file.magic_file_type) and StaticLibFile.RE_FILE_EXTENSION.search(file.name)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ # look up differences in metadata
+ content1 = get_ar_content(self.path)
+ content2 = get_ar_content(other.path)
+ differences.append(Difference.from_unicode(
+ content1, content2, self.path, other.path, source="metadata"))
+ differences.extend(_compare_elf_data(self.path, other.path))
+ return differences
diff --git a/debbindiff/comparators/fonts.py b/debbindiff/comparators/fonts.py
index 0deafcb..7e92a23 100644
--- a/debbindiff/comparators/fonts.py
+++ b/debbindiff/comparators/fonts.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,9 +18,11 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
import locale
+import re
import subprocess
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
@@ -32,7 +34,14 @@ class Showttf(Command):
def filter(self, line):
return line.decode('latin-1').encode('utf-8')
- at binary_fallback
- at returns_details
-def compare_ttf_files(path1, path2, source=None):
- return [Difference.from_command(Showttf, path1, path2)]
+
+class TtfFile(File):
+ RE_FILE_TYPE = re.compile(r'^(TrueType|OpenType) font data$')
+
+ @staticmethod
+ def recognizes(file):
+ return TtfFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(Showttf, self.path, other.path)]
diff --git a/debbindiff/comparators/gettext.py b/debbindiff/comparators/gettext.py
index 6e68702..f65e57a 100644
--- a/debbindiff/comparators/gettext.py
+++ b/debbindiff/comparators/gettext.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -21,7 +21,8 @@ import re
import subprocess
from StringIO import StringIO
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
from debbindiff import logger
@@ -56,7 +57,13 @@ class Msgunfmt(Command):
return line
- at binary_fallback
- at returns_details
-def compare_mo_files(path1, path2, source=None):
- return [Difference.from_command(Msgunfmt, path1, path2)]
+class MoFile(File):
+ RE_FILE_TYPE = re.compile(r'^GNU message catalog\b')
+
+ @staticmethod
+ def recognizes(file):
+ return MoFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(Msgunfmt, self.path, other.path)]
diff --git a/debbindiff/comparators/gzip.py b/debbindiff/comparators/gzip.py
index 7326409..f5203c5 100644
--- a/debbindiff/comparators/gzip.py
+++ b/debbindiff/comparators/gzip.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,47 +18,56 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
from contextlib import contextmanager
+import re
import subprocess
import os.path
import debbindiff.comparators
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory
-from debbindiff.difference import Difference, get_source
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Archive, get_compressed_content_name
+from debbindiff.difference import Difference
+from debbindiff import logger, tool_required
- at contextmanager
- at tool_required('gzip')
-def decompress_gzip(path):
- with make_temp_directory() as temp_dir:
- if path.endswith('.gz'):
- temp_path = os.path.join(temp_dir, os.path.basename(path[:-3]))
- else:
- temp_path = os.path.join(temp_dir, os.path.basename("%s-content" % path))
- with open(temp_path, 'wb') as temp_file:
+class GzipContainer(Archive):
+ @property
+ def path(self):
+ return self._path
+
+ def open_archive(self, path):
+ self._path = path
+ return self
+
+ def close_archive(self):
+ self._path = None
+
+ def get_member_names(self):
+ return [get_compressed_content_name(self.path, '.gz')]
+
+ @tool_required('gzip')
+ def extract(self, member_name, dest_dir):
+ dest_path = os.path.join(dest_dir, member_name)
+ logger.debug('gzip extracting to %s' % dest_path)
+ with open(dest_path, 'wb') as fp:
subprocess.check_call(
- ["gzip", "--decompress", "--stdout", path],
- shell=False, stdout=temp_file, stderr=None)
- yield temp_path
+ ["gzip", "--decompress", "--stdout", self.path],
+ shell=False, stdout=fp, stderr=None)
+ return dest_path
- at tool_required('file')
-def get_gzip_metadata(path):
- return subprocess.check_output(['file', '--brief', path]).decode('utf-8')
+class GzipFile(object):
+ RE_FILE_TYPE = re.compile(r'^gzip compressed data\b')
+ @staticmethod
+ def recognizes(file):
+ return GzipFile.RE_FILE_TYPE.match(file.magic_file_type)
- at binary_fallback
- at returns_details
-def compare_gzip_files(path1, path2, source=None):
- differences = []
- # check metadata
- metadata1 = get_gzip_metadata(path1)
- metadata2 = get_gzip_metadata(path2)
- differences.append(Difference.from_unicode(
- metadata1, metadata2, path1, path2, source='metadata'))
- # check content
- with decompress_gzip(path1) as new_path1:
- with decompress_gzip(path2) as new_path2:
- differences.append(debbindiff.comparators.compare_files(
- new_path1, new_path2,
- source=[os.path.basename(new_path1), os.path.basename(new_path2)]))
- return differences
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(Difference.from_unicode(
+ self.magic_file_type, other.magic_file_type, self, other, source='metadata'))
+ with GzipContainer(self).open() as my_container, \
+ GzipContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/haskell.py b/debbindiff/comparators/haskell.py
index e3c0ad5..a222b44 100644
--- a/debbindiff/comparators/haskell.py
+++ b/debbindiff/comparators/haskell.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,10 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+import re
+import struct
import subprocess
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
+from debbindiff import logger
class ShowIface(Command):
@@ -29,7 +33,42 @@ class ShowIface(Command):
return ['ghc', '--show-iface', self.path]
- at binary_fallback
- at returns_details
-def compare_hi_files(path1, path2, source=None):
- return [Difference.from_command(ShowIface, path1, path2)]
+HI_MAGIC = 33214052
+
+
+class HiFile(File):
+ RE_FILE_EXTENSION = re.compile(r'\.(p_|dyn_)?hi$')
+
+ @staticmethod
+ def recognizes(file):
+ if not HiFile.RE_FILE_EXTENSION.search(file.name):
+ return False
+ if not hasattr(HiFile, 'ghc_version'):
+ try:
+ output = subprocess.check_output(['ghc', '--numeric-version'], shell=False)
+ HiFile.ghc_version = output.decode('utf-8').strip().split('.')
+ logger.debug('Found GHC version %s', HiFile.ghc_version)
+ except OSError:
+ HiFile.ghc_version = None
+ logger.debug('Unable to read GHC version')
+ if HiFile.ghc_version is None:
+ return False
+
+ with file.get_content():
+ with open(file.path) as fp:
+ buf = fp.read(32)
+ magic = struct.unpack_from('!I', buf)[0]
+ if magic != HI_MAGIC:
+ logger.debug('Haskell interface magic mismatch. Found %d instead of %d' % (magic, HI_MAGIC))
+ return False
+ # XXX: what is second field for?
+ version_found = map(unichr, struct.unpack_from('<I4xIB', buf, 16))
+ if version_found != HiFile.ghc_version:
+ logger.debug('Haskell version mismatch. Found %s instead of %s.',
+ version_found, HiFile.ghc_version)
+ return False
+ return True
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(ShowIface, self.path, other.path)]
diff --git a/debbindiff/comparators/ipk.py b/debbindiff/comparators/ipk.py
index 7a95b77..4181738 100644
--- a/debbindiff/comparators/ipk.py
+++ b/debbindiff/comparators/ipk.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,28 +18,15 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+import re
import os.path
-from debbindiff.comparators.utils import binary_fallback, returns_details
-from debbindiff.comparators.tar import compare_tar_files
-from debbindiff.comparators.gzip import decompress_gzip, get_gzip_metadata
+from debbindiff.comparators.gzip import GzipFile
from debbindiff.difference import Difference
- at binary_fallback
- at returns_details
-# ipk packages are just .tar.gz archives
-def compare_ipk_files(path1, path2, source=None):
- differences = []
- # metadata
- metadata1 = get_gzip_metadata(path1)
- metadata2 = get_gzip_metadata(path2)
- differences.append(Difference.from_unicode(
- metadata1, metadata2, path1, path2, source='metadata'))
+class IpkFile(GzipFile):
+ RE_FILE_EXTENSION = re.compile('\.ipk$')
- # content
- with decompress_gzip(path1) as tar1:
- with decompress_gzip(path2) as tar2:
- differences.append(compare_tar_files(tar1, tar2,
- source=[os.path.basename(tar1), os.path.basename(tar2)]))
-
- return differences
+ @staticmethod
+ def recognizes(file):
+ return IpkFile.RE_FILE_EXTENSION.search(file.name)
diff --git a/debbindiff/comparators/iso9660.py b/debbindiff/comparators/iso9660.py
index 04d0356..9ac3748 100644
--- a/debbindiff/comparators/iso9660.py
+++ b/debbindiff/comparators/iso9660.py
@@ -18,10 +18,13 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
import os.path
+import re
import subprocess
import debbindiff.comparators
from debbindiff import logger, tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.libarchive import LibarchiveContainer
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
@@ -59,37 +62,21 @@ class ISO9660Listing(Command):
else:
return line
- at tool_required('isoinfo')
-def extract_from_iso9660(image_path, in_path, dest):
- # Use RockRidge, same as get_iso9660_names
- cmd = ['isoinfo', '-i', image_path, '-R', '-x', in_path]
- return subprocess.check_call(cmd, shell=False, stdout=dest)
-
-
- at binary_fallback
- at returns_details
-def compare_iso9660_files(path1, path2, source=None):
- differences = []
- # compare metadata
- differences.append(Difference.from_command(ISO9660PVD, path1, path2))
- for extension in (None, 'joliet', 'rockridge'):
- differences.append(Difference.from_command(ISO9660Listing, path1, path2, command_args=(extension,)))
+class Iso9660File(File):
+ RE_FILE_TYPE = re.compile(r'\bISO 9660\b')
- # compare files contained in image
- files1 = get_iso9660_names(path1)
- files2 = get_iso9660_names(path2)
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- for name in sorted(set(files1).intersection(files2)):
- logger.debug('extract file %s' % name)
- in_path1 = os.path.join(temp_dir1, os.path.basename(name))
- in_path2 = os.path.join(temp_dir2, os.path.basename(name))
- with open(in_path1, 'w') as dest:
- extract_from_iso9660(path1, name, dest)
- with open(in_path2, 'w') as dest:
- extract_from_iso9660(path2, name, dest)
- differences.append(debbindiff.comparators.compare_files(
- in_path1, in_path2, source=name))
+ @staticmethod
+ def recognizes(file):
+ return Iso9660File.RE_FILE_TYPE.search(file.magic_file_type)
- return differences
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(Difference.from_command(ISO9660PVD, self.path, other.path))
+ for extension in (None, 'joliet', 'rockridge'):
+ differences.append(Difference.from_command(ISO9660Listing, self.path, other.path, command_args=(extension,)))
+ with LibarchiveContainer(self).open() as my_container, \
+ LibarchiveContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/java.py b/debbindiff/comparators/java.py
index de8bb59..615985f 100644
--- a/debbindiff/comparators/java.py
+++ b/debbindiff/comparators/java.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,7 +21,8 @@
import os.path
import re
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
@@ -38,7 +40,14 @@ class Javap(Command):
return ''
return line
- at binary_fallback
- at returns_details
-def compare_class_files(path1, path2, source=None):
- return [Difference.from_command(Javap, path1, path2)]
+
+class ClassFile(File):
+ RE_FILE_TYPE = re.compile(r'^compiled Java class data\b')
+
+ @staticmethod
+ def recognizes(file):
+ return ClassFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(Javap, self.path, other.path)]
diff --git a/debbindiff/comparators/libarchive.py b/debbindiff/comparators/libarchive.py
new file mode 100644
index 0000000..c4791cf
--- /dev/null
+++ b/debbindiff/comparators/libarchive.py
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+#
+# debbindiff: highlight differences between two builds of Debian packages
+#
+# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
+#
+# debbindiff is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# debbindiff is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import absolute_import
+
+from contextlib import contextmanager
+import ctypes
+from itertools import dropwhile
+import os.path
+import libarchive
+from debbindiff import logger
+from debbindiff.comparators.device import Device
+from debbindiff.comparators.directory import Directory
+from debbindiff.comparators.symlink import Symlink
+from debbindiff.comparators.utils import Archive, ArchiveMember
+
+
+# Monkeypatch libarchive-c (<< 2.2)
+if not hasattr(libarchive.ffi, 'entry_rdevmajor'):
+ libarchive.ffi.ffi('entry_rdevmajor', [libarchive.ffi.c_archive_entry_p], ctypes.c_uint)
+ libarchive.ArchiveEntry.rdevmajor = property(lambda self: libarchive.ffi.entry_rdevmajor(self._entry_p))
+if not hasattr(libarchive.ffi, 'entry_rdevminor'):
+ libarchive.ffi.ffi('entry_rdevminor', [libarchive.ffi.c_archive_entry_p], ctypes.c_uint)
+ libarchive.ArchiveEntry.rdevminor = property(lambda self: libarchive.ffi.entry_rdevminor(self._entry_p))
+
+
+class LibarchiveMember(ArchiveMember):
+ def __init__(self, archive, entry):
+ super(LibarchiveMember, self).__init__(archive, entry.pathname)
+
+ def is_directory(self):
+ return False
+
+ def is_symlink(self):
+ return False
+
+ def is_device(self):
+ return False
+
+
+
+class LibarchiveDirectory(Directory, LibarchiveMember):
+ def __init__(self, archive, entry):
+ LibarchiveMember.__init__(self, archive, entry)
+
+ def compare(self, other, source=None):
+ return None
+
+ def has_same_content_as(self, other):
+ return False
+
+ @contextmanager
+ def get_content(self):
+ yield
+
+ def is_directory(self):
+ return True
+
+ def get_member_names(self):
+ raise ValueError("archives are compared as a whole.")
+
+ def get_member(self, member_name):
+ raise ValueError("archives are compared as a whole.")
+
+
+class LibarchiveSymlink(Symlink, LibarchiveMember):
+ def __init__(self, archive, entry):
+ LibarchiveMember.__init__(self, archive, entry)
+ self._destination = entry.linkpath
+
+ @property
+ def symlink_destination(self):
+ return self._destination
+
+ def is_symlink(self):
+ return True
+
+
+class LibarchiveDevice(Device, LibarchiveMember):
+ def __init__(self, container, entry):
+ LibarchiveMember.__init__(self, container, entry)
+ self._mode = entry.mode
+ self._major = entry.rdevmajor
+ self._minor = entry.rdevminor
+
+ def get_device(self):
+ return (self._mode, self._major, self._minor)
+
+ def is_device(self):
+ return True
+
+
+class LibarchiveContainer(Archive):
+ def open_archive(self, path):
+ # libarchive is very very stream oriented an not for random access
+ # so we are going to reopen the archive everytime
+ # not nice, but it'll work
+ return True
+
+ def close_archive(self):
+ return
+
+ def get_member_names(self):
+ with libarchive.file_reader(self.source.path) as archive:
+ member_names = [entry.pathname for entry in archive]
+ return member_names
+
+ def extract(self, member_name, dest_dir):
+ dest_path = os.path.join(dest_dir, os.path.basename(member_name))
+ logger.debug('libarchive extracting %s to %s', member_name, dest_path)
+ with libarchive.file_reader(self.source.path) as archive:
+ for entry in archive:
+ if entry.pathname == member_name:
+ logger.debug('entry found, writing %s', dest_path)
+ with open(dest_path, 'w') as f:
+ for buf in entry.get_blocks():
+ f.write(buf)
+ return dest_path
+ raise KeyError('%s not found in archive', member_name)
+
+ def get_member(self, member_name):
+ with libarchive.file_reader(self.source.path) as archive:
+ for entry in archive:
+ if entry.pathname == member_name:
+ if entry.isdir:
+ return LibarchiveDirectory(self, entry)
+ elif entry.issym:
+ return LibarchiveSymlink(self, entry)
+ elif entry.isblk or entry.ischr:
+ return LibarchiveDevice(self, entry)
+ else:
+ return LibarchiveMember(self, entry)
+ raise KeyError('%s not found in archive', member_name)
diff --git a/debbindiff/comparators/pe.py b/debbindiff/comparators/mono.py
similarity index 65%
rename from debbindiff/comparators/pe.py
rename to debbindiff/comparators/mono.py
index 8d56c2f..6a5b10f 100644
--- a/debbindiff/comparators/pe.py
+++ b/debbindiff/comparators/mono.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Daniel Kahn Gillmor <dkg at fifthhorseman.net>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,8 +18,10 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+import re
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
@@ -28,7 +31,13 @@ class Pedump(Command):
return ['pedump', self.path]
- at binary_fallback
- at returns_details
-def compare_pe_files(path1, path2, source=None):
- return [Difference.from_command(Pedump, path1, path2)]
+class MonoExeFile(File):
+ RE_FILE_TYPE = re.compile(r'\bPE[0-9]+\b.*\bMono\b')
+
+ @staticmethod
+ def recognizes(file):
+ return MonoExeFile.RE_FILE_TYPE.search(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(Pedump, self.path, other.path)]
diff --git a/debbindiff/comparators/pdf.py b/debbindiff/comparators/pdf.py
index 8a35ad5..d4512e6 100644
--- a/debbindiff/comparators/pdf.py
+++ b/debbindiff/comparators/pdf.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2015-2015 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,9 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+import re
import subprocess
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference, get_source
@@ -38,10 +40,16 @@ class Pdftk(Command):
return line.decode('latin-1').encode('utf-8')
- at binary_fallback
- at returns_details
-def compare_pdf_files(path1, path2, source=None):
- differences = []
- differences.append(Difference.from_command(Pdftotext, path1, path2))
- differences.append(Difference.from_command(Pdftk, path1, path2))
- return differences
+class PdfFile(File):
+ RE_FILE_TYPE = re.compile(r'^PDF document\b')
+
+ @staticmethod
+ def recognizes(file):
+ return PdfFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(Difference.from_command(Pdftotext, self.path, other.path))
+ differences.append(Difference.from_command(Pdftk, self.path, other.path))
+ return differences
diff --git a/debbindiff/comparators/png.py b/debbindiff/comparators/png.py
index 64fc104..e99be95 100644
--- a/debbindiff/comparators/png.py
+++ b/debbindiff/comparators/png.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,9 +18,11 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
+import re
import subprocess
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Command
from debbindiff.difference import Difference
@@ -38,8 +40,13 @@ class Sng(Command):
stdin.close()
- at binary_fallback
- at returns_details
-def compare_png_files(path1, path2, source=None):
- return [Difference.from_command(Sng, path1, path2, source='sng')]
+class PngFile(File):
+ RE_FILE_TYPE = re.compile(r'^PNG image data\b')
+ @staticmethod
+ def recognizes(file):
+ return PngFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ return [Difference.from_command(Sng, self.path, other.path, source='sng')]
diff --git a/debbindiff/comparators/rpm.py b/debbindiff/comparators/rpm.py
index 52b92c8..9cf25e7 100644
--- a/debbindiff/comparators/rpm.py
+++ b/debbindiff/comparators/rpm.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,12 +20,15 @@
from __future__ import absolute_import
import os.path
+import re
import subprocess
from contextlib import contextmanager
import rpm
-import debbindiff.comparators
from debbindiff import logger, tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory
+import debbindiff.comparators
+from debbindiff.comparators.rpm_fallback import AbstractRpmFile
+from debbindiff.comparators.binary import File, FilesystemFile, needs_content
+from debbindiff.comparators.utils import Archive, make_temp_directory
from debbindiff.difference import Difference, get_source
def get_rpm_header(path, ts):
@@ -48,27 +52,7 @@ def get_rpm_header(path, ts):
return header
- at contextmanager
- at tool_required('rpm2cpio')
-def extract_rpm_payload(path):
- cmd = ['rpm2cpio', path]
- with make_temp_directory() as temp_dir:
- temp_path = os.path.join(temp_dir, "CONTENTS.cpio")
- with open(temp_path, 'wb') as temp_file:
- p = subprocess.Popen(cmd, shell=False,
- stdout=temp_file, stderr=subprocess.PIPE)
- p.wait()
- if p.returncode != 0:
- logger.error("rpm2cpio exited with error code %d", p.returncode)
-
- yield temp_path
-
-
- at binary_fallback
- at returns_details
-def compare_rpm_files(path1, path2, source=None):
- differences = []
-
+def compare_rpm_headers(path1, path2):
# compare headers
with make_temp_directory() as rpmdb_dir:
rpm.addMacro("_dbpath", rpmdb_dir)
@@ -76,13 +60,40 @@ def compare_rpm_files(path1, path2, source=None):
ts.setVSFlags(-1)
header1 = get_rpm_header(path1, ts)
header2 = get_rpm_header(path2, ts)
- differences.append(Difference.from_unicode(
- header1, header2, path1, path2, source="header"))
+ return Difference.from_unicode(header1, header2, path1, path2, source="header")
+
+
+class RpmContainer(Archive):
+ @property
+ def path(self):
+ return self._path
+
+ def open_archive(self, path):
+ self._path = path
+ return self
+
+ def close_archive(self):
+ self._path = None
+
+ def get_member_names(self):
+ return ['content']
+
+ @tool_required('rpm2cpio')
+ def extract(self, member_name, dest_dir):
+ assert member_name == 'content'
+ dest_path = os.path.join(dest_dir, 'content')
+ cmd = ['rpm2cpio', self._path]
+ with open(dest_path, 'wb') as dest:
+ subprocess.check_call(cmd, shell=False, stdout=dest, stderr=subprocess.PIPE)
+ return dest_path
- # extract cpio archive
- with extract_rpm_payload(path1) as archive1:
- with extract_rpm_payload(path2) as archive2:
- differences.append(debbindiff.comparators.compare_files(
- archive1, archive2, source=get_source(archive1, archive2)))
- return differences
+class RpmFile(AbstractRpmFile):
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(compare_rpm_headers(self.path, other.path))
+ with RpmContainer(self).open() as my_container, \
+ RpmContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/haskell.py b/debbindiff/comparators/rpm_fallback.py
similarity index 55%
copy from debbindiff/comparators/haskell.py
copy to debbindiff/comparators/rpm_fallback.py
index e3c0ad5..db1d352 100644
--- a/debbindiff/comparators/haskell.py
+++ b/debbindiff/comparators/rpm_fallback.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,19 +17,20 @@
# You should have received a copy of the GNU General Public License
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
-import subprocess
-from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, Command
-from debbindiff.difference import Difference
+import re
+from debbindiff.comparators.binary import File
+class AbstractRpmFile(File):
+ RE_FILE_TYPE = re.compile('^RPM\s')
-class ShowIface(Command):
- @tool_required('ghc')
- def cmdline(self):
- return ['ghc', '--show-iface', self.path]
+ @staticmethod
+ def recognizes(file):
+ return AbstractRpmFile.RE_FILE_TYPE.search(file.magic_file_type)
-
- at binary_fallback
- at returns_details
-def compare_hi_files(path1, path2, source=None):
- return [Difference.from_command(ShowIface, path1, path2)]
+class RpmFile(File):
+ def compare(self, other, source=None):
+ difference = self.compare_bytes(other)
+ if not difference:
+ return None
+ difference.comment = 'Unable to find Python rpm module. Falling back to binary comparison.'
+ return difference
diff --git a/debbindiff/comparators/squashfs.py b/debbindiff/comparators/squashfs.py
index e09df27..62d2725 100644
--- a/debbindiff/comparators/squashfs.py
+++ b/debbindiff/comparators/squashfs.py
@@ -3,6 +3,7 @@
# debbindiff: highlight differences between two builds of Debian packages
#
# Copyright © 2015 Reiner Herrmann <reiner at reiner-h.de>
+# 2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,17 +21,17 @@
import re
import subprocess
import os.path
+import stat
import debbindiff.comparators
from debbindiff import logger, tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.device import Device
+from debbindiff.comparators.directory import Directory
+from debbindiff.comparators.libarchive import LibarchiveContainer
+from debbindiff.comparators.symlink import Symlink
+from debbindiff.comparators.utils import Archive, ArchiveMember, Command
from debbindiff.difference import Difference
-
-
- at tool_required('unsquashfs')
-def get_squashfs_names(path):
- cmd = ['unsquashfs', '-d', '', '-ls', path]
- output = subprocess.check_output(cmd, shell=False)
- return [ f.lstrip('/') for f in output.split('\n') ]
+from debbindiff import logger
class SquashfsSuperblock(Command):
@@ -49,39 +50,156 @@ class SquashfsListing(Command):
return ['unsquashfs', '-d', '', '-lls', self.path]
- at tool_required('unsquashfs')
-def extract_squashfs(path, destdir):
- cmd = ['unsquashfs', '-n', '-f', '-d', destdir, path]
- logger.debug("extracting %s into %s", path, destdir)
- p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE)
- p.communicate()
- p.wait()
- if p.returncode != 0:
- logger.error('unsquashfs exited with error code %d', p.returncode)
-
-
- at binary_fallback
- at returns_details
-def compare_squashfs_files(path1, path2, source=None):
- differences = []
-
- # compare metadata
- differences.append(Difference.from_command(SquashfsSuperblock, path1, path2))
- differences.append(Difference.from_command(SquashfsListing, path1, path2))
-
- # compare files contained in archive
- files1 = get_squashfs_names(path1)
- files2 = get_squashfs_names(path2)
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- extract_squashfs(path1, temp_dir1)
- extract_squashfs(path2, temp_dir2)
- for member in sorted(set(files1).intersection(set(files2))):
- in_path1 = os.path.join(temp_dir1, member)
- in_path2 = os.path.join(temp_dir2, member)
- if not os.path.isfile(in_path1) or not os.path.isfile(in_path2):
- continue
- differences.append(debbindiff.comparators.compare_files(
- in_path1, in_path2, source=member))
-
- return differences
+class SquashfsMember(ArchiveMember):
+ def is_directory(self):
+ return False
+
+ def is_symlink(self):
+ return False
+
+ def is_device(self):
+ return False
+
+
+class SquashfsRegularFile(SquashfsMember):
+ # Example line:
+ # -rw-r--r-- user/group 446 2015-06-24 14:49 squashfs-root/text
+ LINE_RE = re.compile(r'^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(?P<member_name>.*)$')
+
+ def __init__(self, archive, line):
+ m = SquashfsRegularFile.LINE_RE.match(line)
+ logger.debug('line %s m %s', line, m)
+ SquashfsMember.__init__(self, archive, m.group('member_name'))
+
+
+class SquashfsDirectory(Directory, SquashfsMember):
+ # Example line:
+ # drwxr-xr-x user/group 51 2015-06-24 14:47 squashfs-root
+ LINE_RE = re.compile(r'^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(?P<member_name>.*)$')
+
+ def __init__(self, archive, line):
+ m = SquashfsDirectory.LINE_RE.match(line)
+ SquashfsMember.__init__(self, archive, m.group('member_name') or '/')
+
+ def compare(self, other, source=None):
+ return None
+
+ def has_same_content_as(self, other):
+ return False
+
+ def is_directory(self):
+ return True
+
+ def get_member_names(self):
+ raise ValueError("squashfs are compared as a whole.")
+
+ def get_member(self, member_name):
+ raise ValueError("squashfs are compared as a whole.")
+
+
+class SquashfsSymlink(Symlink, SquashfsMember):
+ # Example line:
+ # lrwxrwxrwx user/group 6 2015-06-24 14:47 squashfs-root/link -> broken
+ LINE_RE = re.compile(r'^\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(?P<member_name>.*)\s+->\s+(?P<destination>.*)$')
+
+ def __init__(self, archive, line):
+ m = SquashfsSymlink.LINE_RE.match(line)
+ SquashfsMember.__init__(self, archive, m.group('member_name'))
+ self._destination = m.group('destination')
+
+ def is_symlink(self):
+ return True
+
+ @property
+ def symlink_destination(self):
+ return self._destination
+
+
+class SquashfsDevice(Device, SquashfsMember):
+ # Example line:
+ # crw-r--r-- root/root 1, 3 2015-06-24 14:47 squashfs-root/null
+ LINE_RE = re.compile(r'^(?P<kind>c|b)\S+\s+\S+\s+(?P<major>\d+),\s+(?P<minor>\d+)\s+\S+\s+\S+\s+(?P<member_name>.*)$')
+
+ KIND_MAP = { 'c': stat.S_IFCHR,
+ 'b': stat.S_IFBLK,
+ }
+
+ def __init__(self, archive, line):
+ m = SquashfsDevice.LINE_RE.match(line)
+ SquashfsMember.__init__(self, archive, m.group('member_name'))
+ self._mode = SquashfsDevice.KIND_MAP[m.group('kind')]
+ self._major = int(m.group('major'))
+ self._minor = int(m.group('minor'))
+
+ def get_device(self):
+ return (self._mode, self._major, self._minor)
+
+ def is_device(self):
+ return True
+
+
+SQUASHFS_LS_MAPPING = {
+ 'd': SquashfsDirectory,
+ 'l': SquashfsSymlink,
+ 'c': SquashfsDevice,
+ 'b': SquashfsDevice,
+ '-': SquashfsRegularFile
+ }
+
+
+class SquashfsContainer(Archive):
+ @tool_required('unsquashfs')
+ def entries(self, path):
+ # We pass `-d ''` in order to get a listing with the names we actually
+ # need to use when extracting files
+ cmd = ['unsquashfs', '-d', '', '-lls', path]
+ output = subprocess.check_output(cmd, shell=False)
+ header = True
+ for line in output.split('\n'):
+ if header:
+ if line == '':
+ header = False
+ continue
+ if len(line) > 0 and line[0] in SQUASHFS_LS_MAPPING:
+ yield SQUASHFS_LS_MAPPING[line[0]](self, line)
+ else:
+ logger.warning('Unknown squashfs entry: %s', line)
+
+ def open_archive(self, path):
+ return dict([(m.name, m) for m in self.entries(path)])
+
+ def close_archive(self):
+ pass
+
+ def get_member_names(self):
+ return self.archive.keys()
+
+ @tool_required('unsquashfs')
+ def extract(self, member_name, dest_dir):
+ if '..' in member_name.split('/'):
+ raise ValueError('relative path in squashfs')
+ cmd = ['unsquashfs', '-n', '-f', '-d', dest_dir, self.source.path, member_name]
+ logger.debug("unquashfs %s into %s", member_name, dest_dir)
+ subprocess.check_call(cmd, shell=False, stdout=subprocess.PIPE)
+ return '%s%s' % (dest_dir, member_name)
+
+ def get_member(self, member_name):
+ return self.archive[member_name]
+
+
+class SquashfsFile(File):
+ RE_FILE_TYPE = re.compile(r'^Squashfs filesystem\b')
+
+ @staticmethod
+ def recognizes(file):
+ return SquashfsFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ differences.append(Difference.from_command(SquashfsSuperblock, self.path, other.path))
+ differences.append(Difference.from_command(SquashfsListing, self.path, other.path))
+ with SquashfsContainer(self).open() as my_container, \
+ SquashfsContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/symlink.py b/debbindiff/comparators/symlink.py
new file mode 100644
index 0000000..b87cdec
--- /dev/null
+++ b/debbindiff/comparators/symlink.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+#
+# debbindiff: highlight differences between two builds of Debian packages
+#
+# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
+#
+# debbindiff is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# debbindiff is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
+
+from contextlib import contextmanager
+import os
+import tempfile
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import format_symlink
+from debbindiff.difference import Difference
+from debbindiff import logger
+
+
+class Symlink(File):
+ @staticmethod
+ def recognizes(file):
+ return file.is_symlink()
+
+ @property
+ def symlink_destination(self):
+ return os.readlink(self.name)
+
+ @contextmanager
+ def get_content(self):
+ with tempfile.NamedTemporaryFile(suffix='debbindiff') as f:
+ f.write(format_symlink(self.symlink_destination))
+ f.flush()
+ self._path = f.name
+ yield
+ self._path = None
+
+ @needs_content
+ def compare(self, other, source=None):
+ logger.debug('my_content %s' % self.path)
+ with open(self.path) as my_content, \
+ open(other.path) as other_content:
+ return Difference.from_file(my_content, other_content, self.name, other.name, source=source, comment="symlink")
diff --git a/debbindiff/comparators/tar.py b/debbindiff/comparators/tar.py
index 74b6994..d7e0440 100644
--- a/debbindiff/comparators/tar.py
+++ b/debbindiff/comparators/tar.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -18,57 +18,135 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
import os.path
+import re
+import stat
from StringIO import StringIO
import sys
import tarfile
from debbindiff import logger
from debbindiff.difference import Difference
import debbindiff.comparators
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.device import Device
+from debbindiff.comparators.directory import Directory
+from debbindiff.comparators.symlink import Symlink
+from debbindiff.comparators.utils import Archive, ArchiveMember
+class TarMember(ArchiveMember):
+ def is_directory(self):
+ return False
-def get_tar_content(tar):
+ def is_symlink(self):
+ return False
+
+ def is_device(self):
+ return False
+
+
+class TarDirectory(Directory, TarMember):
+ def __init__(self, archive, member_name):
+ ArchiveMember.__init__(self, archive, member_name)
+
+ def compare(self, other, source=None):
+ return None
+
+ def has_same_content_as(self, other):
+ return False
+
+ def is_directory(self):
+ return True
+
+ def get_member_names(self):
+ raise ValueError("Tar archives are compared as a whole.")
+
+ def get_member(self, member_name):
+ raise ValueError("Tar archives are compared as a whole.")
+
+class TarSymlink(Symlink, TarMember):
+ def __init__(self, archive, member_name, destination):
+ TarMember.__init__(self, archive, member_name)
+ self._destination = destination
+
+ def is_symlink(self):
+ return True
+
+ @property
+ def symlink_destination(self):
+ return self._destination
+
+
+class TarDevice(Device, TarMember):
+ def __init__(self, archive, member_name, mode, major, minor):
+ TarMember.__init__(self, archive, member_name)
+ self._mode = mode
+ self._major = major
+ self._minor = minor
+
+ def get_device(self):
+ return (self._mode, self._major, self._minor)
+
+ def is_device(self):
+ return True
+
+
+class TarContainer(Archive):
+ def open_archive(self, path):
+ return tarfile.open(path, 'r')
+
+ def close_archive(self):
+ self.archive.close()
+
+ def get_member_names(self):
+ return self.archive.getnames()
+
+ def extract(self, member_name, dest_dir):
+ logger.debug('tar extracting %s to %s', member_name, dest_dir)
+ self.archive.extract(member_name, dest_dir)
+ return os.path.join(dest_dir, member_name).decode('utf-8')
+
+ def get_member(self, member_name):
+ tarinfo = self.archive.getmember(member_name)
+ if tarinfo.isdir():
+ return TarDirectory(self, member_name)
+ elif tarinfo.issym():
+ return TarSymlink(self, member_name, tarinfo.linkname)
+ elif tarinfo.ischr() or tarinfo.isblk():
+ mode = tarinfo.mode
+ if tarinfo.isblk():
+ mode |= stat.S_IFBLK
+ else:
+ mode |= stat.S_IFCHR
+ return TarDevice(self, member_name, mode, tarinfo.devmajor, tarinfo.devminor)
+ else:
+ return TarMember(self, member_name)
+
+
+def get_tar_listing(tar):
orig_stdout = sys.stdout
output = StringIO()
try:
sys.stdout = output
tar.list(verbose=True)
- return output.getvalue()
+ return output.getvalue().decode('utf-8')
finally:
sys.stdout = orig_stdout
+class TarFile(File):
+ RE_FILE_TYPE = re.compile(r'\btar archive\b')
+
+ @staticmethod
+ def recognizes(file):
+ return TarFile.RE_FILE_TYPE.search(file.magic_file_type)
- at binary_fallback
- at returns_details
-def compare_tar_files(path1, path2, source=None):
- differences = []
- with tarfile.open(path1, 'r') as tar1:
- with tarfile.open(path2, 'r') as tar2:
- # look up differences in content
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- logger.debug('content1 %s', tar1.getnames())
- logger.debug('content2 %s', tar2.getnames())
- for name in sorted(set(tar1.getnames())
- .intersection(tar2.getnames())):
- member1 = tar1.getmember(name)
- member2 = tar2.getmember(name)
- if not member1.isfile() or not member2.isfile():
- continue
- logger.debug('extract member %s', name)
- tar1.extract(name, temp_dir1)
- tar2.extract(name, temp_dir2)
- in_path1 = os.path.join(temp_dir1, name).decode('utf-8')
- in_path2 = os.path.join(temp_dir2, name).decode('utf-8')
- differences.append(
- debbindiff.comparators.compare_files(
- in_path1, in_path2,
- source=name.decode('utf-8')))
- os.unlink(in_path1)
- os.unlink(in_path2)
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ with TarContainer(self).open() as my_container, \
+ TarContainer(other).open() as other_container:
# look up differences in file list and file metadata
- content1 = get_tar_content(tar1).decode('utf-8')
- content2 = get_tar_content(tar2).decode('utf-8')
+ my_listing = get_tar_listing(my_container.archive)
+ other_listing = get_tar_listing(other_container.archive)
differences.append(Difference.from_unicode(
- content1, content2, path1, path2, source="metadata"))
- return differences
+ my_listing, other_listing, self.name, other.name, source="metadata"))
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debbindiff/comparators/text.py b/debbindiff/comparators/text.py
index 3f07769..00ad6c9 100644
--- a/debbindiff/comparators/text.py
+++ b/debbindiff/comparators/text.py
@@ -18,23 +18,37 @@
# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
import codecs
-from debbindiff.comparators.binary import compare_binary_files
+import re
+from debbindiff.comparators.binary import File, needs_content
from debbindiff.difference import Difference
+from debbindiff import logger
-def compare_text_files(path1, path2, encoding=None, source=None, encoding2=None):
- if encoding is None:
- encoding = 'utf-8'
- if encoding2 is None:
- encoding2 = encoding
+class TextFile(File):
+ RE_FILE_TYPE = re.compile(r'\btext\b')
- try:
- file1 = codecs.open(path1, 'r', encoding=encoding)
- file2 = codecs.open(path2, 'r', encoding=encoding2)
- return Difference.from_file(file1, file2, path1, path2, source)
- except (LookupError, UnicodeDecodeError):
- # unknown or misdetected encoding
- if encoding is not 'utf-8':
- return compare_text_files(path1, path2, 'utf-8', source)
- else:
- return compare_binary_files(path1, path2, source)
+ @staticmethod
+ def recognizes(file):
+ return TextFile.RE_FILE_TYPE.search(file.magic_file_type)
+
+ @property
+ def encoding(self):
+ if not hasattr(self, '_encoding'):
+ with self.get_content():
+ self._encoding = File.guess_encoding(self.path)
+ return self._encoding
+
+ @needs_content
+ def compare(self, other, source=None):
+ my_encoding = self.encoding or 'utf-8'
+ other_encoding = other.encoding or 'utf-8'
+ try:
+ with codecs.open(self.path, 'r', encoding=my_encoding) as my_content, \
+ codecs.open(other.path, 'r', encoding=other_encoding) as other_content:
+ difference = Difference.from_file(my_content, other_content, self.name, other.name, source)
+ if my_encoding != other_encoding:
+ difference.add_details([Difference.from_unicode(my_encoding, other_encoding, None, None, source='encoding')])
+ return difference
+ except (LookupError, UnicodeDecodeError):
+ # unknown or misdetected encoding
+ return self.compare_bytes(other, source)
diff --git a/debbindiff/comparators/utils.py b/debbindiff/comparators/utils.py
index 0d0ae32..7734a40 100644
--- a/debbindiff/comparators/utils.py
+++ b/debbindiff/comparators/utils.py
@@ -24,59 +24,18 @@ import hashlib
import re
import os
import shutil
+from stat import S_ISCHR, S_ISBLK
from StringIO import StringIO
import subprocess
import tempfile
from threading import Thread
+import debbindiff.comparators
from debbindiff.comparators.binary import \
- compare_binary_files, are_same_binaries
+ File, compare_binary_files
from debbindiff.difference import Difference
from debbindiff import logger, RequiredToolNotFound
-# decorator that will create a fallback on binary diff if no differences
-# are detected or if an external tool fails
-def binary_fallback(original_function):
- def with_fallback(path1, path2, source=None):
- if are_same_binaries(path1, path2):
- return None
- try:
- difference = original_function(path1, path2, source)
- # no differences detected inside? let's at least do a binary diff
- if difference is None:
- difference = compare_binary_files(path1, path2, source=source)
- difference.comment = (difference.comment or '') + \
- "No differences found inside, yet data differs"
- except subprocess.CalledProcessError as e:
- difference = compare_binary_files(path1, path2, source=source)
- output = re.sub(r'^', ' ', e.output, flags=re.MULTILINE)
- cmd = ' '.join(e.cmd)
- difference.comment = (difference.comment or '') + \
- "Command `%s` exited with %d. Output:\n%s" \
- % (cmd, e.returncode, output)
- except RequiredToolNotFound as e:
- difference = compare_binary_files(path1, path2, source=source)
- difference.comment = (difference.comment or '') + \
- "'%s' not available in path. Falling back to binary comparison." % e.command
- package = e.get_package()
- if package:
- difference.comment += "\nInstall '%s' to get a better output." % package
- return difference
- return with_fallback
-
-
-# decorator that will group multiple differences as details of an empty Difference
-# are detected or if an external tool fails
-def returns_details(original_function):
- def wrap_details(path1, path2, source=None):
- details = [d for d in original_function(path1, path2, source) if d is not None]
- if len(details) == 0:
- return None
- difference = Difference(None, path1, path2, source=source)
- difference.add_details(details)
- return difference
- return wrap_details
-
@contextmanager
def make_temp_directory():
temp_dir = tempfile.mkdtemp(suffix='debbindiff')
@@ -159,3 +118,146 @@ class Command(object):
@property
def stdout(self):
return self._process.stdout
+
+
+
+
+def format_symlink(destination):
+ return 'destination: %s\n' % destination
+
+
+def format_device(mode, major, minor):
+ if S_ISCHR(mode):
+ kind = 'character'
+ elif S_ISBLK(mode):
+ kind = 'block'
+ else:
+ kind = 'weird'
+ return 'device:%s\nmajor: %d\n minor: %d\n' % (kind, major, minor)
+
+
+def get_compressed_content_name(path, expected_extension):
+ return 'content'
+ # XXX: implement fuzzy-matching support to get the thing below to work
+ basename = os.path.basename(path)
+ if basename.endswith(expected_extension):
+ name = basename[:-3]
+ else:
+ name = "%s-content" % basename
+ return name
+
+
+class Container(object):
+ __metaclass__ = ABCMeta
+
+ def __init__(self, source):
+ self._source = source
+
+ @property
+ def source(self):
+ return self._source
+
+ @contextmanager
+ def open(self):
+ raise NotImplemented
+
+ @abstractmethod
+ def get_member_names(self):
+ raise NotImplemented
+
+ @abstractmethod
+ def get_member(self, member_name):
+ raise NotImplemented
+
+ def compare(self, other, source=None):
+ differences = []
+ my_names = self.get_member_names()
+ other_names = other.get_member_names()
+ for name in sorted(set(my_names).intersection(other_names)):
+ logger.debug('compare member %s', name)
+ my_file = self.get_member(name)
+ other_file = other.get_member(name)
+ differences.append(
+ debbindiff.comparators.compare_files(
+ my_file, other_file, source=name))
+ return differences
+
+
+class ArchiveMember(File):
+ def __init__(self, container, member_name):
+ self._container = container
+ self._name = member_name
+ self._path = None
+
+ @property
+ def container(self):
+ return self._container
+
+ @property
+ def name(self):
+ return self._name
+
+ @contextmanager
+ def get_content(self):
+ logger.debug('%s get_content; path %s', self, self._path)
+ if self._path is not None:
+ yield
+ else:
+ with make_temp_directory() as temp_dir, \
+ self._container.open() as container:
+ self._path = container.extract(self._name, temp_dir)
+ yield
+ self._path = None
+
+ def is_directory(self):
+ return False
+
+ def is_symlink(self):
+ return False
+
+ def is_device(self):
+ return False
+
+
+class Archive(Container):
+ __metaclass__ = ABCMeta
+
+ def __init__(self, *args, **kwargs):
+ super(Archive, self).__init__(*args, **kwargs)
+ self._archive = None
+
+ @contextmanager
+ def open(self):
+ if self._archive is not None:
+ yield self
+ else:
+ with self.source.get_content():
+ self._archive = self.open_archive(self.source.path)
+ try:
+ yield self
+ finally:
+ self.close_archive()
+ self._archive = None
+
+ @property
+ def archive(self):
+ return self._archive
+
+ @abstractmethod
+ def open_archive(self, path):
+ raise NotImplemented
+
+ @abstractmethod
+ def close_archive(self):
+ raise NotImplemented
+
+ @abstractmethod
+ def get_member_names(self):
+ raise NotImplemented
+
+ @abstractmethod
+ def extract(self, member_name, dest_dir):
+ raise NotImplemented
+
+ def get_member(self, member_name):
+ return ArchiveMember(self, member_name)
diff --git a/debbindiff/comparators/xz.py b/debbindiff/comparators/xz.py
index fe987f7..261ad75 100644
--- a/debbindiff/comparators/xz.py
+++ b/debbindiff/comparators/xz.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,33 +19,50 @@
from contextlib import contextmanager
import os.path
+import re
import subprocess
import debbindiff.comparators
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory
-from debbindiff.difference import get_source
-
-
- at contextmanager
- at tool_required('xz')
-def decompress_xz(path):
- with make_temp_directory() as temp_dir:
- if path.endswith('.xz'):
- temp_path = os.path.join(temp_dir, os.path.basename(path[:-3]))
- else:
- temp_path = os.path.join(temp_dir, "%s-content" % path)
- with open(temp_path, 'wb') as temp_file:
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.utils import Archive, get_compressed_content_name
+from debbindiff import logger, tool_required
+
+
+class XzContainer(Archive):
+ @property
+ def path(self):
+ return self._path
+
+ def open_archive(self, path):
+ self._path = path
+ return self
+
+ def close_archive(self):
+ self._path = None
+
+ def get_member_names(self):
+ return [get_compressed_content_name(self.path, '.xz')]
+
+ @tool_required('xz')
+ def extract(self, member_name, dest_dir):
+ dest_path = os.path.join(dest_dir, member_name)
+ logger.debug('xz extracting to %s' % dest_path)
+ with open(dest_path, 'wb') as fp:
subprocess.check_call(
- ["xz", "--decompress", "--stdout", path],
- shell=False, stdout=temp_file, stderr=None)
- yield temp_path
-
-
- at binary_fallback
- at returns_details
-def compare_xz_files(path1, path2, source=None):
- with decompress_xz(path1) as new_path1:
- with decompress_xz(path2) as new_path2:
- return [debbindiff.comparators.compare_files(
- new_path1, new_path2,
- source=[os.path.basename(new_path1), os.path.basename(new_path2)])]
+ ["xz", "--decompress", "--stdout", self.path],
+ shell=False, stdout=fp, stderr=None)
+ return dest_path
+
+
+class XzFile(File):
+ RE_FILE_TYPE = re.compile(r'^XZ compressed data$')
+
+ @staticmethod
+ def recognizes(file):
+ return XzFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ with XzContainer(self).open() as my_container, \
+ XzContainer(other).open() as other_container:
+ return my_container.compare(other_container, source)
diff --git a/debbindiff/comparators/zip.py b/debbindiff/comparators/zip.py
index 6c52390..c1befa1 100644
--- a/debbindiff/comparators/zip.py
+++ b/debbindiff/comparators/zip.py
@@ -2,7 +2,7 @@
#
# debbindiff: highlight differences between two builds of Debian packages
#
-# Copyright © 2014 Jérémy Bobbio <lunar at debian.org>
+# Copyright © 2014-2015 Jérémy Bobbio <lunar at debian.org>
#
# debbindiff is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -20,12 +20,14 @@
import os.path
import re
import subprocess
-from zipfile import ZipFile, BadZipfile
+import zipfile
from debbindiff import logger
from debbindiff.difference import Difference
import debbindiff.comparators
from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details, make_temp_directory, Command
+from debbindiff.comparators.binary import File, needs_content
+from debbindiff.comparators.directory import Directory
+from debbindiff.comparators.utils import Archive, ArchiveMember, Command
class Zipinfo(Command):
@@ -46,37 +48,62 @@ class ZipinfoVerbose(Zipinfo):
return ['zipinfo', '-v', self.path]
- at binary_fallback
- at returns_details
-def compare_zip_files(path1, path2, source=None):
- differences = []
- try:
- with ZipFile(path1, 'r') as zip1:
- with ZipFile(path2, 'r') as zip2:
- # look up differences in content
- with make_temp_directory() as temp_dir1:
- with make_temp_directory() as temp_dir2:
- for name in sorted(set(zip1.namelist())
- .intersection(zip2.namelist())):
- # skip directories
- if name.endswith('/'):
- continue
- logger.debug('extract member %s', name)
- in_path1 = zip1.extract(name, temp_dir1)
- in_path2 = zip2.extract(name, temp_dir2)
- differences.append(
- debbindiff.comparators.compare_files(
- in_path1, in_path2,
- source=name))
- os.unlink(in_path1)
- os.unlink(in_path2)
- # look up differences in metadata
- difference = Difference.from_command(Zipinfo, path1, path2)
- if not difference:
- # search harder
- difference = Difference.from_command(ZipinfoVerbose, path1, path2)
- differences.append(difference)
- except BadZipfile:
- logger.debug('Either %s or %s is not a zip file.' % (path1, path2))
- # we'll fallback on binary comparison
- return differences
+class ZipDirectory(Directory, ArchiveMember):
+ def __init__(self, archive, member_name):
+ ArchiveMember.__init__(self, archive, member_name)
+
+ def compare(self, other, source=None):
+ return None
+
+ def has_same_content_as(self, other):
+ return False
+
+ def is_directory(self):
+ return True
+
+ def get_member_names(self):
+ raise ValueError("Zip archives are compared as a whole.")
+
+ def get_member(self, member_name):
+ raise ValueError("Zip archives are compared as a whole.")
+
+
+class ZipContainer(Archive):
+ def open_archive(self, path):
+ return zipfile.ZipFile(path, 'r')
+
+ def close_archive(self):
+ self.archive.close()
+
+ def get_member_names(self):
+ return self.archive.namelist()
+
+ def extract(self, member_name, dest_dir):
+ return self.archive.extract(member_name, dest_dir)
+
+ def get_member(self, member_name):
+ zipinfo = self.archive.getinfo(member_name)
+ if zipinfo.filename[-1] == '/':
+ return ZipDirectory(self, member_name)
+ else:
+ return ArchiveMember(self, member_name)
+
+
+class ZipFile(File):
+ RE_FILE_TYPE = re.compile(r'^(Zip archive|EPUB ebook) data\b')
+
+ @staticmethod
+ def recognizes(file):
+ return ZipFile.RE_FILE_TYPE.match(file.magic_file_type)
+
+ @needs_content
+ def compare_details(self, other, source=None):
+ differences = []
+ # look up differences in metadata
+ zipinfo_difference = Difference.from_command(Zipinfo, self.path, other.path) or \
+ Difference.from_command(ZipinfoVerbose, self.path, other.path)
+ differences.append(zipinfo_difference)
+ with ZipContainer(self).open() as my_container, \
+ ZipContainer(other).open() as other_container:
+ differences.extend(my_container.compare(other_container, source))
+ return differences
diff --git a/debian/control b/debian/control
index 6fb0fdb..895acb3 100644
--- a/debian/control
+++ b/debian/control
@@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 9),
python-all,
python-debian,
python-docutils,
+ python-libarchive-c,
python-magic,
python-pytest,
python-rpm,
diff --git a/setup.py b/setup.py
index 7e7fa1f..5f97127 100644
--- a/setup.py
+++ b/setup.py
@@ -37,6 +37,7 @@ setup(name='debbindiff',
'python-debian',
'magic',
'rpm',
+ 'libarchive-c',
],
classifiers=[
'Development Status :: 3 - Alpha',
diff --git a/tests/comparators/test_binary.py b/tests/comparators/test_binary.py
index 7e5832a..00b84b5 100644
--- a/tests/comparators/test_binary.py
+++ b/tests/comparators/test_binary.py
@@ -20,28 +20,55 @@
import os.path
import shutil
+import subprocess
import pytest
+from debbindiff.comparators import specialize
import debbindiff.comparators.binary
-from debbindiff.comparators.binary import compare_binary_files, are_same_binaries
-from debbindiff import RequiredToolNotFound
+from debbindiff.comparators.binary import File, FilesystemFile
+from debbindiff.difference import Difference
+from debbindiff import RequiredToolNotFound, tool_required
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/binary1')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/binary2')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/binary1')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/binary2')
+TEST_ASCII_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
+TEST_UNICODE_PATH = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
+TEST_ISO8859_PATH = os.path.join(os.path.dirname(__file__), '../data/text_iso8859')
-def test_are_same_binaries(tmpdir):
- new_path = str(tmpdir.join('binary2'))
- shutil.copy(TEST_FILE1_PATH, new_path)
- assert are_same_binaries(TEST_FILE1_PATH, new_path)
+ at pytest.fixture
+def binary1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def binary2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_same_content(binary1):
+ assert binary1.has_same_content_as(binary1) is True
+
+def test_not_same_content(binary1, binary2):
+ assert binary1.has_same_content_as(binary2) is False
+
+def test_guess_file_type():
+ assert File.guess_file_type(TEST_FILE1_PATH) == 'data'
-def test_are_not_same_binaries(tmpdir):
- assert not are_same_binaries(TEST_FILE1_PATH, TEST_FILE2_PATH)
+def test_guess_encoding_binary():
+ assert File.guess_encoding(TEST_FILE1_PATH) == 'binary'
-def test_no_differences_with_xxd():
- difference = compare_binary_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+def test_guess_encoding_ascii():
+ assert File.guess_encoding(TEST_ASCII_PATH) == 'us-ascii'
+
+def test_guess_encoding_unicode():
+ assert File.guess_encoding(TEST_UNICODE_PATH) == 'utf-8'
+
+def test_guess_encoding_iso8859():
+ assert File.guess_encoding(TEST_ISO8859_PATH) == 'iso-8859-1'
+
+def test_no_differences_with_xxd(binary1):
+ difference = binary1.compare_bytes(binary1)
assert difference is None
-def test_compare_with_xxd():
- difference = compare_binary_files(TEST_FILE1_PATH, TEST_FILE2_PATH)
+def test_compare_with_xxd(binary1, binary2):
+ difference = binary1.compare_bytes(binary2)
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/binary_expected_diff')).read()
assert difference.unified_diff == expected_diff
@@ -51,11 +78,59 @@ def xxd_not_found(monkeypatch):
raise RequiredToolNotFound('xxd')
monkeypatch.setattr(debbindiff.comparators.binary, 'xxd', mock_xxd)
-def test_no_differences_without_xxd(xxd_not_found):
- difference = compare_binary_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+def test_no_differences_without_xxd(xxd_not_found, binary1):
+ difference = binary1.compare_bytes(binary1)
assert difference is None
-def test_compare_without_xxd(xxd_not_found):
- difference = compare_binary_files(TEST_FILE1_PATH, TEST_FILE2_PATH)
+def test_compare_without_xxd(xxd_not_found, binary1, binary2):
+ difference = binary1.compare(binary2)
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/binary_hexdump_expected_diff')).read()
assert difference.unified_diff == expected_diff
+
+def test_with_compare_details():
+ d = Difference('diff', TEST_FILE1_PATH, TEST_FILE2_PATH, source='source')
+ class MockFile(FilesystemFile):
+ def compare_details(self, other, source=None):
+ return [d]
+ difference = MockFile(TEST_FILE1_PATH).compare(MockFile(TEST_FILE2_PATH), source='source')
+ assert difference.details[0] == d
+
+def test_with_compare_details_and_fallback():
+ class MockFile(FilesystemFile):
+ def compare_details(self, other, source=None):
+ return []
+ difference = MockFile(TEST_FILE1_PATH).compare(MockFile(TEST_FILE2_PATH))
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/binary_expected_diff')).read()
+ assert 'yet data differs' in difference.comment
+ assert difference.unified_diff == expected_diff
+
+def test_with_compare_details_and_no_actual_differences():
+ class MockFile(FilesystemFile):
+ def compare_details(self, other, source=None):
+ return []
+ difference = MockFile(TEST_FILE1_PATH).compare(MockFile(TEST_FILE1_PATH))
+ assert difference is None
+
+def test_with_compare_details_and_failed_process():
+ output = 'Free Jeremy Hammond'
+ class MockFile(FilesystemFile):
+ def compare_details(self, other, source=None):
+ subprocess.check_output(['sh', '-c', 'echo "%s"; exit 42' % output], shell=False)
+ raise Exception('should not be run')
+ difference = MockFile(TEST_FILE1_PATH).compare(MockFile(TEST_FILE2_PATH))
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/binary_expected_diff')).read()
+ assert output in difference.comment
+ assert '42' in difference.comment
+ assert difference.unified_diff == expected_diff
+
+def test_with_compare_details_and_tool_not_found(monkeypatch):
+ monkeypatch.setattr('debbindiff.RequiredToolNotFound.get_package', lambda _: 'some-package')
+ class MockFile(FilesystemFile):
+ @tool_required('nonexistent')
+ def compare_details(self, other, source=None):
+ raise Exception('should not be run')
+ difference = MockFile(TEST_FILE1_PATH).compare(MockFile(TEST_FILE2_PATH))
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/binary_expected_diff')).read()
+ assert 'nonexistent' in difference.comment
+ assert 'some-package' in difference.comment
+ assert difference.unified_diff == expected_diff
diff --git a/tests/comparators/test_bzip2.py b/tests/comparators/test_bzip2.py
index 6b5bf70..6e9a259 100644
--- a/tests/comparators/test_bzip2.py
+++ b/tests/comparators/test_bzip2.py
@@ -21,29 +21,48 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.bzip2 import compare_bzip2_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.bzip2 import Bzip2File
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.bz2')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.bz2')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.bz2')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.bz2')
-def test_no_differences():
- difference = compare_bzip2_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+
+
+ at pytest.fixture
+def bzip1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def bzip2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(bzip1):
+ assert isinstance(bzip1, Bzip2File)
+
+def test_no_differences(bzip1):
+ difference = bzip1.compare(bzip1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_bzip2_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(bzip1, bzip2):
+ return bzip1.compare(bzip2).details
+ at pytest.mark.xfail # need fuzzy
def test_content_source(differences):
assert differences[0].source1 == 'test1'
assert differences[0].source2 == 'test2'
+ at pytest.mark.xfail # need fuzzy
def test_content_source_without_extension(tmpdir):
path1 = str(tmpdir.join('test1'))
path2 = str(tmpdir.join('test2'))
shutil.copy(TEST_FILE1_PATH, path1)
shutil.copy(TEST_FILE2_PATH, path2)
- differences = compare_bzip2_files(path1, path2).details
+ bzip1 = specialize(FilesystemFile(path1))
+ bzip2 = specialize(FilesystemFile(path2))
+ differences = bzip1.compare(bzip2).details
assert differences[0].source1 == 'test1-content'
assert differences[0].source2 == 'test2-content'
diff --git a/tests/comparators/test_cpio.py b/tests/comparators/test_cpio.py
index b73ab6c..743cd1e 100644
--- a/tests/comparators/test_cpio.py
+++ b/tests/comparators/test_cpio.py
@@ -22,25 +22,44 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.cpio import compare_cpio_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.cpio import CpioFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.cpio')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.cpio')
-def test_no_differences():
- difference = compare_cpio_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def cpio1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def cpio2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(cpio1):
+ assert isinstance(cpio1, CpioFile)
+
+def test_no_differences(cpio1):
+ difference = cpio1.compare(cpio1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_cpio_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(cpio1, cpio2):
+ return cpio1.compare(cpio2).details
def test_listing(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/cpio_listing_expected_diff')).read()
assert differences[0].unified_diff == expected_diff
+def test_symlink(differences):
+ assert differences[1].source1 == 'dir/link'
+ assert differences[1].comment == 'symlink'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/symlink_expected_diff')).read()
+ assert differences[1].unified_diff == expected_diff
+
def test_compressed_files(differences):
- assert differences[1].source1 == 'dir/text'
- assert differences[1].source2 == 'dir/text'
+ assert differences[2].source1 == 'dir/text'
+ assert differences[2].source2 == 'dir/text'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[1].unified_diff == expected_diff
+ assert differences[2].unified_diff == expected_diff
diff --git a/tests/comparators/test_deb.py b/tests/comparators/test_deb.py
index 11bd2cf..265d7fe 100644
--- a/tests/comparators/test_deb.py
+++ b/tests/comparators/test_deb.py
@@ -22,24 +22,58 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.deb import compare_deb_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.deb import DebFile, Md5sumsFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.deb')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.deb')
-def test_no_differences():
- difference = compare_deb_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
- assert difference is None
+ at pytest.fixture
+def deb1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
@pytest.fixture
-def differences():
- return compare_deb_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def deb2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
-def test_compressed_files(differences):
- assert differences[0].source1 == 'control.tar.gz'
- assert differences[1].source1 == 'data.tar.gz'
+def test_identification(deb1):
+ assert isinstance(deb1, DebFile)
+
+def test_no_differences(deb1):
+ difference = deb1.compare(deb1)
+ assert difference is None
+
+ at pytest.fixture
+def differences(deb1, deb2):
+ return deb1.compare(deb2).details
def test_metadata(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/deb_metadata_expected_diff')).read()
- assert differences[-1].unified_diff == expected_diff
+ assert differences[0].unified_diff == expected_diff
+
+def test_compressed_files(differences):
+ assert differences[1].source1 == 'control.tar.gz'
+ assert differences[2].source1 == 'data.tar.gz'
+
+def test_identification_of_md5sums_outside_deb(tmpdir):
+ path = str(tmpdir.join('md5sums'))
+ open(path, 'w')
+ f = specialize(FilesystemFile(path))
+ assert type(f) is FilesystemFile
+
+def test_identification_of_md5sums_in_deb(deb1, deb2, monkeypatch):
+ orig_func = Md5sumsFile.recognizes
+ @staticmethod
+ def probe(file):
+ ret = orig_func(file)
+ if ret:
+ test_identification_of_md5sums_in_deb.found = True
+ return ret
+ test_identification_of_md5sums_in_deb.found = False
+ monkeypatch.setattr(Md5sumsFile, 'recognizes', probe)
+ deb1.compare(deb2)
+ assert test_identification_of_md5sums_in_deb.found
+def test_md5sums(differences):
+ assert differences[1].details[0].details[1].comment == 'Files in package differs'
diff --git a/tests/comparators/test_debian.py b/tests/comparators/test_debian.py
index 4fc4a83..d946b92 100644
--- a/tests/comparators/test_debian.py
+++ b/tests/comparators/test_debian.py
@@ -23,8 +23,9 @@ from __future__ import print_function
import os.path
import shutil
import pytest
-import debbindiff.comparators.deb
-from debbindiff.comparators.debian import compare_dot_changes_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.debian import DotChangesFile
from debbindiff.presenters.text import output_text
TEST_DOT_CHANGES_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.changes')
@@ -32,27 +33,34 @@ TEST_DOT_CHANGES_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/t
TEST_DEB_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.deb')
TEST_DEB_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.deb')
+# XXX: test validate failure
+
@pytest.fixture
-def copydeb(tmpdir):
- changes1_path = str(tmpdir.join('a/test_1.changes'))
- changes2_path = str(tmpdir.join('b/test_1.changes'))
+def dot_changes1(tmpdir):
tmpdir.mkdir('a')
- tmpdir.mkdir('b')
- shutil.copy(TEST_DOT_CHANGES_FILE1_PATH, changes1_path)
- shutil.copy(TEST_DOT_CHANGES_FILE2_PATH, changes2_path)
+ dot_changes_path = str(tmpdir.join('a/test_1.changes'))
+ shutil.copy(TEST_DOT_CHANGES_FILE1_PATH, dot_changes_path)
shutil.copy(TEST_DEB_FILE1_PATH, str(tmpdir.join('a/test_1_all.deb')))
+ return specialize(FilesystemFile(dot_changes_path))
+
+ at pytest.fixture
+def dot_changes2(tmpdir):
+ tmpdir.mkdir('b')
+ dot_changes_path = str(tmpdir.join('b/test_1.changes'))
+ shutil.copy(TEST_DOT_CHANGES_FILE2_PATH, dot_changes_path)
shutil.copy(TEST_DEB_FILE2_PATH, str(tmpdir.join('b/test_1_all.deb')))
- return (changes1_path, changes2_path)
+ return specialize(FilesystemFile(dot_changes_path))
+
+def test_identification(dot_changes1):
+ assert isinstance(dot_changes1, DotChangesFile)
-def test_no_differences(copydeb):
- changes1_path, _ = copydeb
- difference = compare_dot_changes_files(changes1_path, changes1_path)
+def test_no_differences(dot_changes1):
+ difference = dot_changes1.compare(dot_changes1)
assert difference is None
@pytest.fixture
-def differences(copydeb):
- changes1_path, changes2_path = copydeb
- difference = compare_dot_changes_files(changes1_path, changes2_path)
+def differences(dot_changes1, dot_changes2):
+ difference = dot_changes1.compare(dot_changes2)
output_text(difference, print_func=print)
return difference.details
diff --git a/tests/comparators/test_directory.py b/tests/comparators/test_directory.py
index 4a587b1..25be7e2 100644
--- a/tests/comparators/test_directory.py
+++ b/tests/comparators/test_directory.py
@@ -28,8 +28,8 @@ import debbindiff.comparators.binary
from debbindiff.comparators.directory import compare_directories
from debbindiff.presenters.text import output_text
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii2')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii2')
def test_no_differences():
difference = compare_directories(os.path.dirname(__file__), os.path.dirname(__file__))
@@ -48,11 +48,10 @@ def differences(tmpdir):
return compare_directories(str(tmpdir.join('a')), str(tmpdir.join('b'))).details
def test_content(differences):
- assert differences[0].source1 == 'dir'
- assert differences[0].details[0].source1 == 'text'
+ assert differences[0].source1 == 'dir/text'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[0].details[0].unified_diff == expected_diff
+ assert differences[0].unified_diff == expected_diff
def test_stat(differences):
output_text(differences[0], print_func=print)
- assert 'stat' in differences[0].details[0].details[0].source1
+ assert 'stat' in differences[0].details[0].source1
diff --git a/tests/comparators/test_elf.py b/tests/comparators/test_elf.py
index 5764ac6..08a09cf 100644
--- a/tests/comparators/test_elf.py
+++ b/tests/comparators/test_elf.py
@@ -21,34 +21,58 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.elf import compare_elf_files, compare_static_lib_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.elf import ElfFile, StaticLibFile
-TEST_OBJ1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.o')
-TEST_OBJ2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.o')
+TEST_OBJ1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.o')
+TEST_OBJ2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.o')
-def test_obj_no_differences():
- difference = compare_elf_files(TEST_OBJ1_PATH, TEST_OBJ1_PATH)
+ at pytest.fixture
+def obj1():
+ return specialize(FilesystemFile(TEST_OBJ1_PATH))
+
+ at pytest.fixture
+def obj2():
+ return specialize(FilesystemFile(TEST_OBJ2_PATH))
+
+def test_obj_identification(obj1):
+ assert isinstance(obj1, ElfFile)
+
+def test_obj_no_differences(obj1):
+ difference = obj1.compare(obj1)
assert difference is None
@pytest.fixture
-def obj_differences():
- return compare_elf_files(TEST_OBJ1_PATH, TEST_OBJ2_PATH).details
+def obj_differences(obj1, obj2):
+ return obj1.compare(obj2).details
def test_diff(obj_differences):
assert len(obj_differences) == 1
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/elf_obj_expected_diff')).read()
assert obj_differences[0].unified_diff == expected_diff
-TEST_LIB1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.a')
-TEST_LIB2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.a')
+TEST_LIB1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.a')
+TEST_LIB2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.a')
+
+ at pytest.fixture
+def lib1():
+ return specialize(FilesystemFile(TEST_LIB1_PATH))
+
+ at pytest.fixture
+def lib2():
+ return specialize(FilesystemFile(TEST_LIB2_PATH))
+
+def test_lib_identification(lib1):
+ assert isinstance(lib1, StaticLibFile)
-def test_lib_no_differences():
- difference = compare_elf_files(TEST_LIB1_PATH, TEST_LIB1_PATH)
+def test_lib_no_differences(lib1):
+ difference = lib1.compare(lib1)
assert difference is None
@pytest.fixture
-def lib_differences():
- return compare_static_lib_files(TEST_LIB1_PATH, TEST_LIB2_PATH).details
+def lib_differences(lib1, lib2):
+ return lib1.compare(lib2).details
def test_lib_differences(lib_differences):
assert len(lib_differences) == 2
diff --git a/tests/comparators/test_fonts.py b/tests/comparators/test_fonts.py
index ab9ec83..d86cc80 100644
--- a/tests/comparators/test_fonts.py
+++ b/tests/comparators/test_fonts.py
@@ -21,18 +21,31 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.fonts import compare_ttf_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.fonts import TtfFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/Samyak-Malayalam1.ttf')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/Samyak-Malayalam2.ttf')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/Samyak-Malayalam1.ttf')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/Samyak-Malayalam2.ttf')
-def test_no_differences():
- difference = compare_ttf_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def ttf1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def ttf2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(ttf1):
+ assert isinstance(ttf1, TtfFile)
+
+def test_no_differences(ttf1):
+ difference = ttf1.compare(ttf1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_ttf_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(ttf1, ttf2):
+ return ttf1.compare(ttf2).details
def test_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/ttf_expected_diff')).read()
diff --git a/tests/comparators/test_generic.py b/tests/comparators/test_generic.py
deleted file mode 100644
index 04f37a3..0000000
--- a/tests/comparators/test_generic.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# debbindiff: highlight differences between two builds of Debian packages
-#
-# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
-#
-# debbindiff is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# debbindiff is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
-
-import codecs
-import os.path
-import pytest
-
-from debbindiff.comparators import compare_unknown
-
-TEST_TEXT_ASCII_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
-TEST_TEXT_ASCII_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/text_ascii2')
-TEST_TEXT_UNICODE_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
-TEST_TEXT_UNICODE_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/text_unicode2')
-TEST_BINARY_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/binary1')
-TEST_BINARY_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/binary2')
-
-def test_same_binaries():
- difference = compare_unknown(TEST_BINARY_FILE1_PATH, TEST_BINARY_FILE1_PATH)
- assert difference is None
-
-def test_text_ascii_files():
- difference = compare_unknown(TEST_TEXT_ASCII_FILE1_PATH, TEST_TEXT_ASCII_FILE2_PATH)
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert difference.unified_diff == expected_diff
-
-def test_text_unicode_files():
- difference = compare_unknown(TEST_TEXT_UNICODE_FILE1_PATH, TEST_TEXT_UNICODE_FILE2_PATH)
- expected_diff = codecs.open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_expected_diff'), encoding='utf-8').read()
- assert difference.unified_diff == expected_diff
-
-def test_binary_files():
- difference = compare_unknown(TEST_BINARY_FILE1_PATH, TEST_BINARY_FILE2_PATH)
- expected_diff = codecs.open(os.path.join(os.path.dirname(__file__), '../data/binary_expected_diff')).read()
- assert difference.unified_diff == expected_diff
diff --git a/tests/comparators/test_gettext.py b/tests/comparators/test_gettext.py
index 521c3c5..ce32210 100644
--- a/tests/comparators/test_gettext.py
+++ b/tests/comparators/test_gettext.py
@@ -22,25 +22,45 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.gettext import compare_mo_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.gettext import MoFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.mo')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.mo')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.mo')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.mo')
-def test_no_differences():
- difference = compare_mo_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def mo1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def mo2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(mo1):
+ assert isinstance(mo1, MoFile)
+
+def test_no_differences(mo1):
+ difference = mo1.compare(mo1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_mo_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(mo1, mo2):
+ return mo1.compare(mo2).details
def test_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/mo_expected_diff')).read()
assert differences[0].unified_diff == expected_diff
-def test_charsets():
- difference = compare_mo_files(os.path.join(os.path.dirname(__file__), '../data/test_no_charset.mo'),
- os.path.join(os.path.dirname(__file__), '../data/test_iso8859-1.mo'))
+ at pytest.fixture
+def mo_no_charset():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/test_no_charset.mo')))
+
+ at pytest.fixture
+def mo_iso8859_1():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/test_iso8859-1.mo')))
+
+def test_charsets(mo_no_charset, mo_iso8859_1):
+ difference = mo_no_charset.compare(mo_iso8859_1)
expected_diff = codecs.open(os.path.join(os.path.dirname(__file__), '../data/mo_charsets_expected_diff'), encoding='utf-8').read()
assert difference.details[0].unified_diff == expected_diff
diff --git a/tests/comparators/test_gzip.py b/tests/comparators/test_gzip.py
index 53d147b..657fcd6 100644
--- a/tests/comparators/test_gzip.py
+++ b/tests/comparators/test_gzip.py
@@ -21,18 +21,31 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.gzip import compare_gzip_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.gzip import GzipFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.gz')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.gz')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.gz')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.gz')
-def test_no_differences():
- difference = compare_gzip_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def gzip1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def gzip2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(gzip1):
+ assert isinstance(gzip1, GzipFile)
+
+def test_no_differences(gzip1):
+ difference = gzip1.compare(gzip1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_gzip_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(gzip1, gzip2):
+ return gzip1.compare(gzip2).details
def test_metadata(differences):
assert differences[0].source1 == 'metadata'
@@ -40,16 +53,20 @@ def test_metadata(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/gzip_metadata_expected_diff')).read()
assert differences[0].unified_diff == expected_diff
+ at pytest.mark.xfail # need fuzzy matching
def test_content_source(differences):
assert differences[1].source1 == 'test1'
assert differences[1].source2 == 'test2'
+ at pytest.mark.xfail # need fuzzy matching
def test_content_source_without_extension(tmpdir):
path1 = str(tmpdir.join('test1'))
path2 = str(tmpdir.join('test2'))
shutil.copy(TEST_FILE1_PATH, path1)
shutil.copy(TEST_FILE2_PATH, path2)
- difference = compare_gzip_files(path1, path2).details
+ gzip1 = specialize(FilesystemFile(path1))
+ gzip2 = specialize(FilesystemFile(path2))
+ difference = gzip1.compare(gzip2).details
assert difference[1].source1 == 'test1-content'
assert difference[1].source2 == 'test2-content'
diff --git a/tests/comparators/test_ipk.py b/tests/comparators/test_ipk.py
index ea5c55d..e6f7ea6 100644
--- a/tests/comparators/test_ipk.py
+++ b/tests/comparators/test_ipk.py
@@ -22,23 +22,37 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.ipk import compare_ipk_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.ipk import IpkFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/base-files_157-r45695_ar71xx.ipk')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/base-files_157-r45918_ar71xx.ipk')
-def test_no_differences():
- difference = compare_ipk_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def ipk1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def ipk2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(ipk1):
+ assert isinstance(ipk1, IpkFile)
+
+def test_no_differences(ipk1):
+ difference = ipk1.compare(ipk1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_ipk_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(ipk1, ipk2):
+ return ipk1.compare(ipk2).details
def test_metadata(differences):
+ assert differences[0].source1 == 'metadata'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/ipk_metadata_expected_diff')).read()
assert differences[0].unified_diff == expected_diff
def test_compressed_files(differences):
- assert differences[1].details[0].source1 == './control.tar.gz'
- assert differences[1].details[1].source1 == './data.tar.gz'
+ assert differences[1].details[1].source1 == './control.tar.gz'
+ assert differences[1].details[2].source1 == './data.tar.gz'
diff --git a/tests/comparators/test_iso9660.py b/tests/comparators/test_iso9660.py
index fb4b19b..91521e3 100644
--- a/tests/comparators/test_iso9660.py
+++ b/tests/comparators/test_iso9660.py
@@ -22,18 +22,31 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.iso9660 import compare_iso9660_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.iso9660 import Iso9660File
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.iso')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.iso')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.iso')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.iso')
-def test_no_differences():
- difference = compare_iso9660_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def iso1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def iso2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(iso1):
+ assert isinstance(iso1, Iso9660File)
+
+def test_no_differences(iso1):
+ difference = iso1.compare(iso1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_iso9660_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(iso1, iso2):
+ return iso1.compare(iso2).details
def test_iso9660_content(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/iso9660_content_expected_diff')).read()
@@ -43,8 +56,13 @@ def test_iso9660_rockridge(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/iso9660_rockridge_expected_diff')).read()
assert differences[1].unified_diff == expected_diff
+def test_symlink(differences):
+ assert differences[2].comment == 'symlink'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/symlink_expected_diff')).read()
+ assert differences[2].unified_diff == expected_diff
+
def test_compressed_files(differences):
- assert differences[2].source1 == '/text'
- assert differences[2].source2 == '/text'
+ assert differences[3].source1 == 'text'
+ assert differences[3].source2 == 'text'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[2].unified_diff == expected_diff
+ assert differences[3].unified_diff == expected_diff
diff --git a/tests/comparators/test_java.py b/tests/comparators/test_java.py
index 5c119f4..355de5b 100644
--- a/tests/comparators/test_java.py
+++ b/tests/comparators/test_java.py
@@ -21,18 +21,31 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.java import compare_class_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.java import ClassFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/Test1.class')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/Test2.class')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/Test1.class')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/Test2.class')
-def test_no_differences():
- difference = compare_class_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def class1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def class2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(class1):
+ assert isinstance(class1, ClassFile)
+
+def test_no_differences(class1):
+ difference = class1.compare(class1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_class_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(class1, class2):
+ return class1.compare(class2).details
def test_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/class_expected_diff')).read()
diff --git a/tests/comparators/test_pe.py b/tests/comparators/test_mono.py
similarity index 72%
rename from tests/comparators/test_pe.py
rename to tests/comparators/test_mono.py
index 886f0cb..06aab57 100644
--- a/tests/comparators/test_pe.py
+++ b/tests/comparators/test_mono.py
@@ -21,7 +21,9 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.pe import compare_pe_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.mono import MonoExeFile
# these were generated with:
@@ -31,13 +33,24 @@ from debbindiff.comparators.pe import compare_pe_files
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.exe')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.exe')
-def test_no_differences():
- difference = compare_pe_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def exe1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def exe2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(exe1):
+ assert isinstance(exe1, MonoExeFile)
+
+def test_no_differences(exe1):
+ difference = exe1.compare(exe1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_pe_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(exe1, exe2):
+ return exe1.compare(exe2).details
def test_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/pe_expected_diff')).read()
diff --git a/tests/comparators/test_pdf.py b/tests/comparators/test_pdf.py
index 7e75272..7f8ef53 100644
--- a/tests/comparators/test_pdf.py
+++ b/tests/comparators/test_pdf.py
@@ -21,18 +21,31 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.pdf import compare_pdf_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.pdf import PdfFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.pdf')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.pdf')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.pdf')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.pdf')
-def test_no_differences():
- difference = compare_pdf_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def pdf1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def pdf2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(pdf1):
+ assert isinstance(pdf1, PdfFile)
+
+def test_no_differences(pdf1):
+ difference = pdf1.compare(pdf1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_pdf_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(pdf1, pdf2):
+ return pdf1.compare(pdf2).details
def test_text_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/pdf_text_expected_diff')).read()
diff --git a/tests/comparators/test_png.py b/tests/comparators/test_png.py
index 3ef669c..a4b1e91 100644
--- a/tests/comparators/test_png.py
+++ b/tests/comparators/test_png.py
@@ -21,18 +21,31 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.png import compare_png_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.png import PngFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.png')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.png')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.png')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.png')
-def test_no_differences():
- difference = compare_png_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def png1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def png2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(png1):
+ assert isinstance(png1, PngFile)
+
+def test_no_differences(png1):
+ difference = png1.compare(png1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_png_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(png1, png2):
+ return png1.compare(png2).details
def test_diff(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/png_expected_diff')).read()
diff --git a/tests/comparators/test_rpm.py b/tests/comparators/test_rpm.py
index 21eaf68..c9667dc 100644
--- a/tests/comparators/test_rpm.py
+++ b/tests/comparators/test_rpm.py
@@ -22,24 +22,45 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.rpm import compare_rpm_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.rpm import RpmFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.rpm')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.rpm')
-def test_no_differences():
- difference = compare_rpm_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def rpm1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def rpm2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(rpm1):
+ assert isinstance(rpm1, RpmFile)
+
+def test_no_differences(rpm1):
+ difference = rpm1.compare(rpm1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_rpm_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(rpm1, rpm2):
+ return rpm1.compare(rpm2).details
def test_header(differences):
assert differences[0].source1 == 'header'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/rpm_header_expected_diff')).read()
assert differences[0].unified_diff == expected_diff
+def test_listing(differences):
+ assert differences[1].source1 == 'content'
+ assert differences[1].details[0].source1 == 'file list'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/rpm_listing_expected_diff')).read()
+ assert differences[1].details[0].unified_diff == expected_diff
+
def test_content(differences):
- assert differences[1].source1 == 'CONTENTS.cpio'
+ assert differences[1].source1 == 'content'
assert differences[1].details[1].source1 == './dir/text'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
+ assert differences[1].details[1].unified_diff == expected_diff
diff --git a/tests/comparators/test_squashfs.py b/tests/comparators/test_squashfs.py
index 10b335e..2fb34e4 100644
--- a/tests/comparators/test_squashfs.py
+++ b/tests/comparators/test_squashfs.py
@@ -22,24 +22,36 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.squashfs import compare_squashfs_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.squashfs import SquashfsFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.squashfs')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.squashfs')
-def test_no_differences():
- difference = compare_squashfs_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def squashfs1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def squashfs2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(squashfs1):
+ assert isinstance(squashfs1, SquashfsFile)
+
+def test_no_differences(squashfs1):
+ difference = squashfs1.compare(squashfs1)
assert difference is None
- at pytest.mark.xfail
-def test_no_warnings(capfd, differences):
- compare_squashfs_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def test_no_warnings(capfd, squashfs1, squashfs2):
+ _ = squashfs1.compare(squashfs2)
_, err = capfd.readouterr()
assert err == ''
@pytest.fixture
-def differences():
- return compare_squashfs_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(squashfs1, squashfs2):
+ return squashfs1.compare(squashfs2).details
def test_superblock(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/squashfs_superblock_expected_diff')).read()
@@ -49,8 +61,13 @@ def test_listing(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/squashfs_listing_expected_diff')).read()
assert differences[1].unified_diff == expected_diff
+def test_symlink(differences):
+ assert differences[2].comment == 'symlink'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/symlink_expected_diff')).read()
+ assert differences[2].unified_diff == expected_diff
+
def test_compressed_files(differences):
- assert differences[2].source1 == 'text'
- assert differences[2].source2 == 'text'
+ assert differences[3].source1 == '/text'
+ assert differences[3].source2 == '/text'
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[2].unified_diff == expected_diff
+ assert differences[3].unified_diff == expected_diff
diff --git a/tests/comparators/test_tar.py b/tests/comparators/test_tar.py
index 0c3a55d..a6362f7 100644
--- a/tests/comparators/test_tar.py
+++ b/tests/comparators/test_tar.py
@@ -22,26 +22,45 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.tar import compare_tar_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.tar import TarFile
TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.tar')
TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.tar')
-def test_no_differences():
- difference = compare_tar_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
- assert difference is None
+ at pytest.fixture
+def tar1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
@pytest.fixture
-def differences():
- return compare_tar_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def tar2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
-def test_compressed_files(differences):
- assert differences[0].source1 == 'dir/text'
- assert differences[0].source2 == 'dir/text'
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[0].unified_diff == expected_diff
+def test_identification(tar1):
+ assert isinstance(tar1, TarFile)
+
+def test_no_differences(tar1):
+ difference = tar1.compare(tar1)
+ assert difference is None
+
+ at pytest.fixture
+def differences(tar1, tar2):
+ return tar1.compare(tar2).details
def test_listing(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/tar_listing_expected_diff')).read()
+ assert differences[0].unified_diff == expected_diff
+
+def test_symlinks(differences):
+ assert differences[1].source1 == 'dir/link'
+ assert differences[1].source2 == 'dir/link'
+ assert differences[1].comment == 'symlink'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/symlink_expected_diff')).read()
assert differences[1].unified_diff == expected_diff
+def test_text_file(differences):
+ assert differences[2].source1 == 'dir/text'
+ assert differences[2].source2 == 'dir/text'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
+ assert differences[2].unified_diff == expected_diff
diff --git a/tests/comparators/test_text.py b/tests/comparators/test_text.py
index 2ba5110..1809edc 100644
--- a/tests/comparators/test_text.py
+++ b/tests/comparators/test_text.py
@@ -21,41 +21,48 @@
import codecs
import os.path
import pytest
-from debbindiff.comparators.text import compare_text_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.text import TextFile
-def test_no_differences():
- text1 = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
- text2 = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
- assert compare_text_files(text1, text2) is None
+ at pytest.fixture
+def ascii1():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/text_ascii1')))
-def test_difference_in_ascii():
- text1 = os.path.join(os.path.dirname(__file__), '../data/text_ascii1')
- text2 = os.path.join(os.path.dirname(__file__), '../data/text_ascii2')
- difference = compare_text_files(text1, text2)
+ at pytest.fixture
+def ascii2():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/text_ascii2')))
+
+def test_no_differences(ascii1):
+ difference = ascii1.compare(ascii1)
+ assert difference is None
+
+def test_difference_in_ascii(ascii1, ascii2):
+ difference = ascii1.compare(ascii2)
assert difference is not None
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
assert difference.unified_diff == expected_diff
assert difference.comment is None
assert len(difference.details) == 0
-def test_difference_in_unicode():
- text1 = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
- text2 = os.path.join(os.path.dirname(__file__), '../data/text_unicode2')
- difference = compare_text_files(text1, text2)
+ at pytest.fixture
+def unicode1():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/text_unicode1')))
+
+ at pytest.fixture
+def unicode2():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/text_unicode2')))
+
+def test_difference_in_unicode(unicode1, unicode2):
+ difference = unicode1.compare(unicode2)
expected_diff = codecs.open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_expected_diff'), encoding='utf-8').read()
assert difference.unified_diff == expected_diff
-def test_fallback_to_binary():
- text1 = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
- text2 = os.path.join(os.path.dirname(__file__), '../data/text_unicode2')
- difference = compare_text_files(text1, text2, encoding='ascii')
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_binary_fallback')).read()
- assert difference.unified_diff == expected_diff
+ at pytest.fixture
+def iso8859():
+ return specialize(FilesystemFile(os.path.join(os.path.dirname(__file__), '../data/text_iso8859')))
- at pytest.mark.xfail
-def test_difference_between_iso88591_and_unicode():
- text1 = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
- text2 = os.path.join(os.path.dirname(__file__), '../data/text_iso8859')
- difference = compare_text_files(text1, text2)
+def test_difference_between_iso88591_and_unicode(iso8859, unicode1):
+ difference = iso8859.compare(unicode1)
expected_diff = codecs.open(os.path.join(os.path.dirname(__file__), '../data/text_iso8859_expected_diff'), encoding='utf-8').read()
- assert differences.unified_diff == expected_diff
+ assert difference.unified_diff == expected_diff
diff --git a/tests/comparators/test_utils.py b/tests/comparators/test_utils.py
deleted file mode 100644
index 80f706e..0000000
--- a/tests/comparators/test_utils.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# debbindiff: highlight differences between two builds of Debian packages
-#
-# Copyright © 2015 Jérémy Bobbio <lunar at debian.org>
-#
-# debbindiff is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# debbindiff is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with debbindiff. If not, see <http://www.gnu.org/licenses/>.
-
-import os.path
-import shutil
-import subprocess
-import pytest
-from debbindiff import tool_required
-from debbindiff.comparators.utils import binary_fallback, returns_details
-from debbindiff.difference import Difference
-
-def test_same_binaries():
- @binary_fallback
- def mock_comparator(path1, path2, source=None):
- raise Exception('should not be run')
- assert mock_comparator(__file__, __file__) is None
-
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/text_unicode1')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/text_unicode2')
-
-def test_return_differences():
- d = Difference('diff', TEST_FILE1_PATH, TEST_FILE2_PATH, source='source')
- @binary_fallback
- def mock_comparator(path1, path2, source=None):
- return d
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- assert difference == d
-
-def test_fallback():
- @binary_fallback
- def mock_comparator(path1, path2, source=None):
- return None
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_binary_fallback')).read()
- assert 'yet data differs' in difference.comment
- assert difference.unified_diff == expected_diff
-
-def test_process_failed():
- output = 'Free Jeremy Hammond'
- @binary_fallback
- def mock_comparator(path1, path2, source=None):
- subprocess.check_output(['sh', '-c', 'echo "%s"; exit 42' % output], shell=False)
- raise Exception('should not be run')
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_binary_fallback')).read()
- assert output in difference.comment
- assert '42' in difference.comment
- assert difference.unified_diff == expected_diff
-
-def test_tool_not_found(monkeypatch):
- monkeypatch.setattr('debbindiff.RequiredToolNotFound.get_package', lambda _: 'some-package')
- @binary_fallback
- @tool_required('nonexistent')
- def mock_comparator(path1, path2, source=None):
- raise Exception('should not be run')
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_binary_fallback')).read()
- assert 'nonexistent' in difference.comment
- assert 'some-package' in difference.comment
- assert difference.unified_diff == expected_diff
-
-def test_no_details():
- @returns_details
- def mock_comparator(path1, path2, source=None):
- return []
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- assert difference is None
-
-def test_details():
- d = Difference('diff', TEST_FILE1_PATH, TEST_FILE2_PATH)
- @returns_details
- def mock_comparator(path1, path2, source=None):
- return [d]
- difference = mock_comparator(TEST_FILE1_PATH, TEST_FILE2_PATH)
- assert difference.source1 == TEST_FILE1_PATH
- assert difference.source2 == TEST_FILE2_PATH
- assert difference.details == [d]
diff --git a/tests/comparators/test_xz.py b/tests/comparators/test_xz.py
index e262b53..d3d98c7 100644
--- a/tests/comparators/test_xz.py
+++ b/tests/comparators/test_xz.py
@@ -21,29 +21,46 @@
import os.path
import shutil
import pytest
-from debbindiff.comparators.xz import compare_xz_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.xz import XzFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.xz')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.xz')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.xz')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.xz')
-def test_no_differences():
- difference = compare_xz_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
+ at pytest.fixture
+def xz1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
+
+ at pytest.fixture
+def xz2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
+
+def test_identification(xz1):
+ assert isinstance(xz1, XzFile)
+
+def test_no_differences(xz1):
+ difference = xz1.compare(xz1)
assert difference is None
@pytest.fixture
-def differences():
- return compare_xz_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def differences(xz1, xz2):
+ return xz1.compare(xz2).details
+ at pytest.mark.xfail # need fuzzy
def test_content_source(differences):
assert differences[0].source1 == 'test1'
assert differences[0].source2 == 'test2'
+ at pytest.mark.xfail # need fuzzy
def test_content_source_without_extension(tmpdir):
path1 = str(tmpdir.join('test1'))
path2 = str(tmpdir.join('test2'))
shutil.copy(TEST_FILE1_PATH, path1)
shutil.copy(TEST_FILE2_PATH, path2)
- difference = compare_xz_files(path1, path2).details
+ xz1 = specialize(FilesystemFile(path1))
+ xz2 = specialize(FilesystemFile(path2))
+ difference = xz1.compare(xz2).details
assert difference[0].source1 == 'test1-content'
assert difference[0].source2 == 'test2-content'
diff --git a/tests/comparators/test_zip.py b/tests/comparators/test_zip.py
index 913dc4c..d0cba42 100644
--- a/tests/comparators/test_zip.py
+++ b/tests/comparators/test_zip.py
@@ -22,31 +22,38 @@ import codecs
import os.path
import shutil
import pytest
-from debbindiff.comparators.zip import compare_zip_files
+from debbindiff.comparators import specialize
+from debbindiff.comparators.binary import FilesystemFile
+from debbindiff.comparators.zip import ZipFile
-TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.zip')
-TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.zip')
+TEST_FILE1_PATH = os.path.join(os.path.dirname(__file__), '../data/test1.zip')
+TEST_FILE2_PATH = os.path.join(os.path.dirname(__file__), '../data/test2.zip')
-def test_no_differences():
- difference = compare_zip_files(TEST_FILE1_PATH, TEST_FILE1_PATH)
- assert difference is None
+ at pytest.fixture
+def zip1():
+ return specialize(FilesystemFile(TEST_FILE1_PATH))
@pytest.fixture
-def differences():
- return compare_zip_files(TEST_FILE1_PATH, TEST_FILE2_PATH).details
+def zip2():
+ return specialize(FilesystemFile(TEST_FILE2_PATH))
-def test_compressed_files(differences):
- assert differences[0].source1 == 'dir/text'
- assert differences[0].source2 == 'dir/text'
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
- assert differences[0].unified_diff == expected_diff
+def test_identification(zip1):
+ assert isinstance(zip1, ZipFile)
+
+def test_no_differences(zip1):
+ difference = zip1.compare(zip1)
+ assert difference is None
+
+ at pytest.fixture
+def differences(zip1, zip2):
+ return zip1.compare(zip2).details
def test_metadata(differences):
expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/zip_zipinfo_expected_diff')).read()
- assert differences[-1].unified_diff == expected_diff
+ assert differences[0].unified_diff == expected_diff
-def test_bad_zip():
- difference = compare_zip_files(os.path.join(os.path.dirname(__file__), '../data/text_unicode1'),
- os.path.join(os.path.dirname(__file__), '../data/text_unicode2'))
- expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_unicode_binary_fallback')).read()
- assert difference.unified_diff == expected_diff
+def test_compressed_files(differences):
+ assert differences[1].source1 == 'dir/text'
+ assert differences[1].source2 == 'dir/text'
+ expected_diff = open(os.path.join(os.path.dirname(__file__), '../data/text_ascii_expected_diff')).read()
+ assert differences[1].unified_diff == expected_diff
diff --git a/tests/data/pe_expected_diff b/tests/data/pe_expected_diff
index 72f3186..868a208 100644
--- a/tests/data/pe_expected_diff
+++ b/tests/data/pe_expected_diff
@@ -1,4 +1,5 @@
-@@ -2,7 +2,7 @@
+@@ -1,12 +1,12 @@
+
COFF Header:
Machine: 0x014c
Sections: 0x0003
@@ -7,3 +8,7 @@
Pointer to Symbol Table: 0x00000000
Symbol Count: 0x00000000
Optional Header Size: 0x00e0
+ Characteristics: 0x0102
+
+ PE Header:
+ Magic (0x010b): 0x010b
diff --git a/tests/data/rpm_listing_expected_diff b/tests/data/rpm_listing_expected_diff
new file mode 100644
index 0000000..0df261b
--- /dev/null
+++ b/tests/data/rpm_listing_expected_diff
@@ -0,0 +1,3 @@
+@@ -1 +1 @@
+--rw-r--r-- 1 root root 446 Jun 24 17:55 ./dir/text
++-rw-r--r-- 1 root root 671 Jun 24 17:55 ./dir/text
diff --git a/tests/data/symlink_expected_diff b/tests/data/symlink_expected_diff
new file mode 100644
index 0000000..1c872c0
--- /dev/null
+++ b/tests/data/symlink_expected_diff
@@ -0,0 +1,3 @@
+@@ -1 +1 @@
+-destination: broken
++destination: really-broken
diff --git a/tests/data/text_iso8859_expected_diff b/tests/data/text_iso8859_expected_diff
index a1e5f8a..c54bb0c 100644
--- a/tests/data/text_iso8859_expected_diff
+++ b/tests/data/text_iso8859_expected_diff
@@ -1,5 +1,3 @@
---- /dev/fd/63 2015-06-23 10:04:13.051750060 +0000
-+++ tests/data/text_unicode1 2015-06-23 09:46:16.425827486 +0000
@@ -3,10 +3,10 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/debbindiff.git
More information about the Reproducible-commits
mailing list