diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 19:07:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 19:07:17 +0000 |
commit | 744321bc11f7b1552d22a38848c2966befc98fef (patch) | |
tree | 10abfe9e2e150ae3cd3d5c4707fb90493a39d0d8 /tests | |
parent | Adding debian version 1.33.0-1. (diff) | |
download | yamllint-744321bc11f7b1552d22a38848c2966befc98fef.tar.xz yamllint-744321bc11f7b1552d22a38848c2966befc98fef.zip |
Merging upstream version 1.35.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/__init__.py | 1 | ||||
-rw-r--r-- | tests/common.py | 35 | ||||
-rw-r--r-- | tests/rules/test_indentation.py | 8 | ||||
-rw-r--r-- | tests/rules/test_key_duplicates.py | 54 | ||||
-rw-r--r-- | tests/rules/test_quoted_strings.py | 786 | ||||
-rw-r--r-- | tests/rules/test_truthy.py | 88 | ||||
-rw-r--r-- | tests/test_cli.py | 88 | ||||
-rw-r--r-- | tests/test_config.py | 73 | ||||
-rw-r--r-- | tests/test_linter.py | 2 | ||||
-rw-r--r-- | tests/test_module.py | 3 | ||||
-rw-r--r-- | tests/test_parser.py | 11 | ||||
-rw-r--r-- | tests/test_spec_examples.py | 2 |
12 files changed, 1076 insertions, 75 deletions
diff --git a/tests/__init__.py b/tests/__init__.py index da1cd75..d8c46fc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -15,5 +15,4 @@ import locale - locale.setlocale(locale.LC_ALL, 'C') diff --git a/tests/common.py b/tests/common.py index 65af63b..29dcfb9 100644 --- a/tests/common.py +++ b/tests/common.py @@ -14,15 +14,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import contextlib +from io import StringIO import os import shutil +import sys import tempfile import unittest import yaml -from yamllint.config import YamlLintConfig from yamllint import linter +from yamllint.config import YamlLintConfig class RuleTestCase(unittest.TestCase): @@ -54,6 +56,33 @@ class RuleTestCase(unittest.TestCase): self.assertEqual(real_problems, expected_problems) +class RunContext: + """Context manager for ``cli.run()`` to capture exit code and streams.""" + + def __init__(self, case): + self.stdout = self.stderr = None + self._raises_ctx = case.assertRaises(SystemExit) + + def __enter__(self): + self._raises_ctx.__enter__() + self.old_sys_stdout = sys.stdout + self.old_sys_stderr = sys.stderr + sys.stdout = self.outstream = StringIO() + sys.stderr = self.errstream = StringIO() + return self + + def __exit__(self, *exc_info): + self.stdout = self.outstream.getvalue() + self.stderr = self.errstream.getvalue() + sys.stdout = self.old_sys_stdout + sys.stderr = self.old_sys_stderr + return self._raises_ctx.__exit__(*exc_info) + + @property + def returncode(self): + return self._raises_ctx.exception.code + + def build_temp_workspace(files): tempdir = tempfile.mkdtemp(prefix='yamllint-tests-') @@ -62,8 +91,10 @@ def build_temp_workspace(files): if not os.path.exists(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) - if type(content) is list: + if isinstance(content, list): os.mkdir(path) + elif isinstance(content, str) and content.startswith('symlink://'): + os.symlink(content[10:], path) else: mode = 'wb' if isinstance(content, bytes) else 'w' with open(path, mode) as f: diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py index 1c6eddb..b08ada7 100644 --- a/tests/rules/test_indentation.py +++ b/tests/rules/test_indentation.py @@ -15,7 +15,7 @@ from tests.common import RuleTestCase -from yamllint.parser import token_or_comment_generator, Comment +from yamllint.parser import Comment, token_or_comment_generator from yamllint.rules.indentation import check @@ -50,8 +50,8 @@ class IndentationStackTestCase(RuleTestCase): .replace('Mapping', 'Map')) if token_type in ('StreamStart', 'StreamEnd'): continue - output += '{:>9} {}\n'.format(token_type, - self.format_stack(context['stack'])) + stack = self.format_stack(context['stack']) + output += f'{token_type:>9} {stack}\n' return output def test_simple_mapping(self): @@ -192,7 +192,7 @@ class IndentationStackTestCase(RuleTestCase): 'BMapStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n' ' Key B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' - ' Value B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:4\n' # noqa + ' Value B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:4\n' # noqa: E501 ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n' ' BEnd B_MAP:0\n' # missing BEnd here diff --git a/tests/rules/test_key_duplicates.py b/tests/rules/test_key_duplicates.py index 3f8a9e6..c1704e6 100644 --- a/tests/rules/test_key_duplicates.py +++ b/tests/rules/test_key_duplicates.py @@ -179,3 +179,57 @@ class KeyDuplicatesTestCase(RuleTestCase): '[\n' ' flow: sequence, with, key: value, mappings\n' ']\n', conf) + + def test_forbid_duplicated_merge_keys(self): + conf = 'key-duplicates: {forbid-duplicated-merge-keys: true}' + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' <<: *anchor_one\n' + ' <<: *anchor_two\n', conf, problem=(9, 3)) + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_three: &anchor_three\n' + ' two: three\n' + 'anchor_reference:\n' + ' <<: *anchor_one\n' + ' <<: *anchor_two\n' + ' <<: *anchor_three\n', conf, + problem1=(11, 3), problem2=(12, 3)) + self.check('---\n' + 'Multiple Merge Keys are NOT OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' a: 1\n' + ' <<: *anchor_one\n' + ' b: 2\n' + ' <<: *anchor_two\n', conf, problem=(11, 3)) + self.check('---\n' + 'Single Merge Key is OK:\n' + 'anchor_one: &anchor_one\n' + ' one: one\n' + 'anchor_two: &anchor_two\n' + ' two: two\n' + 'anchor_reference:\n' + ' <<: [*anchor_one, *anchor_two]\n', conf) + self.check('---\n' + 'Duplicate keys without Merge Keys:\n' + ' key: a\n' + ' otherkey: b\n' + ' key: c\n', conf, + problem=(5, 3)) + self.check('---\n' + 'No Merge Keys:\n' + ' key: a\n' + ' otherkey: b\n', conf) diff --git a/tests/rules/test_quoted_strings.py b/tests/rules/test_quoted_strings.py index 543cc0d..1bcb6f8 100644 --- a/tests/rules/test_quoted_strings.py +++ b/tests/rules/test_quoted_strings.py @@ -18,7 +18,7 @@ from tests.common import RuleTestCase from yamllint import config -class QuotedTestCase(RuleTestCase): +class QuotedValuesTestCase(RuleTestCase): rule_id = 'quoted-strings' def test_disabled(self): @@ -57,9 +57,14 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' # fails ' - "foo"\n' 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails - conf, problem1=(4, 10), problem2=(17, 5), - problem3=(19, 12), problem4=(20, 15)) + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', + conf, problem1=(4, 10), problem2=(17, 5), problem3=(19, 12), + problem4=(20, 15), problem5=(21, 13), problem6=(22, 16), + problem7=(23, 19), problem8=(23, 28), problem9=(24, 20)) self.check('---\n' 'multiline string 1: |\n' ' line 1\n' @@ -97,11 +102,19 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' # fails ' - "foo"\n' # fails 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf, problem1=(4, 10), problem2=(5, 10), problem3=(6, 10), problem4=(7, 10), problem5=(17, 5), problem6=(18, 5), problem7=(19, 12), problem8=(19, 17), problem9=(20, 15), - problem10=(20, 23)) + problem10=(20, 23), problem11=(21, 13), problem12=(21, 18), + problem13=(21, 29), problem14=(21, 41), problem15=(22, 16), + problem16=(22, 24), problem17=(23, 19), problem18=(23, 28), + problem19=(23, 33), problem20=(24, 20), problem21=(24, 30), + problem22=(24, 45)) self.check('---\n' 'multiline string 1: |\n' ' line 1\n' @@ -139,9 +152,15 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' # fails ' - "foo"\n' 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5), - problem4=(19, 12), problem5=(20, 15)) + problem4=(19, 12), problem5=(20, 15), problem6=(21, 13), + problem7=(22, 16), problem8=(23, 19), problem9=(23, 28), + problem10=(24, 20)) self.check('---\n' 'multiline string 1: |\n' ' line 1\n' @@ -179,7 +198,11 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' # fails ' - "foo"\n' 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf) self.check('---\n' 'multiline string 1: |\n' @@ -218,9 +241,16 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' # fails ' - "foo"\n' 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10), - problem4=(18, 5), problem5=(19, 17), problem6=(20, 23)) + problem4=(18, 5), problem5=(19, 17), problem6=(20, 23), + problem7=(21, 18), problem8=(21, 29), problem9=(21, 41), + problem10=(22, 24), problem11=(23, 33), problem12=(24, 30), + problem13=(24, 45)) self.check('---\n' 'multiline string 1: |\n' ' line 1\n' @@ -258,7 +288,11 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' ' - "foo"\n' # fails 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n' + 'flow-map2: {a: foo, b: "foo,bar"}\n' + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5), problem4=(19, 17), problem5=(20, 23)) self.check('---\n' @@ -299,10 +333,15 @@ class QuotedTestCase(RuleTestCase): ' - foo\n' ' - "foo"\n' # fails 'flow-seq: [foo, "foo"]\n' # fails - 'flow-map: {a: foo, b: "foo"}\n', # fails + 'flow-map: {a: foo, b: "foo"}\n' # fails + 'flow-seq2: [foo, "foo,bar"]\n' # fails + 'flow-map2: {a: foo, b: "foo,bar"}\n' # fails + 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n' + 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n', conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10), problem4=(8, 10), problem5=(18, 5), problem6=(19, 17), - problem7=(20, 23)) + problem7=(20, 23), problem8=(21, 18), problem9=(22, 24), + problem10=(23, 33), problem11=(24, 30), problem12=(24, 45)) self.check('---\n' 'multiline string 1: |\n' ' line 1\n' @@ -556,3 +595,722 @@ class QuotedTestCase(RuleTestCase): "foo1: '[barbaz]'\n" "foo2: '[bar\"baz]'\n", conf) + + +class QuotedKeysTestCase(RuleTestCase): + rule_id = 'quoted-strings' + + def test_disabled(self): + conf_disabled = "quoted-strings: {}" + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf_disabled) + + def test_default(self): + # Default configuration, but with check-keys + conf_default = ("quoted-strings:\n" + " check-keys: true\n") + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf_default, problem1=(4, 1), + problem3=(20, 3), problem4=(23, 2), problem5=(33, 3)) + + def test_quote_type_any(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: any\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(4, 1), problem2=(20, 3), problem3=(23, 2), + problem4=(33, 3)) + + def test_quote_type_single(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(4, 1), problem2=(5, 1), problem3=(6, 1), + problem4=(7, 1), problem5=(20, 3), problem6=(21, 3), + problem7=(23, 2), problem8=(23, 10), problem9=(33, 3), + problem10=(37, 3)) + + def test_quote_type_double(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(4, 1), problem2=(8, 1), problem3=(20, 3), + problem4=(23, 2), problem5=(33, 3)) + + def test_any_quotes_not_required(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: any\n' + ' required: false\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf) + + def test_single_quotes_not_required(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: false\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(5, 1), problem2=(6, 1), problem3=(7, 1), + problem4=(21, 3), problem5=(23, 10), problem6=(37, 3)) + + def test_only_when_needed(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: only-when-needed\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(5, 1), problem2=(8, 1), problem3=(21, 3), + problem4=(23, 10), problem5=(37, 3)) + + def test_only_when_needed_single_quotes(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: only-when-needed\n') + + key_strings = ('---\n' + 'true: 2\n' + '123: 3\n' + 'foo1: 4\n' + '"foo2": 5\n' + '"false": 6\n' + '"234": 7\n' + '\'bar\': 8\n' + '!!str generic_string: 9\n' + '!!str 456: 10\n' + '!!str "quoted_generic_string": 11\n' + '!!binary binstring: 12\n' + '!!int int_string: 13\n' + '!!bool bool_string: 14\n' + '!!bool "quoted_bool_string": 15\n' + # Sequences and mappings + '? - 16\n' + ' - 17\n' + ': 18\n' + '[119, 219]: 19\n' + '? a: 20\n' + ' "b": 21\n' + ': 22\n' + '{a: 123, "b": 223}: 23\n' + # Multiline strings + '? |\n' + ' line 1\n' + ' line 2\n' + ': 27\n' + '? >\n' + ' line 1\n' + ' line 2\n' + ': 31\n' + '?\n' + ' line 1\n' + ' line 2\n' + ': 35\n' + '?\n' + ' "line 1\\\n' + ' line 2"\n' + ': 39\n') + self.check(key_strings, conf, + problem1=(5, 1), problem2=(6, 1), problem3=(7, 1), + problem4=(8, 1), problem5=(21, 3), problem6=(23, 10), + problem7=(37, 3)) + + def test_only_when_needed_corner_cases(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: only-when-needed\n') + + self.check('---\n' + '"": 2\n' + '"- item": 3\n' + '"key: value": 4\n' + '"%H:%M:%S": 5\n' + '"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n' + '\'"quoted"\': 7\n' + '"\'foo\' == \'bar\'": 8\n' + '"\'Mac\' in ansible_facts.product_name": 9\n' + '\'foo # bar\': 10\n', + conf) + self.check('---\n' + '"": 2\n' + '"- item": 3\n' + '"key: value": 4\n' + '"%H:%M:%S": 5\n' + '"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n' + '\'"quoted"\': 7\n' + '"\'foo\' == \'bar\'": 8\n' + '"\'Mac\' in ansible_facts.product_name": 9\n', + conf) + + self.check('---\n' + '---: 2\n' + '"----": 3\n' # fails + '---------: 4\n' + '"----------": 5\n' # fails + ':wq: 6\n' + '":cw": 7\n', # fails + conf, problem1=(3, 1), problem2=(5, 1), problem3=(7, 1)) + + def test_only_when_needed_extras(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: true\n' + ' extra-allowed: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: true\n' + ' extra-required: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: false\n' + ' extra-allowed: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: true\n') + self.check('---\n' + '123: 2\n' + '"234": 3\n' + 'localhost: 4\n' # fails + '"host.local": 5\n' + 'http://localhost: 6\n' # fails + '"http://host.local": 7\n' + 'ftp://localhost: 8\n' # fails + '"ftp://host.local": 9\n', + conf, problem1=(4, 1), problem2=(6, 1), problem3=(8, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: only-when-needed\n' + ' extra-allowed: [^ftp://]\n' + ' extra-required: [^http://]\n') + self.check('---\n' + '123: 2\n' + '"234": 3\n' + 'localhost: 4\n' + '"host.local": 5\n' # fails + 'http://localhost: 6\n' # fails + '"http://host.local": 7\n' + 'ftp://localhost: 8\n' + '"ftp://host.local": 9\n', + conf, problem1=(5, 1), problem2=(6, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: false\n' + ' extra-required: [^http://, ^ftp://]\n') + self.check('---\n' + '123: 2\n' + '"234": 3\n' + 'localhost: 4\n' + '"host.local": 5\n' + 'http://localhost: 6\n' # fails + '"http://host.local": 7\n' + 'ftp://localhost: 8\n' # fails + '"ftp://host.local": 9\n', + conf, problem1=(6, 1), problem2=(8, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: only-when-needed\n' + ' extra-allowed: [^ftp://, ";$", " "]\n') + self.check('---\n' + 'localhost: 2\n' + '"host.local": 3\n' # fails + 'ftp://localhost: 4\n' + '"ftp://host.local": 5\n' + 'i=i+1: 6\n' + '"i=i+2": 7\n' # fails + 'i=i+3;: 8\n' + '"i=i+4;": 9\n' + 'foo1: 10\n' + '"foo2": 11\n' # fails + 'foo bar1: 12\n' + '"foo bar2": 13\n', + conf, problem1=(3, 1), problem2=(7, 1), problem3=(11, 1)) + + def test_octal_values(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' required: true\n') + + self.check('---\n' + '100: 2\n' + '0100: 3\n' + '0o100: 4\n' + '777: 5\n' + '0777: 6\n' + '0o777: 7\n' + '800: 8\n' + '0800: 9\n' # fails + '0o800: 10\n' # fails + '"0900": 11\n' + '"0o900": 12\n', + conf, + problem1=(9, 1), problem2=(10, 1)) + + def test_allow_quoted_quotes(self): + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: false\n' + ' allow-quoted-quotes: false\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: false\n' + ' allow-quoted-quotes: true\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: true\n' + ' allow-quoted-quotes: false\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: true\n' + ' allow-quoted-quotes: true\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: only-when-needed\n' + ' allow-quoted-quotes: false\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: single\n' + ' required: only-when-needed\n' + ' allow-quoted-quotes: true\n') + self.check('---\n' + '"[barbaz]": 2\n' # fails + '"[bar\'baz]": 3\n', + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: false\n' + ' allow-quoted-quotes: false\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: false\n' + ' allow-quoted-quotes: true\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: true\n' + ' allow-quoted-quotes: false\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: true\n' + ' allow-quoted-quotes: true\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: only-when-needed\n' + ' allow-quoted-quotes: false\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", # fails + conf, problem1=(2, 1), problem2=(3, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: double\n' + ' required: only-when-needed\n' + ' allow-quoted-quotes: true\n') + self.check("---\n" + "'[barbaz]': 2\n" # fails + "'[bar\"baz]': 3\n", + conf, problem1=(2, 1)) + + conf = ('quoted-strings:\n' + ' check-keys: true\n' + ' quote-type: any\n') + self.check("---\n" + "'[barbaz]': 2\n" + "'[bar\"baz]': 3\n", + conf) diff --git a/tests/rules/test_truthy.py b/tests/rules/test_truthy.py index c8c8b7a..e485d07 100644 --- a/tests/rules/test_truthy.py +++ b/tests/rules/test_truthy.py @@ -27,7 +27,8 @@ class TruthyTestCase(RuleTestCase): 'True: 1\n', conf) def test_enabled(self): - conf = 'truthy: enable\n' + conf = ('truthy: enable\n' + 'document-start: disable\n') self.check('---\n' '1: True\n' 'True: 1\n', @@ -35,7 +36,8 @@ class TruthyTestCase(RuleTestCase): self.check('---\n' '1: "True"\n' '"True": 1\n', conf) - self.check('---\n' + self.check('%YAML 1.1\n' + '---\n' '[\n' ' true, false,\n' ' "false", "FALSE",\n' @@ -44,9 +46,47 @@ class TruthyTestCase(RuleTestCase): ' on, OFF,\n' ' NO, Yes\n' ']\n', conf, - problem1=(6, 3), problem2=(6, 9), - problem3=(7, 3), problem4=(7, 7), - problem5=(8, 3), problem6=(8, 7)) + problem1=(7, 3), problem2=(7, 9), + problem3=(8, 3), problem4=(8, 7), + problem5=(9, 3), problem6=(9, 7)) + self.check('y: 1\n' + 'yes: 2\n' + 'on: 3\n' + 'true: 4\n' + 'True: 5\n' + '...\n' + '%YAML 1.2\n' + '---\n' + 'y: 1\n' + 'yes: 2\n' + 'on: 3\n' + 'true: 4\n' + 'True: 5\n' + '...\n' + '%YAML 1.1\n' + '---\n' + 'y: 1\n' + 'yes: 2\n' + 'on: 3\n' + 'true: 4\n' + 'True: 5\n' + '---\n' + 'y: 1\n' + 'yes: 2\n' + 'on: 3\n' + 'true: 4\n' + 'True: 5\n', + conf, + problem1=(2, 1), + problem2=(3, 1), + problem3=(5, 1), + problem4=(13, 1), + problem5=(18, 1), + problem6=(19, 1), + problem7=(21, 1), + problem8=(24, 1), + problem9=(25, 1), + problem10=(27, 1)) def test_different_allowed_values(self): conf = ('truthy:\n' @@ -56,15 +96,16 @@ class TruthyTestCase(RuleTestCase): 'key2: yes\n' 'key3: bar\n' 'key4: no\n', conf) - self.check('---\n' + self.check('%YAML 1.1\n' + '---\n' 'key1: true\n' 'key2: Yes\n' 'key3: false\n' 'key4: no\n' 'key5: yes\n', conf, - problem1=(2, 7), problem2=(3, 7), - problem3=(4, 7)) + problem1=(3, 7), problem2=(4, 7), + problem3=(5, 7)) def test_combined_allowed_values(self): conf = ('truthy:\n' @@ -81,6 +122,22 @@ class TruthyTestCase(RuleTestCase): 'key4: no\n' 'key5: yes\n', conf, problem1=(3, 7)) + self.check('%YAML 1.1\n' + '---\n' + 'key1: true\n' + 'key2: Yes\n' + 'key3: false\n' + 'key4: no\n' + 'key5: yes\n', + conf, problem1=(4, 7)) + self.check('%YAML 1.2\n' + '---\n' + 'key1: true\n' + 'key2: Yes\n' + 'key3: false\n' + 'key4: no\n' + 'key5: yes\n', + conf) def test_no_allowed_values(self): conf = ('truthy:\n' @@ -95,6 +152,21 @@ class TruthyTestCase(RuleTestCase): 'key4: no\n', conf, problem1=(2, 7), problem2=(3, 7), problem3=(4, 7), problem4=(5, 7)) + self.check('%YAML 1.1\n' + '---\n' + 'key1: true\n' + 'key2: yes\n' + 'key3: false\n' + 'key4: no\n', conf, + problem1=(3, 7), problem2=(4, 7), + problem3=(5, 7), problem4=(6, 7)) + self.check('%YAML 1.2\n' + '---\n' + 'key1: true\n' + 'key2: yes\n' + 'key3: false\n' + 'key4: no\n', conf, + problem1=(3, 7), problem2=(5, 7)) def test_explicit_types(self): conf = 'truthy: enable\n' diff --git a/tests/test_cli.py b/tests/test_cli.py index 444f2f9..e0ae0fe 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13,7 +13,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from io import StringIO import fcntl import locale import os @@ -22,34 +21,11 @@ import shutil import sys import tempfile import unittest +from io import StringIO -from tests.common import build_temp_workspace, temp_workspace - -from yamllint import cli -from yamllint import config - - -class RunContext: - """Context manager for ``cli.run()`` to capture exit code and streams.""" - - def __init__(self, case): - self.stdout = self.stderr = None - self._raises_ctx = case.assertRaises(SystemExit) - - def __enter__(self): - self._raises_ctx.__enter__() - sys.stdout = self.outstream = StringIO() - sys.stderr = self.errstream = StringIO() - return self - - def __exit__(self, *exc_info): - self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__ - self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__ - return self._raises_ctx.__exit__(*exc_info) +from tests.common import build_temp_workspace, RunContext, temp_workspace - @property - def returncode(self): - return self._raises_ctx.exception.code +from yamllint import cli, config # Check system's UTF-8 availability @@ -57,9 +33,30 @@ def utf8_available(): try: locale.setlocale(locale.LC_ALL, 'C.UTF-8') locale.setlocale(locale.LC_ALL, (None, None)) - return True except locale.Error: # pragma: no cover return False + else: + return True + + +def setUpModule(): + # yamllint uses these environment variables to find a config file. + env_vars_that_could_interfere = ( + 'YAMLLINT_CONFIG_FILE', + 'XDG_CONFIG_HOME', + # These variables are used to determine where the user’s home + # directory is. See + # https://docs.python.org/3/library/os.path.html#os.path.expanduser + 'HOME', + 'USERPROFILE', + 'HOMEPATH', + 'HOMEDRIVE' + ) + for name in env_vars_that_could_interfere: + try: + del os.environ[name] + except KeyError: + pass class CommandLineTestCase(unittest.TestCase): @@ -88,6 +85,9 @@ class CommandLineTestCase(unittest.TestCase): 'key: other value\n', # empty dir 'empty-dir': [], + # symbolic link + 'symlinks/file-without-yaml-extension': '42\n', + 'symlinks/link.yaml': 'symlink://file-without-yaml-extension', # non-YAML file 'no-yaml.json': '---\n' 'key: value\n', @@ -116,8 +116,6 @@ class CommandLineTestCase(unittest.TestCase): shutil.rmtree(cls.wd) - @unittest.skipIf(not utf8_available() and sys.version_info < (3, 7), - 'UTF-8 paths not supported') def test_find_files_recursively(self): conf = config.YamlLintConfig('extends: default') self.assertEqual( @@ -130,6 +128,7 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 'sub/directory.yaml/empty.yml'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')], ) @@ -167,6 +166,7 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 'en.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')] ) @@ -204,6 +204,8 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 'sub/directory.yaml/empty.yml'), os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/file-without-yaml-extension'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')] ) @@ -225,6 +227,8 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 'sub/directory.yaml/empty.yml'), os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/file-without-yaml-extension'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')] ) @@ -306,14 +310,13 @@ class CommandLineTestCase(unittest.TestCase): cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml'))) self.assertEqual(ctx.returncode, 1) - @unittest.skipIf(os.environ.get('GITHUB_RUN_ID'), '$HOME not overridable') def test_run_with_user_global_config_file(self): home = os.path.join(self.wd, 'fake-home') dir = os.path.join(home, '.config', 'yamllint') os.makedirs(dir) config = os.path.join(dir, 'config') - self.addCleanup(os.environ.update, HOME=os.environ['HOME']) + self.addCleanup(os.environ.__delitem__, 'HOME') os.environ['HOME'] = home with open(config, 'w') as f: @@ -690,6 +693,7 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 'sub/directory.yaml/empty.yml'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')] ) @@ -706,9 +710,29 @@ class CommandLineTestCase(unittest.TestCase): os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'), os.path.join(self.wd, 'sub/ok.yaml'), + os.path.join(self.wd, 'symlinks/link.yaml'), os.path.join(self.wd, 'warn.yaml')] ) + config = 'ignore: ["*.yaml", "*.yml", "!a.yaml"]' + with RunContext(self) as ctx: + cli.run(('--list-files', '-d', config, self.wd)) + self.assertEqual(ctx.returncode, 0) + self.assertEqual( + sorted(ctx.stdout.splitlines()), + [os.path.join(self.wd, 'a.yaml')] + ) + with RunContext(self) as ctx: + cli.run(('--list-files', '-d', config, + os.path.join(self.wd, 'a.yaml'), + os.path.join(self.wd, 'en.yaml'), + os.path.join(self.wd, 'c.yaml'))) + self.assertEqual(ctx.returncode, 0) + self.assertEqual( + sorted(ctx.stdout.splitlines()), + [os.path.join(self.wd, 'a.yaml')] + ) + class CommandLineConfigTestCase(unittest.TestCase): def test_config_file(self): diff --git a/tests/test_config.py b/tests/test_config.py index 8e90246..fb570c6 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,18 +13,17 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from io import StringIO import os import shutil import sys import tempfile import unittest +from io import StringIO -from tests.common import build_temp_workspace +from tests.common import build_temp_workspace, RunContext +from yamllint import cli, config from yamllint.config import YamlLintConfigError -from yamllint import cli -from yamllint import config class SimpleConfigTestCase(unittest.TestCase): @@ -212,6 +211,14 @@ class SimpleConfigTestCase(unittest.TestCase): ' colons:\n' ' ignore: yes\n') + def test_invalid_rule_ignore_from_file(self): + self.assertRaises( + config.YamlLintConfigError, + config.YamlLintConfig, + 'rules:\n' + ' colons:\n' + ' ignore-from-file: 1337\n') + def test_invalid_locale(self): with self.assertRaisesRegex( config.YamlLintConfigError, @@ -660,12 +667,19 @@ class IgnoreConfigTestCase(unittest.TestCase): def test_run_with_ignore_from_file(self): with open(os.path.join(self.wd, '.yamllint'), 'w') as f: f.write('extends: default\n' - 'ignore-from-file: .gitignore\n') + 'ignore-from-file: .gitignore\n' + 'rules:\n' + ' key-duplicates:\n' + ' ignore-from-file: .ignore-key-duplicates\n') + with open(os.path.join(self.wd, '.gitignore'), 'w') as f: f.write('*.dont-lint-me.yaml\n' '/bin/\n' '!/bin/*.lint-me-anyway.yaml\n') + with open(os.path.join(self.wd, '.ignore-key-duplicates'), 'w') as f: + f.write('/ign-dup\n') + sys.stdout = StringIO() with self.assertRaises(SystemExit): cli.run(('-f', 'parsable', '.')) @@ -686,10 +700,8 @@ class IgnoreConfigTestCase(unittest.TestCase): './file-at-root.yaml:3:3: ' + keydup, './file-at-root.yaml:4:17: ' + trailing, './file-at-root.yaml:5:5: ' + hyphen, - './ign-dup/file.yaml:3:3: ' + keydup, './ign-dup/file.yaml:4:17: ' + trailing, './ign-dup/file.yaml:5:5: ' + hyphen, - './ign-dup/sub/dir/file.yaml:3:3: ' + keydup, './ign-dup/sub/dir/file.yaml:4:17: ' + trailing, './ign-dup/sub/dir/file.yaml:5:5: ' + hyphen, './ign-trail/file.yaml:3:3: ' + keydup, @@ -761,3 +773,50 @@ class IgnoreConfigTestCase(unittest.TestCase): './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing, './s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen, ))) + + def test_run_with_ignore_with_broken_symlink(self): + wd = build_temp_workspace({ + 'file-without-yaml-extension': '42\n', + 'link.yaml': 'symlink://file-without-yaml-extension', + 'link-404.yaml': 'symlink://file-that-does-not-exist', + }) + backup_wd = os.getcwd() + os.chdir(wd) + + with RunContext(self) as ctx: + cli.run(('-f', 'parsable', '.')) + self.assertNotEqual(ctx.returncode, 0) + + with open(os.path.join(wd, '.yamllint'), 'w') as f: + f.write('extends: default\n' + 'ignore: |\n' + ' *404.yaml\n') + with RunContext(self) as ctx: + cli.run(('-f', 'parsable', '.')) + self.assertEqual(ctx.returncode, 0) + docstart = '[warning] missing document start "---" (document-start)' + out = '\n'.join(sorted(ctx.stdout.splitlines())) + self.assertEqual(out, '\n'.join(( + './.yamllint:1:1: ' + docstart, + './link.yaml:1:1: ' + docstart, + ))) + + os.chdir(backup_wd) + shutil.rmtree(wd) + + def test_run_with_ignore_on_ignored_file(self): + with open(os.path.join(self.wd, '.yamllint'), 'w') as f: + f.write('ignore: file.dont-lint-me.yaml\n' + 'rules:\n' + ' trailing-spaces: enable\n' + ' key-duplicates:\n' + ' ignore: file-at-root.yaml\n') + + sys.stdout = StringIO() + with self.assertRaises(SystemExit): + cli.run(('-f', 'parsable', 'file.dont-lint-me.yaml', + 'file-at-root.yaml')) + self.assertEqual( + sys.stdout.getvalue().strip(), + 'file-at-root.yaml:4:17: [error] trailing spaces (trailing-spaces)' + ) diff --git a/tests/test_linter.py b/tests/test_linter.py index 9855120..ea509d8 100644 --- a/tests/test_linter.py +++ b/tests/test_linter.py @@ -16,8 +16,8 @@ import io import unittest -from yamllint.config import YamlLintConfig from yamllint import linter +from yamllint.config import YamlLintConfig class LinterTestCase(unittest.TestCase): diff --git a/tests/test_module.py b/tests/test_module.py index 299e153..7f4f62b 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -16,11 +16,10 @@ import os import shutil import subprocess -import tempfile import sys +import tempfile import unittest - PYTHON = sys.executable or 'python' diff --git a/tests/test_parser.py b/tests/test_parser.py index dbeb36b..c2b598a 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -17,9 +17,14 @@ import unittest import yaml -from yamllint.parser import (line_generator, token_or_comment_generator, - token_or_comment_or_line_generator, - Line, Token, Comment) +from yamllint.parser import ( + Comment, + Line, + Token, + line_generator, + token_or_comment_generator, + token_or_comment_or_line_generator, +) class ParserTestCase(unittest.TestCase): diff --git a/tests/test_spec_examples.py b/tests/test_spec_examples.py index ac68e12..87a57f6 100644 --- a/tests/test_spec_examples.py +++ b/tests/test_spec_examples.py @@ -17,7 +17,6 @@ import os from tests.common import RuleTestCase - # This file checks examples from YAML 1.2 specification [1] against yamllint. # # [1]: http://www.yaml.org/spec/1.2/spec.html @@ -43,6 +42,7 @@ from tests.common import RuleTestCase # encoding='utf-8') as g: # g.write(text) + class SpecificationTestCase(RuleTestCase): rule_id = None |