From 2a8c184b2e546359782eb497197f7a8628fbeace Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Fri, 18 Oct 2019 09:15:17 +0200 Subject: [PATCH 1/6] Move config parsing from cli.py to config.py Move configuration parsing from `cli.py` to `config.py`. This makes `cli.py` less complex and allows the program to only pass the config object instead of having to pass the updated options. Signed-off-by: Roald Nefs --- saltlint/cli.py | 87 ++++++++++++-------------------------------- saltlint/config.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 64 deletions(-) create mode 100644 saltlint/config.py diff --git a/saltlint/cli.py b/saltlint/cli.py index d58d3ae..3f5a695 100644 --- a/saltlint/cli.py +++ b/saltlint/cli.py @@ -6,33 +6,14 @@ import optparse import sys -import yaml -import os -# Import Salt libs -from salt.ext import six - -import saltlint.linter -import saltlint.formatters as formatters +from saltlint import formatters, NAME, VERSION +from saltlint.linter import default_rulesdir +from saltlint.config import SaltLintConfig, SaltLintConfigError from saltlint.linter import RulesCollection, Runner -from saltlint import NAME, VERSION - - -def load_config(config_file): - config_path = config_file if config_file else ".salt-lint" - - if os.path.exists(config_path): - with open(config_path, "r") as stream: - try: - return yaml.safe_load(stream) - except yaml.YAMLERROR: - pass - - return None def run(): - formatter = formatters.Formatter() parser = optparse.OptionParser("%prog [options] init.sls [state ...]", @@ -45,14 +26,14 @@ def run(): help="specify one or more rules directories using " "one or more -r arguments. Any -r flags override " "the default rules in %s, unless -R is also used." - % saltlint.linter.default_rulesdir) + % default_rulesdir) parser.add_option('-R', action='store_true', default=False, dest='use_default_rules', help="Use default rules in %s in addition to any extra " "rules directories specified with -r. There is " "no need to specify this if no -r flags are used." - % saltlint.linter.default_rulesdir) + % default_rulesdir) parser.add_option('-t', dest='tags', action='append', default=[], @@ -79,72 +60,50 @@ def run(): parser.add_option('-c', help='Specify configuration file to use. Defaults to ".salt-lint"') options, args = parser.parse_args(sys.argv[1:]) - config = load_config(options.c) - - if config: - # TODO parse configuration options - - if 'verbosity' in config: - options.verbosity = options.verbosity + config['verbosity'] - - if 'exclude_paths' in config: - options.exclude_paths = options.exclude_paths + config['exclude_paths'] - - if 'skip_list' in config: - options.skip_list = options.skip_list + config['skip_list'] - - if 'tags' in config: - options.tags = options.tags + config['tags'] - - if 'use_default_rules' in config: - options.use_default_rules = options.use_default_rules or config['use_default_rules'] - - if 'rulesdir' in config: - options.rulesdir = options.rulesdir + config['rulesdir'] + # Read, parse and validate the configration + try: + config = SaltLintConfig(options) + except SaltLintConfigError as exc: + print(exc) + return 2 + # Show a help message on the screen if len(args) == 0 and not (options.listrules or options.listtags): parser.print_help(file=sys.stderr) return 1 - if options.use_default_rules: - rulesdirs = options.rulesdir + [saltlint.linter.default_rulesdir] - else: - rulesdirs = options.rulesdir or [saltlint.linter.default_rulesdir] - + # Collect the rules from the configution rules = RulesCollection() - for rulesdir in rulesdirs: + for rulesdir in config.rulesdirs: rules.extend(RulesCollection.create_from_directory(rulesdir)) + # Show the rules listing if options.listrules: print(rules) return 0 + # Show the tags listing if options.listtags: print(rules.listtags()) return 0 - if isinstance(options.tags, six.string_types): - options.tags = options.tags.split(',') - - skip = set() - for s in options.skip_list: - skip.update(str(s).split(',')) - options.skip_list = frozenset(skip) - states = set(args) matches = list() checked_files = set() for state in states: - runner = Runner(rules, state, options.tags, - options.skip_list, options.exclude_paths, - options.verbosity, checked_files) + runner = Runner(rules, state, config.tags, + config.skip_list, config.exclude_paths, + config.verbosity, checked_files) matches.extend(runner.run()) + # Sort the matches matches.sort(key=lambda x: (x.filename, x.linenumber, x.rule.id)) + # Show the matches on the screen for match in matches: - print(formatter.format(match, options.colored)) + print(formatter.format(match, config.colored)) + # Return the exit code if len(matches): return 2 else: diff --git a/saltlint/config.py b/saltlint/config.py new file mode 100644 index 0000000..24f3da8 --- /dev/null +++ b/saltlint/config.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 Roald Nefs + +import yaml +import os + +# Import Salt libs +from salt.ext import six + +from saltlint.linter import default_rulesdir + + +class SaltLintConfigError(Exception): + pass + + +class SaltLintConfig(object): + + def __init__(self, options): + self._options = options + # Configuration file to use, defaults to ".salt-lint". + file = options.c if options.c else '.salt-lint' + + # Read the file contents + if os.path.exists(file): + with open(file, 'r') as f: + content = f.read() + else: + content = None + + # Parse the content of the file as YAML + self._parse(content) + self._validate() + + def _parse(self, content): + config = dict() + + # Parse the YAML content + if content: + try: + config = yaml.safe_load(content) + except Exception as exc: + raise SaltLintConfigError("invalid config: {}".format(exc)) + + # Parse verbosity + self.verbosity = self._options.verbosity + if 'verbosity' in config: + self.verbosity += config['verbosity'] + + # Parse exclude paths + self.exclude_paths = self._options.exclude_paths + if 'exclude_paths' in config: + self.exclude_paths += config['exclude_paths'] + + # Parse skip list + skip_list = self._options.skip_list + if 'skip_list' in config: + skip_list += config['skip_list'] + skip = set() + for s in skip_list: + skip.update(str(s).split(',')) + self.skip_list = frozenset(skip) + + # Parse tags + self.tags = self._options.tags + if 'tags' in config: + self.tags += config['tags'] + if isinstance(self.tags, six.string_types): + self.tags = self.tags.split(',') + + # Parse use default rules + use_default_rules = self._options.use_default_rules + if 'use_default_rules' in config: + use_default_rules = use_default_rules or config['use_default_rules'] + + # Parse rulesdir + rulesdir = self._options.rulesdir + if 'rulesdir' in config: + rulesdir += config['rulesdir'] + + # Determine the rules directories + if use_default_rules: + self.rulesdirs = rulesdir + [default_rulesdir] + else: + self.rulesdirs = rulesdir or [default_rulesdir] + + # Parse colored + self.colored = self._options.colored + + def _validate(self): + pass From 94a85a5960ba2641da419290805e0e41cf15389e Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Fri, 18 Oct 2019 11:24:07 +0200 Subject: [PATCH 2/6] Pass configuration to the rules Pass the configuration to the rules, so the rules can use information from the configuration. Signed-off-by: Roald Nefs --- saltlint/cli.py | 8 +++----- saltlint/config.py | 2 +- saltlint/linter.py | 34 ++++++++++++++++++++-------------- saltlint/utils.py | 4 ++-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/saltlint/cli.py b/saltlint/cli.py index 3f5a695..d9c6bae 100644 --- a/saltlint/cli.py +++ b/saltlint/cli.py @@ -73,9 +73,9 @@ def run(): return 1 # Collect the rules from the configution - rules = RulesCollection() + rules = RulesCollection(config) for rulesdir in config.rulesdirs: - rules.extend(RulesCollection.create_from_directory(rulesdir)) + rules.extend(RulesCollection.create_from_directory(rulesdir, config)) # Show the rules listing if options.listrules: @@ -91,9 +91,7 @@ def run(): matches = list() checked_files = set() for state in states: - runner = Runner(rules, state, config.tags, - config.skip_list, config.exclude_paths, - config.verbosity, checked_files) + runner = Runner(rules, state, config, checked_files) matches.extend(runner.run()) # Sort the matches diff --git a/saltlint/config.py b/saltlint/config.py index 24f3da8..f3372d3 100644 --- a/saltlint/config.py +++ b/saltlint/config.py @@ -43,7 +43,7 @@ def _parse(self, content): raise SaltLintConfigError("invalid config: {}".format(exc)) # Parse verbosity - self.verbosity = self._options.verbosity + self.verbosity = self._options.verbosity if self._options.verbosity else 0 if 'verbosity' in config: self.verbosity += config['verbosity'] diff --git a/saltlint/linter.py b/saltlint/linter.py index 9b42dff..8c9b1ad 100644 --- a/saltlint/linter.py +++ b/saltlint/linter.py @@ -21,6 +21,9 @@ class SaltLintRule(object): + def __init__(self, config): + self.config = config + def __repr__(self): return self.id + ": " + self.shortdesc @@ -75,8 +78,9 @@ def matchfulltext(self, file, text): class RulesCollection(object): - def __init__(self): + def __init__(self, config): self.rules = [] + self.config = config def register(self, obj): self.rules.append(obj) @@ -128,9 +132,9 @@ def listtags(self): return "\n".join(results) @classmethod - def create_from_directory(cls, rulesdir): - result = cls() - result.rules = saltlint.utils.load_plugins(os.path.expanduser(rulesdir)) + def create_from_directory(cls, rulesdir, config): + result = cls(config) + result.rules = saltlint.utils.load_plugins(os.path.expanduser(rulesdir), config) return result @@ -151,19 +155,24 @@ def __repr__(self): class Runner(object): - def __init__(self, rules, state, tags, skip_list, exclude_paths, - verbosity=0, checked_files=None): + def __init__(self, rules, state, config, checked_files=None): self.rules = rules + self.states = set() - # assume state is directory + # Assume state is directory if os.path.isdir(state): self.states.add((os.path.join(state, 'init.sls'), 'state')) else: self.states.add((state, 'state')) - self.tags = tags - self.skip_list = skip_list - self._update_exclude_paths(exclude_paths) - self.verbosity = verbosity + + # Get configuration options + self.config = config + self.tags = config.tags + self.skip_list = config.skip_list + self.verbosity = config.verbosity + self._update_exclude_paths(config.exclude_paths) + + # Set the checked files if checked_files is None: checked_files = set() self.checked_files = checked_files @@ -194,9 +203,6 @@ def run(self): # remove duplicates from files list files = [value for n, value in enumerate(files) if value not in files[:n]] - # remove duplicates from files list - files = [value for n, value in enumerate(files) if value not in files[:n]] - # remove files that have already been checked files = [x for x in files if x['path'] not in self.checked_files] for file in files: diff --git a/saltlint/utils.py b/saltlint/utils.py index a0d095f..c9a76ab 100644 --- a/saltlint/utils.py +++ b/saltlint/utils.py @@ -7,7 +7,7 @@ import os -def load_plugins(directory): +def load_plugins(directory, config): result = [] fh = None @@ -17,7 +17,7 @@ def load_plugins(directory): try: fh, filename, desc = imp.find_module(pluginname, [directory]) mod = imp.load_module(pluginname, fh, filename, desc) - obj = getattr(mod, pluginname)() + obj = getattr(mod, pluginname)(config) result.append(obj) finally: if fh: From c725c6d85e6de4d5cf894cec5febc102d7bec60d Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Fri, 18 Oct 2019 14:39:04 +0200 Subject: [PATCH 3/6] Fix configuration in tests Fix configuration in tests. The runner requires a SaltLintConfig. Signed-off-by: Roald Nefs --- saltlint/cli.py | 8 ++++---- saltlint/config.py | 26 +++++++++++++++++--------- saltlint/linter.py | 7 ++----- tests/__init__.py | 3 ++- tests/unit/TestFileExtension.py | 5 +++-- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/saltlint/cli.py b/saltlint/cli.py index d9c6bae..4750e05 100644 --- a/saltlint/cli.py +++ b/saltlint/cli.py @@ -8,8 +8,7 @@ import sys from saltlint import formatters, NAME, VERSION -from saltlint.linter import default_rulesdir -from saltlint.config import SaltLintConfig, SaltLintConfigError +from saltlint.config import SaltLintConfig, SaltLintConfigError, default_rulesdir from saltlint.linter import RulesCollection, Runner @@ -58,11 +57,12 @@ def run(): ' is repeatable.', default=[]) parser.add_option('-c', help='Specify configuration file to use. Defaults to ".salt-lint"') - options, args = parser.parse_args(sys.argv[1:]) + (options, args) = parser.parse_args(sys.argv[1:]) # Read, parse and validate the configration + options_dict = vars(options) try: - config = SaltLintConfig(options) + config = SaltLintConfig(options_dict) except SaltLintConfigError as exc: print(exc) return 2 diff --git a/saltlint/config.py b/saltlint/config.py index f3372d3..60bd531 100644 --- a/saltlint/config.py +++ b/saltlint/config.py @@ -3,11 +3,15 @@ import yaml import os +import sys # Import Salt libs from salt.ext import six -from saltlint.linter import default_rulesdir +import saltlint.utils + + +default_rulesdir = os.path.join(os.path.dirname(saltlint.utils.__file__), 'rules') class SaltLintConfigError(Exception): @@ -19,7 +23,8 @@ class SaltLintConfig(object): def __init__(self, options): self._options = options # Configuration file to use, defaults to ".salt-lint". - file = options.c if options.c else '.salt-lint' + config = options.get('c') + file = config if config is not None else '.salt-lint' # Read the file contents if os.path.exists(file): @@ -43,17 +48,17 @@ def _parse(self, content): raise SaltLintConfigError("invalid config: {}".format(exc)) # Parse verbosity - self.verbosity = self._options.verbosity if self._options.verbosity else 0 + self.verbosity = self._options.get('verbosity', 0) if 'verbosity' in config: self.verbosity += config['verbosity'] # Parse exclude paths - self.exclude_paths = self._options.exclude_paths + self.exclude_paths = self._options.get('exclude_paths', []) if 'exclude_paths' in config: self.exclude_paths += config['exclude_paths'] # Parse skip list - skip_list = self._options.skip_list + skip_list = self._options.get('skip_list', []) if 'skip_list' in config: skip_list += config['skip_list'] skip = set() @@ -62,19 +67,19 @@ def _parse(self, content): self.skip_list = frozenset(skip) # Parse tags - self.tags = self._options.tags + self.tags = self._options.get('tags', []) if 'tags' in config: self.tags += config['tags'] if isinstance(self.tags, six.string_types): self.tags = self.tags.split(',') # Parse use default rules - use_default_rules = self._options.use_default_rules + use_default_rules = self._options.get('use_default_rules', False) if 'use_default_rules' in config: use_default_rules = use_default_rules or config['use_default_rules'] # Parse rulesdir - rulesdir = self._options.rulesdir + rulesdir = self._options.get('rulesdir', []) if 'rulesdir' in config: rulesdir += config['rulesdir'] @@ -85,7 +90,10 @@ def _parse(self, content): self.rulesdirs = rulesdir or [default_rulesdir] # Parse colored - self.colored = self._options.colored + self.colored = self._options.get( + 'colored', + hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() + ) def _validate(self): pass diff --git a/saltlint/linter.py b/saltlint/linter.py index 8c9b1ad..12683dd 100644 --- a/saltlint/linter.py +++ b/saltlint/linter.py @@ -16,12 +16,9 @@ import saltlint.utils -default_rulesdir = os.path.join(os.path.dirname(saltlint.utils.__file__), 'rules') - - class SaltLintRule(object): - def __init__(self, config): + def __init__(self, config=None): self.config = config def __repr__(self): @@ -78,7 +75,7 @@ def matchfulltext(self, file, text): class RulesCollection(object): - def __init__(self, config): + def __init__(self, config=None): self.rules = [] self.config = config diff --git a/tests/__init__.py b/tests/__init__.py index 1babc9c..d3d892f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -5,6 +5,7 @@ import tempfile from saltlint.linter import Runner +from saltlint.config import SaltLintConfig class RunFromText(object): @@ -14,7 +15,7 @@ def __init__(self, collection): self.collection = collection def _call_runner(self, path): - runner = Runner(self.collection, path, [], [], []) + runner = Runner(self.collection, path, SaltLintConfig({})) return runner.run() def run_state(self, state_text): diff --git a/tests/unit/TestFileExtension.py b/tests/unit/TestFileExtension.py index 5ac8a43..31bfbf3 100644 --- a/tests/unit/TestFileExtension.py +++ b/tests/unit/TestFileExtension.py @@ -3,6 +3,7 @@ import unittest +from saltlint.config import SaltLintConfig from saltlint.linter import Runner, RulesCollection from saltlint.rules.FileExtensionRule import FileExtensionRule @@ -15,11 +16,11 @@ def setUp(self): def test_file_positive(self): path = 'tests/test-extension-success.sls' - runner = Runner(self.collection, path, [], [], []) + runner = Runner(self.collection, path, SaltLintConfig({})) self.assertEqual([], runner.run()) def test_file_negative(self): path = 'tests/test-extension-failure' - runner = Runner(self.collection, path, [], [], []) + runner = Runner(self.collection, path, SaltLintConfig({})) errors = runner.run() self.assertEqual(1, len(errors)) From 7ec046871849ecc2cd56b5a278e0cb4fa066fd1e Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Sun, 20 Oct 2019 14:38:14 +0200 Subject: [PATCH 4/6] Add config option to ingore rules per file Add configuration option to ignore rules per file, e.g.: ```yaml --- rules: formatting: ignore: | first.sls second.sls 210: ignore: | state/*.sls *.jinja ``` Fixes #48. Signed-off-by: Roald Nefs --- saltlint/config.py | 31 +++++++++++++++++++++++++++---- saltlint/linter.py | 13 +++++++++++-- setup.py | 2 +- tests/__init__.py | 2 +- tests/unit/TestFileExtension.py | 4 ++-- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/saltlint/config.py b/saltlint/config.py index 60bd531..f55c920 100644 --- a/saltlint/config.py +++ b/saltlint/config.py @@ -4,6 +4,7 @@ import yaml import os import sys +import pathspec # Import Salt libs from salt.ext import six @@ -20,7 +21,7 @@ class SaltLintConfigError(Exception): class SaltLintConfig(object): - def __init__(self, options): + def __init__(self, options=dict()): self._options = options # Configuration file to use, defaults to ".salt-lint". config = options.get('c') @@ -35,7 +36,6 @@ def __init__(self, options): # Parse the content of the file as YAML self._parse(content) - self._validate() def _parse(self, content): config = dict() @@ -95,5 +95,28 @@ def _parse(self, content): hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() ) - def _validate(self): - pass + # Parse rule specific configuration, the configration can be listed by + # the rule ID and/or tag. + self.rules = dict() + if 'rules' in config and isinstance(config['rules'], dict): + # Read rule specific configuration from the config dict. + for name, rule in six.iteritems(config['rules']): + # Store the keys as strings. + self.rules[str(name)] = dict() + + if 'ignore' not in rule: + continue + + if not isinstance(rule['ignore'], six.string_types): + raise SaltLintConfigError( + 'invalid config: ignore should contain file patterns') + + # Retrieve the pathspec. + self.rules[str(name)]['ignore'] = pathspec.PathSpec.from_lines( + 'gitwildmatch', rule['ignore'].splitlines()) + + def is_file_ignored(self, filepath, rule): + rule = str(rule) + if rule not in self.rules or 'ignore' not in self.rules[rule]: + return False + return self.rules[rule]['ignore'].match_file(filepath) diff --git a/saltlint/linter.py b/saltlint/linter.py index 12683dd..22290a8 100644 --- a/saltlint/linter.py +++ b/saltlint/linter.py @@ -14,6 +14,7 @@ from salt.ext import six import saltlint.utils +from saltlint.config import SaltLintConfig class SaltLintRule(object): @@ -75,7 +76,7 @@ def matchfulltext(self, file, text): class RulesCollection(object): - def __init__(self, config=None): + def __init__(self, config=SaltLintConfig()): self.rules = [] self.config = config @@ -105,10 +106,18 @@ def run(self, statefile, tags=set(), skip_list=frozenset()): return matches for rule in self.rules: + skip = False if not tags or not set(rule.tags).union([rule.id]).isdisjoint(tags): rule_definition = set(rule.tags) rule_definition.add(rule.id) - if set(rule_definition).isdisjoint(skip_list): + + # Check if the the file is in the rule specific ignore list. + for definition in rule_definition: + if self.config.is_file_ignored(statefile['path'], definition): + skip = True + break + + if not skip and set(rule_definition).isdisjoint(skip_list): matches.extend(rule.matchlines(statefile, text)) matches.extend(rule.matchfulltext(statefile, text)) diff --git a/setup.py b/setup.py index c032cb2..54c6414 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ }, include_package_data=True, python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - install_requires=['salt'], + install_requires=['salt', 'pathspec>=0.6.0'], license=__license__, zip_safe=False, classifiers=[ diff --git a/tests/__init__.py b/tests/__init__.py index d3d892f..d20be8d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -15,7 +15,7 @@ def __init__(self, collection): self.collection = collection def _call_runner(self, path): - runner = Runner(self.collection, path, SaltLintConfig({})) + runner = Runner(self.collection, path, SaltLintConfig()) return runner.run() def run_state(self, state_text): diff --git a/tests/unit/TestFileExtension.py b/tests/unit/TestFileExtension.py index 31bfbf3..8852a37 100644 --- a/tests/unit/TestFileExtension.py +++ b/tests/unit/TestFileExtension.py @@ -16,11 +16,11 @@ def setUp(self): def test_file_positive(self): path = 'tests/test-extension-success.sls' - runner = Runner(self.collection, path, SaltLintConfig({})) + runner = Runner(self.collection, path, SaltLintConfig()) self.assertEqual([], runner.run()) def test_file_negative(self): path = 'tests/test-extension-failure' - runner = Runner(self.collection, path, SaltLintConfig({})) + runner = Runner(self.collection, path, SaltLintConfig()) errors = runner.run() self.assertEqual(1, len(errors)) From ff809942f9c765a0bc28d9fb679e0c72c4a10744 Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Sun, 20 Oct 2019 15:19:57 +0200 Subject: [PATCH 5/6] Add unittests for SaltLintConfig Signed-off-by: Roald Nefs --- tests/unit/TestConfig.py | 75 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/unit/TestConfig.py diff --git a/tests/unit/TestConfig.py b/tests/unit/TestConfig.py new file mode 100644 index 0000000..48094ba --- /dev/null +++ b/tests/unit/TestConfig.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 Roald Nefs + +import unittest +import tempfile + +from saltlint.config import SaltLintConfig + + +SALT_LINT_CONFIG = ''' +--- +exclude_paths: + - exclude_this_file + - exclude_this_directory/ + - exclude/this/sub-directory/ +skip_list: + - 207 + - 208 +tags: + - formatting +verbosity: 1 +rules: + formatting: + ignore: | + tests/test-extension-failure + tests/**/*.jinja +''' + +class TestConfig(unittest.TestCase): + + def setUp(self): + # Open the temporary configuration file and write the contents of + # SALT_LINT_CONFIG to the temporary file. + fp = tempfile.NamedTemporaryFile() + fp.write(SALT_LINT_CONFIG.encode()) + fp.seek(0) + self.fp = fp + + # Specify the temporary file as if it wass passed as a command line + # argument. + self.config = SaltLintConfig(dict(c = self.fp.name)) + + def tearDown(self): + # Close the temporary configuration file. + self.fp.close() + + def test_config(self): + # Check 'verbosity' + self.assertEqual(self.config.verbosity, 1) + + # Check 'skip_list' + self.assertIn('208', self.config.skip_list) + self.assertNotIn('200', self.config.skip_list) + + # Check 'exclude_paths' + self.assertIn('exclude_this_file', self.config.exclude_paths) + self.assertEqual(3, len(self.config.exclude_paths)) + + def test_is_file_ignored(self): + # Check basic ignored rules per file + self.assertTrue( + self.config.is_file_ignored('tests/test-extension-failure', 'formatting') + ) + self.assertFalse( + self.config.is_file_ignored('tests/test-extension-failure', '210') + ) + # Check more complex ignored rules per file using gitwildmatches + self.assertTrue( + self.config.is_file_ignored('tests/other/test.jinja', 'formatting') + ) + self.assertFalse( + self.config.is_file_ignored('test.jinja', 'formatting') + ) + + From 22687e20b2b6ae411ad4a688a16d59faaae5e47e Mon Sep 17 00:00:00 2001 From: Roald Nefs Date: Sun, 20 Oct 2019 16:17:53 +0200 Subject: [PATCH 6/6] Add unittest for saltlint.cli Signed-off-by: Roald Nefs --- saltlint/cli.py | 8 ++++---- tests/unit/TestRunner.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 tests/unit/TestRunner.py diff --git a/saltlint/cli.py b/saltlint/cli.py index 4750e05..8167e4d 100644 --- a/saltlint/cli.py +++ b/saltlint/cli.py @@ -12,7 +12,7 @@ from saltlint.linter import RulesCollection, Runner -def run(): +def run(args=None): formatter = formatters.Formatter() parser = optparse.OptionParser("%prog [options] init.sls [state ...]", @@ -57,7 +57,7 @@ def run(): ' is repeatable.', default=[]) parser.add_option('-c', help='Specify configuration file to use. Defaults to ".salt-lint"') - (options, args) = parser.parse_args(sys.argv[1:]) + (options, parsed_args) = parser.parse_args(args if args is not None else sys.argv[1:]) # Read, parse and validate the configration options_dict = vars(options) @@ -68,7 +68,7 @@ def run(): return 2 # Show a help message on the screen - if len(args) == 0 and not (options.listrules or options.listtags): + if len(parsed_args) == 0 and not (options.listrules or options.listtags): parser.print_help(file=sys.stderr) return 1 @@ -87,7 +87,7 @@ def run(): print(rules.listtags()) return 0 - states = set(args) + states = set(parsed_args) matches = list() checked_files = set() for state in states: diff --git a/tests/unit/TestRunner.py b/tests/unit/TestRunner.py new file mode 100644 index 0000000..8e8cad5 --- /dev/null +++ b/tests/unit/TestRunner.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019 Roald Nefs + +import unittest + +from saltlint.cli import run + +class TestRunner(unittest.TestCase): + + def test_runner_with_matches(self): + # Check the integer returned by saltlint.cli.run(), when matches are + # expected. + args = ['tests/test-extension-failure'] + self.assertEqual(run(args), 2) + + def test_runner_without_matches(self): + # Check the integer returned by saltlint.cli.run(), when no matches are + # expected. + args = ['tests/test-extension-success.sls'] + self.assertEqual(run(args), 0) \ No newline at end of file