# -*- coding: utf-8 -*- # (c) 2020 Matt Martz # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import annotations from ansible.module_utils.compat.version import LooseVersion, StrictVersion import pytest from ansible.utils.version import _Alpha, _Numeric, SemanticVersion EQ = [ ('1.0.0', '1.0.0', True), ('1.0.0', '1.0.0-beta', False), ('1.0.0-beta2+build1', '1.0.0-beta.2+build.1', False), ('1.0.0-beta+build', '1.0.0-beta+build', True), ('1.0.0-beta+build1', '1.0.0-beta+build2', True), ('1.0.0-beta+a', '1.0.0-alpha+bar', False), ] NE = [ ('1.0.0', '1.0.0', False), ('1.0.0', '1.0.0-beta', True), ('1.0.0-beta2+build1', '1.0.0-beta.2+build.1', True), ('1.0.0-beta+build', '1.0.0-beta+build', False), ('1.0.0-beta+a', '1.0.0-alpha+bar', True), ] LT = [ ('1.0.0', '2.0.0', True), ('1.0.0-beta', '2.0.0-alpha', True), ('1.0.0-alpha', '2.0.0-beta', True), ('1.0.0-alpha', '1.0.0', True), ('1.0.0-beta', '1.0.0-alpha3', False), ('1.0.0+foo', '1.0.0-alpha', False), ('1.0.0-beta.1', '1.0.0-beta.a', True), ('1.0.0-beta+a', '1.0.0-alpha+bar', False), ] GT = [ ('1.0.0', '2.0.0', False), ('1.0.0-beta', '2.0.0-alpha', False), ('1.0.0-alpha', '2.0.0-beta', False), ('1.0.0-alpha', '1.0.0', False), ('1.0.0-beta', '1.0.0-alpha3', True), ('1.0.0+foo', '1.0.0-alpha', True), ('1.0.0-beta.1', '1.0.0-beta.a', False), ('1.0.0-beta+a', '1.0.0-alpha+bar', True), ] LE = [ ('1.0.0', '1.0.0', True), ('1.0.0', '2.0.0', True), ('1.0.0-alpha', '1.0.0-beta', True), ('1.0.0-beta', '1.0.0-alpha', False), ] GE = [ ('1.0.0', '1.0.0', True), ('1.0.0', '2.0.0', False), ('1.0.0-alpha', '1.0.0-beta', False), ('1.0.0-beta', '1.0.0-alpha', True), ] VALID = [ "0.0.4", "1.2.3", "10.20.30", "1.1.2-prerelease+meta", "1.1.2+meta", "1.1.2+meta-valid", "1.0.0-alpha", "1.0.0-beta", "1.0.0-alpha.beta", "1.0.0-alpha.beta.1", "1.0.0-alpha.1", "1.0.0-alpha0.valid", "1.0.0-alpha.0valid", "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", "1.0.0-rc.1+build.1", "2.0.0-rc.1+build.123", "1.2.3-beta", "10.2.3-DEV-SNAPSHOT", "1.2.3-SNAPSHOT-123", "1.0.0", "2.0.0", "1.1.7", "2.0.0+build.1848", "2.0.1-alpha.1227", "1.0.0-alpha+beta", "1.2.3----RC-SNAPSHOT.12.9.1--.12+788", "1.2.3----R-S.12.9.1--.12+meta", "1.2.3----RC-SNAPSHOT.12.9.1--.12", "1.0.0+0.build.1-rc.10000aaa-kk-0.1", "99999999999999999999999.999999999999999999.99999999999999999", "1.0.0-0A.is.legal", ] INVALID = [ "1", "1.2", "1.2.3-0123", "1.2.3-0123.0123", "1.1.2+.123", "+invalid", "-invalid", "-invalid+invalid", "-invalid.01", "alpha", "alpha.beta", "alpha.beta.1", "alpha.1", "alpha+beta", "alpha_beta", "alpha.", "alpha..", "beta", "1.0.0-alpha_beta", "-alpha.", "1.0.0-alpha..", "1.0.0-alpha..1", "1.0.0-alpha...1", "1.0.0-alpha....1", "1.0.0-alpha.....1", "1.0.0-alpha......1", "1.0.0-alpha.......1", "01.1.1", "1.01.1", "1.1.01", "1.2", "1.2.3.DEV", "1.2-SNAPSHOT", "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788", "1.2-RC-SNAPSHOT", "-1.0.3-gamma+b7718", "+justmeta", "9.8.7+meta+meta", "9.8.7-whatever+meta+meta", ] PRERELEASE = [ ('1.0.0-alpha', True), ('1.0.0-alpha.1', True), ('1.0.0-0.3.7', True), ('1.0.0-x.7.z.92', True), ('0.1.2', False), ('0.1.2+bob', False), ('1.0.0', False), ] STABLE = [ ('1.0.0-alpha', False), ('1.0.0-alpha.1', False), ('1.0.0-0.3.7', False), ('1.0.0-x.7.z.92', False), ('0.1.2', False), ('0.1.2+bob', False), ('1.0.0', True), ('1.0.0+bob', True), ] LOOSE_VERSION = [ (LooseVersion('1'), SemanticVersion('1.0.0')), (LooseVersion('1-alpha'), SemanticVersion('1.0.0-alpha')), (LooseVersion('1.0.0-alpha+build'), SemanticVersion('1.0.0-alpha+build')), ] LOOSE_VERSION_INVALID = [ LooseVersion('1.a.3'), LooseVersion(), 'bar', StrictVersion('1.2.3'), ] def test_semanticversion_none(): assert SemanticVersion().major is None @pytest.mark.parametrize('left,right,expected', EQ) def test_eq(left, right, expected): assert (SemanticVersion(left) == SemanticVersion(right)) is expected @pytest.mark.parametrize('left,right,expected', NE) def test_ne(left, right, expected): assert (SemanticVersion(left) != SemanticVersion(right)) is expected @pytest.mark.parametrize('left,right,expected', LT) def test_lt(left, right, expected): assert (SemanticVersion(left) < SemanticVersion(right)) is expected @pytest.mark.parametrize('left,right,expected', LE) def test_le(left, right, expected): assert (SemanticVersion(left) <= SemanticVersion(right)) is expected @pytest.mark.parametrize('left,right,expected', GT) def test_gt(left, right, expected): assert (SemanticVersion(left) > SemanticVersion(right)) is expected @pytest.mark.parametrize('left,right,expected', GE) def test_ge(left, right, expected): assert (SemanticVersion(left) >= SemanticVersion(right)) is expected @pytest.mark.parametrize('value', VALID) def test_valid(value): SemanticVersion(value) @pytest.mark.parametrize('value', INVALID) def test_invalid(value): pytest.raises(ValueError, SemanticVersion, value) def test_example_precedence(): # https://semver.org/#spec-item-11 sv = SemanticVersion assert sv('1.0.0') < sv('2.0.0') < sv('2.1.0') < sv('2.1.1') assert sv('1.0.0-alpha') < sv('1.0.0') assert sv('1.0.0-alpha') < sv('1.0.0-alpha.1') < sv('1.0.0-alpha.beta') assert sv('1.0.0-beta') < sv('1.0.0-beta.2') < sv('1.0.0-beta.11') < sv('1.0.0-rc.1') < sv('1.0.0') @pytest.mark.parametrize('value,expected', PRERELEASE) def test_prerelease(value, expected): assert SemanticVersion(value).is_prerelease is expected @pytest.mark.parametrize('value,expected', STABLE) def test_stable(value, expected): assert SemanticVersion(value).is_stable is expected @pytest.mark.parametrize('value,expected', LOOSE_VERSION) def test_from_loose_version(value, expected): assert SemanticVersion.from_loose_version(value) == expected @pytest.mark.parametrize('value', LOOSE_VERSION_INVALID) def test_from_loose_version_invalid(value): pytest.raises((AttributeError, ValueError), SemanticVersion.from_loose_version, value) def test_comparison_with_string(): assert SemanticVersion('1.0.0') > '0.1.0' def test_alpha(): assert _Alpha('a') == _Alpha('a') assert _Alpha('a') == 'a' assert _Alpha('a') != _Alpha('b') assert _Alpha('a') != 1 assert _Alpha('a') < _Alpha('b') assert _Alpha('a') < 'c' assert _Alpha('a') > _Numeric(1) with pytest.raises(ValueError): _Alpha('a') < None assert _Alpha('a') <= _Alpha('a') assert _Alpha('a') <= _Alpha('b') assert _Alpha('b') >= _Alpha('a') assert _Alpha('b') >= _Alpha('b') # The following 3*6 tests check that all comparison operators perform # as expected. DO NOT remove any of them, or reformulate them (to remove # the explicit `not`)! assert _Alpha('a') == _Alpha('a') assert not _Alpha('a') != _Alpha('a') # pylint: disable=unneeded-not assert not _Alpha('a') < _Alpha('a') # pylint: disable=unneeded-not assert _Alpha('a') <= _Alpha('a') assert not _Alpha('a') > _Alpha('a') # pylint: disable=unneeded-not assert _Alpha('a') >= _Alpha('a') assert not _Alpha('a') == _Alpha('b') # pylint: disable=unneeded-not assert _Alpha('a') != _Alpha('b') assert _Alpha('a') < _Alpha('b') assert _Alpha('a') <= _Alpha('b') assert not _Alpha('a') > _Alpha('b') # pylint: disable=unneeded-not assert not _Alpha('a') >= _Alpha('b') # pylint: disable=unneeded-not assert not _Alpha('b') == _Alpha('a') # pylint: disable=unneeded-not assert _Alpha('b') != _Alpha('a') assert not _Alpha('b') < _Alpha('a') # pylint: disable=unneeded-not assert not _Alpha('b') <= _Alpha('a') # pylint: disable=unneeded-not assert _Alpha('b') > _Alpha('a') assert _Alpha('b') >= _Alpha('a') def test_numeric(): assert _Numeric(1) == _Numeric(1) assert _Numeric(1) == 1 assert _Numeric(1) != _Numeric(2) assert _Numeric(1) != 'a' assert _Numeric(1) < _Numeric(2) assert _Numeric(1) < 3 assert _Numeric(1) < _Alpha('b') with pytest.raises(ValueError): _Numeric(1) < None assert _Numeric(1) <= _Numeric(1) assert _Numeric(1) <= _Numeric(2) assert _Numeric(2) >= _Numeric(1) assert _Numeric(2) >= _Numeric(2) # The following 3*6 tests check that all comparison operators perform # as expected. DO NOT remove any of them, or reformulate them (to remove # the explicit `not`)! assert _Numeric(1) == _Numeric(1) assert not _Numeric(1) != _Numeric(1) # pylint: disable=unneeded-not assert not _Numeric(1) < _Numeric(1) # pylint: disable=unneeded-not assert _Numeric(1) <= _Numeric(1) assert not _Numeric(1) > _Numeric(1) # pylint: disable=unneeded-not assert _Numeric(1) >= _Numeric(1) assert not _Numeric(1) == _Numeric(2) # pylint: disable=unneeded-not assert _Numeric(1) != _Numeric(2) assert _Numeric(1) < _Numeric(2) assert _Numeric(1) <= _Numeric(2) assert not _Numeric(1) > _Numeric(2) # pylint: disable=unneeded-not assert not _Numeric(1) >= _Numeric(2) # pylint: disable=unneeded-not assert not _Numeric(2) == _Numeric(1) # pylint: disable=unneeded-not assert _Numeric(2) != _Numeric(1) assert not _Numeric(2) < _Numeric(1) # pylint: disable=unneeded-not assert not _Numeric(2) <= _Numeric(1) # pylint: disable=unneeded-not assert _Numeric(2) > _Numeric(1) assert _Numeric(2) >= _Numeric(1)