File: test_dpkg.py

package info (click to toggle)
debsbom 0.6.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,248 kB
  • sloc: python: 5,965; makefile: 31
file content (197 lines) | stat: -rw-r--r-- 7,567 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# Copyright (C) 2025 Siemens
#
# SPDX-License-Identifier: MIT

import io
from pathlib import Path
from debian.deb822 import PkgRelation
from debian.debian_support import Version
import pytest

from debsbom.dpkg.package import (
    ChecksumAlgo,
    Dependency,
    BinaryPackage,
    SourcePackage,
    Package,
    DpkgStatus,
    filter_binaries,
)
from debsbom.sbom import Reference, SBOMType


def test_parse_dependency():
    dependency_line = (
        "binutils-common (= 2.40-2), libbinutils (= 2.40-2), binutils-x86-64-linux-gnu (= 2.40-2)"
    )
    deps = Dependency.parse_depends_line(dependency_line)
    assert deps == [
        Dependency(name="binutils-common", version=("=", Version("2.40-2"))),
        Dependency(name="libbinutils", version=("=", Version("2.40-2"))),
        Dependency(name="binutils-x86-64-linux-gnu", version=("=", Version("2.40-2"))),
    ]


@pytest.mark.parametrize("mode", ["file", "stream"])
def test_parse_minimal_status_file(mode):
    status_file = Path("tests/data/dpkg-status-minimal")
    if mode == "file":
        packages = list(BinaryPackage.parse_status_file(status_file))
    elif mode == "stream":
        with open(status_file, "r") as stream:
            packages = list(BinaryPackage.parse_pkglist_stream(stream))

    bpkg = [p for p in packages if isinstance(p, BinaryPackage)][0]

    assert bpkg.name == "binutils"
    assert bpkg.section == "devel"
    assert bpkg.maintainer == "Matthias Klose <doko@debian.org>"
    assert bpkg.source == Dependency(bpkg.name, None, ("=", bpkg.version), arch="source")
    assert bpkg.version == "2.40-2"
    assert bpkg.depends == [
        Dependency("binutils-common", None, ("=", bpkg.version)),
        Dependency("libbinutils", None, ("=", bpkg.version)),
        Dependency("binutils-x86-64-linux-gnu", None, ("=", bpkg.version)),
    ]
    assert (
        bpkg.description == "GNU assembler, linker and binary utilities\n"
        "The programs in this package are used to assemble, link and manipulate binary and object files.  "
        "They may be used in conjunction with a compiler and various libraries to build programs."
    )
    assert bpkg.homepage == "https://www.gnu.org/software/binutils/"
    assert bpkg.status == DpkgStatus.INSTALLED

    spkg = packages[1]
    assert spkg.name == "binutils"
    assert spkg.version == bpkg.version
    assert spkg.maintainer == bpkg.maintainer


def test_parse_source_status_file():
    packages = list(BinaryPackage.parse_status_file(Path("tests/data/dpkg-status-source")))
    bpkg = [p for p in packages if isinstance(p, BinaryPackage)][0]

    assert bpkg.name == "apt-utils"
    assert bpkg.section == "admin"
    assert bpkg.maintainer == "APT Development Team <deity@lists.debian.org>"
    assert bpkg.source == Dependency("apt", None, ("=", bpkg.version), arch="source")
    assert bpkg.version == "2.6.1"
    assert bpkg.depends == [
        Dependency("apt", None, ("=", bpkg.version)),
        Dependency("libapt-pkg6.0", None, (">=", bpkg.version)),
        Dependency("libc6", None, (">=", Version("2.34"))),
        Dependency("libdb5.3", None, None),
        Dependency("libgcc-s1", None, (">=", Version("3.0"))),
        Dependency("libstdc++6", None, (">=", Version("11"))),
    ]
    assert (
        bpkg.description == "package management related utility programs\n"
        "This package contains some less used commandline utilities related "
        "to package management with APT.\n\n"
        " * apt-extracttemplates is used by debconf to prompt for configuration\n"
        "   questions before installation.\n"
        " * apt-ftparchive is used to create Packages and other index files\n"
        "   needed to publish an archive of Debian packages\n"
        " * apt-sortpkgs is a Packages/Sources file normalizer."
    )
    assert bpkg.homepage is None

    spkg = [p for p in packages if isinstance(p, SourcePackage)][0]
    assert spkg.name == "apt"
    assert spkg.version == bpkg.version
    assert spkg.maintainer == bpkg.maintainer

    # check source package with version != binary version
    spkg = [p for p in packages if isinstance(p, SourcePackage)][1]
    assert spkg.name == "guestfs-tools"
    assert spkg.version == "1.52.3-1"


