diff --git a/install.requires b/install.requires index e69de29..65569d3 100644 --- a/install.requires +++ b/install.requires @@ -0,0 +1,2 @@ +backports.zstd ; python_version<'3.14' +lxml diff --git a/pyproject.toml b/pyproject.toml index d2d3dbd..050f71d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,5 +56,8 @@ show_missing = true # don't @ me, Hynek line-length = 100 +[tool.mypy] +plugins = ["mypy_plugin_lxml.main"] + [tool.setuptools.dynamic] dependencies = { file = ["install.requires"] } diff --git a/rmdepcheck.py b/rmdepcheck.py index f787fe3..4b31859 100755 --- a/rmdepcheck.py +++ b/rmdepcheck.py @@ -19,6 +19,8 @@ # # Author(s): Adam Williamson +# pylint: disable=c-extension-no-member + """RPM package installability and reverse-dependency checks using a repository modification strategy (hence 'rm'). """ @@ -26,24 +28,34 @@ repository modification strategy (hence 'rm'). # Standard libraries import argparse +import gzip import hashlib import json +import lzma import os import platform import subprocess import sys import tempfile -import xml.etree.ElementTree as et - +from contextlib import contextmanager from functools import partial -from typing import Iterable +from typing import Any, Generator, Iterable from urllib.parse import urlparse +import lxml.etree as et + +if sys.version_info >= (3, 14): + from compression import zstd +else: + from backports import zstd # pragma: no cover + # type alias for the tuples produced by parse_repoclosure # can't properly declare this because type statement was only added in # 3.12, and TypeAlias is deprecated since 3.12 and wasn't in 3.9 DepTuple = tuple[str, str, str] +# Mainly for tests to override to exercise the parser +CHUNKSIZE = 1 << 20 CURLARGS = ("curl", "-s", "-f", "-L", "--retry-delay", "10", "--max-time", "300", "--retry", "5") # use a fresh temporary cache for each run to avoid collisions between # runs and polluting the 'real' cache @@ -59,6 +71,7 @@ SUBPCAPTURE = partial(subprocess.run, capture_output=True, text=True, check=Fals SUBPCAPTCHECK = partial(subprocess.run, capture_output=True, text=True, check=True) SUBPCHECK = partial(subprocess.run, check=True) REPOHASHES = {} +SAFEPARSER = et.XMLParser(resolve_entities=False) def mfind(element: et.Element, string: str, ns: dict) -> et.Element: @@ -122,62 +135,236 @@ def get_file(src: str, dest: str) -> None: SUBPCHECK(CURLARGS + ("-o", dest, src)) -def get_primary(repomdroot: et.Element) -> et.Element: - """Given a repomd.xml root element, return the element for the - primary data file. - """ - return mfind(repomdroot, "repo:data[@type='primary']", XMLNS) - - -def download_primary(primary: et.Element, repourl: str, mrepodir: str) -> str: +def download_element(element: et.Element, repourl: str, mrepodir: str) -> str: """Given the ET element with information about it, and the URL of the repo to download from and the local directory to download to, - download and uncompress the primary data file, returning - the filename. Note mrepodir/repodata is assumed to exist. + download the specified data file, returning the filename. Note + mrepodir/repodata is assumed to exist. """ - primloc = mfind(primary, "repo:location", XMLNS).attrib["href"] - encprimfn = f"{mrepodir}/{primloc}" - get_file(f"{repourl}/{primloc}", encprimfn) - SUBPCHECK(("unzstd", "-q", encprimfn)) - os.remove(encprimfn) - return encprimfn.replace(".zst", "") + elemloc = mfind(element, "repo:location", XMLNS).attrib["href"] + encelemfn = f"{mrepodir}/{elemloc}" + get_file(f"{repourl}/{elemloc}", encelemfn) + return encelemfn -def replace_primary(primfn: str, removes: Iterable[str]) -> tuple[str, int, str, int]: - """Parse the primary data file, remove any packages whose source - package name matches one in removes, and write out a new file - with the correct name (containing its own sha256sum). Return the - checksums and sizes of the new uncompressed and compressed files, - for writing back into the repomd. +# we can stop using Any and use io.Reader / io.Writer once we stop +# caring about Python < 3.14 +@contextmanager +def _open_read(path: str) -> Generator[Any, None, None]: + """Open a repo metadata file for reading, decompressing transparently. + Written by Cursor 2.6.11 + Claude 4.6 Opus. """ - rddir = os.path.dirname(primfn) - et.register_namespace("", "http://linux.duke.edu/metadata/common") - et.register_namespace("rpm", "http://linux.duke.edu/metadata/rpm") - primtree = et.parse(primfn) - primroot = primtree.getroot() - for pkg in primroot.findall("common:package", XMLNS): - if mfind(pkg, "common:arch", XMLNS).text == "src": - spkg = mfind(pkg, "common:name", XMLNS).text + if path.endswith(".gz"): + with gzip.open(path, "rb") as f: + yield f + elif path.endswith(".xz"): + with lzma.open(path, "rb") as f: + yield f + elif path.endswith((".zst", ".zstd")): + with zstd.open(path, "rb") as f: + yield f + else: + with open(path, "rb") as f: + yield f + + +@contextmanager +def _open_write(path: str) -> Generator[Any, None, None]: + """Open a repo metadata file for writing, compressing transparently. + Written by Cursor 2.6.11 + Claude 4.6 Opus. + """ + if path.endswith(".gz"): + with gzip.open(path, "wb", compresslevel=6) as f: + yield f + elif path.endswith(".xz"): + with lzma.open(path, "wb", preset=6) as f: + yield f + elif path.endswith((".zst", ".zstd")): + with zstd.open(path, "wb", level=3) as f: + yield f + else: + with open(path, "wb") as f: + yield f + + +def _next_chunk(infh: Any, size: int = 0) -> bytes: + """Read an arbitrarily-sized chunk of data from infh, ensuring it + ends with a > for ease of parsing. Defaults to quite a large size. + Note that using size=1 acts as 'read to end of next tag'. + """ + if not size: + size = CHUNKSIZE + chunk = infh.read(size) + while chunk and not chunk.endswith(b">"): + char = infh.read(1) + if char: + chunk += char else: - spkg = mfind(mfind(pkg, "common:format", XMLNS), "rpm:sourcerpm", XMLNS).text - if spkg: - spkg = spkg.rsplit("-", 2)[0] - if spkg and spkg in removes: - primroot.remove(pkg) + break + return chunk - tempfn = f"{rddir}/primtemp.xml" - primtree.write(tempfn) - with open(tempfn, "rb") as tempfh: - opensum = hashlib.sha256(tempfh.read()).hexdigest() - opensize = os.path.getsize(tempfn) - SUBPCHECK(("zstd", "-q", tempfn)) - with open(f"{tempfn}.zst", "rb") as tempfhz: - csum = hashlib.sha256(tempfhz.read()).hexdigest() - size = os.path.getsize(f"{tempfn}.zst") - os.rename(f"{tempfn}.zst", f"{rddir}/{csum}-primary.xml.zst") - os.remove(tempfn) - os.remove(primfn) - return (csum, size, opensum, opensize) + +def _maybe_remove(currpkg: bytes, removes: Iterable[str], flmode: bool) -> str: + """Given a bytestring representing a single XML package element + starting "", parse it to find + the (source) package name and compare against removes to decide + whether it should be removed. Returns the pkgid of the package if + it should be removed, empty string if it should not. + """ + # force in namespace definition, ugh. we have to do this or else + # lxml will refuse to parse the fragment. we know it starts with + # ' tuple[set[str], str, int]: + """Parse primary or filelists XML metadata, remove packages + in removes. Works slightly differently in each mode. + """ + removed: set = set() + # parse infh in chunks, pass through non-package content to outfh. + # parse package content one-by-one, using _maybe_remove to decide + # whether to keep (pass through) or drop each package text blob + currpkg = b"" + chunk = _next_chunk(infh) + sha = hashlib.sha256() + size = 0 + while chunk: + if currpkg: + # look for end of package text + endpos = chunk.find(b"") + if endpos != -1: + # decide whether to remove package + currpkg += chunk[: endpos + 10] + nextpos = endpos + 10 + # if we're at the end of the chunk, read in another + # tag and add it, to simplify the next bit + if nextpos >= len(chunk): + chunk += _next_chunk(infh, size=1) + # if next char is \n, include it in the package block + nextchar = chunk[endpos + 10 : endpos + 11] + if nextchar == b"\n": + currpkg += b"\n" + nextpos = endpos + 11 + pkgid = _maybe_remove(currpkg, removes, flmode) + if pkgid: + # we should remove it + removed.add(pkgid) + else: + # pass it through, update csum and size + sha.update(currpkg) + size += len(currpkg) + outfh.write(currpkg) + # reset current package text buffer + currpkg = b"" + # move to appropriate position in chunk + chunk = chunk[nextpos:] + continue + # package does not end in current chunk, add whole + # chunk to buffer and read next + currpkg += chunk + chunk = _next_chunk(infh) + continue + # we're not in a package block, so find the next package tag + # there is a potential issue with packager tags, but we should + # always encounter a package tag before we encounter a + # packager tag + startpos = chunk.find(b" + chunk = chunk[startpos + 8 :] + continue + # no package tag found in chunk, pass through whole + # chunk, updating csum and size + sha.update(chunk) + size += len(chunk) + outfh.write(chunk) + # read next chunk + chunk = _next_chunk(infh) + continue + + return (removed, sha.hexdigest(), size) + + +# pylint: disable-next=too-many-locals +def replace_packages( + primfn: str, flfn: str, removes: Iterable[str] +) -> tuple[tuple[str, int, str, int], ...]: + """Parse the primary and filelists data files, remove any packages + whose source package name matches one in removes, and write out new + files with the correct names (containing their own sha256sum). + Return the checksums and sizes of the new uncompressed and + compressed files, for writing back into the repomd. We use raw + line by line text parsing to do this, because these files are huge + and parsing them with ElementTree.parse uses a huge amount of RAM. + Using iterparse would be messier and harder than doing this. + """ + ret = [] + primremoved: Iterable[str] = set() + # find the repodata directory + rddir = os.path.dirname(primfn) + # parse both primary and filelists metadata + for fn, typ, flmode in ((primfn, "primary", False), (flfn, "filelists", True)): + if flmode: + # remove the same packages we removed in the prior iteration + toremove = primremoved + else: + # remove packages built from the srpm names in 'removes' + toremove = removes + # get the file extensions + exts = os.path.basename(fn).split(".")[1:] + # construct a temporary filename with the same extensions + tempfn = ".".join([f"{rddir}/{typ}temp"] + exts) + with _open_read(fn) as infh, _open_write(tempfn) as outfh: + # parse from input file to temporary file with inline + # compression, checksumming and size discovery + removed, opensum, opensize = parse_xml(infh, outfh, toremove, flmode=flmode) + if not flmode: + # populate the to-remove set for the next iteration + primremoved = removed + # get compressed checksum and size + with open(tempfn, "rb") as outfh: + csum = hashlib.sha256(outfh.read()).hexdigest() + size = os.path.getsize(tempfn) + # rename output file to expected name with checksum and type + os.rename(tempfn, ".".join([f"{rddir}/{csum}-{typ}"] + exts)) + # return compressed and uncompressed sums and sizes + ret.append((csum, size, opensum, opensize)) + return tuple(ret) def get_base_repoclosure(baserepos: Iterable[str], nmbaserepos: Iterable[str]) -> str: @@ -213,26 +400,45 @@ def get_modified_repoclosure( os.makedirs(f"{mrepodir}/repodata") repomdfn = f"{mrepodir}/repodata/repomd.xml" get_file(f"{mrepo}/repodata/repomd.xml", repomdfn) - et.register_namespace("", "http://linux.duke.edu/metadata/repo") - repomdtree = et.parse(repomdfn) + repomdtree = et.parse(repomdfn, parser=SAFEPARSER) repomdroot = repomdtree.getroot() - primary = get_primary(repomdroot) - primfn = download_primary(primary, mrepo, mrepodir) - csum, size, opensum, opensize = replace_primary(primfn, removes) - + # we need to also download and modify filelists, for file + # dependencies that aren't included in primary + filelists = mfind(repomdroot, "repo:data[@type='filelists']", XMLNS) + flfn = download_element(filelists, mrepo, mrepodir) + flexts = os.path.basename(flfn).split(".")[1:] + primary = mfind(repomdroot, "repo:data[@type='primary']", XMLNS) + primfn = download_element(primary, mrepo, mrepodir) + primexts = os.path.basename(primfn).split(".")[1:] + # https://github.com/pylint-dev/pylint/issues/5671#issuecomment-4239834783 + primdata, fldata = replace_packages(primfn, flfn, removes) # pylint:disable=W0632 + # we also need the module metadata if present, or else + # module packages will be treated as non-module and cause + # false results + modules = repomdroot.find("repo:data[@type='modules']", XMLNS) + if modules is not None: + # setting up a test repo with module metadata is a huge pain + download_element(modules, mrepo, mrepodir) # pragma: no cover # modify the repomd - mfind(primary, "repo:checksum", XMLNS).text = csum - mfind(primary, "repo:size", XMLNS).text = str(size) - mfind(primary, "repo:open-checksum", XMLNS).text = opensum - mfind(primary, "repo:open-size", XMLNS).text = str(opensize) - mfind(primary, "repo:location", XMLNS).attrib["href"] = f"repodata/{csum}-primary.xml.zst" - # requires Python 3.10: - # notprimary = repomdroot.findall("repo:data[@type]", XMLNS) + for typ, data in ((primary, primdata), (filelists, fldata)): + csum, size, opensum, opensize = data + mfind(typ, "repo:checksum", XMLNS).text = csum + mfind(typ, "repo:size", XMLNS).text = str(size) + mfind(typ, "repo:open-checksum", XMLNS).text = opensum + mfind(typ, "repo:open-size", XMLNS).text = str(opensize) + if typ == primary: + mfind(primary, "repo:location", XMLNS).attrib["href"] = ".".join( + [f"repodata/{csum}-primary"] + primexts + ) + else: + mfind(filelists, "repo:location", XMLNS).attrib["href"] = ".".join( + [f"repodata/{csum}-filelists"] + flexts + ) + alldata = repomdroot.findall("repo:data[@type]", XMLNS) - notprimary = [data for data in alldata if data is not primary] - for item in notprimary: + notused = [data for data in alldata if data not in [primary, filelists, modules]] + for item in notused: repomdroot.remove(item) - et.register_namespace("", "http://linux.duke.edu/metadata/repo") repomdtree.write(repomdfn) # add the modified repo to the repoclosure command args.extend(["--repofrompath", f"{hash_repo(mrepo)},{mrepodir}"]) @@ -442,7 +648,7 @@ def parse_args() -> argparse.Namespace: def check_utils() -> None: """Check required utilities are installed.""" missing = [] - for prog in (("zstd", "-V"), ("dnf", "--version"), ("curl", "-V")): + for prog in (("dnf", "--version"), ("curl", "-V")): try: subprocess.run(prog, stdout=subprocess.DEVNULL, check=True) except FileNotFoundError: diff --git a/tests/test_rmdepcheck.py b/tests/test_rmdepcheck.py index e70d4f4..35a9bd6 100644 --- a/tests/test_rmdepcheck.py +++ b/tests/test_rmdepcheck.py @@ -22,6 +22,10 @@ """Tests for rmdepcheck.""" +import glob +import gzip +import io +import lzma import os import shutil import sys @@ -29,6 +33,11 @@ import tempfile import xml.etree.ElementTree as et from unittest import mock +if sys.version_info >= (3, 14): + from compression import zstd +else: + from backports import zstd + import pytest import rmdepcheck @@ -79,61 +88,137 @@ def test_get_file(): assert "This file is" in testfh.read() -def test_download_primary(): +def test_download_element(): repomdtree = et.parse(f"{REPOS}/base/repodata/repomd.xml") repomdroot = repomdtree.getroot() - primary = rmdepcheck.get_primary(repomdroot) + primary = repomdroot.find("repo:data[@type='primary']", rmdepcheck.XMLNS) assert isinstance(primary, et.Element) assert primary.attrib == {"type": "primary"} with tempfile.TemporaryDirectory() as tempdir: os.makedirs(f"{tempdir}/repodata") - rmdepcheck.download_primary(primary, f"file://{REPOS}/base", tempdir) + rmdepcheck.download_element(primary, f"file://{REPOS}/base", tempdir) # NOTE: this filename changes any time mkrepos.py is run assert os.path.exists( # pylint: disable-next=line-too-long - f"{tempdir}/repodata/54942fbb3415a66f4ca49463d36439407c816b3eed8e30fc43016bd70ac9d456-primary.xml" + f"{tempdir}/repodata/54942fbb3415a66f4ca49463d36439407c816b3eed8e30fc43016bd70ac9d456-primary.xml.zst" ) +def test_parse_xml_empty(): + """Test parse_xml can handle an empty repo. We mostly test it + implicitly, but easiest to test this explicitly. + """ + empty = b""" + + +""" + infh = io.BytesIO(empty) + outfh = io.BytesIO() + assert rmdepcheck.parse_xml(infh, outfh, ["somepackage"], False) == ( + set(), + "0f9fd176ae380f60833f432e009774b256c2e77fc50740be60eaaf06d49ee004", + 168, + ) + outfh.seek(0) + assert outfh.read() == empty + + +@pytest.mark.parametrize("extension", (".xml", ".xml.zst", ".xml.gz", ".xml.xz")) @pytest.mark.parametrize( "repotup", ( ( "binary", ( - "42daebf05f6c3cabe9d029acb3a8056f7fc533cde9820b7388d90acb1f6e7dbe", - 1067, - "562981a96ba946cd0f993d180108c4dd107d1a5f24210c5768b19cdd93e8f33a", - 7531, + ( + "8cba35dd9233f535f90d2c1a6f7c00a8ae8bfcc9b0854f50ef593ab8f685cd7c", + 9596, + ), + ( + "03254e576554e240d9d2434ac9e6f06e71d50adf5e5699acdecd28d0ceafaa37", + 1562, + ), ), ), ( "source", ( - "3378e32450503892f65c6ff2a77a451f069146c4284a69f21752e113918da64f", - 566, - "7ac52d6a0f3c0314a58f78eff39f4a7b5721127816b1b59d8ef30f4a29e091d9", - 1045, + ( + "c55ba80f7615e041c9d84b8237a6dd4afb9e56c9d7e1776324a164d4e0b921a1", + 1082, + ), + ( + "97c9e867dca2f9f1d0c1f45e77c5ffb9e6b91e5a76b73240bc162aa4d8b8bd9b", + 282, + ), ), ), ), ) -def test_replace_primary(repotup): +def test_replace_packages(extension, repotup): + # this exercises some rarely-encountered edge case parser paths + if extension == ".xml": + rmdepcheck.CHUNKSIZE = 1 repo, expected = repotup with tempfile.TemporaryDirectory() as tempdir: shutil.copy2( # binary.xml is an old version of base's primary file # source.xml is a primary file from a repo with just # ccc.src and ddd.src packages - f"{TESTDATA}/test_replace_primary_{repo}.xml", - f"{tempdir}/test.xml", + f"{TESTDATA}/test_replace_primary_{repo}{extension}", + f"{tempdir}/testprim{extension}", ) - ret = rmdepcheck.replace_primary(f"{tempdir}/test.xml", "ccc") + shutil.copy2( + f"{TESTDATA}/test_replace_fl_{repo}{extension}", + f"{tempdir}/testfl{extension}", + ) + ret = rmdepcheck.replace_packages( + f"{tempdir}/testprim{extension}", f"{tempdir}/testfl{extension}", "ccc" + ) + # skip the compressed sum and size as they may change with + # python version + ret = ((ret[0][2], ret[0][3]), (ret[1][2], ret[1][3])) assert ret == expected - assert os.path.exists( - # pylint: disable-next=line-too-long - f"{tempdir}/{expected[0]}-primary.xml.zst" + gotprim = glob.glob(f"{tempdir}/*primary.xml*")[0] + gotfl = glob.glob(f"{tempdir}/*filelists.xml*")[0] + funcmap = { + ".xml": open, + ".xml.zst": zstd.open, + ".xml.gz": gzip.open, + ".xml.xz": lzma.open, + } + with funcmap[extension](gotprim, "rb") as gotpfh: + with open(f"{TESTDATA}/test_replace_primary_{repo}_expected.xml", "rb") as exppfh: + assert gotpfh.read() == exppfh.read() + with funcmap[extension](gotfl, "rb") as gotffh: + with open(f"{TESTDATA}/test_replace_fl_{repo}_expected.xml", "rb") as expffh: + assert gotffh.read() == expffh.read() + + +def test_replace_packages_minified(): + rmdepcheck.CHUNKSIZE = 1 + with tempfile.TemporaryDirectory() as tempdir: + shutil.copy2( + # same as source but with all newlines removed + f"{TESTDATA}/test_replace_primary_source_minified.xml", + f"{tempdir}/testprim.xml", ) + shutil.copy2( + # same as source but with all newlines removed + f"{TESTDATA}/test_replace_fl_source_minified.xml", + f"{tempdir}/testfl.xml", + ) + rmdepcheck.replace_packages(f"{tempdir}/testprim.xml", f"{tempdir}/testfl.xml", "ccc") + gotprim = glob.glob(f"{tempdir}/*primary.xml*")[0] + gotfl = glob.glob(f"{tempdir}/*filelists.xml*")[0] + with open(gotprim, "rb") as gotpfh: + with open( + f"{TESTDATA}/test_replace_primary_source_minified_expected.xml", "rb" + ) as exppfh: + assert gotpfh.read() == exppfh.read() + with open(gotfl, "rb") as gotffh: + with open(f"{TESTDATA}/test_replace_fl_source_minified_expected.xml", "rb") as expffh: + assert gotffh.read() == expffh.read() def test_get_base_repoclosure(): @@ -256,10 +341,10 @@ def test_check_arch(_): @mock.patch("subprocess.run", autospec=True) def test_check_utils(mock_run): rmdepcheck.check_utils() - mock_run.side_effect = [FileNotFoundError, None, FileNotFoundError] + mock_run.side_effect = [None, FileNotFoundError] with pytest.raises(SystemExit) as excinfo: rmdepcheck.check_utils() - assert excinfo.value.code == "Please install missing required utilities: zstd curl" + assert excinfo.value.code == "Please install missing required utilities: curl" @mock.patch("rmdepcheck.check_utils", side_effect=KeyboardInterrupt) diff --git a/tests/testdata/test_replace_fl_binary.xml b/tests/testdata/test_replace_fl_binary.xml new file mode 100644 index 0000000..0cd47e0 --- /dev/null +++ b/tests/testdata/test_replace_fl_binary.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/testdata/test_replace_fl_binary.xml.gz b/tests/testdata/test_replace_fl_binary.xml.gz new file mode 100644 index 0000000..0d5d2e4 Binary files /dev/null and b/tests/testdata/test_replace_fl_binary.xml.gz differ diff --git a/tests/testdata/test_replace_fl_binary.xml.xz b/tests/testdata/test_replace_fl_binary.xml.xz new file mode 100644 index 0000000..774802c Binary files /dev/null and b/tests/testdata/test_replace_fl_binary.xml.xz differ diff --git a/tests/testdata/test_replace_fl_binary.xml.zst b/tests/testdata/test_replace_fl_binary.xml.zst new file mode 100644 index 0000000..f11cc15 Binary files /dev/null and b/tests/testdata/test_replace_fl_binary.xml.zst differ diff --git a/tests/testdata/test_replace_fl_binary_expected.xml b/tests/testdata/test_replace_fl_binary_expected.xml new file mode 100644 index 0000000..5b4818d --- /dev/null +++ b/tests/testdata/test_replace_fl_binary_expected.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/testdata/test_replace_fl_source.xml b/tests/testdata/test_replace_fl_source.xml new file mode 100644 index 0000000..301576a --- /dev/null +++ b/tests/testdata/test_replace_fl_source.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/tests/testdata/test_replace_fl_source.xml.gz b/tests/testdata/test_replace_fl_source.xml.gz new file mode 100644 index 0000000..67d32b7 Binary files /dev/null and b/tests/testdata/test_replace_fl_source.xml.gz differ diff --git a/tests/testdata/test_replace_fl_source.xml.xz b/tests/testdata/test_replace_fl_source.xml.xz new file mode 100644 index 0000000..3397f61 Binary files /dev/null and b/tests/testdata/test_replace_fl_source.xml.xz differ diff --git a/tests/testdata/test_replace_fl_source.xml.zst b/tests/testdata/test_replace_fl_source.xml.zst new file mode 100644 index 0000000..3b45f8e Binary files /dev/null and b/tests/testdata/test_replace_fl_source.xml.zst differ diff --git a/tests/testdata/test_replace_fl_source_expected.xml b/tests/testdata/test_replace_fl_source_expected.xml new file mode 100644 index 0000000..338c98a --- /dev/null +++ b/tests/testdata/test_replace_fl_source_expected.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/testdata/test_replace_fl_source_minified.xml b/tests/testdata/test_replace_fl_source_minified.xml new file mode 100644 index 0000000..de799b1 --- /dev/null +++ b/tests/testdata/test_replace_fl_source_minified.xml @@ -0,0 +1 @@ + diff --git a/tests/testdata/test_replace_fl_source_minified_expected.xml b/tests/testdata/test_replace_fl_source_minified_expected.xml new file mode 100644 index 0000000..d9e30d7 --- /dev/null +++ b/tests/testdata/test_replace_fl_source_minified_expected.xml @@ -0,0 +1 @@ + diff --git a/tests/testdata/test_replace_primary_binary.xml b/tests/testdata/test_replace_primary_binary.xml index ad87cd9..2029628 100644 --- a/tests/testdata/test_replace_primary_binary.xml +++ b/tests/testdata/test_replace_primary_binary.xml @@ -1,15 +1,15 @@ - + aaa x86_64 - 7188c68d3407d04571b70e3a8d3c8e6329c34d2dd7af8a6c7364a0b29c991cec + a3d06ac0d14a113c2c40f407b6d9089aab53f2cade404a285d70eef03d54a4fb Dummy summary This is a dummy description. Fedora Project - @@ -57,12 +57,12 @@ ccc x86_64 - 3c810c039112c6d626a9dd628b38af6cbcb387c710b702d51028f6c215c34ad6 + 4fc7facc674ab82600e38cc9a3bfe46f5593de27c4acfe27585622c6893820f8 Dummy summary This is a dummy description. Fedora Project - \ No newline at end of file diff --git a/tests/testdata/test_replace_primary_binary.xml.gz b/tests/testdata/test_replace_primary_binary.xml.gz new file mode 100644 index 0000000..3badbbf Binary files /dev/null and b/tests/testdata/test_replace_primary_binary.xml.gz differ diff --git a/tests/testdata/test_replace_primary_binary.xml.xz b/tests/testdata/test_replace_primary_binary.xml.xz new file mode 100644 index 0000000..95608bf Binary files /dev/null and b/tests/testdata/test_replace_primary_binary.xml.xz differ diff --git a/tests/testdata/test_replace_primary_binary.xml.zst b/tests/testdata/test_replace_primary_binary.xml.zst new file mode 100644 index 0000000..2f5249f Binary files /dev/null and b/tests/testdata/test_replace_primary_binary.xml.zst differ diff --git a/tests/testdata/test_replace_primary_binary_expected.xml b/tests/testdata/test_replace_primary_binary_expected.xml new file mode 100644 index 0000000..823f016 --- /dev/null +++ b/tests/testdata/test_replace_primary_binary_expected.xml @@ -0,0 +1,243 @@ + + + + aaa + x86_64 + + a3d06ac0d14a113c2c40f407b6d9089aab53f2cade404a285d70eef03d54a4fb + Dummy summary + This is a dummy description. + Fedora Project + + + + bbb + x86_64 + + 8af0eb8f054b804671a0815bffb22f49a4e8171b54e9610438ae07463bf812ae + Dummy summary + This is a dummy description. + Fedora Project + + + + ddd + x86_64 + + 8299adf0ecb62a00e1b4f3fa1ab8d2288a30f38602642470d61c56b9f2e7dabc + Dummy summary + This is a dummy description. + Fedora Project + + + + eee + x86_64 + + 95ab3d70bdf8b1c2d202c4e04cb7960153fa4b6460a300c838a62fd6b0840632 + Dummy summary + This is a dummy description. + Fedora Project + + + + fff + x86_64 + + e26065fed581c695ba8cd3647609b6c2aa6d5bd9faffbb8ad1c2c28e533c87d6 + Dummy summary + This is a dummy description. + Fedora Project + + + + ggg + i686 + + 2141e54e9285d94ef2bb53db4e1ae0b9086886a5a07a5f1930dd678682e95ef8 + Dummy summary + This is a dummy description. + Fedora Project + + + + ggg + x86_64 + + 7b6f760e650902d0a94ef5fcd480f77a1f6818e9f15cea54a0318be34d364c83 + Dummy summary + This is a dummy description. + Fedora Project + + + + hhh + i686 + + 8280ab6b2707f63add64ad79ee4ff01faf343d6317cadd6f18f4aa745abf145c + Dummy summary + This is a dummy description. + Fedora Project + + + + hhh + x86_64 + + f0aa917356d9c3cba05c2b346d5a73e5ab919b3df9d3417731c2a25cf3d5ae80 + Dummy summary + This is a dummy description. + Fedora Project + + + \ No newline at end of file diff --git a/tests/testdata/test_replace_primary_source.xml b/tests/testdata/test_replace_primary_source.xml index de5d101..363ee33 100644 --- a/tests/testdata/test_replace_primary_source.xml +++ b/tests/testdata/test_replace_primary_source.xml @@ -4,12 +4,12 @@ ccc src - 81b6e8246bcf98be9d18b0848c70e1614d522939231f08559c80970a6dd367ae + 04f2d544eba701ba69cd064a6b35ca4854497674abfc76b5acd05cc1b597b73a Dummy summary This is a dummy description. Fedora Project -