medias 0.6.0

TV media player on bare DRM/KMS: Vulkan UI plane over libmpv video, for local files, UPnP/DLNA servers and YouTube, controlled by HDMI-CEC remote, evdev or terminal
"""Checks of dist.py's ar, tar and .deb writers; no compiler or dpkg needed."""

import hashlib
import io
import sys
import tarfile
import tempfile
import unittest
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import dist  # noqa: E402
import index  # noqa: E402
import repo  # noqa: E402


def ar_members(data):
    assert data.startswith(b"!<arch>\n")
    pos, members = 8, []
    while pos < len(data):
        header = data[pos:pos + 60]
        assert header[58:60] == b"`\n"
        name, size = header[:16].decode().rstrip(), int(header[48:58])
        members.append((name, data[pos + 60:pos + 60 + size]))
        pos += 60 + size + size % 2
    return members


class Ar(unittest.TestCase):
    def test_odd_member_padded(self):
        data = dist.ar_archive([("a", b"xyz"), ("b", b"12")], 0)
        self.assertEqual(ar_members(data), [("a", b"xyz"), ("b", b"12")])


class WithParents(unittest.TestCase):
    def test_directories_precede_files(self):
        entries = dist.with_parents({"./usr/bin/x": (b"1", 0o755)})
        self.assertEqual([e[0] for e in entries], ["./", "./usr/", "./usr/bin/", "./usr/bin/x"])


class Deb(unittest.TestCase):
    def test_layout(self):
        with tempfile.TemporaryDirectory() as tmp:
            binary = Path(tmp) / "medias"
            binary.write_bytes(b"\x7fELF fake")
            deb = dist.build_deb(binary, Path(tmp), "medias", "1.2.3", "Short: long text",
                                 "arm64", ["libc6 (>= 2.34)", "libmpv2"], "A <a@b>", 1_700_000_000)
            self.assertEqual(deb.name, "medias_1.2.3-1_arm64.deb")
            members = dict(ar_members(deb.read_bytes()))
        self.assertEqual(list(members), ["debian-binary", "control.tar.xz", "data.tar.xz"])
        self.assertEqual(members["debian-binary"], b"2.0\n")

        with tarfile.open(fileobj=io.BytesIO(members["control.tar.xz"])) as tar:
            control = tar.extractfile("./control").read().decode()
            md5sums = tar.extractfile("./md5sums").read().decode()
            self.assertEqual(tar.getmember("./postinst").mode, 0o755)
            self.assertEqual(tar.extractfile("./conffiles").read(), b"/etc/default/medias\n")
        self.assertIn("Architecture: arm64\n", control)
        self.assertIn("Version: 1.2.3-1\n", control)
        self.assertIn("Depends: libc6 (>= 2.34), libmpv2\n", control)
        self.assertIn("Description: Short\n Long text.\n", control)

        with tarfile.open(fileobj=io.BytesIO(members["data.tar.xz"])) as tar:
            names = tar.getnames()
            self.assertEqual(len(names), len(set(names)))
            exe = tar.getmember("./usr/bin/medias")
            self.assertEqual((exe.mode, exe.uid, exe.uname), (0o755, 0, "root"))
            self.assertIn("./usr/lib/systemd/system/medias@.service", names)
        self.assertIn(f"{hashlib.md5(b'\x7fELF fake').hexdigest()}  usr/bin/medias\n", md5sums)


class Repo(unittest.TestCase):
    def test_sources_embeds_key(self):
        text = repo.sources_text("https://h/m", "trixie", "-----BEGIN-----\n\nAAA\n-----END-----\n")
        self.assertEqual(text, "Types: deb\nURIs: https://h/m\nSuites: trixie\nComponents: main\n"
                               "Architectures: amd64 arm64\nSigned-By:\n -----BEGIN-----\n .\n AAA\n -----END-----\n")

    def test_release_hashes(self):
        with tempfile.TemporaryDirectory() as tmp:
            index = Path(tmp) / "dists" / "trixie" / "main" / "binary-amd64" / "Packages"
            index.parent.mkdir(parents=True)
            index.write_bytes(b"Package: x\n")
            text = repo.release_text(Path(tmp), "trixie", [index.relative_to(index.parents[2])], 0)
        digest = hashlib.sha256(b"Package: x\n").hexdigest()
        self.assertIn("Date: Thu, 01 Jan 1970 00:00:00 GMT\n", text)
        self.assertIn(f"SHA256:\n {digest} {11:>16} main/binary-amd64/Packages\n", text)


class Index(unittest.TestCase):
    def test_versions_and_pool(self):
        with tempfile.TemporaryDirectory() as tmp:
            out = Path(tmp)
            pool = out / "debian" / "pool" / "main" / "m" / "medias"
            pool.mkdir(parents=True)
            for path in (out / "medias-1.2.0-linux-aarch64.tar.gz", out / "medias_1.2.0-1_arm64.deb",
                         pool / "medias_1.2.0-1_arm64.deb", pool / "medias_1.1.0-1_amd64.deb", out / "notes.txt"):
                path.write_bytes(b"x")
            Path(f"{out}/medias_1.2.0-1_arm64.deb.sha256").write_text("abc  medias_1.2.0-1_arm64.deb\n")
            found = index.collect(out)
            page = index.render(out, "1.2.0", "Short: long <text>", "https://h/debian/", 0)
        self.assertEqual(sorted(found), ["1.1.0", "1.2.0"])
        # dist/ wins over the pool copy; the .deb precedes the tar.gz.
        self.assertEqual([(r[1], r[2], r[4]) for r in found["1.2.0"]],
                         [("deb", "medias_1.2.0-1_arm64.deb", "abc"),
                          ("tar.gz", "medias-1.2.0-linux-aarch64.tar.gz", hashlib.sha256(b"x").hexdigest())])
        self.assertEqual(found["1.1.0"][0][2], "debian/pool/main/m/medias/medias_1.1.0-1_amd64.deb")
        self.assertIn("https://h/debian/medias.sources", page)
        self.assertIn("Short: long &lt;text&gt;.", page)
        self.assertIn("Older versions", page)
        self.assertNotIn("$", page.replace("$USER", ""))


if __name__ == "__main__":
    unittest.main()