Coverage for britney2/policies/lintian.py: 90%
106 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-29 17:21 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-29 17:21 +0000
1import optparse
2import os
3from typing import TYPE_CHECKING, Any, Optional
4from urllib.parse import quote
6import yaml
8from britney2 import PackageId, SuiteClass
9from britney2.hints import HintAnnotate, HintType
10from britney2.migrationitem import MigrationItem
11from britney2.policies import PolicyVerdict
12from britney2.policies.policy import AbstractBasePolicy
13from britney2.utils import filter_out_faux, parse_option
15if TYPE_CHECKING: 15 ↛ 16line 15 didn't jump to line 16 because the condition on line 15 was never true
16 from .. import SourcePackage, Suites
17 from ..britney import Britney
18 from ..excuse import Excuse
19 from ..hints import HintParser
22class LintianPolicy(AbstractBasePolicy):
23 def __init__(self, options: optparse.Values, suite_info: "Suites") -> None:
24 super().__init__(
25 "lintian",
26 options,
27 suite_info,
28 {SuiteClass.PRIMARY_SOURCE_SUITE},
29 )
30 self._lintian: dict[PackageId, tuple[str, set[str]]] = {}
32 # Default values for this policy's options
33 parse_option(options, "lintian_url")
35 def register_hints(self, hint_parser: "HintParser") -> None:
36 hint_parser.register_hint_type(
37 HintType(
38 "ignore-lintian",
39 versioned=HintAnnotate.OPTIONAL,
40 )
41 )
43 def initialise(self, britney: "Britney") -> None:
44 super().initialise(britney)
45 try:
46 filename = os.path.join(self.state_dir, "lintian.yaml")
47 except AttributeError as e: # pragma: no cover
48 raise RuntimeError(
49 "Please set STATE_DIR in the britney configuration"
50 ) from e
52 self._read_lintian_status(filename)
54 def apply_src_policy_impl(
55 self,
56 lintian_info: dict[str, Any],
57 source_data_tdist: Optional["SourcePackage"],
58 source_data_srcdist: "SourcePackage",
59 excuse: "Excuse",
60 ) -> PolicyVerdict:
61 verdict = PolicyVerdict.PASS
63 item = excuse.item
64 source_name = item.package
65 src_suite_bins = self.suite_info.primary_source_suite.all_binaries_in_suite
66 src_archs = set()
67 for pkg_id in filter_out_faux(source_data_srcdist.binaries):
68 src_archs.add(src_suite_bins[pkg_id].architecture)
70 if self.options.lintian_url: 70 ↛ 74line 70 didn't jump to line 74 because the condition on line 70 was always true
71 url = self.options.lintian_url.format(package=quote(source_name))
72 url_html = f' - <a href="{url}">info</a>'
73 else:
74 url = None
75 url_html = ""
77 # skip until a new package is built somewhere
78 if not filter_out_faux(source_data_srcdist.binaries):
79 verdict = PolicyVerdict.REJECTED_TEMPORARILY
80 self.logger.debug(
81 "%s hasn't been built anywhere, skipping lintian policy",
82 source_name,
83 )
84 excuse.add_verdict_info(
85 verdict, "Lintian check deferred: missing all builds"
86 )
87 lintian_info["result"] = "not built"
88 else:
89 assert item.version # for type checking
90 src_pkg_id = PackageId(item.package, item.version, "source")
91 try:
92 results = self._lintian[src_pkg_id]
93 except KeyError:
94 verdict = PolicyVerdict.REJECTED_TEMPORARILY
95 self.logger.debug(
96 "%s doesn't have lintian results yet",
97 source_name,
98 )
99 excuse.add_verdict_info(
100 verdict,
101 f"Lintian check waiting for test results{url_html}",
102 )
103 lintian_info["result"] = "no data available"
105 if not verdict.is_rejected:
106 if results[0] == "FAILED":
107 verdict = PolicyVerdict.REJECTED_PERMANENTLY
108 self.logger.debug(
109 "lintian failed on %s",
110 source_name,
111 )
112 excuse.add_verdict_info(
113 verdict,
114 f"Lintian crashed and produced no output, please contact "
115 f"{self.options.distribution}-release for a hint{url_html}",
116 )
117 lintian_info["result"] = "lintian failed"
118 elif results[0] == "TAGS":
119 verdict = PolicyVerdict.REJECTED_PERMANENTLY
120 lintian_info["result"] = "triggered tags"
121 lintian_info["tags"] = sorted(results[1])
122 self.logger.debug(
123 "%s triggered lintian tags",
124 source_name,
125 )
126 excuse.add_verdict_info(
127 verdict,
128 f"Lintian triggered tags: {', '.join(results[1])}{url_html}",
129 )
130 elif src_archs - results[1]:
131 # britney has more architecture
132 verdict = PolicyVerdict.REJECTED_TEMPORARILY
133 archs_missing = src_archs - results[1]
134 lintian_info["result"] = "lintian misses archs"
135 lintian_info["archs"] = ", ".join(sorted(archs_missing))
136 self.logger.debug(
137 "%s lintian misses architectures",
138 source_name,
139 )
140 excuse.add_verdict_info(
141 verdict,
142 f"Lintian check waiting for test results on {', '.join(archs_missing)}{url_html}",
143 )
144 elif results[1] - src_archs: 144 ↛ 146line 144 didn't jump to line 146 because the condition on line 144 was never true
145 # lintian had more architecutes than britney, e.g. because of new/old architecture
146 lintian_info["result"] = "lintian saw more archs"
147 lintian_info["archs"] = ", ".join(results[1] - src_archs)
148 else:
149 pass
151 if verdict.is_rejected:
152 assert self.hints is not None
153 hints = self.hints.search(
154 "ignore-lintian",
155 package=source_name,
156 version=source_data_srcdist.version,
157 )
158 if hints:
159 verdict = PolicyVerdict.PASS_HINTED
160 lintian_info.setdefault("ignored-lintian", {}).setdefault(
161 "issued-by", hints[0].user
162 )
163 excuse.addinfo(
164 f"Lintian issues ignored as requested by {hints[0].user}"
165 )
167 if verdict != PolicyVerdict.PASS:
168 lintian_info["url"] = url
170 return verdict
172 def _read_lintian_status(
173 self, filename: str
174 ) -> dict[PackageId, tuple[str, set[str]]]:
175 summary = self._lintian
176 self.logger.debug("Loading lintian status from %s", filename)
177 with open(filename) as fd: 177 ↛ exitline 177 didn't return from function '_read_lintian_status' because the return on line 179 wasn't executed
178 if os.fstat(fd.fileno()).st_size < 1: 178 ↛ 179line 178 didn't jump to line 179 because the condition on line 178 was never true
179 return summary
180 data = yaml.safe_load(fd)
182 for pkgver, info in data.items():
183 pkg, ver = pkgver.split("/")
184 pkg_id = PackageId(pkg, ver, "source")
185 # TODO: might want to enumerate potential values
186 if "status" in info.keys() and info["status"] == "lintian failed":
187 summary[pkg_id] = ("FAILED", set())
188 elif info["tags"]:
189 summary[pkg_id] = ("TAGS", set(info["tags"].keys()))
190 else:
191 archs = info["architectures"]
192 assert "source" in archs, (
193 "LintianPolicy expects at least source as architecure in lintian.yaml, "
194 f"missing for {pkgver}"
195 )
196 summary[pkg_id] = ("ARCHS", set(archs.split()) - {"source"})
198 return summary