Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Make the ``distro`` package importable without an install.

Tries, in order: this workdir's ``distro-src/src`` checkout and the
repo root's ``src`` layout (the layout the PR targets).
"""

import os
import sys

_HERE = os.path.dirname(os.path.abspath(__file__))
_CANDIDATES = (
os.path.join(_HERE, "..", "distro-src", "src"),
os.path.join(_HERE, "..", "..", "src"),
)
for _candidate in _CANDIDATES:
if os.path.isdir(os.path.join(_candidate, "distro")):
sys.path.insert(0, os.path.normpath(_candidate))
168 changes: 168 additions & 0 deletions tests/test_distro_release_lsb_extended.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""Extended distro-release / lsb-release / uname coverage for distro.

Probed behaviours pinned here:

* A distro-release file's ``id`` comes from its *file name*
(``centos-release`` -> ``centos``, ``redhat-release`` -> ``redhat``
then normalised to ``rhel``); a file with a random name yields no
``id`` even though name/version/codename still parse.
* ``lsb_release`` output is only used when ``include_lsb`` is true,
and its ``Distributor ID`` goes through the same normalisation
table (``RedHatEnterpriseServer`` -> ``rhel``).
* ``_parse_uname_content`` deliberately returns ``{}`` for Linux
(so the kernel version never becomes the distro version) and
parses other systems (``FreeBSD 13.2-RELEASE`` -> freebsd/13.2).
"""

import os
import stat

import pytest

from distro import LinuxDistribution

MISSING = "/nonexistent/os-release"


def distro_only(tmp_path, filename, content):
path = tmp_path / filename
path.write_text(content)
return LinuxDistribution(
os_release_file=MISSING,
distro_release_file=str(path),
include_lsb=False,
include_uname=False,
)


def test_centos_release_file(tmp_path):
d = distro_only(tmp_path, "centos-release", "CentOS Linux release 7.9.2009 (Core)\n")
assert d.id() == "centos"
assert d.name() == "CentOS Linux"
assert d.version() == "7.9.2009"
assert d.version_parts() == ("7", "9", "2009")
assert d.codename() == "Core"
assert d.distro_release_info() == {
"name": "CentOS Linux",
"version_id": "7.9.2009",
"codename": "Core",
"id": "centos",
}


def test_redhat_release_file_id_is_normalized_to_rhel(tmp_path):
d = distro_only(tmp_path, "redhat-release", "Red Hat Enterprise Linux release 7.9 (Maipo)\n")
assert d.id() == "rhel"
assert d.name() == "Red Hat Enterprise Linux"
assert d.version() == "7.9"
assert d.codename() == "Maipo"


