rpm-version 0.5.0

A library for dealing with RPM versions (NEVRA, EVR) correctly. Sort algorithm is identical to RPM.
Documentation
"""Tests for Evr, Nevra, evr_compare, evr_sort, and Requirement."""

from rpm_version import Evr, Nevra, evr_compare, evr_sort, nevra_sort


class TestEvr:
    def test_construct(self):
        e = Evr("1", "2.3.4", "5.el9")
        assert e.epoch == "1"
        assert e.version == "2.3.4"
        assert e.release == "5.el9"

    def test_parse(self):
        e = Evr.parse("1:2.3.4-5")
        assert e.epoch == "1"
        assert e.version == "2.3.4"
        assert e.release == "5"

    def test_parse_no_epoch(self):
        e = Evr.parse("2.3.4-5")
        assert e.epoch == ""
        assert e.version == "2.3.4"
        assert e.release == "5"

    def test_str(self):
        e = Evr("1", "2.3", "4")
        assert str(e) == "1:2.3-4"

    def test_repr(self):
        e = Evr("1", "2.3", "4")
        assert "Evr" in repr(e)

    def test_normalized_form(self):
        e = Evr("", "1.0", "1")
        assert e.evr() == "0:1.0-1"

    def test_normalized_form_with_epoch(self):
        e = Evr("2", "1.0", "1")
        assert e.evr() == "2:1.0-1"

    def test_ordering(self):
        a = Evr.parse("1.0-1")
        b = Evr.parse("2.0-1")
        assert a < b
        assert b > a
        assert a <= b
        assert b >= a

    def test_equality(self):
        a = Evr.parse("1.0-1")
        b = Evr.parse("1.0-1")
        assert a == b
        assert not (a != b)

    def test_epoch_comparison(self):
        a = Evr.parse("1:1.0-1")
        b = Evr.parse("2:1.0-1")
        assert a < b

    def test_tilde_prerelease(self):
        """Tilde (~) denotes a pre-release: 1.0~rc1 sorts before 1.0."""
        assert Evr.parse("1.0~rc1-1") < Evr.parse("1.0-1")
        assert Evr.parse("1.0~rc1-1") < Evr.parse("1.0~rc2-1")

    def test_caret_snapshot(self):
        """Caret (^) denotes a post-release snapshot: 1.0^git1 sorts after 1.0 but before 1.1."""
        assert Evr.parse("1.0-1") < Evr.parse("1.0^git1-1")
        assert Evr.parse("1.0^git1-1") < Evr.parse("1.1-1")

    def test_tilde_and_caret_combined(self):
        """Tilde and caret can combine: 1.0~rc1^git1 sorts after 1.0~rc1 but before 1.0."""
        assert Evr.parse("1.0~rc1-1") < Evr.parse("1.0~rc1^git1-1")
        assert Evr.parse("1.0~rc1^git1-1") < Evr.parse("1.0-1")


class TestEvrCompare:
    def test_less(self):
        assert evr_compare("1.0-1", "2.0-1") == -1

    def test_greater(self):
        assert evr_compare("2:1.0-1", "1:9.9-1") == 1

    def test_equal(self):
        assert evr_compare("1.0-1", "1.0-1") == 0

    def test_release_comparison(self):
        assert evr_compare("1.0-1", "1.0-2") == -1


