Commits (10)
......@@ -100,7 +100,6 @@ Vcs-Browser: https://salsa.debian.org/reproducible-builds/diffoscope
Package: diffoscope-minimal
Architecture: all
Depends:
python3-distutils,
python3-pkg-resources,
${misc:Depends},
${python3:Depends},
......
......@@ -294,10 +294,8 @@ class LlvmObjdump(Command):
def objdump_options(self):
return [
"--arch-name",
self._arch,
"--section",
self._section,
f"--arch-name={self._arch}",
f"--section={self._section}",
"--macho",
"--demangle",
"--no-leading-addr",
......
......@@ -19,20 +19,18 @@
import collections
import functools
import platform
import shutil
try:
import distro
except ImportError:
distro = None
from distutils.spawn import find_executable
from .profiling import profile
from .external_tools import EXTERNAL_TOOLS, REMAPPED_TOOL_NAMES, GNU_TOOL_NAMES
# Memoize calls to ``distutils.spawn.find_executable`` to avoid excessive stat
# calls
find_executable = functools.lru_cache()(find_executable)
# Memoize calls to ``which`` to avoid excessive stat calls
find_executable = functools.lru_cache()(shutil.which)
# The output of --help and --list-tools will use the order of this dict.
# Please keep it alphabetized.
......
......@@ -27,6 +27,7 @@ from diffoscope.comparators.missing_file import MissingFile
from ..utils.data import load_fixture, assert_diff
from ..utils.tools import skip_unless_tools_exist
from .test_rlib import llvm_version
obj1 = load_fixture("test1.macho")
......@@ -93,13 +94,18 @@ def test_llvm_obj_compare_non_existing(monkeypatch, obj1):
@skip_unless_tools_exist("llvm-readobj", "llvm-objdump")
def test_llvm_diff(obj_differences):
if llvm_version() < "13":
diff_symbols = "macho_llvm_expected_diff_symbols_llvm_11"
else:
diff_symbols = "macho_llvm_expected_diff_symbols"
# Headers
assert len(obj_differences) == 8
filenames = [
"macho_llvm_expected_diff_strings",
"macho_llvm_expected_diff_file_headers",
"macho_llvm_expected_diff_needed_libs",
"macho_llvm_expected_diff_symbols",
diff_symbols,
"macho_llvm_expected_diff_dyn_symbols",
"macho_llvm_expected_diff_relocations",
"macho_llvm_expected_diff_dyn_relocations",
......
......@@ -19,6 +19,7 @@
import pytest
import subprocess
import re
from diffoscope.config import Config
from diffoscope.comparators.openssh import PublicKeyFile
......@@ -36,7 +37,7 @@ opensshpubkey2 = load_fixture("test_openssh_pub_key2.pub")
def openssh_version():
out = subprocess.check_output(("ssh", "-V"), stderr=subprocess.STDOUT)
return out.decode().split()[0].split("_")[1]
return re.match(r"OpenSSH_([a-zA-Z0-9._+-]+)", out.decode()).group(1)
def test_identification(opensshpubkey1):
......
......@@ -22,7 +22,6 @@ import pytest
import subprocess
from diffoscope.config import Config
from distutils.version import LooseVersion
from diffoscope.comparators.ar import ArFile
from ..utils import diff_ignore_line_numbers
......@@ -33,6 +32,7 @@ from ..utils.tools import (
skip_if_binutils_does_not_support_x86,
)
from ..utils.nonexisting import assert_non_existing
from ..utils.versions import Version
rlib1 = load_fixture("test1.rlib")
......@@ -46,7 +46,7 @@ def init_tests(request, monkeypatch):
def llvm_version():
return (
return Version(
subprocess.check_output(["llvm-config", "--version"])
.decode("utf-8")
.strip()
......@@ -71,16 +71,16 @@ def differences(rlib1, rlib2):
def rlib_dis_expected_diff():
actual_ver = llvm_version()
if LooseVersion(str(actual_ver)) >= LooseVersion("3.8"):
if actual_ver >= "3.8":
diff_file = "rlib_llvm_dis_expected_diff"
if LooseVersion(str(actual_ver)) >= LooseVersion("5.0"):
if actual_ver >= "5.0":
diff_file = "rlib_llvm_dis_expected_diff_5"
if LooseVersion(str(actual_ver)) >= LooseVersion("7.0"):
if actual_ver >= "7.0":
diff_file = "rlib_llvm_dis_expected_diff_7"
if LooseVersion(str(actual_ver)) >= LooseVersion("10.0"):
if actual_ver >= "10.0":
diff_file = "rlib_llvm_dis_expected_diff_10"
return get_data(diff_file)
......
......@@ -66,7 +66,7 @@
Symbol {
Name: __mh_execute_header (2)
Extern
@@ -70,49 +11,37 @@
@@ -70,49 +11,39 @@
RefType: UndefinedNonLazy (0x0)
Flags [ (0x10)
ReferencedDynamically (0x10)
......@@ -105,6 +105,7 @@
- Flags [ (0x200)
- AltEntry (0x200)
+ Flags [ (0x100)
+ SymbolResolver (0x100)
]
Value: 0x0
}
......@@ -118,6 +119,7 @@
- Flags [ (0x200)
- AltEntry (0x200)
+ Flags [ (0x100)
+ SymbolResolver (0x100)
]
Value: 0x0
}
......
@@ -1,67 +1,8 @@
-Format: Mach-O 32-bit i386
-Arch: i386
-AddressSize: 32bit
-Symbols [
- Symbol {
- Name: __mh_execute_header (2)
- Extern
- Type: Section (0xE)
- Section: __text (0x1)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x10)
- ReferencedDynamically (0x10)
- ]
- Value: 0x1000
- }
- Symbol {
- Name: _i (22)
- Extern
- Type: Section (0xE)
- Section: __data (0x8)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x0)
- ]
- Value: 0x200C
- }
- Symbol {
- Name: _main (25)
- Extern
- Type: Section (0xE)
- Section: __text (0x1)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x0)
- ]
- Value: 0x1F30
- }
- Symbol {
- Name: _printf (31)
- Extern
- Type: Undef (0x0)
- Section: (0x0)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x200)
- AltEntry (0x200)
- ]
- Value: 0x0
- }
- Symbol {
- Name: dyld_stub_binder (39)
- Extern
- Type: Undef (0x0)
- Section: (0x0)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x200)
- AltEntry (0x200)
- ]
- Value: 0x0
- }
-]
-
Format: Mach-O 64-bit x86-64
Arch: x86_64
AddressSize: 64bit
Symbols [
Symbol {
Name: __mh_execute_header (2)
Extern
@@ -70,49 +11,37 @@
RefType: UndefinedNonLazy (0x0)
Flags [ (0x10)
ReferencedDynamically (0x10)
]
Value: 0x100000000
}
Symbol {
- Name: _i (22)
- Extern
- Type: Section (0xE)
- Section: __data (0x9)
- RefType: UndefinedNonLazy (0x0)
- Flags [ (0x0)
- ]
- Value: 0x100001018
- }
- Symbol {
- Name: _main (25)
+ Name: _main (22)
Extern
Type: Section (0xE)
Section: __text (0x1)
RefType: UndefinedNonLazy (0x0)
Flags [ (0x0)
]
- Value: 0x100000F20
+ Value: 0x100000F40
}
Symbol {
- Name: _printf (31)
+ Name: _printf (28)
Extern
Type: Undef (0x0)
Section: (0x0)
RefType: UndefinedNonLazy (0x0)
- Flags [ (0x200)
- AltEntry (0x200)
+ Flags [ (0x100)
]
Value: 0x0
}
Symbol {
- Name: dyld_stub_binder (39)
+ Name: dyld_stub_binder (36)
Extern
Type: Undef (0x0)
Section: (0x0)
RefType: UndefinedNonLazy (0x0)
- Flags [ (0x200)
- AltEntry (0x200)
+ Flags [ (0x100)
]
Value: 0x0
}
]
......@@ -21,12 +21,12 @@ import sys
import json
import pytest
from distutils.version import LooseVersion
from diffoscope.main import main
from diffoscope.progress import ProgressManager, StatusFD
from .utils.tools import skip_unless_module_exists
from .utils.versions import Version
TEST_TAR1_PATH = os.path.join(os.path.dirname(__file__), "data", "test1.tar")
TEST_TAR2_PATH = os.path.join(os.path.dirname(__file__), "data", "test2.tar")
......@@ -56,7 +56,7 @@ def progressbar_err():
actual_ver = progressbar_version()
for k, v in expected_err.items():
if LooseVersion(actual_ver) < LooseVersion(k):
if Version(actual_ver) < Version(k):
return v
return ""
......
#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
#
# diffoscope 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.
#
# diffoscope 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 diffoscope. If not, see <https://www.gnu.org/licenses/>.
from .utils.versions import Version
import pytest
cases = [
("1.0", "1.0", 0),
("1.0", "2.0", -1),
("2.0", "1.0", 1),
("2.0.1", "2.0.1", 0),
("2.0", "2.0.1", -1),
("2.0.1", "2.0", 1),
("2.0.1a", "2.0.1a", 0),
("2.0.1a", "2.0.1", 1),
("2.0.1", "2.0.1a", -1),
("5.5p1", "5.5p1", 0),
("5.5p1", "5.5p2", -1),
("5.5p2", "5.5p1", 1),
("5.5p10", "5.5p10", 0),
("5.5p1", "5.5p10", -1),
("5.5p10", "5.5p1", 1),
("10xyz", "10.1xyz", -1),
("10.1xyz", "10xyz", 1),
("xyz10", "xyz10", 0),
("xyz10", "xyz10.1", -1),
("xyz10.1", "xyz10", 1),
("xyz.4", "xyz.4", 0),
("xyz.4", "8", -1),
("8", "xyz.4", 1),
("xyz.4", "2", -1),
("2", "xyz.4", 1),
("5.5p2", "5.6p1", -1),
("5.6p1", "5.5p2", 1),
("5.6p1", "6.5p1", -1),
("6.5p1", "5.6p1", 1),
("6.0.rc1", "6.0", 1),
("6.0", "6.0.rc1", -1),
("10b2", "10a1", 1),
("10a2", "10b2", -1),
("1.0aa", "1.0aa", 0),
("1.0a", "1.0aa", -1),
("1.0aa", "1.0a", 1),
("10.0001", "10.0001", 0),
("10.0001", "10.1", 0),
("10.1", "10.0001", 0),
("10.0001", "10.0039", -1),
("10.0039", "10.0001", 1),
("4.999.9", "5.0", -1),
("5.0", "4.999.9", 1),
("20101121", "20101121", 0),
("20101121", "20101122", -1),
("20101122", "20101121", 1),
("2_0", "2_0", 0),
("2.0", "2_0", 0),
("2_0", "2.0", 0),
# Additional nastiness
("2_0.", "2_0", 0),
("2..0", "2_0__", 0),
("2_0", "__2.0", 0),
("2_1.", "2_0", +1),
("2..1", "2_0__", +1),
("2_1", "__2.0", +1),
("2_1.", "2_2", -1),
("2..1", "2_2__", -1),
("2_1", "__2.2", -1),
# https://bugzilla.redhat.com/178798
("a", "a", 0),
("a+", "a+", 0),
("a+", "a_", 0),
("a_", "a+", 0),
("+a", "+a", 0),
("+a", "_a", 0),
("_a", "+a", 0),
("+_", "+_", 0),
("_+", "+_", 0),
("_+", "_+", 0),
("+", "_", 0),
("_", "+", 0),
# Basic testcases for tilde sorting
("1.0~rc1", "1.0~rc1", 0),
("1.0~rc1", "1.0", -1),
("1.0", "1.0~rc1", 1),
("1.0~rc1", "1.0~rc2", -1),
("1.0~rc2", "1.0~rc1", 1),
("1.0~rc1~git123", "1.0~rc1~git123", 0),
("1.0~rc1~git123", "1.0~rc1", -1),
("1.0~rc1", "1.0~rc1~git123", 1),
("a", "a", 0),
("a~", "a", -1),
("a~~", "a", -1),
("a~~~", "a", -1),
("a~~~^", "a", -1),
("a^", "a", +1),
("a^", "a", +1),
("a^", "a^", 0),
("a^", "a^^", -1),
("a^b", "a^^", +1),
# Basic testcases for caret sorting
("1.0^", "1.0^", 0),
("1.0^", "1.0", 1),
("1.0", "1.0^", -1),
("1.0^git1", "1.0^git1", 0),
("1.0^git1", "1.0", 1),
("1.0", "1.0^git1", -1),
("1.0^git1", "1.0^git2", -1),
("1.0^git2", "1.0^git1", 1),
("1.0^git1", "1.01", -1),
("1.01", "1.0^git1", 1),
("1.0^20160101", "1.0^20160101", 0),
("1.0^20160101", "1.0.1", -1),
("1.0.1", "1.0^20160101", 1),
("1.0^20160101^git1", "1.0^20160101^git1", 0),
("1.0^20160102", "1.0^20160101^git1", 1),
("1.0^20160101^git1", "1.0^20160102", -1),
# Basic testcases for tilde and caret sorting
("1.0~rc1^git1", "1.0~rc1^git1", 0),
("1.0~rc1^git1", "1.0~rc1", 1),
("1.0~rc1", "1.0~rc1^git1", -1),
("1.0^git1~pre", "1.0^git1~pre", 0),
("1.0^git1", "1.0^git1~pre", 1),
("1.0^git1~pre", "1.0^git1", -1),
# Additional testing for zeroes
("0", "0", 0),
("0", "00", 0),
("0", "000", 0),
("00", "000", 0),
("000", "000", 0),
("00", "0", 0),
("000", "0", 0),
("0", "0.0", -1),
("0.0", "0", +1),
("0~", "0", -1),
("0^", "0", +1),
("0^", "0^", 0),
("0^", "0^~", 1),
]
@pytest.mark.parametrize("a,b,expected", cases)
def test_version_comparisons(a, b, expected):
assert Version(a)._cmp(b) == expected
@pytest.mark.parametrize("a,b", [c[:2] for c in cases if c[2] < 0])
def test_version_lt(a, b):
assert Version(a) < b
assert Version(a) < Version(b)
@pytest.mark.parametrize("a,b", [c[:2] for c in cases if c[2] > 0])
def test_version_gt(a, b):
assert Version(a) > b
assert Version(a) > Version(b)
@pytest.mark.parametrize("a,b", [c[:2] for c in cases if c[2] == 0])
def test_version_gt(a, b):
assert Version(a) == b
assert Version(a) == Version(b)
......@@ -24,10 +24,9 @@ import functools
import importlib.util
import subprocess
from distutils.spawn import find_executable
from distutils.version import LooseVersion
from diffoscope.tools import get_package_provider, find_executable
from diffoscope.tools import get_package_provider
from ..utils.versions import Version
def file_version():
......@@ -40,11 +39,11 @@ def file_version():
def file_version_is_lt(version):
return file_version() < version
return Version(file_version()) < Version(version)
def file_version_is_ge(version):
return file_version() >= version
return Version(file_version()) >= Version(version)
def tools_missing(*required):
......@@ -105,7 +104,7 @@ def skip_unless_tools_exist(*required):
)
def skip_if_tool_version_is(tool, actual_ver, target_ver, vcls=LooseVersion):
def skip_if_tool_version_is(tool, actual_ver, target_ver, vcls=Version):
if tools_missing(tool):
return skipif(True, reason=reason(tool), tools=(tool,))
if callable(actual_ver):
......@@ -119,7 +118,7 @@ def skip_if_tool_version_is(tool, actual_ver, target_ver, vcls=LooseVersion):
)
def skip_unless_tool_is_at_least(tool, actual_ver, min_ver, vcls=LooseVersion):
def skip_unless_tool_is_at_least(tool, actual_ver, min_ver, vcls=Version):
if tools_missing(tool) and module_is_not_importable(tool):
return skipif(True, reason=reason(tool), tools=(tool,))
if callable(actual_ver):
......@@ -133,7 +132,7 @@ def skip_unless_tool_is_at_least(tool, actual_ver, min_ver, vcls=LooseVersion):
)
def skip_unless_tool_is_at_most(tool, actual_ver, max_ver, vcls=LooseVersion):
def skip_unless_tool_is_at_most(tool, actual_ver, max_ver, vcls=Version):
if tools_missing(tool) and module_is_not_importable(tool):
return skipif(True, reason=reason(tool), tools=(tool,))
if callable(actual_ver):
......@@ -148,7 +147,7 @@ def skip_unless_tool_is_at_most(tool, actual_ver, max_ver, vcls=LooseVersion):
def skip_unless_tool_is_between(
tool, actual_ver, min_ver, max_ver, vcls=LooseVersion
tool, actual_ver, min_ver, max_ver, vcls=Version
):
if tools_missing(tool):
return skipif(True, reason=reason(tool), tools=(tool,))
......
#
# diffoscope: in-depth comparison of files, archives, and directories
#
# Copyright © 2021 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
#
# diffoscope 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.
#
# diffoscope 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 diffoscope. If not, see <https://www.gnu.org/licenses/>.
import itertools
import functools
import string
@functools.total_ordering
class Version(str):
CHARS = string.ascii_letters + string.digits + "~^"
_ALPHA_ORD = {
"~": -1,
"^": +1,
None: 0,
**{c: ord(c) for c in string.ascii_letters},
}
@staticmethod
def _len_prefix(s, chars, matching=True):
for i in range(len(s)):
if (s[i] in chars) != matching:
return i
return len(s)
@classmethod
def _cmp_alphabetical(cls, string_a, string_b):
len_a = cls._len_prefix(string_a, string.ascii_letters + "~^")
len_b = cls._len_prefix(string_b, string.ascii_letters + "~^")
for i in range(max(len_a, len_b)):
a = string_a[i] if i < len_a else None
b = string_b[i] if i < len_b else None
c = cls._ALPHA_ORD[a] - cls._ALPHA_ORD[b]
if c:
return i, c
return len_a, 0
def _cmp(self, other):
I, J = len(self), len(other)
i = j = 0
k = 20
while i < I or j < J:
# print(f'{i=} {j=} {self[i:]=!r} {other[j:]=!r}')
i += self._len_prefix(self[i:], self.CHARS, matching=False)
j += self._len_prefix(other[j:], self.CHARS, matching=False)
# print(f'{i=} {j=} {self[i:]=!r} {other[j:]=!r}')
if (i < I and self[i] in string.digits) or (
j < J and other[j] in string.digits
):
ii = i + self._len_prefix(self[i:], string.digits)
jj = j + self._len_prefix(other[j:], string.digits)
c = (ii == i) - (jj == j)
# print(f'digits: {c=}')
if c:
return -c
c = int(self[i:ii]) - int(other[j:jj])
# print(f'digits: {c=}')
i, j = ii, jj
else:
skip, c = self._cmp_alphabetical(self[i:], other[j:])
# print(f'alphas: {c=}')
i += skip
j += skip
if c:
return -1 if c < 0 else +1
k -= 1
assert k >= 0
return 0
def __eq__(self, other):
return self._cmp(other) == 0
def __lt__(self, other):
return self._cmp(other) < 0