def test_fedora_release_file(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
assert d.id() == "fedora"
assert d.version() == "30"
assert d.codename() == "Thirty"


def test_randomly_named_release_file_has_no_id(tmp_path):
d = distro_only(tmp_path, "myfile.txt", "CentOS Linux release 7.9.2009 (Core)\n")
assert d.id() == ""
assert d.name() == "CentOS Linux"
assert d.version() == "7.9.2009"


def test_parse_distro_release_content_directly(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
assert d._parse_distro_release_content("Fedora release 30 (Thirty)\n") == {
"name": "Fedora",
"version_id": "30",
"codename": "Thirty",
}
assert d._parse_distro_release_content("Some Unparsed Line\n") == {
"name": "Some Unparsed Line"
}
assert d._parse_distro_release_content("") == {}


def test_parse_lsb_release_content_directly(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
parsed = d._parse_lsb_release_content(
[
"Distributor ID:\tDebian\n",
"Description:\tDebian GNU/Linux 12 (bookworm)\n",
"Release:\t12\n",
"Codename:\tbookworm\n",
"No colon here\n",
]
)
assert parsed == {
"distributor_id": "Debian",
"description": "Debian GNU/Linux 12 (bookworm)",
"release": "12",
"codename": "bookworm",
}


def test_lsb_n_a_becomes_empty(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
parsed = d._parse_lsb_release_content(["Codename:\tn/a\n"])
assert parsed == {"codename": ""}


def test_fake_lsb_release_command_via_path(tmp_path, monkeypatch):
bindir = tmp_path / "bin"
bindir.mkdir()
script = bindir / "lsb_release"
script.write_text(
"#!/bin/sh\n"
"printf 'Distributor ID:\\tRedHatEnterpriseServer\\n"
"Description:\\tRed Hat Enterprise Linux Server release 7.9 (Maipo)\\n"
"Release:\\t7.9\\nCodename:\\tMaipo\\n'\n"
)
script.chmod(script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
monkeypatch.setenv("PATH", str(bindir) + os.pathsep + os.environ["PATH"])
d = LinuxDistribution(
os_release_file=MISSING,
distro_release_file="/nonexistent/nofile",
include_lsb=True,
include_uname=False,
)
assert d.id() == "rhel"
assert d.name() == "RedHatEnterpriseServer"
assert d.version() == "7.9"
assert d.codename() == "Maipo"
assert d.lsb_release_info()["release"] == "7.9"


def test_lsb_disabled_ignores_command(tmp_path, monkeypatch):
bindir = tmp_path / "bin"
bindir.mkdir()
script = bindir / "lsb_release"
script.write_text("#!/bin/sh\nprintf 'Distributor ID:\\tDebian\\nRelease:\\t12\\n'\n")
script.chmod(script.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
monkeypatch.setenv("PATH", str(bindir) + os.pathsep + os.environ["PATH"])
d = LinuxDistribution(
os_release_file=MISSING,
distro_release_file="/nonexistent/nofile",
include_lsb=False,
include_uname=False,
)
assert d.lsb_release_info() == {}
assert d.id() == ""


def test_parse_uname_content(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
assert d._parse_uname_content(["Linux 6.8.0"]) == {}
assert d._parse_uname_content([]) == {}
assert d._parse_uname_content(["FreeBSD 13.2-RELEASE"]) == {
"id": "freebsd",
"name": "FreeBSD",
"release": "13.2",
}


def test_to_str_decodes_bytes(tmp_path):
d = distro_only(tmp_path, "fedora-release", "Fedora release 30 (Thirty)\n")
assert d._to_str(b"hello") == "hello"
125 changes: 125 additions & 0 deletions tests/test_info_version_extended.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Extended info / version / best-version coverage for distro.

Probed behaviours pinned here:

* ``version(best=True)`` picks the candidate with the most dots
(``1.2.3`` from a pretty-name parse beats ``1.2`` from
``VERSION_ID``); without ``best`` the first non-empty candidate
wins.
* ``version(pretty=True)`` appends ``" (<codename>)"`` only when a
codename exists.
* ``info()`` returns the same values as the individual accessors,
with ``version_parts`` as a dict (not the tuple returned by
``version_parts()``).
* ``LinuxDistribution.__repr__`` starts with the class name and the
file paths used.
"""

import pytest

from distro import LinuxDistribution

MISSING = "/nonexistent/os-release"
NO_RELEASE = "/nonexistent/nofile"


def make(tmp_path, content):
path = tmp_path / "os-release"
path.write_text(content)
return LinuxDistribution(
os_release_file=str(path),
distro_release_file=NO_RELEASE,
include_lsb=False,
include_uname=False,
)


def test_info_matches_accessors(tmp_path):
path = tmp_path / "os-release"
path.write_text(
'PRETTY_NAME="Ubuntu 24.04.5 LTS"\nNAME="Ubuntu"\nVERSION_ID="24.04"\n'
'VERSION="24.04.5 LTS (Noble Numbat)"\nVERSION_CODENAME=noble\nID=ubuntu\nID_LIKE=debian\n'
)
d = LinuxDistribution(
os_release_file=str(path),
distro_release_file=NO_RELEASE,
include_lsb=False,
include_uname=False,
)
assert d.info() == {
"id": "ubuntu",
"version": "24.04",
"version_parts": {"major": "24", "minor": "04", "build_number": ""},
"like": "debian",
"codename": "noble",
}
assert d.info(pretty=True)["version"] == "24.04 (noble)"


def test_version_best_picks_most_dots(tmp_path):
d = make(tmp_path, 'ID=x\nVERSION_ID="1.2"\nPRETTY_NAME="X 1.2.3"\n')
assert d.version() == "1.2"
assert d.version(best=True) == "1.2.3"
assert d.version_parts(best=True) == ("1", "2", "3")
assert d.version_parts() == ("1", "2", "")


def test_version_pretty_without_codename_has_no_suffix(tmp_path):
d = make(tmp_path, 'ID=x\nVERSION_ID="8.10"\nNAME="Oracle Linux"\n')
assert d.version(pretty=True) == "8.10"


def test_version_parts_for_single_number_and_empty(tmp_path):
d = make(tmp_path, 'ID=x\nVERSION_ID="7"\n')
assert d.version_parts() == ("7", "", "")
assert d.major_version() == "7"
assert d.minor_version() == ""
assert d.build_number() == ""


def test_version_parts_best_flag(tmp_path):
d = make(tmp_path, 'ID=x\nVERSION_ID="1.2"\nPRETTY_NAME="X 1.2.3"\n')
assert d.major_version(best=True) == "1"
assert d.minor_version(best=True) == "2"
assert d.build_number(best=True) == "3"


def test_like_returns_id_like_verbatim(tmp_path):
d = make(tmp_path, 'ID=x\nID_LIKE="rhel fedora"\n')
assert d.like() == "rhel fedora"


def test_name_pretty_falls_back_to_name_plus_version(tmp_path):
# The pretty fallback does *not* use the os-release NAME: it uses
# the distro-release name (empty here) plus the pretty version,
# hence the leading space -- the same quirk as the Oracle case.
d = make(tmp_path, 'ID=x\nNAME="X"\nVERSION_ID="2.0"\nVERSION_CODENAME=two\n')
assert d.name(pretty=True) == " 2.0 (two)"


def test_repr_contains_class_and_paths(tmp_path):
d = make(tmp_path, 'ID=x\n')
text = repr(d)
assert text.startswith("LinuxDistribution(")
assert "os_release_file=" in text
assert "distro_release_file=" in text


def test_package_version_is_pinned(tmp_path):
import distro

assert distro.__version__ == "1.9.0"


def test_linux_distribution_tuple_shape(tmp_path):
d = make(tmp_path, 'ID=x\nNAME="X"\nVERSION_ID="1.0"\n')
result = d.linux_distribution()
assert isinstance(result, tuple) and len(result) == 3


def test_deprecated_linux_distribution_module_function_shape(tmp_path):
# The module-level linux_distribution() is deprecated but still
# returns a 3-tuple for the instance; check the instance method
# here, since the module function reflects the host, not fixtures.
d = make(tmp_path, 'ID=x\nNAME="X"\nVERSION_ID="1.0"\n')
assert d.linux_distribution(full_distribution_name=False)[0] == "x"
Loading