summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/common.py35
-rw-r--r--tests/rules/test_indentation.py8
-rw-r--r--tests/rules/test_key_duplicates.py54
-rw-r--r--tests/rules/test_quoted_strings.py786
-rw-r--r--tests/rules/test_truthy.py88
-rw-r--r--tests/test_cli.py88
-rw-r--r--tests/test_config.py73
-rw-r--r--tests/test_linter.py2
-rw-r--r--tests/test_module.py3
-rw-r--r--tests/test_parser.py11
-rw-r--r--tests/test_spec_examples.py2
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