From 327f92e47262db90efb77da7f0848d7d17d75006 Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Fri, 3 Jun 2022 21:53:52 -0700 Subject: [PATCH] tests: Increase test coverage - Add a `temp_workspace` context manager to simplify writing new tests. - Add `# pragma: no cover` to unit test code paths used for skipping tests. These code paths are only covered when tests are skipped. That makes it impractical to reach full code coverage on the unit test code. Having full coverage of unit tests is helpful for identifying unused tests. - Test the `octal-values` rule with a custom tag. - Test the cli `-d` option with the `default` config. - Test support for the `XDG_CONFIG_HOME` env var. - Test warning message output. - Test support for `.yamllint.yml` config files. - Test support for `.yamllint.yaml` config files. - Test error handling of a rule with a non-enable|disable|dict value. - Test error handling of `ignore` with a non-pattern value. - Test error handling of a rule `ignore` with a non-pattern value. - Test error handling of `locale` with a non-string value. - Test error handling of `yaml-files` with a non-list value. - Test extending config containing `ignore`. - Test `LintProblem.__repr__` without a rule. - Test `LintProblem.__repr__` with a rule. --- tests/common.py | 16 ++++++++ tests/rules/test_key_ordering.py | 4 +- tests/rules/test_octal_values.py | 1 + tests/test_cli.py | 66 ++++++++++++++++++++++++++++++-- tests/test_config.py | 45 ++++++++++++++++++++++ tests/test_linter.py | 10 +++++ 6 files changed, 137 insertions(+), 5 deletions(-) diff --git a/tests/common.py b/tests/common.py index 8cbb341..65af63b 100644 --- a/tests/common.py +++ b/tests/common.py @@ -13,7 +13,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import contextlib import os +import shutil import tempfile import unittest @@ -68,3 +70,17 @@ def build_temp_workspace(files): f.write(content) return tempdir + + +@contextlib.contextmanager +def temp_workspace(files): + """Provide a temporary workspace that is automatically cleaned up.""" + backup_wd = os.getcwd() + wd = build_temp_workspace(files) + + try: + os.chdir(wd) + yield + finally: + os.chdir(backup_wd) + shutil.rmtree(wd) diff --git a/tests/rules/test_key_ordering.py b/tests/rules/test_key_ordering.py index f71af02..7d17603 100644 --- a/tests/rules/test_key_ordering.py +++ b/tests/rules/test_key_ordering.py @@ -116,7 +116,7 @@ class KeyOrderingTestCase(RuleTestCase): self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None)) try: locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') - except locale.Error: + except locale.Error: # pragma: no cover self.skipTest('locale en_US.UTF-8 not available') conf = ('key-ordering: enable') self.check('---\n' @@ -135,7 +135,7 @@ class KeyOrderingTestCase(RuleTestCase): self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None)) try: locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') - except locale.Error: + except locale.Error: # pragma: no cover self.skipTest('locale en_US.UTF-8 not available') conf = ('key-ordering: enable') self.check('---\n' diff --git a/tests/rules/test_octal_values.py b/tests/rules/test_octal_values.py index 8e8f5e4..be5b039 100644 --- a/tests/rules/test_octal_values.py +++ b/tests/rules/test_octal_values.py @@ -32,6 +32,7 @@ class OctalValuesTestCase(RuleTestCase): ' forbid-explicit-octal: false\n' 'new-line-at-end-of-file: disable\n' 'document-start: disable\n') + self.check('after-tag: !custom_tag 010', conf) self.check('user-city: 010', conf, problem=(1, 15)) self.check('user-city: abc', conf) self.check('user-city: 010,0571', conf) diff --git a/tests/test_cli.py b/tests/test_cli.py index 8bce87a..3ac5b09 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,7 +23,7 @@ import sys import tempfile import unittest -from tests.common import build_temp_workspace +from tests.common import build_temp_workspace, temp_workspace from yamllint import cli from yamllint import config @@ -58,7 +58,7 @@ def utf8_available(): locale.setlocale(locale.LC_ALL, 'C.UTF-8') locale.setlocale(locale.LC_ALL, (None, None)) return True - except locale.Error: + except locale.Error: # pragma: no cover return False @@ -281,6 +281,16 @@ class CommandLineTestCase(unittest.TestCase): self.assertEqual(ctx.stdout, '') self.assertRegex(ctx.stderr, r'^invalid config: not a dict') + def test_run_with_implicit_extends_config(self): + path = os.path.join(self.wd, 'warn.yaml') + + with RunContext(self) as ctx: + cli.run(('-d', 'default', '-f', 'parsable', path)) + expected_out = ('%s:1:1: [warning] missing document start "---" ' + '(document-start)\n' % path) + self.assertEqual( + (ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, '')) + def test_run_with_config_file(self): with open(os.path.join(self.wd, 'config'), 'w') as f: f.write('rules: {trailing-spaces: disable}') @@ -320,6 +330,19 @@ class CommandLineTestCase(unittest.TestCase): cli.run((os.path.join(self.wd, 'a.yaml'), )) self.assertEqual(ctx.returncode, 1) + def test_run_with_user_xdg_config_home_in_env(self): + self.addCleanup(os.environ.__delitem__, 'XDG_CONFIG_HOME') + + with tempfile.TemporaryDirectory('w') as d: + os.environ['XDG_CONFIG_HOME'] = d + os.makedirs(os.path.join(d, 'yamllint')) + with open(os.path.join(d, 'yamllint', 'config'), 'w') as f: + f.write('extends: relaxed') + with RunContext(self) as ctx: + cli.run(('-f', 'parsable', os.path.join(self.wd, 'warn.yaml'))) + + self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', '')) + def test_run_with_user_yamllint_config_file_in_env(self): self.addCleanup(os.environ.__delitem__, 'YAMLLINT_CONFIG_FILE') @@ -345,7 +368,7 @@ class CommandLineTestCase(unittest.TestCase): # as the first two runs don't use setlocale() try: locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') - except locale.Error: + except locale.Error: # pragma: no cover self.skipTest('locale en_US.UTF-8 not available') locale.setlocale(locale.LC_ALL, (None, None)) @@ -546,6 +569,19 @@ class CommandLineTestCase(unittest.TestCase): self.assertEqual( (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) + def test_run_format_colored_warning(self): + path = os.path.join(self.wd, 'warn.yaml') + + with RunContext(self) as ctx: + cli.run((path, '--format', 'colored')) + expected_out = ( + '\033[4m%s\033[0m\n' + ' \033[2m1:1\033[0m \033[33mwarning\033[0m ' + 'missing document start "---" \033[2m(document-start)\033[0m\n' + '\n' % path) + self.assertEqual( + (ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, '')) + def test_run_format_github(self): path = os.path.join(self.wd, 'a.yaml') @@ -641,3 +677,27 @@ class CommandLineTestCase(unittest.TestCase): '\n' % path) self.assertEqual( (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) + + +class CommandLineConfigTestCase(unittest.TestCase): + def test_config_file(self): + workspace = {'a.yml': 'hello: world\n'} + conf = ('---\n' + 'extends: relaxed\n') + + for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'): + with self.subTest(conf_file): + with temp_workspace(workspace): + with RunContext(self) as ctx: + cli.run(('-f', 'parsable', '.')) + + self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), + (0, './a.yml:1:1: [warning] missing document ' + 'start "---" (document-start)\n', '')) + + with temp_workspace({**workspace, **{conf_file: conf}}): + with RunContext(self) as ctx: + cli.run(('-f', 'parsable', '.')) + + self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), + (0, '', '')) diff --git a/tests/test_config.py b/tests/test_config.py index 105ae18..3618a30 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -189,6 +189,41 @@ class SimpleConfigTestCase(unittest.TestCase): config.validate_rule_conf, Rule, {'multiple': ['item4']}) + def test_invalid_rule(self): + with self.assertRaisesRegex( + config.YamlLintConfigError, + 'invalid config: rule "colons": should be either ' + '"enable", "disable" or a dict'): + config.YamlLintConfig('rules:\n' + ' colons: invalid\n') + + def test_invalid_ignore(self): + with self.assertRaisesRegex( + config.YamlLintConfigError, + 'invalid config: ignore should contain file patterns'): + config.YamlLintConfig('ignore: yes\n') + + def test_invalid_rule_ignore(self): + with self.assertRaisesRegex( + config.YamlLintConfigError, + 'invalid config: ignore should contain file patterns'): + config.YamlLintConfig('rules:\n' + ' colons:\n' + ' ignore: yes\n') + + def test_invalid_locale(self): + with self.assertRaisesRegex( + config.YamlLintConfigError, + 'invalid config: locale should be a string'): + config.YamlLintConfig('locale: yes\n') + + def test_invalid_yaml_files(self): + with self.assertRaisesRegex( + config.YamlLintConfigError, + 'invalid config: yaml-files should be a list of file ' + 'patterns'): + config.YamlLintConfig('yaml-files: yes\n') + class ExtendedConfigTestCase(unittest.TestCase): def test_extend_on_object(self): @@ -333,6 +368,16 @@ class ExtendedConfigTestCase(unittest.TestCase): self.assertEqual(c.rules['colons']['max-spaces-before'], 0) self.assertEqual(c.rules['colons']['max-spaces-after'], 1) + def test_extended_ignore(self): + with tempfile.NamedTemporaryFile('w') as f: + f.write('ignore: |\n' + ' *.template.yaml\n') + f.flush() + c = config.YamlLintConfig('extends: ' + f.name + '\n') + + self.assertEqual(c.ignore.match_file('test.template.yaml'), True) + self.assertEqual(c.ignore.match_file('test.yaml'), False) + class ExtendedLibraryConfigTestCase(unittest.TestCase): def test_extend_config_disable_rule(self): diff --git a/tests/test_linter.py b/tests/test_linter.py index 51714fe..686068b 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -54,3 +54,13 @@ class LinterTestCase(unittest.TestCase): u'# الأَبْجَدِيَّة العَرَبِيَّة\n') linter.run(s, self.fake_config()) linter.run(s.encode('utf-8'), self.fake_config()) + + def test_linter_problem_repr_without_rule(self): + problem = linter.LintProblem(1, 2, 'problem') + + self.assertEqual(str(problem), '1:2: problem') + + def test_linter_problem_repr_with_rule(self): + problem = linter.LintProblem(1, 2, 'problem', 'rule-id') + + self.assertEqual(str(problem), '1:2: problem (rule-id)')