Add a dependent_package parameter to Dependency's constructor, and use it in exceptions the class raises.
245 lines
8.8 KiB
Python
245 lines
8.8 KiB
Python
from jw.pkg.lib.version import Component, Dependency, Syntax, Version
|
|
|
|
class FakeApp:
|
|
"""Minimal stand-in for jw.pkg.App providing get_version()"""
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
def __init__(self, versions: dict[str, str]) -> None:
|
|
self.__versions = versions
|
|
|
|
def get_version(self, project: str) -> str:
|
|
if project not in self.__versions:
|
|
raise self.Error(f"Can't get version of project {project}")
|
|
return self.__versions[project]
|
|
|
|
app = FakeApp({'foo': '1.2.3-45', 'bar': '0.9.1-2'})
|
|
|
|
# Name only
|
|
d = Dependency('foo')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo'
|
|
assert d.version_boundaries() == []
|
|
assert d.constraint_str() == 'foo'
|
|
|
|
# Subpackage suffixes are stripped from base_name
|
|
d = Dependency('foo-dev')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo-dev'
|
|
d = Dependency('foo-devel')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo-devel'
|
|
d = Dependency('foo-run')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo-run'
|
|
|
|
# A package literally named 'dev' keeps its name
|
|
d = Dependency('dev')
|
|
assert d.base_name == 'dev'
|
|
assert d.full_name == 'dev'
|
|
|
|
# Operators with and without whitespace
|
|
for op in ['=', '==', '<', '<=', '>', '>=']:
|
|
d = Dependency(f'foo {op} 1.0')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo'
|
|
b = d.version_boundaries()
|
|
assert len(b) == 1
|
|
assert b[0].op == op
|
|
assert b[0].version.id(False) == '1.0'
|
|
d = Dependency(f'foo{op}1.0')
|
|
assert d.constraint_str(untemplated = False) == f'foo {op} 1.0'
|
|
|
|
# Without a lookup, literals render as written and macros fail
|
|
d = Dependency('foo-devel >= 2.0')
|
|
assert d.constraint_str() == 'foo-devel >= 2.0'
|
|
try:
|
|
Dependency('foo = VERSION').constraint_str()
|
|
assert False, 'Should have raised'
|
|
except Version.Error:
|
|
pass
|
|
|
|
# The lookup resolves the macros
|
|
d = Dependency('foo = VERSION', app.get_version)
|
|
assert d.constraint_str() == 'foo = 1.2.3-45'
|
|
d = Dependency('foo = VERSION', app.get_version)
|
|
assert d.constraint_str(include_revision = False) == 'foo = 1.2.3'
|
|
# Only the VERSION macro loses its revision, VERSION-REVISION and
|
|
# literals keep it
|
|
d = Dependency('foo = VERSION-REVISION', app.get_version)
|
|
assert d.constraint_str() == 'foo = 1.2.3-45'
|
|
assert d.constraint_str(include_revision = False) == 'foo = 1.2.3-45'
|
|
d = Dependency('foo = 1.2.3-45')
|
|
assert d.constraint_str(include_revision = False) == 'foo = 1.2.3-45'
|
|
# untemplated, the macro has no revision to strip
|
|
d = Dependency('foo = VERSION', app.get_version)
|
|
assert d.constraint_str(untemplated=False, include_revision=False) == \
|
|
'foo = VERSION'
|
|
# untemplated=False keeps the specifiers as written
|
|
d = Dependency('foo = VERSION', app.get_version)
|
|
assert d.constraint_str(untemplated = False) == 'foo = VERSION'
|
|
# A bare REVISION is rejected on the grounds that is assumed that
|
|
# the user wanted VERSION instead
|
|
try:
|
|
Dependency('foo = REVISION', app.get_version).constraint_str()
|
|
assert False, 'Should have raised'
|
|
except Dependency.Error:
|
|
pass
|
|
|
|
# The lookup uses the base name, not the subpackage name
|
|
d = Dependency('foo-devel = VERSION', app.get_version)
|
|
assert d.constraint_str() == 'foo-devel = 1.2.3-45'
|
|
# Unknown projects fail when untemplating
|
|
try:
|
|
Dependency('baz = VERSION', app.get_version).constraint_str()
|
|
assert False, 'Should have raised'
|
|
except FakeApp.Error:
|
|
pass
|
|
|
|
# The lookup is cached: the mapper is called once
|
|
calls: list[str] = []
|
|
|
|
def mapper(project: str) -> str:
|
|
calls.append(project)
|
|
return '1.2.3-45'
|
|
|
|
d = Dependency('foo = VERSION', mapper)
|
|
assert d.constraint_str() == 'foo = 1.2.3-45'
|
|
assert d.constraint_str() == 'foo = 1.2.3-45'
|
|
assert calls == ['foo']
|
|
|
|
# as_range expands full boundaries into a revision range
|
|
d = Dependency('foo = VERSION-REVISION', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo >= 1.2.3-45 foo < 1.2.4'
|
|
d = Dependency('foo >= VERSION-REVISION', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo >= 1.2.3-45 foo < 1.2.4'
|
|
d = Dependency('foo > 1.0.0-259', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo > 1.0.0-259 foo < 1.0.1'
|
|
# A raw render keeps the macro, with the computed bound alongside it
|
|
d = Dependency('foo = VERSION-REVISION', app.get_version)
|
|
constraint = d.constraint_str(untemplated = False, as_range = True)
|
|
assert constraint == 'foo >= VERSION-REVISION foo < 1.2.4'
|
|
# VERSION is not full, so a VERSION constraint is not expanded
|
|
d = Dependency('foo = VERSION', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo = 1.2.3-45'
|
|
d = Dependency('foo >= VERSION', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo >= 1.2.3-45'
|
|
# '<' and '<=' are not expanded
|
|
d = Dependency('foo <= VERSION', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo <= 1.2.3-45'
|
|
# versions without a full major.minor.micro-revision are not expanded
|
|
d = Dependency('foo = 1.0', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo = 1.0'
|
|
d = Dependency('foo = 1.2.3', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo = 1.2.3'
|
|
d = Dependency('foo = 1.0-rc1', app.get_version)
|
|
assert d.constraint_str(as_range = True) == 'foo = 1.0-rc1'
|
|
# unimplemented operators raise
|
|
d = Dependency('foo == 1.2.3-45', app.get_version)
|
|
try:
|
|
d.constraint_str(as_range = True)
|
|
assert False, 'Should have raised'
|
|
except NotImplementedError:
|
|
pass
|
|
|
|
# Default syntax is SEM_VER
|
|
d = Dependency('foo < 2.0')
|
|
assert d.constraint_str(untemplated = False) == 'foo < 2.0'
|
|
|
|
# DEBIAN converts strict inequalities, other operators pass through
|
|
d = Dependency('foo < 2.0')
|
|
assert d.constraint_str(Syntax.DEBIAN, untemplated = False) == 'foo << 2.0'
|
|
d = Dependency('foo > 1.0')
|
|
assert d.constraint_str(Syntax.DEBIAN, untemplated = False) == 'foo >> 1.0'
|
|
d = Dependency('foo <= 1.0')
|
|
assert d.constraint_str(Syntax.DEBIAN, untemplated = False) == 'foo <= 1.0'
|
|
d = Dependency('foo = 1.0')
|
|
assert d.constraint_str(Syntax.DEBIAN, untemplated = False) == 'foo = 1.0'
|
|
|
|
# NAMES_ONLY drops the version
|
|
d = Dependency('foo-devel >= 1.0')
|
|
assert d.constraint_str(Syntax.NAMES_ONLY) == 'foo-devel'
|
|
|
|
# no_subpackages strips the suffix from the name
|
|
d = Dependency('foo-devel >= 1.0')
|
|
assert d.constraint_str(no_subpackages = True, untemplated = False) == 'foo >= 1.0'
|
|
|
|
# quote wraps the result
|
|
d = Dependency('foo-devel >= 1.0')
|
|
assert d.constraint_str(quote = '"', untemplated = False) == '"foo-devel >= 1.0"'
|
|
|
|
# Version parts
|
|
v = Version('foo', '1.2.3-45', app.get_version)
|
|
assert v.major == 1
|
|
assert v.minor == 2
|
|
assert v.micro == 3
|
|
assert v.core(True) == '1.2.3'
|
|
assert v.revision(True) == '45'
|
|
assert str(v.next_binary_compatible) == '1.2.3-46'
|
|
assert str(v.next_binary_incompatible) == '1.2.4'
|
|
assert str(v.next_source_incompatible) == '1.3.0'
|
|
assert v.parts_str(Component.ID, True) == '1.2.3-45'
|
|
assert v.parts_str(Component.CORE, True) == '1.2.3'
|
|
assert v.parts_str(Component.MAJOR | Component.MINOR, True) == '1.2'
|
|
|
|
# next_binary_compatible steps to the next revision, dropping any suffix
|
|
for spec, expected in [
|
|
('1.2.3-4', '1.2.3-5'),
|
|
('1.2.3-4blah', '1.2.3-5'),
|
|
('1.2.3-4.myvariant', '1.2.3-5'),
|
|
('1.2.3-4-broken', '1.2.3-5'),
|
|
('1.0.0-259', '1.0.0-260'),
|
|
]:
|
|
v = Version('foo', spec)
|
|
assert str(v.next_binary_compatible) == expected, spec
|
|
# A macro resolves before the increment
|
|
v = Version('foo', 'VERSION-REVISION', app.get_version)
|
|
assert str(v.next_binary_compatible) == '1.2.3-46'
|
|
# A revision without a leading digit cannot be incremented
|
|
for spec in ['1.2.3-rc1', '1.2.3']:
|
|
v = Version('foo', spec)
|
|
try:
|
|
v.next_binary_compatible
|
|
assert False, f'Should have raised for {spec!r}'
|
|
except Version.Error:
|
|
pass
|
|
|
|
# constructor
|
|
d = Dependency('foo-devel = 1.0')
|
|
assert d.base_name == 'foo'
|
|
assert d.full_name == 'foo-devel'
|
|
assert d.constraint_str(untemplated = False) == 'foo-devel = 1.0'
|
|
|
|
# parse_deps_spec with a comma-separated string
|
|
deps = Dependency.parse_deps_spec('foo = 1.0, bar-devel >= 2.0, baz')
|
|
assert len(deps) == 3
|
|
assert [d.full_name for d in deps] == ['foo', 'bar-devel', 'baz']
|
|
assert deps[1].base_name == 'bar'
|
|
assert deps[1].constraint_str(untemplated = False) == 'bar-devel >= 2.0'
|
|
assert deps[2].constraint_str() == 'baz'
|
|
|
|
# parse_deps_spec passes the lookup to the created packages
|
|
deps = Dependency.parse_deps_spec('foo = VERSION, bar', app.get_version)
|
|
assert deps[0].constraint_str() == 'foo = 1.2.3-45'
|
|
assert deps[1].constraint_str() == 'bar'
|
|
|
|
# Malformed specs are rejected when parsed
|
|
try:
|
|
Dependency('foo = 1.0 > 2.0').full_name
|
|
assert False, 'Should have raised'
|
|
except Dependency.Error:
|
|
pass
|
|
# Empty names and versions are rejected
|
|
for bad in ['', 'foo =', ' = 1.0']:
|
|
try:
|
|
Dependency(bad).full_name
|
|
assert False, f'Should have raised for {bad!r}'
|
|
except Dependency.Error:
|
|
pass
|
|
|
|
# str
|
|
assert str(Dependency('foo-devel >= 1.0')) == 'foo-devel >= 1.0'
|
|
assert str(Dependency('foo')) == 'foo'
|
|
|
|
print('All lib.version tests passed')
|