def test_description_storing(tmpdir): """ Test if we can store extra destcriptions and formatting is up par """ lorem_formated = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n\n""" cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) assert len(result.results) == 0 result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') # two options so we check the description is added only once result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') # nothing is populated assert not result.get_description('suse-other-error') # add descriptions result.error_details.update({ 'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' }) assert result.get_description('suse-other-error') == lorem_formated
def test_package_dev_dependency(tmpdir, package, tagscheck): """Test if a package check, - in out, devel-dependency, - not in out, non-standard-group.""" CONFIG.configuration['ValidGroups'] = ['Devel/Something'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package is not a devel package itself but requires a devel dependency assert 'E: devel-dependency glibc-devel' in out # Test if a package does not have a Group tag assert 'W: non-standard-group Devel/Something' not in out
def test_check_non_standard_group(tmpdir, package, tagscheck): """Test if a package has check, - in out, non-standard-group - not in out, not-standard-release-extension.""" CONFIG.configuration['ValidGroups'] = ['Devel/Something'] CONFIG.configuration['ReleaseExtension'] = '0' output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a different Group: tag value than ValidGroups = [] assert 'W: non-standard-group non/standard/group' in out # Test if a package matches the Release tag regex assert 'not-standard-release-extension 0' not in out
def test_check_invalid_license(tmpdir, package, tagscheck): """Test if a package check, - in out, invalid-license, - not in out, requires-on-release.""" CONFIG.configuration['ValidLicenses'] = ['MIT'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a License: tag value different from # ValidLicense = [] list in configuration assert 'W: invalid-license Apache License' in out # Test if a package does not Requires: a specific version of a package assert 'W: requires-on-release' not in out
def test_package_not_std_release_extension(tmpdir, package, tagscheck): """Test if package has check, - in out, not-standard-release-extension - not in out, invalid-license.""" CONFIG.configuration['ReleaseExtension'] = 'hello$' CONFIG.configuration['ValidLicenses'] = ['Apache-2.0 License'] output = Filter(CONFIG) test = TagsCheck(CONFIG, output) test.check(get_tested_package(package, tmpdir)) out = output.print_results(output.results) # Test if a package has a ReleaseExtension regex does not match with the Release: tag value expression # i.e. Release tag value must not match regex expression 'hello$' assert 'W: not-standard-release-extension 1.1' in out # Test if a package does have the same License value as defined in the ValidLicense in configdefaults assert 'W: invalid-license Apache-2.0 License' not in out
def test_filters_regexp(): """ Load some filters and make sure we generate nice regexp """ cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) assert len(cfg.configuration['Filters']) == 9 assert cfg.configuration['Filters'][0] == '.*invalid-buildhost.*' assert isinstance(result.filters_re, Pattern)
def __init__(self, options): # initialize configuration self.checks = {} self.options = options self.packages_checked = 0 self.specfiles_checked = 0 if options['config']: self.config = Config(options['config']) else: self.config = Config() if options['rpmlintrc']: self.config.load_rpmlintrc(options['rpmlintrc']) if options['verbose']: self.config.info = options['verbose'] if not self.config.configuration['ExtractDir']: self.config.configuration['ExtractDir'] = gettempdir() # initialize output buffer self.output = Filter(self.config) # preload the check list self.load_checks()
def test_description_from_conf(tmpdir): """ Test that descriptions strings are updated from configuration file. Load [Descriptions] from TEST_DESCRIPTIONS config file and test that the rpmlint error details are updated to the new values. """ cfg = Config(TEST_DESCRIPTIONS) result = Filter(cfg) assert result.get_description('no-binary', cfg) assert result.get_description('no-binary', cfg) == \ 'A new text for no-binary error.\n\n' assert result.get_description('no-soname', cfg) assert result.get_description('no-soname', cfg) == \ 'A new text for no-soname error.\n\n' # At this point, only basic descriptions from "descriptions" directory are # loaded. "Dynamic" descriptions that are defined directly in the check # file (e.g. see FHSCheck.py and its fhs_details_dict) are loaded later so # we can't test it now. It's tested in test_lint.py. assert not result.get_description('non-standard-dir-in-usr', cfg) assert not result.get_description('non-standard-dir-in-usr', cfg) == \ 'A new text for non-standard-dir-in-usr error.\n\n' assert not result.get_description('non-standard-dir-in-var', cfg) assert not result.get_description('non-standard-dir-in-var', cfg) == \ 'A new text for non-standard-dir-in-var error.\n\n'
def test_data_storing(tmpdir): """ Load some filters and make sure we generate nice regexp """ cfg = Config(TEST_CONFIG_FILTERS) cfg.load_rpmlintrc(TEST_RPMLINTRC) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) # this should be filtered result.add_info('E', pkg, 'invalid-vendor', '') assert len(result.results) == 0 # this should be upgraded to error result.add_info('I', pkg, 'suse-other-error', '') assert len(result.results) == 1 assert result.printed_messages['I'] == 0 assert result.printed_messages['E'] == 1 # this should be downgraded result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') assert len(result.results) == 2 assert result.printed_messages['W'] == 1 assert result.printed_messages['E'] == 1
def appdatacheck(): CONFIG.info = True output = Filter(CONFIG) test = AppDataCheck(CONFIG, output) return output, test
def bashismscheck(): CONFIG.info = True output = Filter(CONFIG) test = BashismsCheck(CONFIG, output) return output, test
def kmpcheck(): CONFIG.info = True output = Filter(CONFIG) test = KMPPolicyCheck(CONFIG, output) return output, test
def sourcescheck(): CONFIG.info = True output = Filter(CONFIG) test = SourceCheck(CONFIG, output) return output, test
def zipcheck(): CONFIG.info = True output = Filter(CONFIG) test = ZipCheck(CONFIG, output) return output, test
class Lint(object): """ Generic object handling the basic rpmlint operations """ def __init__(self, options): # initialize configuration self.checks = {} self.options = options self.packages_checked = 0 self.specfiles_checked = 0 if options['config']: self.config = Config(options['config']) else: self.config = Config() if options['rpmlintrc']: self.config.load_rpmlintrc(options['rpmlintrc']) if options['verbose']: self.config.info = options['verbose'] if not self.config.configuration['ExtractDir']: self.config.configuration['ExtractDir'] = gettempdir() # initialize output buffer self.output = Filter(self.config) # preload the check list self.load_checks() def run(self): # if we just want to print config, do so and leave if self.options['print_config']: self.print_config() return 0 # just explain the error and abort too if self.options['explain']: self.print_explanation(self.options['explain']) return 0 # if no exclusive option is passed then just loop over all the # arguments that are supposed to be either rpm or spec files self.validate_files(self.options['rpmfile']) print(self.output.print_results(self.output.results)) print('{} packages and {} specfiles checked; {} errors, {} warnings'. format(self.packages_checked, self.specfiles_checked, self.output.printed_messages['E'], self.output.printed_messages['W'])) if self.output.badness_threshold > 0 and self.output.score > self.output.badness_threshold: print_warning( f'(none): E: Badness {self.output.score} exceeeds threshold {self.output.badness_threshold}, aborting.' ) return 66 if self.output.printed_messages['E'] > 0: return 64 return 0 def info_error(self, errors): """ Print details for specified error/s. """ self.output.info = True for e in sorted(errors): print(f'{e}:') print(self.output.get_description(e)) def validate_files(self, files): """ Run all the check for passed file list """ if not files: print('There are no files to process nor additional arguments.', file=sys.stderr) print('Nothing to do, aborting.', file=sys.stderr) return for pkg in files: self.validate_file(pkg) def validate_file(self, pname): try: if pname.suffix == '.rpm' or pname.suffix == '.spm': with Pkg(pname, self.config.configuration['ExtractDir']) as pkg: self.run_checks(pkg) self.packages_checked += 1 elif pname.suffix == '.spec': with FakePkg(pname) as pkg: self.run_spec_checks(pkg) self.specfiles_checked += 1 except Exception as e: print_warning(f'(none): E: while reading {pname}: {e}') def run_checks(self, pkg): for checker in self.checks: self.checks[checker].check(pkg) def run_spec_checks(self, pkg): for checker in self.checks: self.checks[checker].check_spec(pkg) def print_config(self): """ Just output the current configuration """ self.config.print_config() def print_explanation(self, message): """ Print out detailed explanation for the specified message """ explanation = self.output.get_description(message) if explanation: print(explanation) else: print(f'Unknown message {message}, or no known description') def load_checks(self): """ Load all checks based on the config, skipping those already loaded SingletonTM """ for check in self.config.configuration['Checks']: if check in self.checks: continue self.checks[check] = self.load_check(check) def load_check(self, name): """Load a (check) module by its name, unless it is already loaded.""" module = importlib.import_module(f'.{name}', package='rpmlint.checks') klass = getattr(module, name) obj = klass(self.config, self.output) return obj
def filescheck(): CONFIG.info = True output = Filter(CONFIG) test = FilesCheck(CONFIG, output) return output, test
def test_output(tmpdir): """ Test the actual output of rpmlint on one file """ expected_output = """ngircd.x86_64: I: suse-other-error /usr/bin/1 ngircd.x86_64: I: suse-other-error /usr/bin/2 dovecot.x86_64: E: suse-other-error /usr/bin/3 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ngircd.x86_64: E: suse-dbus-unauthorized-service\n""" cfg = Config(TEST_CONFIG_FILTERS) result = Filter(cfg) pkg = get_tested_package(TEST_PACKAGE, tmpdir) pkg2 = get_tested_package(TEST_PACKAGE2, tmpdir) # here we check if empty detail will not add whitespace result.add_info('E', pkg, 'suse-dbus-unauthorized-service', '') # two options so we check the description is added only once result.add_info('I', pkg, 'suse-other-error', '/usr/bin/1') result.add_info('I', pkg, 'suse-other-error', '/usr/bin/2') result.add_info('E', pkg2, 'suse-other-error', '/usr/bin/3') result.error_details.update({ 'suse-other-error': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' }) assert len(result.print_results(result.results).splitlines()) == 4 result.info = True assert len(result.print_results(result.results).splitlines()) == 11 assert result.print_results(result.results) == expected_output
kv = o[1].split(None, 1) if len(kv) == 1: config_overrides[kv[0]] = None else: config_overrides[kv[0]] = eval(kv[1]) # apply config overrides for key, value in config_overrides.items(): cfg.configuration[key] = value if not extract_dir: extract_dir = tempfile.gettempdir() if info_error: cfg.info = True output = Filter(cfg) for c in checks: cfg.add_check(c) for c in cfg.configuration['Checks']: loadCheck(c, cfg, output) for e in sorted(info_error): print('{}:'.format(e)) print(output.get_description(e)) sys.exit(0) # if no argument print usage if not args: usage(argv0) sys.exit(1) if __name__ == '__main__':
def pamcheck(): CONFIG.info = True output = Filter(CONFIG) test = PamCheck(CONFIG, output) return output, test
def duplicatescheck(): CONFIG.info = True output = Filter(CONFIG) test = DuplicatesCheck(CONFIG, output) return output, test
def xinetdcheck(): CONFIG.info = True output = Filter(CONFIG) test = XinetdDepCheck(CONFIG, output) return output, test
def erlangcheck(): CONFIG.info = True output = Filter(CONFIG) test = ErlangCheck(CONFIG, output) return output, test
class Lint(object): """ Generic object handling the basic rpmlint operations """ def __init__(self, options): # initialize configuration self.checks = {} self.options = options self.packages_checked = 0 self.specfiles_checked = 0 if options['config']: self.config = Config(options['config']) else: self.config = Config() self._load_rpmlintrc() if options['verbose']: self.config.info = options['verbose'] if options['strict']: self.config.strict = options['strict'] if not self.config.configuration['ExtractDir']: self.config.configuration['ExtractDir'] = gettempdir() # initialize output buffer self.output = Filter(self.config) # preload the check list self.load_checks() def run(self): retcode = 0 # if we just want to print config, do so and leave if self.options['print_config']: self.print_config() return retcode # just explain the error and abort too if self.options['explain']: self.print_explanation(self.options['explain']) return retcode # if there are installed arguments just load them up as extra # items to the rpmfile option if self.options['installed']: self.validate_installed_packages(self._load_installed_rpms(self.options['installed'])) # if no exclusive option is passed then just loop over all the # arguments that are supposed to be either rpm or spec files self.validate_files(self.options['rpmfile']) self._print_header() print(self.output.print_results(self.output.results)) quit_color = Color.Bold if self.output.printed_messages['W'] > 0: quit_color = Color.Yellow if self.output.badness_threshold > 0 and self.output.score > self.output.badness_threshold: msg = string_center(f'Badness {self.output.score} exceeeds threshold {self.output.badness_threshold}, aborting.', '-') print(f'{Color.Red}{msg}{Color.Reset}') quit_color = Color.Red retcode = 66 elif self.output.printed_messages['E'] > 0: quit_color = Color.Red retcode = 64 msg = string_center('{} packages and {} specfiles checked; {} errors, {} warnings'.format(self.packages_checked, self.specfiles_checked, self.output.printed_messages['E'], self.output.printed_messages['W']), '=') print(f'{quit_color}{msg}{Color.Reset}') return retcode def _load_installed_rpms(self, packages): existing_packages = [] for name in packages: pkg = getInstalledPkgs(name) if pkg: existing_packages.extend(pkg) else: print_warning(f'(none): E: there is no installed rpm "{name}".') return existing_packages def _load_rpmlintrc(self): """ Load rpmlintrc from argument or load up from folder """ if self.options['rpmlintrc']: self.config.load_rpmlintrc(self.options['rpmlintrc']) else: # load only from the same folder specname.rpmlintrc or specname-rpmlintrc # do this only in a case where there is one folder parameter or one file # to avoid multiple folders handling rpmlintrc = [] if not len(self.options['rpmfile']) == 1: return pkg = self.options['rpmfile'][0] if pkg.is_file(): pkg = pkg.parent rpmlintrc += sorted(pkg.glob('*.rpmlintrc')) rpmlintrc += sorted(pkg.glob('*-rpmlintrc')) if len(rpmlintrc) > 1: # multiple rpmlintrcs are highly undesirable print_warning('There are multiple items to be loaded for rpmlintrc, ignoring them: {}.'.format(' '.join(map(str, rpmlintrc)))) elif len(rpmlintrc) == 1: self.options['rpmlintrc'] = rpmlintrc[0] self.config.load_rpmlintrc(rpmlintrc[0]) def _print_header(self): """ Print out header information about the state of the rpmlint prior printing out the check report. """ intro = string_center('rpmlint session starts', '=') print(f'{Color.Bold}{intro}{Color.Reset}') print(f'rpmlint: {__version__}') print(f'configuration:') for config in self.config.conf_files: print(f' {config}') if self.options['rpmlintrc']: rpmlintrc = self.options['rpmlintrc'] print(f'rpmlintrc: {rpmlintrc}') no_checks = len(self.config.configuration['Checks']) no_pkgs = len(self.options['installed']) + len(self.options['rpmfile']) print(f'{Color.Bold}checks: {no_checks}, packages: {no_pkgs}{Color.Reset}') print('') print('') def validate_installed_packages(self, packages): for pkg in packages: self.run_checks(pkg) def validate_files(self, files): """ Run all the check for passed file list """ if not files: if self.packages_checked == 0: # print warning only if we didn't process even installed files print_warning('There are no files to process nor additional arguments.') print_warning('Nothing to do, aborting.') return # check all elements if they are a folder or a file with proper suffix # and expand everything packages = self._expand_filelist(files) for pkg in packages: self.validate_file(pkg) def _expand_filelist(self, files): packages = [] for pkg in files: if pkg.is_file() and self._check_valid_suffix(pkg): packages.append(pkg) elif pkg.is_dir(): packages.extend(self._expand_filelist(pkg.iterdir())) return packages @staticmethod def _check_valid_suffix(filename): if any(ext == filename.suffix for ext in ['.rpm', '.spm', '.spec']): return True return False def validate_file(self, pname): try: if pname.suffix == '.rpm' or pname.suffix == '.spm': with Pkg(pname, self.config.configuration['ExtractDir']) as pkg: self.run_checks(pkg) elif pname.suffix == '.spec': with FakePkg(pname) as pkg: self.run_spec_checks(pkg) except Exception as e: print_warning(f'(none): E: while reading {pname}: {e}') def run_checks(self, pkg): for checker in self.checks: self.checks[checker].check(pkg) self.packages_checked += 1 def run_spec_checks(self, pkg): for checker in self.checks: self.checks[checker].check_spec(pkg) self.specfiles_checked += 1 def print_config(self): """ Just output the current configuration """ self.config.print_config() def print_explanation(self, messages): """ Print out detailed explanation for the specified messages """ for message in messages: explanation = self.output.get_description(message) if not explanation: explanation = 'Unknown message, please report a bug if the description should be present.\n\n' print(f'{message}:\n{explanation}') def load_checks(self): """ Load all checks based on the config, skipping those already loaded SingletonTM """ for check in self.config.configuration['Checks']: if check in self.checks: continue self.checks[check] = self.load_check(check) def load_check(self, name): """Load a (check) module by its name, unless it is already loaded.""" module = importlib.import_module(f'.{name}', package='rpmlint.checks') klass = getattr(module, name) obj = klass(self.config, self.output) return obj
def buildrootcheck(): CONFIG.info = True output = Filter(CONFIG) test = BuildRootCheck(CONFIG, output) return output, test
def menuxdgcheck(): CONFIG.info = True output = Filter(CONFIG) test = MenuXDGCheck(CONFIG, output) return output, test
def main(): locale.setlocale(locale.LC_COLLATE, '') output = Filter(cfg) # Load all checks for c in cfg.configuration['Checks']: loadCheck(c, cfg, output) packages_checked = 0 specfiles_checked = 0 try: # Loop over all file names given in arguments dirs = [] for arg in args: pkgs = [] isfile = False try: if arg == '-': arg = '(standard input)' # Short-circuit stdin spec file check stdin = sys.stdin.readlines() if not stdin: continue with Pkg.FakePkg(arg) as pkg: runSpecChecks(pkg, None, spec_lines=stdin) specfiles_checked += 1 continue try: st = os.stat(arg) isfile = True if stat.S_ISREG(st[stat.ST_MODE]): if arg.endswith('.spec'): # Short-circuit spec file checks with Pkg.FakePkg(arg) as pkg: runSpecChecks(pkg, arg) specfiles_checked += 1 elif '/' in arg or arg.endswith('.rpm') or \ arg.endswith('.spm'): pkgs.append(Pkg.Pkg(arg, extract_dir)) else: raise OSError elif stat.S_ISDIR(st[stat.ST_MODE]): dirs.append(arg) continue else: raise OSError except OSError: ipkgs = Pkg.getInstalledPkgs(arg) if not ipkgs: print_warning( '(none): E: no installed packages by name %s' % arg) else: ipkgs.sort(key=lambda x: locale.strxfrm( x.header.sprintf('%{NAME}.%{ARCH}'))) pkgs.extend(ipkgs) except KeyboardInterrupt: if isfile: arg = os.path.abspath(arg) print_warning( '(none): E: interrupted, exiting while reading %s' % arg) sys.exit(2) except Exception as e: if isfile: arg = os.path.abspath(arg) print_warning('(none): E: error while reading %s: %s' % (arg, e)) pkgs = [] continue for pkg in pkgs: with pkg: runChecks(pkg) packages_checked += 1 for dname in dirs: try: for path, _, files in os.walk(dname): for fname in files: fname = os.path.abspath(os.path.join(path, fname)) try: if fname.endswith('.rpm') or \ fname.endswith('.spm'): with Pkg.Pkg(fname, extract_dir) as pkg: runChecks(pkg) packages_checked += 1 elif fname.endswith('.spec'): with Pkg.FakePkg(fname) as pkg: runSpecChecks(pkg, fname) specfiles_checked += 1 except KeyboardInterrupt: print_warning( '(none): E: interrupted while reading %s' % fname) sys.exit(2) except Exception as e: print_warning( '(none): E: while reading %s: %s' % (fname, e)) continue except Exception as e: print_warning( '(none): E: error while reading dir %s: %s' % (dname, e)) continue print(output.print_results(output.results)) if output.badness_threshold > 0 and output.score > output.badness_threshold: print_warning('(none): E: badness %d exceeds threshold %d, aborting.' % (output.score, output.badness_threshold)) sys.exit(66) finally: print('%d packages and %d specfiles checked; %d errors, %d warnings.' % (packages_checked, specfiles_checked, output.printed_messages['E'], output.printed_messages['W'])) if output.printed_messages['E'] > 0: sys.exit(64) sys.exit(0)
def slpcheck(): CONFIG.info = True output = Filter(CONFIG) test = SharedLibraryPolicyCheck(CONFIG, output) return output, test
def logrotatecheck(): CONFIG.info = True output = Filter(CONFIG) test = LogrotateCheck(CONFIG, output) return output, test
def speccheck(): CONFIG.info = True output = Filter(CONFIG) test = SpecCheck(CONFIG, output) return output, test
def binariescheck(): print(CONFIG) CONFIG.info = True output = Filter(CONFIG) test = BinariesCheck(CONFIG, output) return output, test