def test_package_merge():
    pkg_foo = BinaryPackage(
        name="foo",
        version="1.0-r1",
        depends=[Dependency("bar")],
        built_using=[Dependency("bar-src")],
        checksums={ChecksumAlgo.MD5SUM: "50a2fabfdd276f573ff97ace8b11c5f4"},
    )
    pkg_foo_apt = BinaryPackage(
        name="foo",
        version="1.0-r1",
        depends=[Dependency("top")],
        built_using=[Dependency("bar-src"), Dependency("foo-src")],
        checksums={ChecksumAlgo.SHA1SUM: "34973274ccef6ab4dfaaf86599792fa9c3fe4689"},
        description="description of foo",
        homepage="http://example.com/",
        manually_installed=True,
    )
    pkg_foo.merge_with(pkg_foo_apt)
    assert "bar" in [d.name for d in pkg_foo.depends]
    assert "top" in [d.name for d in pkg_foo.depends]
    assert "bar-src" in [d.name for d in pkg_foo.built_using]
    assert "foo-src" in [d.name for d in pkg_foo.built_using]
    assert ChecksumAlgo.MD5SUM in pkg_foo.checksums.keys()
    assert ChecksumAlgo.SHA1SUM in pkg_foo.checksums.keys()
    assert pkg_foo.description.startswith("desc")
    assert pkg_foo.manually_installed


@pytest.mark.parametrize(
    "data",
    [
        ["binutils-arm-none-eabi 2.40-2+18+b1 amd64", "binutils-bpf 2.40-2+1 amd64"],
        [
            "pkg:deb/debian/binutils-arm-none-eabi@2.40-2+18+b1?arch=amd64",
            "pkg:deb/debian/binutils-bpf@2.40-2+1?arch=amd64",
        ],
        [
            "binutils-arm-none-eabi|2.40-2+18+b1|binutils-arm-none-eabi:amd64|2.40-2+18+b1",
            "binutils-bpf|2.40-2+1|binutils-bpf:amd64|2.40-2+1",
        ],
    ],
)
def test_parse_pkgs_stream(data):
    stream = io.BytesIO("\n".join(data).encode())
    pkgs_it = filter_binaries(Package.parse_pkglist_stream(stream))

    pkg: BinaryPackage = next(pkgs_it)
    assert pkg.name == "binutils-arm-none-eabi"
    assert pkg.version.debian_revision == "2+18+b1"
    assert pkg.architecture == "amd64"

    pkg: BinaryPackage = next(pkgs_it)
    assert pkg.name == "binutils-bpf"
    assert pkg.version.upstream_version == "2.40"
    assert pkg.architecture == "amd64"


def test_unique_depends():
    deps = [
        Dependency("libc6", None, (">>", Version("2.41")), arch="amd64"),
        Dependency("libc6", None, ("<<", Version("2.42")), arch="amd64"),
        Dependency("foo", None, ("", Version("2.41"))),
    ]
    pkg = BinaryPackage("foo", "1.0", architecture="amd64", depends=deps)
    assert len(pkg.depends) == 3
    assert len(pkg.unique_depends) == 2


@pytest.mark.parametrize("sbom_type", [SBOMType.SPDX, SBOMType.CycloneDX])
def test_cross_arch_lookup(sbom_type):
    bar_dep = Dependency("bar", None, ("=", Version("1.0")))
    foo = BinaryPackage("foo", "1.0", architecture="all", depends=[bar_dep])
    bar = BinaryPackage("bar", "1.0", architecture="riscv64")

    refs = dict(map(lambda p: (Reference.make_from_pkg(p).as_str(sbom_type), p), [foo, bar]))
    ref_bar = Reference.lookup(foo, bar_dep, sbom_type, refs, native_arch="riscv64")
    assert "riscv64" in ref_bar
    ref_bar = Reference.lookup(foo, bar_dep, sbom_type, refs, native_arch="amd64")
    assert ref_bar is None


def test_package_str_repr():
    spkg = SourcePackage("foo", "1.0")
    assert str(spkg) == "foo@1.0"

    bpkg = BinaryPackage("bar", "2.0")
    assert str(bpkg) == "bar@2.0"