root/releases/pkgcore-checks/0.3/pkgcore_checks/metadata_checks.py @ ferringb%2540gmail.com-20070211111850-2j3re2rmc37rbisi

Revision ferringb%2540gmail.com-20070211111850-2j3re2rmc37rbisi, 12.4 KB (checked in by Brian Harring <ferringb@…>, 2 years ago)

full set of tests; use related testing is via test.test_addons.

Line 
1# Copyright: 2006 Brian Harring <ferringb@gmail.com>
2# License: GPL2
3
4import os
5from operator import attrgetter
6from pkgcore_checks import base, util, addons
7
8from pkgcore.util.compatibility import any
9from pkgcore.package.errors import MetadataException
10from pkgcore.ebuild.atom import MalformedAtom, atom
11from pkgcore.fetch import fetchable
12from pkgcore.restrictions import packages
13from pkgcore.util.osutils import listdir_files
14
15from pkgcore.util.demandload import demandload
16demandload(globals(), "pkgcore.util.xml:escape logging")
17
18
19class MetadataError(base.Result):
20    """problem detected with a packages metadata"""
21    __slots__ = ("category", "package", "version", "attr", "msg")
22    threshold = base.versioned_feed
23   
24    def __init__(self, pkg, attr, msg):
25        base.Result.__init__(self)
26        self._store_cpv(pkg)
27        self.attr, self.msg = attr, str(msg)
28   
29    @property
30    def short_desc(self):
31        return "attr(%s): %s" % (self.attr, self.msg)
32
33
34class MissingLicense(base.Result):
35    """used license(s) have no matching license file(s)"""
36
37    __slots__ = ("category", "package", "version", "licenses")
38    threshold = base.versioned_feed
39   
40    def __init__(self, pkg, licenses):
41        self._store_cpv(pkg)
42        self.licenses = tuple(sorted(licenses))
43   
44    @property
45    def short_desc(self):
46        return "no license files: %s" % ', '.join(self.licenses)
47
48
49class LicenseMetadataReport(base.Template):
50
51    """LICENSE metadata key validity checks"""
52
53    known_results = (MetadataError, MissingLicense) + \
54        addons.UseAddon.known_results
55    feed_type = base.versioned_feed
56
57    required_addons = (addons.UseAddon, addons.ProfileAddon,
58        addons.LicenseAddon)
59
60    def __init__(self, options, iuse_handler, profiles, licenses):
61        base.Template.__init__(self, options)
62        self.iuse_filter = iuse_handler.get_filter()
63        self.license_handler = licenses
64
65    def start(self):
66        self.licenses = self.license_handler.licenses
67
68    def finish(self, reporter):
69        self.licenses = None
70
71    def feed(self, pkg, reporter):
72        try:
73            licenses = pkg.license
74        except (KeyboardInterrupt, SystemExit):
75            raise
76        except (MetadataException, MalformedAtom, ValueError), e:
77            reporter.add_report(MetadataError(pkg, 'license',
78                "error- %s" % e))
79            del e
80        except Exception, e:
81            logging.exception("unknown exception caught for pkg(%s) attr(%s): "
82                "type(%s), %s" % (pkg, 'license', type(e), e))
83            reporter.add_report(MetadataError(pkg, 'license',
84                "exception- %s" % e))
85            del e
86        else:
87            i = self.iuse_filter((basestring,), pkg, licenses, reporter)
88            if self.licenses is None:
89                # force a walk of it so it'll report if needs be.
90                for x in i:
91                    pass
92            else:
93                licenses = set(i)
94                if not licenses:
95                    reporter.add_report(MetadataError(pkg, "license",
96                        "no license defined"))
97                else:
98                    licenses.difference_update(self.licenses)
99                    if licenses:
100                        reporter.add_report(MissingLicense(pkg, licenses))
101
102
103class IUSEMetadataReport(base.Template):
104
105    """Check IUSE for valid use flags"""
106
107    required_addons = (addons.UseAddon,)
108    known_results = (MetadataError,) + addons.UseAddon.known_results
109
110    feed_type = base.versioned_feed
111
112    def __init__(self, options, iuse_handler):
113        base.Template.__init__(self, options)
114        self.iuse_handler = iuse_handler
115
116    def feed(self, pkg, reporter):
117        if not self.iuse_handler.ignore:
118            iuse = set(pkg.iuse).difference(self.iuse_handler.allowed_iuse(pkg))
119            if iuse:
120                reporter.add_report(MetadataError(pkg, "iuse",
121                    "iuse unknown flags- [ %s ]" % ", ".join(iuse)))
122
123
124class DependencyReport(base.Template):
125
126    """check DEPEND, PDEPEND, RDEPEND and PROVIDES"""
127
128    required_addons = (addons.UseAddon,)
129    known_results = (MetadataError,) + addons.UseAddon.known_results
130
131    feed_type = base.versioned_feed
132
133    attrs = tuple((x, attrgetter(x)) for x in
134        ("depends", "rdepends", "post_rdepends", "provides"))
135
136    def __init__(self, options, iuse_handler):
137        base.Template.__init__(self, options)
138        self.iuse_filter = iuse_handler.get_filter()
139
140    def feed(self, pkg, reporter):
141        for attr_name, getter in self.attrs:
142            try:
143                for x in self.iuse_filter((atom,), pkg, getter(pkg), reporter):
144                    pass
145            except (KeyboardInterrupt, SystemExit):
146                raise
147            except (MetadataException, MalformedAtom, ValueError), e:
148                reporter.add_report(MetadataError(pkg, attr_name,
149                    "error- %s" % e))
150                del e
151            except Exception, e:
152                logging.exception(
153                    "unknown exception caught for pkg(%s) attr(%s): "
154                    "type(%s), %s" % (pkg, attr_name, type(e), e))
155                reporter.add_report(MetadataError(pkg, attr_name,
156                    "exception- %s" % e))
157                del e
158
159
160class EmptyKeywords(base.Result):
161    """pkg has no set keywords"""
162
163    __slots__ = ('category', 'package', 'version')
164    threshold = base.versioned_feed
165
166    def __init__(self, pkg):
167        base.Result.__init__(self)
168        self._store_cpv(pkg)
169
170    short_desc = "no keywords defined"
171   
172       
173class StupidKeywords(base.Result):
174    """pkg that is using -*; package.mask in profiles addresses this already"""
175
176    __slots__ = ('category', 'package', 'version')
177    threshold = base.versioned_feed
178
179    def __init__(self, pkg):
180        base.Result.__init__(self)
181        self._store_cpv(pkg)
182   
183    short_desc = "keywords contain -*, use package.mask instead"
184       
185
186class KeywordsReport(base.Template):
187   
188    """
189    check pkgs keywords for sanity; empty keywords, and -* are flagged
190    """
191   
192    feed_type = base.versioned_feed
193    known_results = (EmptyKeywords, StupidKeywords, MetadataError)
194   
195    def feed(self, pkg, reporter):
196        if not pkg.keywords:
197            reporter.add_report(EmptyKeywords(pkg))
198
199        if "-*" in pkg.keywords:
200            reporter.add_report(StupidKeywords(pkg))
201
202
203class MissingUri(base.Result):
204    """restrict=fetch isn't set, yet no full uri exists"""
205    __slots__ = ("category", "package", "version", "filename")
206    threshold = base.versioned_feed
207
208    def __init__(self, pkg, filename):
209        base.Result.__init__(self)
210        self._store_cpv(pkg)
211        self.filename = filename
212   
213    @property
214    def short_desc(self):
215        return "file %s is unfetchable- no URI available, and RESTRICT=fetch " \
216            "isn't set" % self.filename
217   
218
219class BadProto(base.Result):
220    """bad protocol"""
221    __slots__ = ("category", "package", "version", "filename", "bad_uri")
222
223    def __init__(self, pkg, filename, bad_uri):
224        base.Result.__init__(self)
225        self._store_cpv(pkg)
226        self.filename = filename
227        self.bad_uri = tuple(sorted(bad_uri))
228   
229    @property
230    def short_desc(self):
231        return "file %s: bad protocol/uri: %r " % (self.filename, self.bad_uri)
232   
233
234class SrcUriReport(base.Template):
235
236    """SRC_URI related checks.
237
238    verify that it's a valid/fetchable uri, port 80,443,23
239    """
240
241    required_addons = (addons.UseAddon,)
242    feed_type = base.versioned_feed
243    known_results = (BadProto, MissingUri, MetadataError) + \
244        addons.UseAddon.known_results
245
246    valid_protos = frozenset(["http", "https", "ftp"])
247
248    def __init__(self, options, iuse_handler):
249        base.Template.__init__(self, options)
250        self.iuse_filter = iuse_handler.get_filter()
251
252    def feed(self, pkg, reporter):
253        try:
254            lacks_uri = set()
255            # set is required here, due to the fact fetchables can
256            # have duplicate entries.
257            for f_inst in set(self.iuse_filter((fetchable,), pkg,
258                pkg.fetchables, reporter)):
259                if not f_inst.uri:
260                    lacks_uri.add(f_inst.filename)
261                else:
262                    bad = set()
263                    for x in f_inst.uri:
264                        i = x.find("://")
265                        if i == -1:
266                            lacks_uri.add(x)
267                        else:
268                            if x[:i] not in self.valid_protos:
269                                bad.add(x)
270                    if bad:
271                        reporter.add_report(
272                            BadProto(pkg, f_inst.filename, bad))
273            if not "fetch" in pkg.restrict:
274                for x in sorted(lacks_uri):
275                    reporter.add_report(MissingUri(pkg, x))
276
277        except (KeyboardInterrupt, SystemExit):
278            raise
279        except (MetadataException, MalformedAtom, ValueError), e:
280            reporter.add_report(MetadataError(pkg, 'fetchables',
281                "error- %s" % e))
282            del e
283        except Exception, e:
284            logging.exception("unknown exception caught for pkg(%s): "
285                "type(%s), %s" % (pkg, type(e), e))
286            reporter.add_report(MetadataError(pkg, 'fetchables',
287                "exception- %s" % e))
288            del e
289
290
291class CrappyDescription(base.Result):
292   
293    """pkg's description sucks in some fashion"""
294
295    __slots__ = ("category", "package", "version", "msg")
296    threshold = base.versioned_feed
297
298    def __init__(self, pkg, msg):
299        base.Result.__init__(self)
300        self._store_cpv(pkg)
301        self.msg = msg
302   
303    @property
304    def short_desc(self):
305        return "description needs improvement: %s" % self.msg
306   
307
308class DescriptionReport(base.Template):
309    """
310    DESCRIPTION checks.
311    check on length (<=250), too short (<5), or generic (lifted from eclass or
312    just using the pkgs name
313    """
314   
315    feed_type = base.versioned_feed
316    known_results = (CrappyDescription,)
317
318    def feed(self, pkg, reporter):
319        s = pkg.description.lower()
320
321        if s.startswith("based on") and "eclass" in s:
322            reporter.add_report(CrappyDescription(pkg,
323                "generic eclass defined description"))
324
325        elif pkg.package == s or pkg.key == s:
326            reporter.add_report(CrappyDescription(pkg,
327                "using the pkg name as the description isn't very helpful"))
328
329        else:
330            l = len(pkg.description)
331            if not l:
332                reporter.add_report(CrappyDescription(pkg,
333                    "empty/unset"))
334            elif l > 250:
335                reporter.add_report(CrappyDescription(pkg,
336                    "over 250 chars in length, bit long"))
337            elif l < 5:
338                reporter.add_report(CrappyDescription(pkg,
339                    "under 10 chars in length- too short"))
340
341
342class BadRestricts(base.Result):
343    """pkg's restrict metadata has unknown/deprecated entries"""
344   
345    __slots__ = ("category", "package", "version", "restricts", "deprecated")
346    threshold = base.versioned_feed
347   
348    def __init__(self, pkg, restricts, deprecated=None):
349        base.Result.__init__(self)
350        self._store_cpv(pkg)
351        self.restricts = restricts
352        self.deprecated = deprecated
353        if not restricts and not deprecated:
354            raise TypeError("deprecated or restricts must not be empty")
355   
356    @property
357    def short_desc(self):
358        s = ''
359        if self.restricts:
360            s = "unknown restricts: %s" % ", ".join(self.restricts)
361        if self.deprecated:
362            if s:
363                s += "; "
364            s += "deprecated (drop the 'no') [ %s ]" % ", ".join(
365                self.deprecated)
366        return s
367       
368
369class RestrictsReport(base.Template):
370    feed_type = base.versioned_feed
371    known_restricts = frozenset(("confcache", "stricter", "mirror", "fetch",
372        "test", "sandbox", "userpriv", "primaryuri", "binchecks", "strip",
373        "multilib-strict"))
374
375    known_results = (BadRestricts,)
376
377    __doc__ = "check over RESTRICT, looking for unknown restricts\nvalid " \
378        "restricts:%s" % ", ".join(sorted(known_restricts))
379
380    def feed(self, pkg, reporter):
381        bad = set(pkg.restrict).difference(self.known_restricts)
382        if bad:
383            deprecated = set(x for x in bad if x.startswith("no")
384                and x[2:] in self.known_restricts)
385            reporter.add_report(BadRestricts(
386                    pkg, bad.difference(deprecated), deprecated))
Note: See TracBrowser for help on using the browser.