_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file tests/debsrc.py
#!/usr/bin/env python3
"""converts Debian source packages into holy recipes and builds the manifests."""
import hashlib
import io
from pathlib import Path
import shutil
import subprocess
import sys
import tarfile
import tempfile
binary = str(Path(sys.argv[1]).resolve())
def run(*args, status=0):
result = subprocess.run([binary, *map(str, args)], text=True, capture_output=True,
errors="replace")
assert result.returncode == status, (args, result.returncode, result.stdout, result.stderr)
return result.stdout
def read_metadata(artifact):
result = subprocess.run(["lz4", "-dc", str(artifact)], capture_output=True)
assert result.returncode == 0, (artifact, result.returncode, result.stderr)
with tarfile.open(fileobj=io.BytesIO(result.stdout)) as archive:
def read(name):
return archive.extractfile(name).read().decode()
return {name: read(name) for name in
("HOLY/meta", "HOLY/files", "HOLY/deps", "HOLY/origin", "HOLY/transform")}, None
def write(path, text):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text)
def main():
if not shutil.which("lz4"):
print("lz4 required for the debsrc fixture", file=sys.stderr)
return 6
for tool in ("cc", "make"):
if not shutil.which(tool):
print(f"{tool} required for the debsrc fixture", file=sys.stderr)
return 6
with tempfile.TemporaryDirectory() as scratch:
root = Path(scratch)
package = root / "deb-demo"
debian = package / "debian"
debian.mkdir(parents=True)
# a source tree the rules file can build from
source = root / "deb-demo-2.6.0"
(source / "usr/share").mkdir(parents=True)
write(source / "Makefile", """all: bin/deb-demo
bin/deb-demo: hello.c
\tmkdir -p bin
\t$(CC) $(CFLAGS) -o $@ hello.c
install: bin/deb-demo
\tmkdir -p $(DESTDIR)/usr/bin $(DESTDIR)/usr/lib $(DESTDIR)/usr/include
\tcp bin/deb-demo $(DESTDIR)/usr/bin/
\tcp hello.txt $(DESTDIR)/usr/lib/
\tcp hello.h $(DESTDIR)/usr/include/
""")
write(source / "hello.c", '#include <stdio.h>\nint main(void){puts("hi");return 0;}\n')
write(source / "hello.h", "/* header */\n")
write(source / "hello.txt", "greeting\n")
write(source / "README", "the deb-demo fixture\n")
write(source / "COPYING", "fixture licence text\n")
digest = hashlib.sha256((source / "hello.c").read_bytes()).hexdigest()
write(debian / "control", """Source: deb-demo
Section: devel
Priority: optional
Maintainer: Fixture <fixture@example.org>
Uploaders: Someone Else <else@example.org>,
Third Person <third@example.org>
Build-Depends: debhelper-compat (= 13),
dpkg-dev (>= 1.15.5), make
Standards-Version: 4.7.0
Homepage: https://example.org/deb-demo
Vcs-Git: https://salsa.debian.org/fixture/deb-demo.git
Rules-Requires-Root: no
Package: deb-demo
Architecture: all
Multi-Arch: foreign
Depends: ${misc:Depends}, libc6 (>= 2.34)
Recommends: hello
Suggests: bye
Conflicts: deb-demo-old (<< 1.0-1)
Replaces: deb-demo-old (<< 1.0-1)
Provides: deb-demo-interface
Description: A Debian source conversion fixture
The deb-demo program greets the operator once and then stops.
It exists so the converter has a source package to read.
Package: deb-demo-dev
Architecture: all
Multi-Arch: foreign
Depends: deb-demo (= ${binary:Version}),
${misc:Depends}
Description: Development files for deb-demo
Headers for deb-demo.
""")
write(debian / "changelog", """deb-demo (2.6.0-1) unstable; urgency=medium
* The fixture release.
* Another line.
-- Fixture <fixture@example.org> Mon, 01 Jan 2024 00:00:00 +0000
deb-demo (2.5.0-1) unstable; urgency=low
* An older release.
-- Fixture <fixture@example.org> Sun, 01 Jan 2023 00:00:00 +0000
""")
write(debian / "rules", """#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
make CFLAGS="-O2"
override_dh_auto_install:
make install DESTDIR=$(CURDIR)/debian/deb-demo
mkdir -p $(CURDIR)/debian/deb-demo/usr/share/doc/deb-demo
cp README $(CURDIR)/debian/deb-demo/usr/share/doc/deb-demo/
""")
write(debian / "deb-demo-dev.install",
"usr/include/hello.h usr/include\nusr/share/doc/deb-demo/README\n")
write(debian / "deb-demo.postinst", "#!/bin/sh\nset -e\ncase \"$1\" in\n configure) ;;\nesac\n")
write(debian / "copyright", "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n")
write(debian / "watch", "version=4\n")
write(debian / "source" / "format", "3.0 (quilt)\n")
write(debian / "lintian-overrides", "deb-demo: package-installs-python-bytecode\n")
out = run("convert", debian, "--source", "debian", "--output", root / "conv", status=3)
assert "converted deb-demo status review-required" in out
recipe = (root / "conv" / "deb-demo.recipe").read_text()
report = (root / "conv" / "conversion").read_text()
assert (root / "conv" / "control").read_text() == (debian / "control").read_text()
assert hashlib.sha256((root / "conv" / "control").read_bytes()).hexdigest() in report
assert (root / "conv" / "changelog").is_file()
assert (root / "conv" / "rules").is_file()
# the changelog gives the version, split into upstream and Debian revision
assert 'name "deb-demo"' in recipe
assert 'version "2.6.0"' in recipe and 'release "1"' in recipe
assert 'arch "any"' in recipe and 'libc any' in recipe
assert 'homepage "https://example.org/deb-demo"' in recipe
assert 'summary "A Debian source conversion fixture"' in recipe
assert 'x-source-family deb' in recipe and 'x-converter debsrc-1' in recipe
# a wrapped dependency list keeps its entries
assert 'build-depend "debhelper-compat" "eq" "13"' in recipe
assert 'build-depend "dpkg-dev" "ge" "1.15.5"' in recipe
assert 'build-depend "make" "any" "-"' in recipe
assert 'depend "libc6" "ge" "2.34"' in recipe
assert 'x-conflicts "deb-demo-old"' in recipe
assert 'x-suggests "hello"' in recipe
assert 'x-suggests "bye"' in recipe
assert 'x-provides "deb-demo-interface"' in recipe
assert 'depend "deb-demo"' not in recipe
# the dpkg substitution variables are reported, not carried
assert 'unknown the substitution ${misc:Depends} in Depends is filled in by dpkg' in report
# the binary that names its own files becomes a split output
assert 'output "deb-demo" runtime' in recipe
assert 'output "deb-demo-dev" runtime' in recipe
assert 'split-step deb-demo-dev split /bin/sh' in recipe
assert '"usr/include" "usr/share/doc/deb-demo/README"' in recipe
assert 'cp -a "$HOLY_DEST/$entry" "$HOLY_SPLIT_DEST/$entry"' in recipe
# the rules keep their own shell and dh is reported rather than run
assert "step build /bin/sh <<STEP" in recipe
assert "dh $@" in recipe
assert "override_dh_auto_build:" in recipe
# the report names what was carried, preserved and reported
assert "status review-required" in report
assert "carried debian/control" in report
assert "carried Package deb-demo 14" in report
assert "carried Package deb-demo-dev 27" in report
assert "carried changelog version 2.6.0" in report
assert "semantic-change the Debian revision 1 becomes the Holy release" in report
assert "preserved debian/rules" in report
assert "preserved subpackage deb-demo-dev from its file list" in report
assert "preserved maintainer script deb-demo.postinst" in report
assert "unknown debhelper calls the recipe make" in report
assert "unknown lintian-overrides is a lint suppression list" in report
# unreviewed steps still need a decision
run("build", root / "conv" / "deb-demo.recipe", "--output", root / "unreviewed",
"--noninteractive", status=3)
# a native package has no Debian revision, and an epoch is preserved
write(debian / "changelog", """deb-demo (2.6.0) unstable; urgency=medium
* A native package.
-- Fixture <fixture@example.org> Mon, 01 Jan 2024 00:00:00 +0000
""")
run("convert", debian, "--source", "debian", "--output", root / "native", status=3)
recipe = (root / "native" / "deb-demo.recipe").read_text()
assert 'version "2.6.0"' in recipe and 'release "1"' in recipe
write(debian / "changelog", """deb-demo (1:2.6.0-1) unstable; urgency=medium
* A package with an epoch.
-- Fixture <fixture@example.org> Mon, 01 Jan 2024 00:00:00 +0000
""")
run("convert", debian, "--source", "debian", "--output", root / "epoch", status=3)
recipe = (root / "epoch" / "deb-demo.recipe").read_text()
assert 'version "2.6.0"' in recipe and 'release "1"' in recipe
assert "preserved the epoch 1 of the changelog version" in (
root / "epoch" / "conversion").read_text()
# a malformed changelog, a missing control and a rich dependency
write(debian / "changelog", "deb-demo 2.6.0 unstable\n")
run("convert", debian, "--source", "debian", "--output", root / "badlog", status=2)
(debian / "changelog").write_text("deb-demo (2.6.0-1) unstable; urgency=medium\n")
(debian / "control").write_text("Section: devel\nMaintainer: Fixture\n")
run("convert", debian, "--source", "debian", "--output", root / "nocontrol", status=2)
write(debian / "control", """Source: deb-demo
Section: devel
Priority: optional
Maintainer: Fixture <fixture@example.org>
Build-Depends: make
Architecture: all
Package: deb-demo
Architecture: all
Depends: deb-demo-old | deb-demo-new, libc6 [amd64 !i386] <!nocheck>
Description: A rich dependency fixture
It has an alternative and a qualifier.
""")
run("convert", debian, "--source", "debian", "--output", root / "rich", status=3)
report = (root / "rich" / "conversion").read_text()
assert ("unknown Depends offers alternatives for deb-demo-old, only the first is "
"carried") in report
# a file list that names no path is reported instead of an empty split step
write(debian / "control", """Source: deb-demo
Section: devel
Priority: optional
Maintainer: Fixture <fixture@example.org>
Build-Depends: make
Architecture: all
Package: deb-demo
Architecture: all
Depends: ${misc:Depends}
Description: An empty file list fixture
It has a subpackage whose list is empty.
Package: deb-demo-dev
Architecture: all
Depends: ${misc:Depends}
Description: Development files for deb-demo
Headers for deb-demo.
""")
write(debian / "deb-demo-dev.install", "\n# a comment only\n")
write(debian / "rules", "#!/usr/bin/make -f\n\n%:\n\tdh $@\n")
run("convert", debian, "--source", "debian", "--output", root / "emptylist", status=3)
report = (root / "emptylist" / "conversion").read_text()
assert "unknown the file list deb-demo-dev.install of deb-demo-dev names no path" in report
assert "split-step deb-demo-dev" not in (root / "emptylist" / "deb-demo.recipe").read_text()
# unusable arguments
run("convert", debian, "--source", "debian", status=2)
run("convert", debian, "--source", "local", "--output", root / "nope", status=2)
run("convert", debian, "--source", "debian:extra", "--output", root / "nope", status=2)
run("convert", root / "missing", "--source", "debian", "--output", root / "nope",
status=6)
# import takes the same conversion path
write(debian / "control", """Source: deb-imported
Section: misc
Priority: optional
Maintainer: Fixture <fixture@example.org>
Architecture: all
Package: deb-imported
Architecture: all
Depends: ${misc:Depends}
Description: An import fixture
It only has to convert.
""")
write(debian / "changelog", "deb-imported (1-1) unstable; urgency=low\n\n -- F <f@e.org> Mon, 01 Jan 2024 00:00:00 +0000\n")
write(debian / "rules", "#!/usr/bin/make -f\n\n%:\n\tdh $@\n")
run("import", debian, "--source", "debian", "--format", "debian",
"--output", root / "imported", status=3)
assert (root / "imported" / "conversion").is_file()
assert (root / "imported" / "deb-imported.recipe").is_file()
assert digest
return 0
if __name__ == "__main__":
sys.exit(main())