Coverage for britney2/policies/lintian.py: 90%
106 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +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(verdict, "nothing built yet, lintian delayed")
86 lintian_info["result"] = "not built"
87 else:
88 assert item.version # for type checking
89 src_pkg_id = PackageId(item.package, item.version, "source")
90 try:
91 results = self._lintian[src_pkg_id]
92 except KeyError:
93 verdict = PolicyVerdict.REJECTED_TEMPORARILY
94 self.logger.debug(
95 "%s doesn't have lintian results yet",
96 source_name,
97 )
98 excuse.add_verdict_info(
99 verdict,
100 f"Waiting for lintian test results (stalls migration){url_html}",
101 )
102 lintian_info["result"] = "no data available"
104 if not verdict.is_rejected:
105 if results[0] == "FAILED":
106 verdict = PolicyVerdict.REJECTED_PERMANENTLY
107 self.logger.debug(
108 "lintian failed on %s",
109 source_name,
110 )
111 excuse.add_verdict_info(
112 verdict,
113 f"lintian crashed and produced no output, please contact "
114 f"{self.options.distribution}-release for a hint{url_html}",
115 )
116 lintian_info["result"] = "lintian failed"
117 elif results[0] == "TAGS":
118 verdict = PolicyVerdict.REJECTED_PERMANENTLY
119 lintian_info["result"] = "triggered tags"
120 lintian_info["tags"] = sorted(results[1])
121 self.logger.debug(
122 "%s triggered lintian tags",
123 source_name,
124 )
125 excuse.add_verdict_info(
126 verdict,
127 f"lintian triggered tags: {', '.join(results[1])}{url_html}",
128 )
129 elif src_archs - results[1]:
130 # britney has more architecture
131 verdict = PolicyVerdict.REJECTED_TEMPORARILY
132 archs_missing = src_archs - results[1]
133 lintian_info["result"] = "lintian misses archs"
134 lintian_info["archs"] = ", ".join(sorted(archs_missing))
135 self.logger.debug(
136 "%s lintian misses architectures",
137 source_name,
138 )
139 excuse.add_verdict_info(
140 verdict,
141 f"Waiting for lintian test results on {', '.join(archs_missing)}{url_html}",
142 )
143 elif results[1] - src_archs: 143 ↛ 145line 143 didn't jump to line 145 because the condition on line 143 was never true
144 # lintian had more architecutes than britney, e.g. because of new/old architecture
145 lintian_info["result"] = "lintian saw more archs"
146 lintian_info["archs"] = ", ".join(results[1] - src_archs)
147 else:
148 pass
150 if verdict.is_rejected:
151 assert self.hints is not None
152 hints = self.hints.search(
153 "ignore-lintian",
154 package=source_name,
155 version=source_data_srcdist.version,
156 )
157 if hints:
158 verdict = PolicyVerdict.PASS_HINTED
159 lintian_info.setdefault("ignored-lintian", {}).setdefault(
160 "issued-by", hints[0].user
161 )
162 excuse.addinfo(
163 f"Ignoring lintian issues as requested by {hints[0].user}"
164 )
166 if verdict != PolicyVerdict.PASS:
167 lintian_info["url"] = url
169 return verdict
171 def _read_lintian_status(
172 self, filename: str
173 ) -> dict[PackageId, tuple[str, set[str]]]:
174 summary = self._lintian
175 self.logger.debug("Loading lintian status from %s", filename)
176 with open(filename) as fd: 176 ↛ exitline 176 didn't return from function '_read_lintian_status' because the return on line 178 wasn't executed
177 if os.fstat(fd.fileno()).st_size < 1: 177 ↛ 178line 177 didn't jump to line 178 because the condition on line 177 was never true
178 return summary
179 data = yaml.safe_load(fd)
181 for pkgver, info in data.items():
182 pkg, ver = pkgver.split("/")
183 pkg_id = PackageId(pkg, ver, "source")
184 # TODO: might want to enumerate potential values
185 if "status" in info.keys() and info["status"] == "lintian failed":
186 summary[pkg_id] = ("FAILED", set())
187 elif info["tags"]:
188 summary[pkg_id] = ("TAGS", set(info["tags"].keys()))
189 else:
190 archs = info["architectures"]
191 assert "source" in archs, (
192 "LintianPolicy expects at least source as architecure in lintian.yaml, "
193 f"missing for {pkgver}"
194 )
195 summary[pkg_id] = ("ARCHS", set(archs.split()) - {"source"})
197 return summary