Coverage for britney2/policies/lintian.py: 90%
106 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-30 09:44 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-30 09:44 +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 ApplySrcPolicy, PolicyVerdict
12from britney2.policies.policy import AbstractBasePolicy
13from britney2.utils import iter_except, 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 source_suite = self.suite_info.primary_source_suite
46 try:
47 filename = os.path.join(self.state_dir, "lintian.yaml")
48 except AttributeError as e: # pragma: no cover
49 raise RuntimeError(
50 "Please set STATE_DIR in the britney configuration"
51 ) from e
53 self._read_lintian_status(filename)
55 def apply_src_policy_impl(
56 self,
57 lintian_info: dict[str, Any],
58 item: MigrationItem,
59 source_data_tdist: Optional["SourcePackage"],
60 source_data_srcdist: "SourcePackage",
61 excuse: "Excuse",
62 ) -> PolicyVerdict:
63 verdict = PolicyVerdict.PASS
65 source_name = item.package
66 src_suite_bins = self.suite_info.primary_source_suite.all_binaries_in_suite
67 src_archs = set()
68 for pkg_id in source_data_srcdist.binaries:
69 src_archs.add(src_suite_bins[pkg_id].architecture)
71 if self.options.lintian_url: 71 ↛ 75line 71 didn't jump to line 75 because the condition on line 71 was always true
72 url = self.options.lintian_url.format(package=quote(source_name))
73 url_html = f' - <a href="{url}">info</a>'
74 else:
75 url = None
76 url_html = ""
78 # skip until a new package is built somewhere
79 if not source_data_srcdist.binaries:
80 verdict = PolicyVerdict.REJECTED_TEMPORARILY
81 self.logger.debug(
82 "%s hasn't been built anywhere, skipping lintian policy",
83 source_name,
84 )
85 excuse.add_verdict_info(
86 verdict, "Lintian check deferred: missing all builds"
87 )
88 lintian_info["result"] = "not built"
89 else:
90 assert item.version # for type checking
91 src_pkg_id = PackageId(item.package, item.version, "source")
92 try:
93 results = self._lintian[src_pkg_id]
94 except KeyError:
95 verdict = PolicyVerdict.REJECTED_TEMPORARILY
96 self.logger.debug(
97 "%s doesn't have lintian results yet",
98 source_name,
99 )
100 excuse.add_verdict_info(
101 verdict,
102 f"Lintian check waiting for test results{url_html}",
103 )
104 lintian_info["result"] = "no data available"
106 if not verdict.is_rejected:
107 if results[0] == "FAILED":
108 verdict = PolicyVerdict.REJECTED_PERMANENTLY
109 self.logger.debug(
110 "lintian failed on %s",
111 source_name,
112 )
113 excuse.add_verdict_info(
114 verdict,
115 f"Lintian crashed and produced no output, please contact "
116 f"{self.options.distribution}-release for a hint{url_html}",
117 )
118 lintian_info["result"] = "lintian failed"
119 elif results[0] == "TAGS":
120 verdict = PolicyVerdict.REJECTED_PERMANENTLY
121 lintian_info["result"] = "triggered tags"
122 lintian_info["tags"] = sorted(results[1])
123 self.logger.debug(
124 "%s triggered lintian tags",
125 source_name,
126 )
127 excuse.add_verdict_info(
128 verdict,
129 f"Lintian triggered tags: {', '.join(results[1])}{url_html}",
130 )
131 elif src_archs - results[1]:
132 # britney has more architecture
133 verdict = PolicyVerdict.REJECTED_TEMPORARILY
134 archs_missing = src_archs - results[1]
135 lintian_info["result"] = "lintian misses archs"
136 lintian_info["archs"] = ", ".join(sorted(archs_missing))
137 self.logger.debug(
138 "%s lintian misses architectures",
139 source_name,
140 )
141 excuse.add_verdict_info(
142 verdict,
143 f"Lintian check waiting for test results on {', '.join(archs_missing)}{url_html}",
144 )
145 elif results[1] - src_archs: 145 ↛ 147line 145 didn't jump to line 147 because the condition on line 145 was never true
146 # lintian had more architecutes than britney, e.g. because of new/old architecture
147 lintian_info["result"] = "lintian saw more archs"
148 lintian_info["archs"] = ", ".join(results[1] - src_archs)
149 else:
150 pass
152 if verdict.is_rejected:
153 assert self.hints is not None
154 hints = self.hints.search(
155 "ignore-lintian",
156 package=source_name,
157 version=source_data_srcdist.version,
158 )
159 if hints:
160 verdict = PolicyVerdict.PASS_HINTED
161 lintian_info.setdefault("ignored-lintian", {}).setdefault(
162 "issued-by", hints[0].user
163 )
164 excuse.addinfo(
165 f"Lintian issues ignored as requested by {hints[0].user}"
166 )
168 if verdict != PolicyVerdict.PASS:
169 lintian_info["url"] = url
171 return verdict
173 def _read_lintian_status(
174 self, filename: str
175 ) -> dict[PackageId, tuple[str, set[str]]]:
176 summary = self._lintian
177 self.logger.debug("Loading lintian status from %s", filename)
178 with open(filename) as fd: 178 ↛ exitline 178 didn't return from function '_read_lintian_status' because the return on line 180 wasn't executed
179 if os.fstat(fd.fileno()).st_size < 1: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 return summary
181 data = yaml.safe_load(fd)
183 for pkgver, info in data.items():
184 pkg, ver = pkgver.split("/")
185 pkg_id = PackageId(pkg, ver, "source")
186 # TODO: might want to enumerate potential values
187 if "status" in info.keys() and info["status"] == "lintian failed":
188 summary[pkg_id] = ("FAILED", set())
189 elif info["tags"]:
190 summary[pkg_id] = ("TAGS", set(info["tags"].keys()))
191 else:
192 archs = info["architectures"]
193 assert "source" in archs, (
194 "LintianPolicy expects at least source as architecure in lintian.yaml, "
195 f"missing for {pkgver}"
196 )
197 summary[pkg_id] = ("ARCHS", set(archs.split()) - {"source"})
199 return summary