class TestNevra:
    def test_construct(self):
        n = Nevra("foo", "1", "2.3", "4", "x86_64")
        assert n.name == "foo"
        assert n.epoch == "1"
        assert n.version == "2.3"
        assert n.release == "4"
        assert n.arch == "x86_64"

    def test_parse(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        assert n.name == "foo"
        assert n.epoch == "1"
        assert n.version == "2.3"
        assert n.release == "4"
        assert n.arch == "x86_64"

    def test_str(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        assert str(n) == "foo-1:2.3-4.x86_64"

    def test_repr(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        assert "Nevra" in repr(n)

    def test_nvra(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        assert n.nvra() == "foo-2.3-4.x86_64"

    def test_nevra_short(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        assert n.nevra_short() == "foo-1:2.3-4.x86_64"
        n = Nevra.parse("foo-2.3-4.x86_64")
        assert n.nevra_short() == "foo-2.3-4.x86_64"

    def test_evr(self):
        n = Nevra.parse("foo-1:2.3-4.x86_64")
        evr = n.evr()
        assert isinstance(evr, Evr)
        assert evr.epoch == "1"
        assert evr.version == "2.3"

    def test_ordering(self):
        a = Nevra.parse("foo-1.0-1.x86_64")
        b = Nevra.parse("foo-2.0-1.x86_64")
        assert a < b

    def test_equality(self):
        a = Nevra.parse("foo-1.0-1.x86_64")
        b = Nevra.parse("foo-1.0-1.x86_64")
        assert a == b


class TestEvrSort:
    def test_basic_sort(self):
        result = evr_sort(["2.0-1", "1.0-1", "3.0-1"])
        assert result == ["1.0-1", "2.0-1", "3.0-1"]

    def test_epoch_sort(self):
        result = evr_sort(["2.0-1", "1:0.5-1", "1.0-1"])
        assert result == ["1.0-1", "2.0-1", "1:0.5-1"]

    def test_tilde_and_caret(self):
        result = evr_sort(["1.0^git1-1", "1.0-1", "1.0~rc1-1", "1.1-1"])
        assert result == ["1.0~rc1-1", "1.0-1", "1.0^git1-1", "1.1-1"]

    def test_empty_list(self):
        assert evr_sort([]) == []

    def test_single_element(self):
        assert evr_sort(["1.0-1"]) == ["1.0-1"]

    def test_matches_elementwise(self):
        versions = ["3.0-1", "1:0.1-1", "1.0~rc1-1", "2.0-1", "1.0-1"]
        assert evr_sort(versions) == sorted(versions, key=Evr.parse)


class TestNevraSort:
    def test_basic_sort(self):
        result = nevra_sort(
            ["foo-2.0-1.x86_64", "bar-1.0-1.x86_64", "foo-1.0-1.x86_64"]
        )
        assert result == ["bar-1.0-1.x86_64", "foo-1.0-1.x86_64", "foo-2.0-1.x86_64"]

    def test_epoch_sort(self):
        result = nevra_sort(
            ["foo-2.0-1.x86_64", "foo-1:0.5-1.x86_64", "foo-1.0-1.x86_64"]
        )
        assert result == ["foo-1.0-1.x86_64", "foo-2.0-1.x86_64", "foo-1:0.5-1.x86_64"]

    def test_different_arches(self):
        result = nevra_sort(["foo-1.0-1.x86_64", "foo-1.0-1.i686", "foo-1.0-1.noarch"])
        assert result == ["foo-1.0-1.i686", "foo-1.0-1.noarch", "foo-1.0-1.x86_64"]

    def test_empty_list(self):
        assert nevra_sort([]) == []

    def test_matches_elementwise(self):
        packages = ["foo-2.0-1.x86_64", "bar-1:0.1-1.noarch", "baz-1.0~rc1-1.i686"]
        assert nevra_sort(packages) == sorted(packages, key=Nevra.parse)


class TestHash:
    def test_evr_hashable(self):
        s = {Evr.parse("1.0-1"), Evr.parse("2.0-1")}
        assert len(s) == 2

    def test_evr_epoch_hash_consistency(self):
        a = Evr.parse("1.0-1")
        b = Evr.parse("0:1.0-1")
        assert a == b
        assert hash(a) == hash(b)

    def test_nevra_hashable(self):
        s = {Nevra.parse("foo-1.0-1.x86_64"), Nevra.parse("bar-1.0-1.x86_64")}
        assert len(s) == 2

    def test_nevra_epoch_hash_consistency(self):
        a = Nevra.parse("foo-1.0-1.x86_64")
        b = Nevra.parse("foo-0:1.0-1.x86_64")
        assert a == b
        assert hash(a) == hash(b)