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

1import optparse 

2import os 

3from typing import TYPE_CHECKING, Any, Optional 

4from urllib.parse import quote 

5 

6import yaml 

7 

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 

14 

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 

20 

21 

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]]] = {} 

31 

32 # Default values for this policy's options 

33 parse_option(options, "lintian_url") 

34 

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 ) 

42 

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 

51 

52 self._read_lintian_status(filename) 

53 

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 

62 

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) 

69 

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 = "" 

76 

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" 

104 

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 

150 

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 ) 

166 

167 if verdict != PolicyVerdict.PASS: 

168 lintian_info["url"] = url 

169 

170 return verdict 

171 

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) 

181 

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"}) 

197 

198 return summary