diff options
Diffstat (limited to 'tests/rules')
25 files changed, 6480 insertions, 0 deletions
diff --git a/tests/rules/__init__.py b/tests/rules/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/rules/__init__.py diff --git a/tests/rules/test_anchors.py b/tests/rules/test_anchors.py new file mode 100644 index 0000000..7d7cbb7 --- /dev/null +++ b/tests/rules/test_anchors.py @@ -0,0 +1,281 @@ +# Copyright (C) 2023 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class AnchorsTestCase(RuleTestCase): + rule_id = 'anchors' + + def test_disabled(self): + conf = 'anchors: disable' + self.check('---\n' + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- &f_m {k: v}\n' + '- &f_s [1, 2]\n' + '- *b\n' + '- *i\n' + '- *s\n' + '- *f_m\n' + '- *f_s\n' + '---\n' # redeclare anchors in a new document + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- *b\n' + '- *i\n' + '- *s\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n' + '...\n', conf) + self.check('---\n' + '- &i 42\n' + '---\n' + '- &b true\n' + '- &b true\n' + '- &b true\n' + '- &s hello\n' + '- *b\n' + '- *i\n' # declared in a previous document + '- *f_m\n' # never declared + '- *f_m\n' + '- *f_m\n' + '- *f_s\n' # declared after + '- &f_s [1, 2]\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + '---\n' + 'block mapping 1: &b_m_bis\n' + ' key: value\n' + 'block mapping 2: &b_m_bis\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n' + '...\n', conf) + + def test_forbid_undeclared_aliases(self): + conf = ('anchors:\n' + ' forbid-undeclared-aliases: true\n' + ' forbid-duplicated-anchors: false\n' + ' forbid-unused-anchors: false\n') + self.check('---\n' + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- &f_m {k: v}\n' + '- &f_s [1, 2]\n' + '- *b\n' + '- *i\n' + '- *s\n' + '- *f_m\n' + '- *f_s\n' + '---\n' # redeclare anchors in a new document + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- *b\n' + '- *i\n' + '- *s\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n' + '...\n', conf) + self.check('---\n' + '- &i 42\n' + '---\n' + '- &b true\n' + '- &b true\n' + '- &b true\n' + '- &s hello\n' + '- *b\n' + '- *i\n' # declared in a previous document + '- *f_m\n' # never declared + '- *f_m\n' + '- *f_m\n' + '- *f_s\n' # declared after + '- &f_s [1, 2]\n' + '...\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + '---\n' + 'block mapping 1: &b_m_bis\n' + ' key: value\n' + 'block mapping 2: &b_m_bis\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n' + '...\n', conf, + problem1=(9, 3), + problem2=(10, 3), + problem3=(11, 3), + problem4=(12, 3), + problem5=(13, 3), + problem6=(25, 7), + problem7=(28, 37)) + + def test_forbid_duplicated_anchors(self): + conf = ('anchors:\n' + ' forbid-undeclared-aliases: false\n' + ' forbid-duplicated-anchors: true\n' + ' forbid-unused-anchors: false\n') + self.check('---\n' + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- &f_m {k: v}\n' + '- &f_s [1, 2]\n' + '- *b\n' + '- *i\n' + '- *s\n' + '- *f_m\n' + '- *f_s\n' + '---\n' # redeclare anchors in a new document + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- *b\n' + '- *i\n' + '- *s\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n' + '...\n', conf) + self.check('---\n' + '- &i 42\n' + '---\n' + '- &b true\n' + '- &b true\n' + '- &b true\n' + '- &s hello\n' + '- *b\n' + '- *i\n' # declared in a previous document + '- *f_m\n' # never declared + '- *f_m\n' + '- *f_m\n' + '- *f_s\n' # declared after + '- &f_s [1, 2]\n' + '...\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + '---\n' + 'block mapping 1: &b_m_bis\n' + ' key: value\n' + 'block mapping 2: &b_m_bis\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n' + '...\n', conf, + problem1=(5, 3), + problem2=(6, 3), + problem3=(22, 18), + problem4=(28, 20)) + + def test_forbid_unused_anchors(self): + conf = ('anchors:\n' + ' forbid-undeclared-aliases: false\n' + ' forbid-duplicated-anchors: false\n' + ' forbid-unused-anchors: true\n') + + self.check('---\n' + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- &f_m {k: v}\n' + '- &f_s [1, 2]\n' + '- *b\n' + '- *i\n' + '- *s\n' + '- *f_m\n' + '- *f_s\n' + '---\n' # redeclare anchors in a new document + '- &b true\n' + '- &i 42\n' + '- &s hello\n' + '- *b\n' + '- *i\n' + '- *s\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n' + '...\n', conf) + self.check('---\n' + '- &i 42\n' + '---\n' + '- &b true\n' + '- &b true\n' + '- &b true\n' + '- &s hello\n' + '- *b\n' + '- *i\n' # declared in a previous document + '- *f_m\n' # never declared + '- *f_m\n' + '- *f_m\n' + '- *f_s\n' # declared after + '- &f_s [1, 2]\n' + '...\n' + '---\n' + 'block mapping: &b_m\n' + ' key: value\n' + '---\n' + 'block mapping 1: &b_m_bis\n' + ' key: value\n' + 'block mapping 2: &b_m_bis\n' + ' key: value\n' + 'extended:\n' + ' <<: *b_m\n' + ' foo: bar\n' + '---\n' + '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n' + '...\n', conf, + problem1=(2, 3), + problem2=(7, 3), + problem3=(14, 3), + problem4=(17, 16), + problem5=(22, 18)) diff --git a/tests/rules/test_braces.py b/tests/rules/test_braces.py new file mode 100644 index 0000000..03636a9 --- /dev/null +++ b/tests/rules/test_braces.py @@ -0,0 +1,340 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class ColonTestCase(RuleTestCase): + rule_id = 'braces' + + def test_disabled(self): + conf = 'braces: disable' + self.check('---\n' + 'dict1: {}\n' + 'dict2: { }\n' + 'dict3: { a: 1, b}\n' + 'dict4: {a: 1, b, c: 3 }\n' + 'dict5: {a: 1, b, c: 3 }\n' + 'dict6: { a: 1, b, c: 3 }\n' + 'dict7: { a: 1, b, c: 3 }\n', conf) + + def test_forbid(self): + conf = ('braces:\n' + ' forbid: false\n') + self.check('---\n' + 'dict: {}\n', conf) + self.check('---\n' + 'dict: {a}\n', conf) + self.check('---\n' + 'dict: {a: 1}\n', conf) + self.check('---\n' + 'dict: {\n' + ' a: 1\n' + '}\n', conf) + + conf = ('braces:\n' + ' forbid: true\n') + self.check('---\n' + 'dict:\n' + ' a: 1\n', conf) + self.check('---\n' + 'dict: {}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {a}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {a: 1}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {\n' + ' a: 1\n' + '}\n', conf, problem=(2, 8)) + + conf = ('braces:\n' + ' forbid: non-empty\n') + self.check('---\n' + 'dict:\n' + ' a: 1\n', conf) + self.check('---\n' + 'dict: {}\n', conf) + self.check('---\n' + 'dict: {\n' + '}\n', conf) + self.check('---\n' + 'dict: {\n' + '# commented: value\n' + '# another: value2\n' + '}\n', conf) + self.check('---\n' + 'dict: {a}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {a: 1}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {\n' + ' a: 1\n' + '}\n', conf, problem=(2, 8)) + + def test_min_spaces(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: { }\n', conf) + self.check('---\n' + 'dict: {a: 1, b}\n', conf, + problem1=(2, 8), problem2=(2, 15)) + self.check('---\n' + 'dict: { a: 1, b }\n', conf) + self.check('---\n' + 'dict: {\n' + ' a: 1,\n' + ' b\n' + '}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: { a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 17)) + self.check('---\n' + 'dict: { a: 1, b }\n', conf) + + def test_max_spaces(self): + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {}\n', conf) + self.check('---\n' + 'dict: { }\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {a: 1, b}\n', conf) + self.check('---\n' + 'dict: { a: 1, b }\n', conf, + problem1=(2, 8), problem2=(2, 16)) + self.check('---\n' + 'dict: { a: 1, b }\n', conf, + problem1=(2, 10), problem2=(2, 20)) + self.check('---\n' + 'dict: {\n' + ' a: 1,\n' + ' b\n' + '}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: { a: 1, b }\n', conf) + self.check('---\n' + 'dict: { a: 1, b }\n', conf, + problem1=(2, 11), problem2=(2, 23)) + + def test_min_and_max_spaces(self): + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {}\n', conf) + self.check('---\n' + 'dict: { }\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: { a: 1, b}\n', conf, problem=(2, 10)) + + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8)) + + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'dict: {a: 1, b, c: 3 }\n', conf) + self.check('---\n' + 'dict: { a: 1, b, c: 3 }\n', conf) + self.check('---\n' + 'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10)) + + def test_min_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: {}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + def test_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 10)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 17)) + self.check('---\n' + 'array: {a: 1, b}\n', conf) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 18)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 11)) + + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) diff --git a/tests/rules/test_brackets.py b/tests/rules/test_brackets.py new file mode 100644 index 0000000..e17566b --- /dev/null +++ b/tests/rules/test_brackets.py @@ -0,0 +1,337 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class ColonTestCase(RuleTestCase): + rule_id = 'brackets' + + def test_disabled(self): + conf = 'brackets: disable' + self.check('---\n' + 'array1: []\n' + 'array2: [ ]\n' + 'array3: [ a, b]\n' + 'array4: [a, b, c ]\n' + 'array5: [a, b, c ]\n' + 'array6: [ a, b, c ]\n' + 'array7: [ a, b, c ]\n', conf) + + def test_forbid(self): + conf = ('brackets:\n' + ' forbid: false\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf) + + conf = ('brackets:\n' + ' forbid: true\n') + self.check('---\n' + 'array:\n' + ' - a\n' + ' - b\n', conf) + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [a, b]\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf, problem=(2, 9)) + + conf = ('brackets:\n' + ' forbid: non-empty\n') + self.check('---\n' + 'array:\n' + ' - a\n' + ' - b\n', conf) + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [\n\n' + ']\n', conf) + self.check('---\n' + 'array: [\n' + '# a comment\n' + ']\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf, problem=(2, 9)) + + def test_min_spaces(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 10), problem2=(2, 15)) + self.check('---\n' + 'array: [ a, b ]\n', conf) + + def test_max_spaces(self): + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [a, b]\n', conf) + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 14)) + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 11), problem2=(2, 18)) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 12), problem2=(2, 21)) + + def test_min_and_max_spaces(self): + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ a, b]\n', conf, problem=(2, 11)) + + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: [a, b, c ]\n', conf, problem=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: [a, b, c ]\n', conf) + self.check('---\n' + 'array: [ a, b, c ]\n', conf) + self.check('---\n' + 'array: [ a, b, c ]\n', conf, problem=(2, 11)) + + def test_min_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: []\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + def test_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 10)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 14)) + self.check('---\n' + 'array: [a, b]\n', conf) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 15)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 11)) + + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) diff --git a/tests/rules/test_colons.py b/tests/rules/test_colons.py new file mode 100644 index 0000000..5467c8b --- /dev/null +++ b/tests/rules/test_colons.py @@ -0,0 +1,274 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class ColonTestCase(RuleTestCase): + rule_id = 'colons' + + def test_disabled(self): + conf = 'colons: disable' + self.check('---\n' + 'object:\n' + ' k1 : v1\n' + 'obj2:\n' + ' k2 :\n' + ' - 8\n' + ' k3:\n' + ' val\n' + ' property : value\n' + ' prop2 : val2\n' + ' propriété : [valeur]\n' + ' o:\n' + ' k1: [v1, v2]\n' + ' p:\n' + ' - k3: >\n' + ' val\n' + ' - o: {k1: v1}\n' + ' - p: kdjf\n' + ' - q: val0\n' + ' - q2:\n' + ' - val1\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' k1: v1\n' + 'obj2:\n' + ' k2:\n' + ' - 8\n' + ' k3:\n' + ' val\n' + ' property: value\n' + ' prop2: val2\n' + ' propriété: [valeur]\n' + ' o:\n' + ' k1: [v1, v2]\n', conf) + self.check('---\n' + 'obj:\n' + ' p:\n' + ' - k1: >\n' + ' val\n' + ' - k3: >\n' + ' val\n' + ' - o: {k1: v1}\n' + ' - o: {k1: v1}\n' + ' - q2:\n' + ' - val1\n' + '...\n', conf) + self.check('---\n' + 'a: {b: {c: d, e : f}}\n', conf) + + def test_before_enabled(self): + conf = 'colons: {max-spaces-before: 0, max-spaces-after: -1}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' k1 :\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + '...\n', conf, problem=(3, 5)) + self.check('---\n' + 'lib :\n' + ' - var\n' + '...\n', conf, problem=(2, 4)) + self.check('---\n' + '- lib :\n' + ' - var\n' + '...\n', conf, problem=(2, 6)) + self.check('---\n' + 'a: {b: {c : d, e : f}}\n', conf, + problem1=(2, 10), problem2=(2, 17)) + + def test_before_max(self): + conf = 'colons: {max-spaces-before: 3, max-spaces-after: -1}' + self.check('---\n' + 'object :\n' + ' k1 :\n' + ' - a\n' + ' - b\n' + ' k2 : v2\n' + '...\n', conf) + self.check('---\n' + 'object :\n' + ' k1 :\n' + ' - a\n' + ' - b\n' + ' k2 : v2\n' + '...\n', conf, problem=(3, 8)) + + def test_before_with_explicit_block_mappings(self): + conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}' + self.check('---\n' + 'object:\n' + ' ? key\n' + ' : value\n' + '...\n', conf) + self.check('---\n' + 'object :\n' + ' ? key\n' + ' : value\n' + '...\n', conf, problem=(2, 7)) + self.check('---\n' + '? >\n' + ' multi-line\n' + ' key\n' + ': >\n' + ' multi-line\n' + ' value\n' + '...\n', conf) + self.check('---\n' + '- ? >\n' + ' multi-line\n' + ' key\n' + ' : >\n' + ' multi-line\n' + ' value\n' + '...\n', conf) + self.check('---\n' + '- ? >\n' + ' multi-line\n' + ' key\n' + ' : >\n' + ' multi-line\n' + ' value\n' + '...\n', conf, problem=(5, 5)) + + def test_after_enabled(self): + conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}' + self.check('---\n' + 'key: value\n', conf) + self.check('---\n' + 'key: value\n', conf, problem=(2, 6)) + self.check('---\n' + 'object:\n' + ' k1: [a, b]\n' + ' k2: string\n', conf, problem=(3, 7)) + self.check('---\n' + 'object:\n' + ' k1: [a, b]\n' + ' k2: string\n', conf, problem=(4, 7)) + self.check('---\n' + 'object:\n' + ' other: {key: value}\n' + '...\n', conf, problem=(3, 16)) + self.check('---\n' + 'a: {b: {c: d, e : f}}\n', conf, + problem1=(2, 12), problem2=(2, 20)) + + def test_after_enabled_question_mark(self): + conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}' + self.check('---\n' + '? key\n' + ': value\n', conf) + self.check('---\n' + '? key\n' + ': value\n', conf, problem=(2, 3)) + self.check('---\n' + '? key\n' + ': value\n', conf, problem1=(2, 3), problem2=(3, 3)) + self.check('---\n' + '- ? key\n' + ' : value\n', conf, problem1=(2, 5), problem2=(3, 5)) + + def test_after_max(self): + conf = 'colons: {max-spaces-before: -1, max-spaces-after: 3}' + self.check('---\n' + 'object:\n' + ' k1: [a, b]\n', conf) + self.check('---\n' + 'object:\n' + ' k1: [a, b]\n', conf, problem=(3, 9)) + self.check('---\n' + 'object:\n' + ' k2: string\n', conf) + self.check('---\n' + 'object:\n' + ' k2: string\n', conf, problem=(3, 9)) + self.check('---\n' + 'object:\n' + ' other: {key: value}\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' other: {key: value}\n' + '...\n', conf, problem=(3, 18)) + + def test_after_with_explicit_block_mappings(self): + conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}' + self.check('---\n' + 'object:\n' + ' ? key\n' + ' : value\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' ? key\n' + ' : value\n' + '...\n', conf, problem=(4, 5)) + + def test_after_do_not_confound_with_trailing_space(self): + conf = ('colons: {max-spaces-before: 1, max-spaces-after: 1}\n' + 'trailing-spaces: disable\n') + self.check('---\n' + 'trailing: \n' + ' - spaces\n', conf) + + def test_both_before_and_after(self): + conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}' + self.check('---\n' + 'obj:\n' + ' string: text\n' + ' k:\n' + ' - 8\n' + ' k3:\n' + ' val\n' + ' property: [value]\n', conf) + self.check('---\n' + 'object:\n' + ' k1 : v1\n', conf, problem1=(3, 5), problem2=(3, 8)) + self.check('---\n' + 'obj:\n' + ' string: text\n' + ' k :\n' + ' - 8\n' + ' k3:\n' + ' val\n' + ' property: {a: 1, b: 2, c : 3}\n', conf, + problem1=(3, 11), problem2=(4, 4), + problem3=(8, 23), problem4=(8, 28)) + + # Although accepted by PyYAML, `{*x: 4}` is not valid YAML: it should be + # noted `{*x : 4}`. The reason is that a colon can be part of an anchor + # name. See commit message for more details. + def test_with_alias_as_key(self): + conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}' + self.check('---\n' + '- anchor: &a key\n' + '- *a: 42\n' + '- {*a: 42}\n' + '- *a : 42\n' + '- {*a : 42}\n' + '- *a : 42\n' + '- {*a : 42}\n', + conf, + problem1=(7, 6), problem2=(8, 7)) diff --git a/tests/rules/test_commas.py b/tests/rules/test_commas.py new file mode 100644 index 0000000..0d0abd8 --- /dev/null +++ b/tests/rules/test_commas.py @@ -0,0 +1,264 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class CommaTestCase(RuleTestCase): + rule_id = 'commas' + + def test_disabled(self): + conf = 'commas: disable' + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n' + 'array: [\n' + ' elem ,\n' + ' key: val ,\n' + ']\n' + 'map: {\n' + ' key1: val1 ,\n' + ' key2: val2,\n' + '}\n' + '...\n', conf) + self.check('---\n' + '- [one, two , three,four]\n' + '- {five,six , seven, eight}\n' + '- [\n' + ' nine, ten\n' + ' , eleven\n' + ' ,twelve\n' + ']\n' + '- {\n' + ' thirteen: 13, fourteen\n' + ' , fifteen: 15\n' + ' ,sixteen: 16\n' + '}\n', conf) + + def test_before_max(self): + conf = ('commas:\n' + ' max-spaces-before: 0\n' + ' min-spaces-after: 0\n' + ' max-spaces-after: -1\n') + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf) + self.check('---\n' + 'array: [1, 2 , 3, 4]\n' + '...\n', conf, problem=(2, 13)) + self.check('---\n' + 'array: [1 , 2, 3 , 4]\n' + '...\n', conf, problem1=(2, 10), problem2=(2, 23)) + self.check('---\n' + 'dict: {a: b, c: "1 2 3", d: e, f: [g, h]}\n' + '...\n', conf) + self.check('---\n' + 'dict: {a: b, c: "1 2 3" , d: e, f: [g, h]}\n' + '...\n', conf, problem=(2, 24)) + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e, f: [g , h]}\n' + '...\n', conf, problem1=(2, 12), problem2=(2, 42)) + self.check('---\n' + 'array: [\n' + ' elem,\n' + ' key: val,\n' + ']\n', conf) + self.check('---\n' + 'array: [\n' + ' elem ,\n' + ' key: val,\n' + ']\n', conf, problem=(3, 7)) + self.check('---\n' + 'map: {\n' + ' key1: val1,\n' + ' key2: val2,\n' + '}\n', conf) + self.check('---\n' + 'map: {\n' + ' key1: val1,\n' + ' key2: val2 ,\n' + '}\n', conf, problem=(4, 13)) + + def test_before_max_with_comma_on_new_line(self): + conf = ('commas:\n' + ' max-spaces-before: 0\n' + ' min-spaces-after: 0\n' + ' max-spaces-after: -1\n') + self.check('---\n' + 'flow-seq: [1, 2, 3\n' + ' , 4, 5, 6]\n' + '...\n', conf, problem=(3, 11)) + self.check('---\n' + 'flow-map: {a: 1, b: 2\n' + ' , c: 3}\n' + '...\n', conf, problem=(3, 11)) + + conf = ('commas:\n' + ' max-spaces-before: 0\n' + ' min-spaces-after: 0\n' + ' max-spaces-after: -1\n' + 'indentation: disable\n') + self.check('---\n' + 'flow-seq: [1, 2, 3\n' + ' , 4, 5, 6]\n' + '...\n', conf, problem=(3, 9)) + self.check('---\n' + 'flow-map: {a: 1, b: 2\n' + ' , c: 3}\n' + '...\n', conf, problem=(3, 9)) + self.check('---\n' + '[\n' + '1,\n' + '2\n' + ', 3\n' + ']\n', conf, problem=(5, 1)) + self.check('---\n' + '{\n' + 'a: 1,\n' + 'b: 2\n' + ', c: 3\n' + '}\n', conf, problem=(5, 1)) + + def test_before_max_3(self): + conf = ('commas:\n' + ' max-spaces-before: 3\n' + ' min-spaces-after: 0\n' + ' max-spaces-after: -1\n') + self.check('---\n' + 'array: [1 , 2, 3 , 4]\n' + '...\n', conf) + self.check('---\n' + 'array: [1 , 2, 3 , 4]\n' + '...\n', conf, problem=(2, 20)) + self.check('---\n' + 'array: [\n' + ' elem1 ,\n' + ' elem2 ,\n' + ' key: val,\n' + ']\n', conf, problem=(4, 11)) + + def test_after_min(self): + conf = ('commas:\n' + ' max-spaces-before: -1\n' + ' min-spaces-after: 1\n' + ' max-spaces-after: -1\n') + self.check('---\n' + '- [one, two , three,four]\n' + '- {five,six , seven, eight}\n' + '- [\n' + ' nine, ten\n' + ' , eleven\n' + ' ,twelve\n' + ']\n' + '- {\n' + ' thirteen: 13, fourteen\n' + ' , fifteen: 15\n' + ' ,sixteen: 16\n' + '}\n', conf, + problem1=(2, 21), problem2=(3, 9), + problem3=(7, 4), problem4=(12, 4)) + + def test_after_max(self): + conf = ('commas:\n' + ' max-spaces-before: -1\n' + ' min-spaces-after: 0\n' + ' max-spaces-after: 1\n') + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf) + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf, problem=(2, 15)) + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf, problem1=(2, 12), problem2=(2, 22)) + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n' + '...\n', conf) + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n' + '...\n', conf, problem=(2, 27)) + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n' + '...\n', conf, problem1=(2, 15), problem2=(2, 44)) + self.check('---\n' + 'array: [\n' + ' elem,\n' + ' key: val,\n' + ']\n', conf) + self.check('---\n' + 'array: [\n' + ' elem, key: val,\n' + ']\n', conf, problem=(3, 9)) + self.check('---\n' + 'map: {\n' + ' key1: val1, key2: [val2, val3]\n' + '}\n', conf, problem1=(3, 16), problem2=(3, 30)) + + def test_after_max_3(self): + conf = ('commas:\n' + ' max-spaces-before: -1\n' + ' min-spaces-after: 1\n' + ' max-spaces-after: 3\n') + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf) + self.check('---\n' + 'array: [1, 2, 3, 4]\n' + '...\n', conf, problem=(2, 21)) + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n' + '...\n', conf, problem1=(2, 31), problem2=(2, 49)) + + def test_both_before_and_after(self): + conf = ('commas:\n' + ' max-spaces-before: 0\n' + ' min-spaces-after: 1\n' + ' max-spaces-after: 1\n') + self.check('---\n' + 'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n' + 'array: [\n' + ' elem ,\n' + ' key: val ,\n' + ']\n' + 'map: {\n' + ' key1: val1 ,\n' + ' key2: val2,\n' + '}\n' + '...\n', conf, + problem1=(2, 12), problem2=(2, 16), problem3=(2, 31), + problem4=(2, 36), problem5=(2, 50), problem6=(4, 8), + problem7=(5, 11), problem8=(8, 13)) + conf = ('commas:\n' + ' max-spaces-before: 0\n' + ' min-spaces-after: 1\n' + ' max-spaces-after: 1\n' + 'indentation: disable\n') + self.check('---\n' + '- [one, two , three,four]\n' + '- {five,six , seven, eight}\n' + '- [\n' + ' nine, ten\n' + ' , eleven\n' + ' ,twelve\n' + ']\n' + '- {\n' + ' thirteen: 13, fourteen\n' + ' , fifteen: 15\n' + ' ,sixteen: 16\n' + '}\n', conf, + problem1=(2, 12), problem2=(2, 21), problem3=(3, 9), + problem4=(3, 12), problem5=(5, 9), problem6=(6, 2), + problem7=(7, 2), problem8=(7, 4), problem9=(10, 17), + problem10=(11, 2), problem11=(12, 2), problem12=(12, 4)) diff --git a/tests/rules/test_comments.py b/tests/rules/test_comments.py new file mode 100644 index 0000000..1f5a20c --- /dev/null +++ b/tests/rules/test_comments.py @@ -0,0 +1,236 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class CommentsTestCase(RuleTestCase): + rule_id = 'comments' + + def test_disabled(self): + conf = ('comments: disable\n' + 'comments-indentation: disable\n') + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + '################################\n' + '## comment 4\n' + '##comment 5\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf) + + def test_starting_space(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: -1\n' + 'comments-indentation: disable\n') + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello # world\n' + '\n' + '# comment 2\n' + '# comment 3\n' + ' # comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + '################################\n' + '## comment 4\n' + '## comment 5\n', conf) + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + '################################\n' + '## comment 4\n' + '##comment 5\n', conf, + problem1=(2, 2), problem2=(6, 13), + problem3=(9, 2), problem4=(10, 4), + problem5=(15, 3)) + + def test_shebang(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' ignore-shebangs: false\n' + 'comments-indentation: disable\n' + 'document-start: disable\n') + self.check('#!/bin/env my-interpreter\n', + conf, problem1=(1, 2)) + self.check('# comment\n' + '#!/bin/env my-interpreter\n', conf, + problem1=(2, 2)) + self.check('#!/bin/env my-interpreter\n' + '---\n' + '#comment\n' + '#!/bin/env my-interpreter\n' + '', conf, + problem1=(1, 2), problem2=(3, 2), problem3=(4, 2)) + self.check('#! is a valid shebang too\n', + conf, problem1=(1, 2)) + self.check('key: #!/not/a/shebang\n', + conf, problem1=(1, 8)) + + def test_ignore_shebang(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' ignore-shebangs: true\n' + 'comments-indentation: disable\n' + 'document-start: disable\n') + self.check('#!/bin/env my-interpreter\n', conf) + self.check('# comment\n' + '#!/bin/env my-interpreter\n', conf, + problem1=(2, 2)) + self.check('#!/bin/env my-interpreter\n' + '---\n' + '#comment\n' + '#!/bin/env my-interpreter\n', conf, + problem2=(3, 2), problem3=(4, 2)) + self.check('#! is a valid shebang too\n', conf) + self.check('key: #!/not/a/shebang\n', + conf, problem1=(1, 8)) + + def test_spaces_from_content(self): + conf = ('comments:\n' + ' require-starting-space: false\n' + ' min-spaces-from-content: 2\n') + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf) + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf, + problem1=(4, 7), problem2=(6, 11), problem3=(8, 30)) + + def test_both(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n' + 'comments-indentation: disable\n') + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + '################################\n' + '## comment 4\n' + '##comment 5\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf, + problem1=(2, 2), + problem2=(4, 7), + problem3=(6, 11), problem4=(6, 12), + problem5=(9, 2), + problem6=(10, 4), + problem7=(15, 3), + problem8=(17, 30)) + + def test_empty_comment(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n') + self.check('---\n' + '# This is paragraph 1.\n' + '#\n' + '# This is paragraph 2.\n', conf) + self.check('---\n' + 'inline: comment #\n' + 'foo: bar\n', conf) + + def test_empty_comment_crlf_dos_newlines(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n' + 'new-lines:\n' + ' type: dos\n') + self.check('---\r\n' + '# This is paragraph 1.\r\n' + '#\r\n' + '# This is paragraph 2.\r\n', conf) + + def test_empty_comment_crlf_disabled_newlines(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n' + 'new-lines: disable\n') + self.check('---\r\n' + '# This is paragraph 1.\r\n' + '#\r\n' + '# This is paragraph 2.\r\n', conf) + + def test_first_line(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n') + self.check('# comment\n', conf) + + def test_last_line(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n' + 'new-line-at-end-of-file: disable\n') + self.check('# comment with no newline char:\n' + '#', conf) + + def test_multi_line_scalar(self): + conf = ('comments:\n' + ' require-starting-space: true\n' + ' min-spaces-from-content: 2\n' + 'trailing-spaces: disable\n') + self.check('---\n' + 'string: >\n' + ' this is plain text\n' + '\n' + '# comment\n', conf) + self.check('---\n' + '- string: >\n' + ' this is plain text\n' + ' \n' + ' # comment\n', conf) diff --git a/tests/rules/test_comments_indentation.py b/tests/rules/test_comments_indentation.py new file mode 100644 index 0000000..0aa5aac --- /dev/null +++ b/tests/rules/test_comments_indentation.py @@ -0,0 +1,156 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class CommentsIndentationTestCase(RuleTestCase): + rule_id = 'comments-indentation' + + def test_disable(self): + conf = 'comments-indentation: disable' + self.check('---\n' + ' # line 1\n' + '# line 2\n' + ' # line 3\n' + ' # line 4\n' + '\n' + 'obj:\n' + ' # these\n' + ' # are\n' + ' # [good]\n' + '# bad\n' + ' # comments\n' + ' a: b\n' + '\n' + 'obj1:\n' + ' a: 1\n' + ' # comments\n' + '\n' + 'obj2:\n' + ' b: 2\n' + '\n' + '# empty\n' + '#\n' + '# comment\n' + '...\n', conf) + + def test_enabled(self): + conf = 'comments-indentation: enable' + self.check('---\n' + '# line 1\n' + '# line 2\n', conf) + self.check('---\n' + ' # line 1\n' + '# line 2\n', conf, problem=(2, 2)) + self.check('---\n' + ' # line 1\n' + ' # line 2\n', conf, problem1=(2, 3)) + self.check('---\n' + 'obj:\n' + ' # normal\n' + ' a: b\n', conf) + self.check('---\n' + 'obj:\n' + ' # bad\n' + ' a: b\n', conf, problem=(3, 2)) + self.check('---\n' + 'obj:\n' + '# bad\n' + ' a: b\n', conf, problem=(3, 1)) + self.check('---\n' + 'obj:\n' + ' # bad\n' + ' a: b\n', conf, problem=(3, 4)) + self.check('---\n' + 'obj:\n' + ' # these\n' + ' # are\n' + ' # [good]\n' + '# bad\n' + ' # comments\n' + ' a: b\n', conf, + problem1=(3, 2), problem2=(4, 4), + problem3=(6, 1), problem4=(7, 7)) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # the following line is disabled\n' + ' # b: 2\n', conf) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # b: 2\n' + '\n' + 'obj2:\n' + ' b: 2\n', conf) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # b: 2\n' + '# this object is useless\n' + 'obj2: "no"\n', conf) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + '# this object is useless\n' + ' # b: 2\n' + 'obj2: "no"\n', conf, problem=(5, 3)) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # comments\n' + ' b: 2\n', conf) + self.check('---\n' + 'my list for today:\n' + ' - todo 1\n' + ' - todo 2\n' + ' # commented for now\n' + ' # - todo 3\n' + '...\n', conf) + + def test_first_line(self): + conf = 'comments-indentation: enable' + self.check('# comment\n', conf) + self.check(' # comment\n', conf, problem=(1, 3)) + + def test_no_newline_at_end(self): + conf = ('comments-indentation: enable\n' + 'new-line-at-end-of-file: disable\n') + self.check('# comment', conf) + self.check(' # comment', conf, problem=(1, 3)) + + def test_empty_comment(self): + conf = 'comments-indentation: enable' + self.check('---\n' + '# hey\n' + '# normal\n' + '#\n', conf) + self.check('---\n' + '# hey\n' + '# normal\n' + ' #\n', conf, problem=(4, 2)) + + def test_inline_comment(self): + conf = 'comments-indentation: enable' + self.check('---\n' + '- a # inline\n' + '# ok\n', conf) + self.check('---\n' + '- a # inline\n' + ' # not ok\n', conf, problem=(3, 2)) + self.check('---\n' + ' # not ok\n' + '- a # inline\n', conf, problem=(2, 2)) diff --git a/tests/rules/test_common.py b/tests/rules/test_common.py new file mode 100644 index 0000000..196b419 --- /dev/null +++ b/tests/rules/test_common.py @@ -0,0 +1,43 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import unittest + +import yaml + +from yamllint.rules.common import get_line_indent + + +class CommonTestCase(unittest.TestCase): + def test_get_line_indent(self): + tokens = list(yaml.scan('a: 1\n' + 'b:\n' + ' - c: [2, 3, {d: 4}]\n')) + + self.assertEqual(tokens[3].value, 'a') + self.assertEqual(tokens[5].value, '1') + self.assertEqual(tokens[7].value, 'b') + self.assertEqual(tokens[13].value, 'c') + self.assertEqual(tokens[16].value, '2') + self.assertEqual(tokens[18].value, '3') + self.assertEqual(tokens[22].value, 'd') + self.assertEqual(tokens[24].value, '4') + + for i in (3, 5): + self.assertEqual(get_line_indent(tokens[i]), 0) + for i in (7,): + self.assertEqual(get_line_indent(tokens[i]), 0) + for i in (13, 16, 18, 22, 24): + self.assertEqual(get_line_indent(tokens[i]), 2) diff --git a/tests/rules/test_document_end.py b/tests/rules/test_document_end.py new file mode 100644 index 0000000..8340c6f --- /dev/null +++ b/tests/rules/test_document_end.py @@ -0,0 +1,92 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class DocumentEndTestCase(RuleTestCase): + rule_id = 'document-end' + + def test_disabled(self): + conf = 'document-end: disable' + self.check('---\n' + 'with:\n' + ' document: end\n' + '...\n', conf) + self.check('---\n' + 'without:\n' + ' document: end\n', conf) + + def test_required(self): + conf = 'document-end: {present: true}' + self.check('', conf) + self.check('\n', conf) + self.check('---\n' + 'with:\n' + ' document: end\n' + '...\n', conf) + self.check('---\n' + 'without:\n' + ' document: end\n', conf, problem=(3, 1)) + + def test_forbidden(self): + conf = 'document-end: {present: false}' + self.check('---\n' + 'with:\n' + ' document: end\n' + '...\n', conf, problem=(4, 1)) + self.check('---\n' + 'without:\n' + ' document: end\n', conf) + + def test_multiple_documents(self): + conf = ('document-end: {present: true}\n' + 'document-start: disable\n') + self.check('---\n' + 'first: document\n' + '...\n' + '---\n' + 'second: document\n' + '...\n' + '---\n' + 'third: document\n' + '...\n', conf) + self.check('---\n' + 'first: document\n' + '...\n' + '---\n' + 'second: document\n' + '---\n' + 'third: document\n' + '...\n', conf, problem=(6, 1)) + + def test_directives(self): + conf = 'document-end: {present: true}' + self.check('%YAML 1.2\n' + '---\n' + 'document: end\n' + '...\n', conf) + self.check('%YAML 1.2\n' + '%TAG ! tag:clarkevans.com,2002:\n' + '---\n' + 'document: end\n' + '...\n', conf) + self.check('---\n' + 'first: document\n' + '...\n' + '%YAML 1.2\n' + '---\n' + 'second: document\n' + '...\n', conf) diff --git a/tests/rules/test_document_start.py b/tests/rules/test_document_start.py new file mode 100644 index 0000000..ee2e9d3 --- /dev/null +++ b/tests/rules/test_document_start.py @@ -0,0 +1,103 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class DocumentStartTestCase(RuleTestCase): + rule_id = 'document-start' + + def test_disabled(self): + conf = 'document-start: disable' + self.check('', conf) + self.check('key: val\n', conf) + self.check('---\n' + 'key: val\n', conf) + + def test_required(self): + conf = ('document-start: {present: true}\n' + 'empty-lines: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('key: val\n', conf, problem=(1, 1)) + self.check('\n' + '\n' + 'key: val\n', conf, problem=(3, 1)) + self.check('---\n' + 'key: val\n', conf) + self.check('\n' + '\n' + '---\n' + 'key: val\n', conf) + + def test_forbidden(self): + conf = ('document-start: {present: false}\n' + 'empty-lines: disable\n') + self.check('', conf) + self.check('key: val\n', conf) + self.check('\n' + '\n' + 'key: val\n', conf) + self.check('---\n' + 'key: val\n', conf, problem=(1, 1)) + self.check('\n' + '\n' + '---\n' + 'key: val\n', conf, problem=(3, 1)) + self.check('first: document\n' + '---\n' + 'key: val\n', conf, problem=(2, 1)) + + def test_multiple_documents(self): + conf = 'document-start: {present: true}' + self.check('---\n' + 'first: document\n' + '...\n' + '---\n' + 'second: document\n' + '...\n' + '---\n' + 'third: document\n', conf) + self.check('---\n' + 'first: document\n' + '---\n' + 'second: document\n' + '---\n' + 'third: document\n', conf) + self.check('---\n' + 'first: document\n' + '...\n' + 'second: document\n' + '---\n' + 'third: document\n', conf, problem=(4, 1, 'syntax')) + + def test_directives(self): + conf = 'document-start: {present: true}' + self.check('%YAML 1.2\n' + '---\n' + 'doc: ument\n' + '...\n', conf) + self.check('%YAML 1.2\n' + '%TAG ! tag:clarkevans.com,2002:\n' + '---\n' + 'doc: ument\n' + '...\n', conf) + self.check('---\n' + 'doc: 1\n' + '...\n' + '%YAML 1.2\n' + '---\n' + 'doc: 2\n' + '...\n', conf) diff --git a/tests/rules/test_empty_lines.py b/tests/rules/test_empty_lines.py new file mode 100644 index 0000000..fbecbcd --- /dev/null +++ b/tests/rules/test_empty_lines.py @@ -0,0 +1,98 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class EmptyLinesTestCase(RuleTestCase): + rule_id = 'empty-lines' + + def test_disabled(self): + conf = ('empty-lines: disable\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('\n\n', conf) + self.check('\n\n\n\n\n\n\n\n\n', conf) + self.check('some text\n\n\n\n\n\n\n\n\n', conf) + self.check('\n\n\n\n\n\n\n\n\nsome text', conf) + self.check('\n\n\nsome text\n\n\n', conf) + + def test_empty_document(self): + conf = ('empty-lines: {max: 0, max-start: 0, max-end: 0}\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + + def test_0_empty_lines(self): + conf = ('empty-lines: {max: 0, max-start: 0, max-end: 0}\n' + 'new-line-at-end-of-file: disable\n') + self.check('---\n', conf) + self.check('---\ntext\n\ntext', conf, problem=(3, 1)) + self.check('---\ntext\n\ntext\n', conf, problem=(3, 1)) + + def test_10_empty_lines(self): + conf = 'empty-lines: {max: 10, max-start: 0, max-end: 0}' + self.check('---\nintro\n\n\n\n\n\n\n\n\n\n\nconclusion\n', conf) + self.check('---\nintro\n\n\n\n\n\n\n\n\n\n\n\nconclusion\n', conf, + problem=(13, 1)) + + def test_spaces(self): + conf = ('empty-lines: {max: 1, max-start: 0, max-end: 0}\n' + 'trailing-spaces: disable\n') + self.check('---\nintro\n\n \n\nconclusion\n', conf) + self.check('---\nintro\n\n \n\n\nconclusion\n', conf, problem=(6, 1)) + + def test_empty_lines_at_start(self): + conf = ('empty-lines: {max: 2, max-start: 4, max-end: 0}\n' + 'document-start: disable\n') + self.check('\n\n\n\nnon empty\n', conf) + self.check('\n\n\n\n\nnon empty\n', conf, problem=(5, 1)) + + conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n' + 'document-start: disable\n') + self.check('non empty\n', conf) + self.check('\nnon empty\n', conf, problem=(1, 1)) + + def test_empty_lines_at_end(self): + conf = ('empty-lines: {max: 2, max-start: 0, max-end: 4}\n' + 'document-start: disable\n') + self.check('non empty\n\n\n\n\n', conf) + self.check('non empty\n\n\n\n\n\n', conf, problem=(6, 1)) + conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n' + 'document-start: disable\n') + self.check('non empty\n', conf) + self.check('non empty\n\n', conf, problem=(2, 1)) + + def test_with_dos_newlines(self): + conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n' + 'new-lines: {type: dos}\n' + 'document-start: disable\n') + self.check('---\r\n', conf) + self.check('---\r\ntext\r\n\r\ntext\r\n', conf) + self.check('\r\n---\r\ntext\r\n\r\ntext\r\n', conf, + problem=(1, 1)) + self.check('\r\n\r\n\r\n---\r\ntext\r\n\r\ntext\r\n', conf, + problem=(3, 1)) + self.check('---\r\ntext\r\n\r\n\r\n\r\ntext\r\n', conf, + problem=(5, 1)) + self.check('---\r\ntext\r\n\r\n\r\n\r\n\r\n\r\n\r\ntext\r\n', conf, + problem=(8, 1)) + self.check('---\r\ntext\r\n\r\ntext\r\n\r\n', conf, + problem=(5, 1)) + self.check('---\r\ntext\r\n\r\ntext\r\n\r\n\r\n\r\n', conf, + problem=(7, 1)) diff --git a/tests/rules/test_empty_values.py b/tests/rules/test_empty_values.py new file mode 100644 index 0000000..653f218 --- /dev/null +++ b/tests/rules/test_empty_values.py @@ -0,0 +1,368 @@ +# Copyright (C) 2017 Greg Dubicki +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class EmptyValuesTestCase(RuleTestCase): + rule_id = 'empty-values' + + def test_disabled(self): + conf = ('empty-values: disable\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + 'foo:\n', conf) + self.check('---\n' + 'foo:\n' + ' bar:\n', conf) + self.check('---\n' + '{a:}\n', conf) + self.check('---\n' + 'foo: {a:}\n', conf) + self.check('---\n' + '- {a:}\n' + '- {a:, b: 2}\n' + '- {a: 1, b:}\n' + '- {a: 1, b: , }\n', conf) + self.check('---\n' + '{a: {b: , c: {d: 4, e:}}, f:}\n', conf) + + def test_in_block_mappings_disabled(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n', conf) + self.check('---\n' + 'foo:\n' + 'bar: aaa\n', conf) + + def test_in_block_mappings_single_line(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'implicitly-null:\n', conf, problem1=(2, 17)) + self.check('---\n' + 'implicitly-null:with-colons:in-key:\n', conf, + problem1=(2, 36)) + self.check('---\n' + 'implicitly-null:with-colons:in-key2:\n', conf, + problem1=(2, 37)) + + def test_in_block_mappings_all_lines(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + 'bar:\n' + 'foobar:\n', conf, problem1=(2, 5), + problem2=(3, 5), problem3=(4, 8)) + + def test_in_block_mappings_explicit_end_of_document(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + '...\n', conf, problem1=(2, 5)) + + def test_in_block_mappings_not_end_of_document(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + 'bar:\n' + ' aaa\n', conf, problem1=(2, 5)) + + def test_in_block_mappings_different_level(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + ' bar:\n' + 'aaa: bbb\n', conf, problem1=(3, 6)) + + def test_in_block_mappings_empty_flow_mapping(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + 'foo: {a:}\n', conf) + self.check('---\n' + '- {a:, b: 2}\n' + '- {a: 1, b:}\n' + '- {a: 1, b: , }\n', conf) + + def test_in_block_mappings_empty_block_sequence(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + ' -\n', conf) + + def test_in_block_mappings_not_empty_or_explicit_null(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + ' bar:\n' + ' aaa\n', conf) + self.check('---\n' + 'explicitly-null: null\n', conf) + self.check('---\n' + 'explicitly-null:with-colons:in-key: null\n', conf) + self.check('---\n' + 'false-null: nulL\n', conf) + self.check('---\n' + 'empty-string: \'\'\n', conf) + self.check('---\n' + 'nullable-boolean: false\n', conf) + self.check('---\n' + 'nullable-int: 0\n', conf) + self.check('---\n' + 'First occurrence: &anchor Foo\n' + 'Second occurrence: *anchor\n', conf) + + def test_in_block_mappings_various_explicit_null(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'null-alias: ~\n', conf) + self.check('---\n' + 'null-key1: {?: val}\n', conf) + self.check('---\n' + 'null-key2: {? !!null "": val}\n', conf) + + def test_in_block_mappings_comments(self): + conf = ('empty-values: {forbid-in-block-mappings: true,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n' + 'comments: disable\n') + self.check('---\n' + 'empty: # comment\n' + 'foo:\n' + ' bar: # comment\n', conf, + problem1=(2, 7), + problem2=(4, 7)) + + def test_in_flow_mappings_disabled(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + '{a:}\n', conf) + self.check('---\n' + 'foo: {a:}\n', conf) + self.check('---\n' + '- {a:}\n' + '- {a:, b: 2}\n' + '- {a: 1, b:}\n' + '- {a: 1, b: , }\n', conf) + self.check('---\n' + '{a: {b: , c: {d: 4, e:}}, f:}\n', conf) + + def test_in_flow_mappings_single_line(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: true,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + '{a:}\n', conf, + problem=(2, 4)) + self.check('---\n' + 'foo: {a:}\n', conf, + problem=(2, 9)) + self.check('---\n' + '- {a:}\n' + '- {a:, b: 2}\n' + '- {a: 1, b:}\n' + '- {a: 1, b: , }\n', conf, + problem1=(2, 6), + problem2=(3, 6), + problem3=(4, 12), + problem4=(5, 12)) + self.check('---\n' + '{a: {b: , c: {d: 4, e:}}, f:}\n', conf, + problem1=(2, 8), + problem2=(2, 23), + problem3=(2, 29)) + + def test_in_flow_mappings_multi_line(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: true,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + 'foo: {\n' + ' a:\n' + '}\n', conf, + problem=(3, 5)) + self.check('---\n' + '{\n' + ' a: {\n' + ' b: ,\n' + ' c: {\n' + ' d: 4,\n' + ' e:\n' + ' }\n' + ' },\n' + ' f:\n' + '}\n', conf, + problem1=(4, 7), + problem2=(7, 9), + problem3=(10, 5)) + + def test_in_flow_mappings_various_explicit_null(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: true,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n') + self.check('---\n' + '{explicit-null: null}\n', conf) + self.check('---\n' + '{null-alias: ~}\n', conf) + self.check('---\n' + 'null-key1: {?: val}\n', conf) + self.check('---\n' + 'null-key2: {? !!null "": val}\n', conf) + + def test_in_flow_mappings_comments(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: true,\n' + ' forbid-in-block-sequences: false}\n' + 'braces: disable\n' + 'commas: disable\n' + 'comments: disable\n') + self.check('---\n' + '{\n' + ' a: {\n' + ' b: , # comment\n' + ' c: {\n' + ' d: 4, # comment\n' + ' e: # comment\n' + ' }\n' + ' },\n' + ' f: # comment\n' + '}\n', conf, + problem1=(4, 7), + problem2=(7, 9), + problem3=(10, 5)) + + def test_in_block_sequences_disabled(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: false}\n') + self.check('---\n' + 'foo:\n' + ' - bar\n' + ' -\n', conf) + self.check('---\n' + 'foo:\n' + ' -\n', conf) + + def test_in_block_sequences_primative_item(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: true}\n') + self.check('---\n' + 'foo:\n' + ' -\n', conf, + problem=(3, 4)) + self.check('---\n' + 'foo:\n' + ' - bar\n' + ' -\n', conf, + problem=(4, 4)) + self.check('---\n' + 'foo:\n' + ' - 1\n' + ' - 2\n' + ' -\n', conf, + problem=(5, 4)) + self.check('---\n' + 'foo:\n' + ' - true\n', conf) + + def test_in_block_sequences_complex_objects(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: true}\n') + self.check('---\n' + 'foo:\n' + ' - a: 1\n', conf) + self.check('---\n' + 'foo:\n' + ' - a: 1\n' + ' -\n', conf, + problem=(4, 4)) + self.check('---\n' + 'foo:\n' + ' - a: 1\n' + ' b: 2\n' + ' -\n', conf, + problem=(5, 4)) + self.check('---\n' + 'foo:\n' + ' - a: 1\n' + ' - b: 2\n' + ' -\n', conf, + problem=(5, 4)) + self.check('---\n' + 'foo:\n' + ' - - a\n' + ' - b: 2\n' + ' -\n', conf, + problem=(5, 6)) + self.check('---\n' + 'foo:\n' + ' - - a\n' + ' - b: 2\n' + ' -\n', conf, + problem=(5, 4)) + + def test_in_block_sequences_various_explicit_null(self): + conf = ('empty-values: {forbid-in-block-mappings: false,\n' + ' forbid-in-flow-mappings: false,\n' + ' forbid-in-block-sequences: true}\n') + self.check('---\n' + 'foo:\n' + ' - null\n', conf) + self.check('---\n' + '- null\n', conf) + self.check('---\n' + 'foo:\n' + ' - bar: null\n' + ' - null\n', conf) + self.check('---\n' + '- null\n' + '- null\n', conf) + self.check('---\n' + '- - null\n' + ' - null\n', conf) diff --git a/tests/rules/test_float_values.py b/tests/rules/test_float_values.py new file mode 100644 index 0000000..8aa980c --- /dev/null +++ b/tests/rules/test_float_values.py @@ -0,0 +1,128 @@ +# Copyright (C) 2022 the yamllint contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class FloatValuesTestCase(RuleTestCase): + rule_id = 'float-values' + + def test_disabled(self): + conf = 'float-values: disable\n' + self.check('---\n' + '- 0.0\n' + '- .NaN\n' + '- .INF\n' + '- .1\n' + '- 10e-6\n', + conf) + + def test_numeral_before_decimal(self): + conf = ( + 'float-values:\n' + ' require-numeral-before-decimal: true\n' + ' forbid-scientific-notation: false\n' + ' forbid-nan: false\n' + ' forbid-inf: false\n') + self.check('---\n' + '- 0.0\n' + '- .1\n' + '- \'.1\'\n' + '- string.1\n' + '- .1string\n' + '- !custom_tag .2\n' + '- &angle1 0.0\n' + '- *angle1\n' + '- &angle2 .3\n' + '- *angle2\n', + conf, + problem1=(3, 3), + problem2=(10, 11)) + + def test_scientific_notation(self): + conf = ( + 'float-values:\n' + ' require-numeral-before-decimal: false\n' + ' forbid-scientific-notation: true\n' + ' forbid-nan: false\n' + ' forbid-inf: false\n') + self.check('---\n' + '- 10e6\n' + '- 10e-6\n' + '- 0.00001\n' + '- \'10e-6\'\n' + '- string10e-6\n' + '- 10e-6string\n' + '- !custom_tag 10e-6\n' + '- &angle1 0.000001\n' + '- *angle1\n' + '- &angle2 10e-6\n' + '- *angle2\n' + '- &angle3 10e6\n' + '- *angle3\n', + conf, + problem1=(2, 3), + problem2=(3, 3), + problem3=(11, 11), + problem4=(13, 11)) + + def test_nan(self): + conf = ( + 'float-values:\n' + ' require-numeral-before-decimal: false\n' + ' forbid-scientific-notation: false\n' + ' forbid-nan: true\n' + ' forbid-inf: false\n') + self.check('---\n' + '- .NaN\n' + '- .NAN\n' + '- \'.NaN\'\n' + '- a.NaN\n' + '- .NaNa\n' + '- !custom_tag .NaN\n' + '- &angle .nan\n' + '- *angle\n', + conf, + problem1=(2, 3), + problem2=(3, 3), + problem3=(8, 10)) + + def test_inf(self): + conf = ( + 'float-values:\n' + ' require-numeral-before-decimal: false\n' + ' forbid-scientific-notation: false\n' + ' forbid-nan: false\n' + ' forbid-inf: true\n') + self.check('---\n' + '- .inf\n' + '- .INF\n' + '- -.inf\n' + '- -.INF\n' + '- \'.inf\'\n' + '- ∞.infinity\n' + '- .infinity∞\n' + '- !custom_tag .inf\n' + '- &angle .inf\n' + '- *angle\n' + '- &angle -.inf\n' + '- *angle\n', + conf, + problem1=(2, 3), + problem2=(3, 3), + problem3=(4, 3), + problem4=(5, 3), + problem5=(10, 10), + problem6=(12, 10)) diff --git a/tests/rules/test_hyphens.py b/tests/rules/test_hyphens.py new file mode 100644 index 0000000..a0ec577 --- /dev/null +++ b/tests/rules/test_hyphens.py @@ -0,0 +1,105 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class HyphenTestCase(RuleTestCase): + rule_id = 'hyphens' + + def test_disabled(self): + conf = 'hyphens: disable' + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf) + self.check('---\n' + 'object:\n' + ' subobject:\n' + ' - elem1\n' + ' - elem2\n', conf) + self.check('---\n' + 'object:\n' + ' subobject:\n' + ' - elem1\n' + ' - elem2\n', conf) + + def test_enabled(self): + conf = 'hyphens: {max-spaces-after: 1}' + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf, problem=(3, 3)) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf, problem1=(2, 3), problem2=(3, 3)) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf, problem=(2, 3)) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf, problem=(4, 5)) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf, problem1=(3, 5), problem2=(4, 5)) + self.check('---\n' + 'object:\n' + ' subobject:\n' + ' - elem1\n' + ' - elem2\n', conf, problem=(5, 7)) + self.check('---\n' + 'object:\n' + ' subobject:\n' + ' - elem1\n' + ' - elem2\n', conf, problem1=(4, 7), problem2=(5, 7)) + + def test_max_3(self): + conf = 'hyphens: {max-spaces-after: 3}' + self.check('---\n' + '- elem1\n' + '- elem2\n', conf) + self.check('---\n' + '- elem1\n' + '- elem2\n', conf, problem=(2, 5)) + self.check('---\n' + 'a:\n' + ' b:\n' + ' - elem1\n' + ' - elem2\n', conf) + self.check('---\n' + 'a:\n' + ' b:\n' + ' - elem1\n' + ' - elem2\n', conf, problem1=(4, 9), problem2=(5, 9)) diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py new file mode 100644 index 0000000..1c6eddb --- /dev/null +++ b/tests/rules/test_indentation.py @@ -0,0 +1,2160 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + +from yamllint.parser import token_or_comment_generator, Comment +from yamllint.rules.indentation import check + + +class IndentationStackTestCase(RuleTestCase): + # This test suite checks that the "indentation stack" built by the + # indentation rule is valid. It is important, since everything else in the + # rule relies on this stack. + + maxDiff = None + + def format_stack(self, stack): + """Transform the stack at a given moment into a printable string like: + + B_MAP:0 KEY:0 VAL:5 + """ + return ' '.join(map(str, stack[1:])) + + def full_stack(self, source): + conf = {'spaces': 2, 'indent-sequences': True, + 'check-multi-line-strings': False} + context = {} + output = '' + for elem in [t for t in token_or_comment_generator(source) + if not isinstance(t, Comment)]: + list(check(conf, elem.curr, elem.prev, elem.next, elem.nextnext, + context)) + + token_type = (elem.curr.__class__.__name__ + .replace('Token', '') + .replace('Block', 'B').replace('Flow', 'F') + .replace('Sequence', 'Seq') + .replace('Mapping', 'Map')) + if token_type in ('StreamStart', 'StreamEnd'): + continue + output += '{:>9} {}\n'.format(token_type, + self.format_stack(context['stack'])) + return output + + def test_simple_mapping(self): + self.assertMultiLineEqual( + self.full_stack('key: val\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack(' key: val\n'), + 'BMapStart B_MAP:5\n' + ' Key B_MAP:5 KEY:5\n' + ' Scalar B_MAP:5 KEY:5\n' + ' Value B_MAP:5 KEY:5 VAL:10\n' + ' Scalar B_MAP:5\n' + ' BEnd \n') + + def test_simple_sequence(self): + self.assertMultiLineEqual( + self.full_stack('- 1\n' + '- 2\n' + '- 3\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('key:\n' + ' - 1\n' + ' - 2\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + ' BEnd \n') + + def test_non_indented_sequences(self): + # There seems to be a bug in pyyaml: depending on the indentation, a + # sequence does not produce the same tokens. More precisely, the + # following YAML: + # usr: + # - lib + # produces a BlockSequenceStartToken and a BlockEndToken around the + # "lib" sequence, whereas the following: + # usr: + # - lib + # does not (both two tokens are omitted). + # So, yamllint must create fake 'B_SEQ'. This test makes sure it does. + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + ' - lib\n' + 'var: cache\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- lib\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- lib\n' + 'var: cache\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0\n' + # missing BEnd here + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- []\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + 'FSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 F_SEQ:3\n' + ' FSeqEnd B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- k:\n' + ' v\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + '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 + ' 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 + ' BEnd \n') + + def test_flows(self): + self.assertMultiLineEqual( + self.full_stack('usr: [\n' + ' {k:\n' + ' v}\n' + ' ]\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + 'FSeqStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n' + 'FMapStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n' + ' Key B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n' + ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n' + ' Value B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3 VAL:5\n' + ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n' + ' FMapEnd B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n' + ' FSeqEnd B_MAP:0\n' + ' BEnd \n') + + def test_anchors(self): + self.assertMultiLineEqual( + self.full_stack('key: &anchor value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Anchor B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('key: &anchor\n' + ' value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Anchor B_MAP:0 KEY:0 VAL:2\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('- &anchor value\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Anchor B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('- &anchor\n' + ' value\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Anchor B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('- &anchor\n' + ' - 1\n' + ' - 2\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Anchor B_SEQ:0 B_ENT:2\n' + 'BSeqStart B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEnd B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('&anchor key:\n' + ' value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Anchor B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('pre:\n' + ' &anchor1 0\n' + '&anchor2 key:\n' + ' value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Anchor B_MAP:0 KEY:0 VAL:2\n' + ' Scalar B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Anchor B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('sequence: &anchor\n' + '- entry\n' + '- &anchor\n' + ' - nested\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Anchor B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Anchor B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + def test_tags(self): + self.assertMultiLineEqual( + self.full_stack('key: !!tag value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Tag B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('- !!map # Block collection\n' + ' foo : bar\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Tag B_SEQ:0 B_ENT:2\n' + 'BMapStart B_SEQ:0 B_ENT:2 B_MAP:2\n' + ' Key B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' + ' Scalar B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' + ' Value B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:8\n' + ' Scalar B_SEQ:0 B_ENT:2 B_MAP:2\n' + ' BEnd B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('- !!seq\n' + ' - nested item\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Tag B_SEQ:0 B_ENT:2\n' + 'BSeqStart B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEnd B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('sequence: !!seq\n' + '- entry\n' + '- !!seq\n' + ' - nested\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + ' Tag B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Tag B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + def test_flows_imbrication(self): + self.assertMultiLineEqual( + self.full_stack('[[val]]\n'), + 'FSeqStart F_SEQ:1\n' + 'FSeqStart F_SEQ:1 F_SEQ:2\n' + ' Scalar F_SEQ:1 F_SEQ:2\n' + ' FSeqEnd F_SEQ:1\n' + ' FSeqEnd \n') + + self.assertMultiLineEqual( + self.full_stack('[[val], [val2]]\n'), + 'FSeqStart F_SEQ:1\n' + 'FSeqStart F_SEQ:1 F_SEQ:2\n' + ' Scalar F_SEQ:1 F_SEQ:2\n' + ' FSeqEnd F_SEQ:1\n' + ' FEntry F_SEQ:1\n' + 'FSeqStart F_SEQ:1 F_SEQ:9\n' + ' Scalar F_SEQ:1 F_SEQ:9\n' + ' FSeqEnd F_SEQ:1\n' + ' FSeqEnd \n') + + self.assertMultiLineEqual( + self.full_stack('{{key}}\n'), + 'FMapStart F_MAP:1\n' + 'FMapStart F_MAP:1 F_MAP:2\n' + ' Scalar F_MAP:1 F_MAP:2\n' + ' FMapEnd F_MAP:1\n' + ' FMapEnd \n') + + self.assertMultiLineEqual( + self.full_stack('[key]: value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n' + ' Scalar B_MAP:0 KEY:0 F_SEQ:1\n' + ' FSeqEnd B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:7\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('[[key]]: value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n' + 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n' + ' Scalar B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n' + ' FSeqEnd B_MAP:0 KEY:0 F_SEQ:1\n' + ' FSeqEnd B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:9\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('{key}: value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n' + ' Scalar B_MAP:0 KEY:0 F_MAP:1\n' + ' FMapEnd B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:7\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('{key: value}: value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n' + ' Key B_MAP:0 KEY:0 F_MAP:1 KEY:1\n' + ' Scalar B_MAP:0 KEY:0 F_MAP:1 KEY:1\n' + ' Value B_MAP:0 KEY:0 F_MAP:1 KEY:1 VAL:6\n' + ' Scalar B_MAP:0 KEY:0 F_MAP:1\n' + ' FMapEnd B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:14\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('{{key}}: value\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n' + 'FMapStart B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n' + ' Scalar B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n' + ' FMapEnd B_MAP:0 KEY:0 F_MAP:1\n' + ' FMapEnd B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:9\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + self.assertMultiLineEqual( + self.full_stack('{{key}: val, {key2}: {val2}}\n'), + 'FMapStart F_MAP:1\n' + ' Key F_MAP:1 KEY:1\n' + 'FMapStart F_MAP:1 KEY:1 F_MAP:2\n' + ' Scalar F_MAP:1 KEY:1 F_MAP:2\n' + ' FMapEnd F_MAP:1 KEY:1\n' + ' Value F_MAP:1 KEY:1 VAL:8\n' + ' Scalar F_MAP:1\n' + ' FEntry F_MAP:1\n' + ' Key F_MAP:1 KEY:1\n' + 'FMapStart F_MAP:1 KEY:1 F_MAP:14\n' + ' Scalar F_MAP:1 KEY:1 F_MAP:14\n' + ' FMapEnd F_MAP:1 KEY:1\n' + ' Value F_MAP:1 KEY:1 VAL:21\n' + 'FMapStart F_MAP:1 KEY:1 VAL:21 F_MAP:22\n' + ' Scalar F_MAP:1 KEY:1 VAL:21 F_MAP:22\n' + ' FMapEnd F_MAP:1\n' + ' FMapEnd \n') + + self.assertMultiLineEqual( + self.full_stack('{[{{[val]}}, [{[key]: val2}]]}\n'), + 'FMapStart F_MAP:1\n' + 'FSeqStart F_MAP:1 F_SEQ:2\n' + 'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3\n' + 'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n' + 'FSeqStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n' + ' Scalar F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n' + ' FSeqEnd F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n' + ' FMapEnd F_MAP:1 F_SEQ:2 F_MAP:3\n' + ' FMapEnd F_MAP:1 F_SEQ:2\n' + ' FEntry F_MAP:1 F_SEQ:2\n' + 'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14\n' + 'FMapStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n' + ' Key F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n' + 'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n' + ' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n' + ' FSeqEnd F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n' + ' Value F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 VAL:22\n' + ' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n' + ' FMapEnd F_MAP:1 F_SEQ:2 F_SEQ:14\n' + ' FSeqEnd F_MAP:1 F_SEQ:2\n' + ' FSeqEnd F_MAP:1\n' + ' FMapEnd \n') + + +class IndentationTestCase(RuleTestCase): + rule_id = 'indentation' + + def test_disabled(self): + conf = 'indentation: disable' + self.check('---\n' + 'object:\n' + ' k1: v1\n' + 'obj2:\n' + ' k2:\n' + ' - 8\n' + ' k3:\n' + ' val\n' + '...\n', conf) + self.check('---\n' + ' o:\n' + ' k1: v1\n' + ' p:\n' + ' k3:\n' + ' val\n' + '...\n', conf) + self.check('---\n' + ' - o:\n' + ' k1: v1\n' + ' - p: kdjf\n' + ' - q:\n' + ' k3:\n' + ' - val\n' + '...\n', conf) + + def test_one_space(self): + conf = 'indentation: {spaces: 1, indent-sequences: false}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + conf = 'indentation: {spaces: 1, indent-sequences: true}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + + def test_two_spaces(self): + conf = 'indentation: {spaces: 2, indent-sequences: false}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + ' k4:\n' + ' -\n' + ' k5: v3\n' + '...\n', conf) + conf = 'indentation: {spaces: 2, indent-sequences: true}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + + def test_three_spaces(self): + conf = 'indentation: {spaces: 3, indent-sequences: false}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + conf = 'indentation: {spaces: 3, indent-sequences: true}' + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + + def test_consistent_spaces(self): + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: whatever}\n' + 'document-start: disable\n') + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + ' - b\n' + ' k2: v2\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf) + self.check('first is not indented:\n' + ' value is indented\n', conf) + self.check('first is not indented:\n' + ' value:\n' + ' is indented\n', conf) + self.check('- first is already indented:\n' + ' value is indented too\n', conf) + self.check('- first is already indented:\n' + ' value:\n' + ' is indented too\n', conf) + self.check('- first is already indented:\n' + ' value:\n' + ' is indented too\n', conf, problem=(3, 14)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem=(7, 5)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + + def test_consistent_spaces_and_indent_sequences(self): + conf = 'indentation: {spaces: consistent, indent-sequences: true}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(3, 1)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(7, 1)) + + conf = 'indentation: {spaces: consistent, indent-sequences: false}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 3)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(3, 3)) + + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: consistent}') + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(7, 1)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + + conf = 'indentation: {spaces: consistent, indent-sequences: whatever}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem1=(7, 5)) + + def test_indent_sequences_whatever(self): + conf = 'indentation: {spaces: 4, indent-sequences: whatever}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem=(3, 3)) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem=(7, 3)) + self.check('---\n' + 'list:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + '- a\n' + '- b\n' + '- c\n', conf, problem=(6, 1, 'syntax')) + + def test_indent_sequences_consistent(self): + conf = 'indentation: {spaces: 4, indent-sequences: consistent}' + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list:\n' + ' two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list:\n' + ' two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf) + self.check('---\n' + 'list one:\n' + '- 1\n' + '- 2\n' + '- 3\n' + 'list two:\n' + ' - a\n' + ' - b\n' + ' - c\n', conf, problem=(7, 5)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem=(7, 1)) + self.check('---\n' + 'list one:\n' + ' - 1\n' + ' - 2\n' + ' - 3\n' + 'list two:\n' + '- a\n' + '- b\n' + '- c\n', conf, problem1=(3, 2), problem2=(7, 1)) + + def test_direct_flows(self): + # flow: [ ... + # ] + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'a: {x: 1,\n' + ' y,\n' + ' z: 1}\n', conf) + self.check('---\n' + 'a: {x: 1,\n' + ' y,\n' + ' z: 1}\n', conf, problem=(3, 4)) + self.check('---\n' + 'a: {x: 1,\n' + ' y,\n' + ' z: 1}\n', conf, problem=(3, 6)) + self.check('---\n' + 'a: {x: 1,\n' + ' y, z: 1}\n', conf, problem=(3, 3)) + self.check('---\n' + 'a: {x: 1,\n' + ' y, z: 1\n' + '}\n', conf) + self.check('---\n' + 'a: {x: 1,\n' + ' y, z: 1\n' + '}\n', conf, problem=(3, 3)) + self.check('---\n' + 'a: [x,\n' + ' y,\n' + ' z]\n', conf) + self.check('---\n' + 'a: [x,\n' + ' y,\n' + ' z]\n', conf, problem=(3, 4)) + self.check('---\n' + 'a: [x,\n' + ' y,\n' + ' z]\n', conf, problem=(3, 6)) + self.check('---\n' + 'a: [x,\n' + ' y, z]\n', conf, problem=(3, 3)) + self.check('---\n' + 'a: [x,\n' + ' y, z\n' + ']\n', conf) + self.check('---\n' + 'a: [x,\n' + ' y, z\n' + ']\n', conf, problem=(3, 3)) + + def test_broken_flows(self): + # flow: [ + # ... + # ] + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'a: {\n' + ' x: 1,\n' + ' y, z: 1\n' + '}\n', conf) + self.check('---\n' + 'a: {\n' + ' x: 1,\n' + ' y, z: 1}\n', conf) + self.check('---\n' + 'a: {\n' + ' x: 1,\n' + ' y, z: 1\n' + '}\n', conf, problem=(4, 3)) + self.check('---\n' + 'a: {\n' + ' x: 1,\n' + ' y, z: 1\n' + ' }\n', conf, problem=(5, 3)) + self.check('---\n' + 'a: [\n' + ' x,\n' + ' y, z\n' + ']\n', conf) + self.check('---\n' + 'a: [\n' + ' x,\n' + ' y, z]\n', conf) + self.check('---\n' + 'a: [\n' + ' x,\n' + ' y, z\n' + ']\n', conf, problem=(4, 3)) + self.check('---\n' + 'a: [\n' + ' x,\n' + ' y, z\n' + ' ]\n', conf, problem=(5, 3)) + self.check('---\n' + 'obj: {\n' + ' a: 1,\n' + ' b: 2,\n' + ' c: 3\n' + '}\n', conf, problem1=(4, 4), problem2=(5, 2)) + self.check('---\n' + 'list: [\n' + ' 1,\n' + ' 2,\n' + ' 3\n' + ']\n', conf, problem1=(4, 4), problem2=(5, 2)) + self.check('---\n' + 'top:\n' + ' rules: [\n' + ' 1, 2,\n' + ' ]\n', conf) + self.check('---\n' + 'top:\n' + ' rules: [\n' + ' 1, 2,\n' + ']\n' + ' rulez: [\n' + ' 1, 2,\n' + ' ]\n', conf, problem1=(5, 1), problem2=(8, 5)) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' here: {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' here: {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n' + ' there: {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf, problem1=(7, 7), problem2=(11, 3)) + conf = 'indentation: {spaces: 2}' + self.check('---\n' + 'a: {\n' + ' x: 1,\n' + ' y, z: 1\n' + '}\n', conf, problem=(3, 4)) + self.check('---\n' + 'a: [\n' + ' x,\n' + ' y, z\n' + ']\n', conf, problem=(3, 4)) + + def test_cleared_flows(self): + # flow: + # [ + # ... + # ] + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'top:\n' + ' rules:\n' + ' {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf, problem=(5, 8)) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf, problem=(4, 4)) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf, problem=(7, 4)) + self.check('---\n' + 'top:\n' + ' rules:\n' + ' {\n' + ' foo: 1,\n' + ' bar: 2\n' + ' }\n', conf, problem=(7, 6)) + self.check('---\n' + 'top:\n' + ' [\n' + ' a, b, c\n' + ' ]\n', conf) + self.check('---\n' + 'top:\n' + ' [\n' + ' a, b, c\n' + ' ]\n', conf, problem=(4, 6)) + self.check('---\n' + 'top:\n' + ' [\n' + ' a, b, c\n' + ' ]\n', conf, problem=(4, 6)) + self.check('---\n' + 'top:\n' + ' [\n' + ' a, b, c\n' + ' ]\n', conf, problem=(5, 4)) + self.check('---\n' + 'top:\n' + ' rules: [\n' + ' {\n' + ' foo: 1\n' + ' },\n' + ' {\n' + ' foo: 2,\n' + ' bar: [\n' + ' a, b, c\n' + ' ],\n' + ' },\n' + ' ]\n', conf) + self.check('---\n' + 'top:\n' + ' rules: [\n' + ' {\n' + ' foo: 1\n' + ' },\n' + ' {\n' + ' foo: 2,\n' + ' bar: [\n' + ' a, b, c\n' + ' ],\n' + ' },\n' + ']\n', conf, problem1=(5, 6), problem2=(6, 6), + problem3=(9, 9), problem4=(11, 7), problem5=(13, 1)) + + def test_under_indented(self): + conf = 'indentation: {spaces: 2, indent-sequences: consistent}' + self.check('---\n' + 'object:\n' + ' val: 1\n' + '...\n', conf, problem=(3, 2)) + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + '...\n', conf, problem=(4, 4)) + self.check('---\n' + 'object:\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + '...\n', conf, problem=(5, 6, 'syntax')) + conf = 'indentation: {spaces: 4, indent-sequences: consistent}' + self.check('---\n' + 'object:\n' + ' val: 1\n' + '...\n', conf, problem=(3, 4)) + self.check('---\n' + '- el1\n' + '- el2:\n' + ' - subel\n' + '...\n', conf, problem=(4, 4)) + self.check('---\n' + 'object:\n' + ' k3:\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf, problem=(5, 10, 'syntax')) + conf = 'indentation: {spaces: 2, indent-sequences: true}' + self.check('---\n' + 'a:\n' + '-\n' # empty list + 'b: c\n' + '...\n', conf, problem=(3, 1)) + conf = 'indentation: {spaces: 2, indent-sequences: consistent}' + self.check('---\n' + 'a:\n' + ' -\n' # empty list + 'b:\n' + '-\n' + 'c: d\n' + '...\n', conf, problem=(5, 1)) + + def test_over_indented(self): + conf = 'indentation: {spaces: 2, indent-sequences: consistent}' + self.check('---\n' + 'object:\n' + ' val: 1\n' + '...\n', conf, problem=(3, 4)) + self.check('---\n' + 'object:\n' + ' k1:\n' + ' - a\n' + '...\n', conf, problem=(4, 6)) + self.check('---\n' + 'object:\n' + ' k3:\n' + ' - name: Unix\n' + ' date: 1969\n' + '...\n', conf, problem=(5, 12, 'syntax')) + conf = 'indentation: {spaces: 4, indent-sequences: consistent}' + self.check('---\n' + 'object:\n' + ' val: 1\n' + '...\n', conf, problem=(3, 6)) + self.check('---\n' + ' object:\n' + ' val: 1\n' + '...\n', conf, problem=(2, 2)) + self.check('---\n' + '- el1\n' + '- el2:\n' + ' - subel\n' + '...\n', conf, problem=(4, 6)) + self.check('---\n' + '- el1\n' + '- el2:\n' + ' - subel\n' + '...\n', conf, problem=(4, 15)) + self.check('---\n' + ' - el1\n' + ' - el2:\n' + ' - subel\n' + '...\n', conf, + problem=(2, 3)) + self.check('---\n' + 'object:\n' + ' k3:\n' + ' - name: Linux\n' + ' date: 1991\n' + '...\n', conf, problem=(5, 16, 'syntax')) + conf = 'indentation: {spaces: 4, indent-sequences: whatever}' + self.check('---\n' + ' - el1\n' + ' - el2:\n' + ' - subel\n' + '...\n', conf, + problem=(2, 3)) + conf = 'indentation: {spaces: 2, indent-sequences: false}' + self.check('---\n' + 'a:\n' + ' -\n' # empty list + 'b: c\n' + '...\n', conf, problem=(3, 3)) + conf = 'indentation: {spaces: 2, indent-sequences: consistent}' + self.check('---\n' + 'a:\n' + '-\n' # empty list + 'b:\n' + ' -\n' + 'c: d\n' + '...\n', conf, problem=(5, 3)) + + def test_multi_lines(self): + conf = 'indentation: {spaces: consistent, indent-sequences: true}' + self.check('---\n' + 'long_string: >\n' + ' bla bla blah\n' + ' blah bla bla\n' + '...\n', conf) + self.check('---\n' + '- long_string: >\n' + ' bla bla blah\n' + ' blah bla bla\n' + '...\n', conf) + self.check('---\n' + 'obj:\n' + ' - long_string: >\n' + ' bla bla blah\n' + ' blah bla bla\n' + '...\n', conf) + + def test_empty_value(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'key1:\n' + 'key2: not empty\n' + 'key3:\n' + '...\n', conf) + self.check('---\n' + '-\n' + '- item 2\n' + '-\n' + '...\n', conf) + + def test_nested_collections(self): + conf = 'indentation: {spaces: 2}' + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf) + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf, problem=(3, 2, 'syntax')) + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf, problem=(3, 4)) + conf = 'indentation: {spaces: 4}' + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf) + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf, problem=(3, 6)) + self.check('---\n' + '- o:\n' + ' k1: v1\n' + '...\n', conf, problem=(3, 8)) + self.check('---\n' + '- - - - item\n' + ' - elem 1\n' + ' - elem 2\n' + ' - - - - - very nested: a\n' + ' key: value\n' + '...\n', conf) + self.check('---\n' + ' - - - - item\n' + ' - elem 1\n' + ' - elem 2\n' + ' - - - - - very nested: a\n' + ' key: value\n' + '...\n', conf, problem=(2, 2)) + + def test_nested_collections_with_spaces_consistent(self): + """Tests behavior of {spaces: consistent} in nested collections to + ensure wrong-indentation is properly caught--especially when the + expected indent value is initially unknown. For details, see + https://github.com/adrienverge/yamllint/issues/485. + """ + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: true}') + self.check('---\n' + '- item:\n' + ' - elem\n' + '- item:\n' + ' - elem\n' + '...\n', conf, problem=(3, 3)) + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: false}') + self.check('---\n' + '- item:\n' + ' - elem\n' + '- item:\n' + ' - elem\n' + '...\n', conf, problem=(5, 5)) + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: consistent}') + self.check('---\n' + '- item:\n' + ' - elem\n' + '- item:\n' + ' - elem\n' + '...\n', conf, problem=(5, 5)) + conf = ('indentation: {spaces: consistent,\n' + ' indent-sequences: whatever}') + self.check('---\n' + '- item:\n' + ' - elem\n' + '- item:\n' + ' - elem\n' + '...\n', conf) + + def test_return(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'a:\n' + ' b:\n' + ' c:\n' + ' d:\n' + ' e:\n' + ' f:\n' + 'g:\n' + '...\n', conf) + self.check('---\n' + 'a:\n' + ' b:\n' + ' c:\n' + ' d:\n' + '...\n', conf, problem=(5, 4, 'syntax')) + self.check('---\n' + 'a:\n' + ' b:\n' + ' c:\n' + ' d:\n' + '...\n', conf, problem=(5, 2, 'syntax')) + + def test_first_line(self): + conf = ('indentation: {spaces: consistent}\n' + 'document-start: disable\n') + self.check(' a: 1\n', conf, problem=(1, 3)) + + def test_explicit_block_mappings(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'object:\n' + ' ? key\n' + ' : value\n', conf) + self.check('---\n' + 'object:\n' + ' ? key\n' + ' :\n' + ' value\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' ?\n' + ' key\n' + ' : value\n', conf) + self.check('---\n' + 'object:\n' + ' ?\n' + ' key\n' + ' :\n' + ' value\n' + '...\n', conf) + self.check('---\n' + '- ? key\n' + ' : value\n', conf) + self.check('---\n' + '- ? key\n' + ' :\n' + ' value\n' + '...\n', conf) + self.check('---\n' + '- ?\n' + ' key\n' + ' : value\n', conf) + self.check('---\n' + '- ?\n' + ' key\n' + ' :\n' + ' value\n' + '...\n', conf) + self.check('---\n' + 'object:\n' + ' ? key\n' + ' :\n' + ' value\n' + '...\n', conf, problem=(5, 8)) + self.check('---\n' + '- - ?\n' + ' key\n' + ' :\n' + ' value\n' + '...\n', conf, problem=(5, 7)) + self.check('---\n' + 'object:\n' + ' ?\n' + ' key\n' + ' :\n' + ' value\n' + '...\n', conf, problem1=(4, 8), problem2=(6, 10)) + self.check('---\n' + 'object:\n' + ' ?\n' + ' key\n' + ' :\n' + ' value\n' + '...\n', conf, problem1=(4, 10), problem2=(6, 8)) + + def test_clear_sequence_item(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + '-\n' + ' string\n' + '-\n' + ' map: ping\n' + '-\n' + ' - sequence\n' + ' -\n' + ' nested\n' + ' -\n' + ' >\n' + ' multi\n' + ' line\n' + '...\n', conf) + self.check('---\n' + '-\n' + ' string\n' + '-\n' + ' string\n', conf, problem=(5, 4)) + self.check('---\n' + '-\n' + ' map: ping\n' + '-\n' + ' map: ping\n', conf, problem=(5, 4)) + self.check('---\n' + '-\n' + ' - sequence\n' + '-\n' + ' - sequence\n', conf, problem=(5, 4)) + self.check('---\n' + '-\n' + ' -\n' + ' nested\n' + ' -\n' + ' nested\n', conf, problem1=(4, 4), problem2=(6, 6)) + self.check('---\n' + '-\n' + ' -\n' + ' >\n' + ' multi\n' + ' line\n' + '...\n', conf, problem=(4, 6)) + conf = 'indentation: {spaces: 2}' + self.check('---\n' + '-\n' + ' string\n' + '-\n' + ' string\n', conf, problem1=(3, 2), problem2=(5, 4)) + self.check('---\n' + '-\n' + ' map: ping\n' + '-\n' + ' map: ping\n', conf, problem1=(3, 2), problem2=(5, 4)) + self.check('---\n' + '-\n' + ' - sequence\n' + '-\n' + ' - sequence\n', conf, problem1=(3, 2), problem2=(5, 4)) + self.check('---\n' + '-\n' + ' -\n' + ' nested\n' + ' -\n' + ' nested\n', conf, problem1=(4, 4), problem2=(6, 6)) + + def test_anchors(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + 'key: &anchor value\n', conf) + self.check('---\n' + 'key: &anchor\n' + ' value\n', conf) + self.check('---\n' + '- &anchor value\n', conf) + self.check('---\n' + '- &anchor\n' + ' value\n', conf) + self.check('---\n' + 'key: &anchor [1,\n' + ' 2]\n', conf) + self.check('---\n' + 'key: &anchor\n' + ' [1,\n' + ' 2]\n', conf) + self.check('---\n' + 'key: &anchor\n' + ' - 1\n' + ' - 2\n', conf) + self.check('---\n' + '- &anchor [1,\n' + ' 2]\n', conf) + self.check('---\n' + '- &anchor\n' + ' [1,\n' + ' 2]\n', conf) + self.check('---\n' + '- &anchor\n' + ' - 1\n' + ' - 2\n', conf) + self.check('---\n' + 'key:\n' + ' &anchor1\n' + ' value\n', conf) + self.check('---\n' + 'pre:\n' + ' &anchor1 0\n' + '&anchor2 key:\n' + ' value\n', conf) + self.check('---\n' + 'machine0:\n' + ' /etc/hosts: &ref-etc-hosts\n' + ' content:\n' + ' - 127.0.0.1: localhost\n' + ' - ::1: localhost\n' + ' mode: 0644\n' + 'machine1:\n' + ' /etc/hosts: *ref-etc-hosts\n', conf) + self.check('---\n' + 'list:\n' + ' - k: v\n' + ' - &a truc\n' + ' - &b\n' + ' truc\n' + ' - k: *a\n', conf) + + def test_tags(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + '-\n' + ' "flow in block"\n' + '- >\n' + ' Block scalar\n' + '- !!map # Block collection\n' + ' foo: bar\n', conf) + + conf = 'indentation: {spaces: consistent, indent-sequences: false}' + self.check('---\n' + 'sequence: !!seq\n' + '- entry\n' + '- !!seq\n' + ' - nested\n', conf) + self.check('---\n' + 'mapping: !!map\n' + ' foo: bar\n' + 'Block style: !!map\n' + ' Clark: Evans\n' + ' Ingy: döt Net\n' + ' Oren: Ben-Kiki\n', conf) + self.check('---\n' + 'Flow style: !!map {Clark: Evans, Ingy: döt Net}\n' + 'Block style: !!seq\n' + '- Clark Evans\n' + '- Ingy döt Net\n', conf) + + def test_flows_imbrication(self): + conf = 'indentation: {spaces: consistent}' + self.check('---\n' + '[val]: value\n', conf) + self.check('---\n' + '{key}: value\n', conf) + self.check('---\n' + '{key: val}: value\n', conf) + self.check('---\n' + '[[val]]: value\n', conf) + self.check('---\n' + '{{key}}: value\n', conf) + self.check('---\n' + '{{key: val1}: val2}: value\n', conf) + self.check('---\n' + '- [val, {{key: val}: val}]: value\n' + '- {[val,\n' + ' {{key: val}: val}]}\n' + '- {[val,\n' + ' {{key: val,\n' + ' key2}}]}\n' + '- {{{{{moustaches}}}}}\n' + '- {{{{{moustache,\n' + ' moustache},\n' + ' moustache}},\n' + ' moustache}}\n', conf) + self.check('---\n' + '- {[val,\n' + ' {{key: val}: val}]}\n', + conf, problem=(3, 6)) + self.check('---\n' + '- {[val,\n' + ' {{key: val,\n' + ' key2}}]}\n', + conf, problem=(4, 6)) + self.check('---\n' + '- {{{{{moustache,\n' + ' moustache},\n' + ' moustache}},\n' + ' moustache}}\n', + conf, problem1=(4, 8), problem2=(5, 4)) + + +class ScalarIndentationTestCase(RuleTestCase): + rule_id = 'indentation' + + def test_basics_plain(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: false}\n' + 'document-start: disable\n') + self.check('multi\n' + 'line\n', conf) + self.check('multi\n' + ' line\n', conf) + self.check('- multi\n' + ' line\n', conf) + self.check('- multi\n' + ' line\n', conf) + self.check('a key: multi\n' + ' line\n', conf) + self.check('a key: multi\n' + ' line\n', conf) + self.check('a key: multi\n' + ' line\n', conf) + self.check('a key:\n' + ' multi\n' + ' line\n', conf) + self.check('- C code: void main() {\n' + ' printf("foo");\n' + ' }\n', conf) + self.check('- C code:\n' + ' void main() {\n' + ' printf("foo");\n' + ' }\n', conf) + + def test_check_multi_line_plain(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('multi\n' + ' line\n', conf, problem=(2, 2)) + self.check('- multi\n' + ' line\n', conf, problem=(2, 4)) + self.check('a key: multi\n' + ' line\n', conf, problem=(2, 3)) + self.check('a key: multi\n' + ' line\n', conf, problem=(2, 9)) + self.check('a key:\n' + ' multi\n' + ' line\n', conf, problem=(3, 4)) + self.check('- C code: void main() {\n' + ' printf("foo");\n' + ' }\n', conf, problem=(2, 15)) + self.check('- C code:\n' + ' void main() {\n' + ' printf("foo");\n' + ' }\n', conf, problem=(3, 9)) + + def test_basics_quoted(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: false}\n' + 'document-start: disable\n') + self.check('"multi\n' + ' line"\n', conf) + self.check('- "multi\n' + ' line"\n', conf) + self.check('a key: "multi\n' + ' line"\n', conf) + self.check('a key:\n' + ' "multi\n' + ' line"\n', conf) + self.check('- jinja2: "{% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}"\n', conf) + self.check('- jinja2:\n' + ' "{% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}"\n', conf) + self.check('["this is a very long line\n' + ' that needs to be split",\n' + ' "other line"]\n', conf) + self.check('["multi\n' + ' line 1", "multi\n' + ' line 2"]\n', conf) + + def test_check_multi_line_quoted(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('"multi\n' + 'line"\n', conf, problem=(2, 1)) + self.check('"multi\n' + ' line"\n', conf, problem=(2, 3)) + self.check('- "multi\n' + ' line"\n', conf, problem=(2, 3)) + self.check('- "multi\n' + ' line"\n', conf, problem=(2, 5)) + self.check('a key: "multi\n' + ' line"\n', conf, problem=(2, 3)) + self.check('a key: "multi\n' + ' line"\n', conf, problem=(2, 8)) + self.check('a key: "multi\n' + ' line"\n', conf, problem=(2, 10)) + self.check('a key:\n' + ' "multi\n' + ' line"\n', conf, problem=(3, 3)) + self.check('a key:\n' + ' "multi\n' + ' line"\n', conf, problem=(3, 5)) + self.check('- jinja2: "{% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}"\n', conf, + problem1=(2, 14), problem2=(4, 14)) + self.check('- jinja2:\n' + ' "{% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}"\n', conf, + problem1=(3, 8), problem2=(5, 8)) + self.check('["this is a very long line\n' + ' that needs to be split",\n' + ' "other line"]\n', conf) + self.check('["this is a very long line\n' + ' that needs to be split",\n' + ' "other line"]\n', conf, problem=(2, 2)) + self.check('["this is a very long line\n' + ' that needs to be split",\n' + ' "other line"]\n', conf, problem=(2, 4)) + self.check('["multi\n' + ' line 1", "multi\n' + ' line 2"]\n', conf) + self.check('["multi\n' + ' line 1", "multi\n' + ' line 2"]\n', conf, problem=(3, 12)) + self.check('["multi\n' + ' line 1", "multi\n' + ' line 2"]\n', conf, problem=(3, 14)) + + def test_basics_folded_style(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: false}\n' + 'document-start: disable\n') + self.check('>\n' + ' multi\n' + ' line\n', conf) + self.check('- >\n' + ' multi\n' + ' line\n', conf) + self.check('- key: >\n' + ' multi\n' + ' line\n', conf) + self.check('- key:\n' + ' >\n' + ' multi\n' + ' line\n', conf) + self.check('- ? >\n' + ' multi-line\n' + ' key\n' + ' : >\n' + ' multi-line\n' + ' value\n', conf) + self.check('- ?\n' + ' >\n' + ' multi-line\n' + ' key\n' + ' :\n' + ' >\n' + ' multi-line\n' + ' value\n', conf) + self.check('- jinja2: >\n' + ' {% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}\n', conf) + + def test_check_multi_line_folded_style(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('>\n' + ' multi\n' + ' line\n', conf, problem=(3, 4)) + self.check('- >\n' + ' multi\n' + ' line\n', conf, problem=(3, 6)) + self.check('- key: >\n' + ' multi\n' + ' line\n', conf, problem=(3, 6)) + self.check('- key:\n' + ' >\n' + ' multi\n' + ' line\n', conf, problem=(4, 8)) + self.check('- ? >\n' + ' multi-line\n' + ' key\n' + ' : >\n' + ' multi-line\n' + ' value\n', conf, + problem1=(3, 8), problem2=(6, 8)) + self.check('- ?\n' + ' >\n' + ' multi-line\n' + ' key\n' + ' :\n' + ' >\n' + ' multi-line\n' + ' value\n', conf, + problem1=(4, 8), problem2=(8, 8)) + self.check('- jinja2: >\n' + ' {% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}\n', conf, + problem1=(3, 7), problem2=(5, 7)) + + def test_basics_literal_style(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: false}\n' + 'document-start: disable\n') + self.check('|\n' + ' multi\n' + ' line\n', conf) + self.check('- |\n' + ' multi\n' + ' line\n', conf) + self.check('- key: |\n' + ' multi\n' + ' line\n', conf) + self.check('- key:\n' + ' |\n' + ' multi\n' + ' line\n', conf) + self.check('- ? |\n' + ' multi-line\n' + ' key\n' + ' : |\n' + ' multi-line\n' + ' value\n', conf) + self.check('- ?\n' + ' |\n' + ' multi-line\n' + ' key\n' + ' :\n' + ' |\n' + ' multi-line\n' + ' value\n', conf) + self.check('- jinja2: |\n' + ' {% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}\n', conf) + + def test_check_multi_line_literal_style(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('|\n' + ' multi\n' + ' line\n', conf, problem=(3, 4)) + self.check('- |\n' + ' multi\n' + ' line\n', conf, problem=(3, 6)) + self.check('- key: |\n' + ' multi\n' + ' line\n', conf, problem=(3, 6)) + self.check('- key:\n' + ' |\n' + ' multi\n' + ' line\n', conf, problem=(4, 8)) + self.check('- ? |\n' + ' multi-line\n' + ' key\n' + ' : |\n' + ' multi-line\n' + ' value\n', conf, + problem1=(3, 8), problem2=(6, 8)) + self.check('- ?\n' + ' |\n' + ' multi-line\n' + ' key\n' + ' :\n' + ' |\n' + ' multi-line\n' + ' value\n', conf, + problem1=(4, 8), problem2=(8, 8)) + self.check('- jinja2: |\n' + ' {% if ansible is defined %}\n' + ' {{ ansible }}\n' + ' {% else %}\n' + ' {{ chef }}\n' + ' {% endif %}\n', conf, + problem1=(3, 7), problem2=(5, 7)) + + # The following "paragraph" examples are inspired from + # http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines + + def test_paragraph_plain(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('- long text: very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf) + self.check('- long text: very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf, + problem1=(2, 5), problem2=(4, 5), problem3=(5, 5)) + self.check('- long text:\n' + ' very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf) + + def test_paragraph_double_quoted(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('- long text: "very \\"long\\"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces."\n', conf) + self.check('- long text: "very \\"long\\"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces."\n', conf, + problem1=(2, 5), problem2=(4, 5), problem3=(5, 5)) + self.check('- long text: "very \\"long\\"\n' + '\'string\' with\n' + '\n' + 'paragraph gap, \\n and\n' + 'spaces."\n', conf, + problem1=(2, 1), problem2=(4, 1), problem3=(5, 1)) + self.check('- long text:\n' + ' "very \\"long\\"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces."\n', conf) + + def test_paragraph_single_quoted(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('- long text: \'very "long"\n' + ' \'\'string\'\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\'\n', conf) + self.check('- long text: \'very "long"\n' + ' \'\'string\'\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\'\n', conf, + problem1=(2, 5), problem2=(4, 5), problem3=(5, 5)) + self.check('- long text: \'very "long"\n' + '\'\'string\'\' with\n' + '\n' + 'paragraph gap, \\n and\n' + 'spaces.\'\n', conf, + problem1=(2, 1), problem2=(4, 1), problem3=(5, 1)) + self.check('- long text:\n' + ' \'very "long"\n' + ' \'\'string\'\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\'\n', conf) + + def test_paragraph_folded(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('- long text: >\n' + ' very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf) + self.check('- long text: >\n' + ' very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf, + problem1=(3, 6), problem2=(5, 7), problem3=(6, 8)) + + def test_paragraph_literal(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('- long text: |\n' + ' very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf) + self.check('- long text: |\n' + ' very "long"\n' + ' \'string\' with\n' + '\n' + ' paragraph gap, \\n and\n' + ' spaces.\n', conf, + problem1=(3, 6), problem2=(5, 7), problem3=(6, 8)) + + def test_consistent(self): + conf = ('indentation: {spaces: consistent,\n' + ' check-multi-line-strings: true}\n' + 'document-start: disable\n') + self.check('multi\n' + 'line\n', conf) + self.check('multi\n' + ' line\n', conf, problem=(2, 2)) + self.check('- multi\n' + ' line\n', conf) + self.check('- multi\n' + ' line\n', conf, problem=(2, 4)) + self.check('a key: multi\n' + ' line\n', conf, problem=(2, 3)) + self.check('a key: multi\n' + ' line\n', conf, problem=(2, 9)) + self.check('a key:\n' + ' multi\n' + ' line\n', conf, problem=(3, 4)) + self.check('- C code: void main() {\n' + ' printf("foo");\n' + ' }\n', conf, problem=(2, 15)) + self.check('- C code:\n' + ' void main() {\n' + ' printf("foo");\n' + ' }\n', conf, problem=(3, 9)) + self.check('>\n' + ' multi\n' + ' line\n', conf) + self.check('>\n' + ' multi\n' + ' line\n', conf) + self.check('>\n' + ' multi\n' + ' line\n', conf, problem=(3, 7)) diff --git a/tests/rules/test_key_duplicates.py b/tests/rules/test_key_duplicates.py new file mode 100644 index 0000000..3f8a9e6 --- /dev/null +++ b/tests/rules/test_key_duplicates.py @@ -0,0 +1,181 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class KeyDuplicatesTestCase(RuleTestCase): + rule_id = 'key-duplicates' + + def test_disabled(self): + conf = 'key-duplicates: disable' + self.check('---\n' + 'block mapping:\n' + ' key: a\n' + ' otherkey: b\n' + ' key: c\n', conf) + self.check('---\n' + 'flow mapping:\n' + ' {key: a, otherkey: b, key: c}\n', conf) + self.check('---\n' + 'duplicated twice:\n' + ' - k: a\n' + ' ok: b\n' + ' k: c\n' + ' k: d\n', conf) + self.check('---\n' + 'duplicated twice:\n' + ' - {k: a, ok: b, k: c, k: d}\n', conf) + self.check('---\n' + 'multiple duplicates:\n' + ' a: 1\n' + ' b: 2\n' + ' c: 3\n' + ' d: 4\n' + ' d: 5\n' + ' b: 6\n', conf) + self.check('---\n' + 'multiple duplicates:\n' + ' {a: 1, b: 2, c: 3, d: 4, d: 5, b: 6}\n', conf) + self.check('---\n' + 'at: root\n' + 'multiple: times\n' + 'at: root\n', conf) + self.check('---\n' + 'nested but OK:\n' + ' a: {a: {a: 1}}\n' + ' b:\n' + ' b: 2\n' + ' c: 3\n', conf) + self.check('---\n' + 'nested duplicates:\n' + ' a: {a: 1, a: 1}\n' + ' b:\n' + ' c: 3\n' + ' d: 4\n' + ' d: 4\n' + ' b: 2\n', conf) + self.check('---\n' + 'duplicates with many styles: 1\n' + '"duplicates with many styles": 1\n' + '\'duplicates with many styles\': 1\n' + '? duplicates with many styles\n' + ': 1\n' + '? >-\n' + ' duplicates with\n' + ' many styles\n' + ': 1\n', conf) + self.check('---\n' + 'Merge Keys are 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) + self.check('---\n' + '{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax')) + self.check('---\n' + '[a, b, c]]\n', conf, problem=(2, 10, 'syntax')) + + def test_enabled(self): + conf = 'key-duplicates: enable' + self.check('---\n' + 'block mapping:\n' + ' key: a\n' + ' otherkey: b\n' + ' key: c\n', conf, + problem=(5, 3)) + self.check('---\n' + 'flow mapping:\n' + ' {key: a, otherkey: b, key: c}\n', conf, + problem=(3, 25)) + self.check('---\n' + 'duplicated twice:\n' + ' - k: a\n' + ' ok: b\n' + ' k: c\n' + ' k: d\n', conf, + problem1=(5, 5), problem2=(6, 5)) + self.check('---\n' + 'duplicated twice:\n' + ' - {k: a, ok: b, k: c, k: d}\n', conf, + problem1=(3, 19), problem2=(3, 25)) + self.check('---\n' + 'multiple duplicates:\n' + ' a: 1\n' + ' b: 2\n' + ' c: 3\n' + ' d: 4\n' + ' d: 5\n' + ' b: 6\n', conf, + problem1=(7, 3), problem2=(8, 3)) + self.check('---\n' + 'multiple duplicates:\n' + ' {a: 1, b: 2, c: 3, d: 4, d: 5, b: 6}\n', conf, + problem1=(3, 28), problem2=(3, 34)) + self.check('---\n' + 'at: root\n' + 'multiple: times\n' + 'at: root\n', conf, + problem=(4, 1)) + self.check('---\n' + 'nested but OK:\n' + ' a: {a: {a: 1}}\n' + ' b:\n' + ' b: 2\n' + ' c: 3\n', conf) + self.check('---\n' + 'nested duplicates:\n' + ' a: {a: 1, a: 1}\n' + ' b:\n' + ' c: 3\n' + ' d: 4\n' + ' d: 4\n' + ' b: 2\n', conf, + problem1=(3, 13), problem2=(7, 5), problem3=(8, 3)) + self.check('---\n' + 'duplicates with many styles: 1\n' + '"duplicates with many styles": 1\n' + '\'duplicates with many styles\': 1\n' + '? duplicates with many styles\n' + ': 1\n' + '? >-\n' + ' duplicates with\n' + ' many styles\n' + ': 1\n', conf, + problem1=(3, 1), problem2=(4, 1), problem3=(5, 3), + problem4=(7, 3)) + self.check('---\n' + 'Merge Keys are 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) + self.check('---\n' + '{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax')) + self.check('---\n' + '[a, b, c]]\n', conf, problem=(2, 10, 'syntax')) + + def test_key_tokens_in_flow_sequences(self): + conf = 'key-duplicates: enable' + self.check('---\n' + '[\n' + ' flow: sequence, with, key: value, mappings\n' + ']\n', conf) diff --git a/tests/rules/test_key_ordering.py b/tests/rules/test_key_ordering.py new file mode 100644 index 0000000..7d17603 --- /dev/null +++ b/tests/rules/test_key_ordering.py @@ -0,0 +1,149 @@ +# Copyright (C) 2017 Johannes F. Knauf +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import locale + +from tests.common import RuleTestCase + + +class KeyOrderingTestCase(RuleTestCase): + rule_id = 'key-ordering' + + def test_disabled(self): + conf = 'key-ordering: disable' + self.check('---\n' + 'block mapping:\n' + ' secondkey: a\n' + ' firstkey: b\n', conf) + self.check('---\n' + 'flow mapping:\n' + ' {secondkey: a, firstkey: b}\n', conf) + self.check('---\n' + 'second: before_first\n' + 'at: root\n', conf) + self.check('---\n' + 'nested but OK:\n' + ' second: {first: 1}\n' + ' third:\n' + ' second: 2\n', conf) + + def test_enabled(self): + conf = 'key-ordering: enable' + self.check('---\n' + 'block mapping:\n' + ' secondkey: a\n' + ' firstkey: b\n', conf, + problem=(4, 3)) + self.check('---\n' + 'flow mapping:\n' + ' {secondkey: a, firstkey: b}\n', conf, + problem=(3, 18)) + self.check('---\n' + 'second: before_first\n' + 'at: root\n', conf, + problem=(3, 1)) + self.check('---\n' + 'nested but OK:\n' + ' second: {first: 1}\n' + ' third:\n' + ' second: 2\n', conf) + + def test_word_length(self): + conf = 'key-ordering: enable' + self.check('---\n' + 'a: 1\n' + 'ab: 1\n' + 'abc: 1\n', conf) + self.check('---\n' + 'a: 1\n' + 'abc: 1\n' + 'ab: 1\n', conf, + problem=(4, 1)) + + def test_key_duplicates(self): + conf = ('key-duplicates: disable\n' + 'key-ordering: enable') + self.check('---\n' + 'key: 1\n' + 'key: 2\n', conf) + + def test_case(self): + conf = 'key-ordering: enable' + self.check('---\n' + 'T-shirt: 1\n' + 'T-shirts: 2\n' + 't-shirt: 3\n' + 't-shirts: 4\n', conf) + self.check('---\n' + 'T-shirt: 1\n' + 't-shirt: 2\n' + 'T-shirts: 3\n' + 't-shirts: 4\n', conf, + problem=(4, 1)) + + def test_accents(self): + conf = 'key-ordering: enable' + self.check('---\n' + 'hair: true\n' + 'hais: true\n' + 'haïr: true\n' + 'haïssable: true\n', conf) + self.check('---\n' + 'haïr: true\n' + 'hais: true\n', conf, + problem=(3, 1)) + + def test_key_tokens_in_flow_sequences(self): + conf = 'key-ordering: enable' + self.check('---\n' + '[\n' + ' key: value, mappings, in, flow: sequence\n' + ']\n', conf) + + def test_locale_case(self): + self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None)) + try: + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + except locale.Error: # pragma: no cover + self.skipTest('locale en_US.UTF-8 not available') + conf = ('key-ordering: enable') + self.check('---\n' + 't-shirt: 1\n' + 'T-shirt: 2\n' + 't-shirts: 3\n' + 'T-shirts: 4\n', conf) + self.check('---\n' + 't-shirt: 1\n' + 't-shirts: 2\n' + 'T-shirt: 3\n' + 'T-shirts: 4\n', conf, + problem=(4, 1)) + + def test_locale_accents(self): + self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None)) + try: + locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') + except locale.Error: # pragma: no cover + self.skipTest('locale en_US.UTF-8 not available') + conf = ('key-ordering: enable') + self.check('---\n' + 'hair: true\n' + 'haïr: true\n' + 'hais: true\n' + 'haïssable: true\n', conf) + self.check('---\n' + 'hais: true\n' + 'haïr: true\n', conf, + problem=(3, 1)) diff --git a/tests/rules/test_line_length.py b/tests/rules/test_line_length.py new file mode 100644 index 0000000..ef68178 --- /dev/null +++ b/tests/rules/test_line_length.py @@ -0,0 +1,198 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class LineLengthTestCase(RuleTestCase): + rule_id = 'line-length' + + def test_disabled(self): + conf = ('line-length: disable\n' + 'empty-lines: disable\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('---\n', conf) + self.check(81 * 'a', conf) + self.check('---\n' + 81 * 'a' + '\n', conf) + self.check(1000 * 'b', conf) + self.check('---\n' + 1000 * 'b' + '\n', conf) + self.check('content: |\n' + ' {% this line is' + 99 * ' really' + ' long %}\n', + conf) + + def test_default(self): + conf = ('line-length: {max: 80}\n' + 'empty-lines: disable\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('---\n', conf) + self.check(80 * 'a', conf) + self.check('---\n' + 80 * 'a' + '\n', conf) + self.check(16 * 'aaaa ' + 'z', conf, problem=(1, 81)) + self.check('---\n' + 16 * 'aaaa ' + 'z' + '\n', conf, problem=(2, 81)) + self.check(1000 * 'word ' + 'end', conf, problem=(1, 81)) + self.check('---\n' + 1000 * 'word ' + 'end\n', conf, problem=(2, 81)) + + def test_max_length_10(self): + conf = ('line-length: {max: 10}\n' + 'new-line-at-end-of-file: disable\n') + self.check('---\nABCD EFGHI', conf) + self.check('---\nABCD EFGHIJ', conf, problem=(2, 11)) + self.check('---\nABCD EFGHIJ\n', conf, problem=(2, 11)) + + def test_spaces(self): + conf = ('line-length: {max: 80}\n' + 'new-line-at-end-of-file: disable\n' + 'trailing-spaces: disable\n') + self.check('---\n' + 81 * ' ', conf, problem=(2, 81)) + self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81)) + + def test_non_breakable_word(self): + conf = 'line-length: {max: 20, allow-non-breakable-words: true}' + self.check('---\n' + 30 * 'A' + '\n', conf) + self.check('---\n' + 'this:\n' + ' is:\n' + ' - a:\n' + ' http://localhost/very/long/url\n' + '...\n', conf) + self.check('---\n' + 'this:\n' + ' is:\n' + ' - a:\n' + ' # http://localhost/very/long/url\n' + ' comment\n' + '...\n', conf) + self.check('---\n' + 'this:\n' + 'is:\n' + 'another:\n' + ' - https://localhost/very/very/long/url\n' + '...\n', conf) + self.check('---\n' + 'long_line: http://localhost/very/very/long/url\n', conf, + problem=(2, 21)) + + conf = 'line-length: {max: 20, allow-non-breakable-words: false}' + self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21)) + self.check('---\n' + 'this:\n' + ' is:\n' + ' - a:\n' + ' http://localhost/very/long/url\n' + '...\n', conf, problem=(5, 21)) + self.check('---\n' + 'this:\n' + ' is:\n' + ' - a:\n' + ' # http://localhost/very/long/url\n' + ' comment\n' + '...\n', conf, problem=(5, 21)) + self.check('---\n' + 'this:\n' + 'is:\n' + 'another:\n' + ' - https://localhost/very/very/long/url\n' + '...\n', conf, problem=(5, 21)) + self.check('---\n' + 'long_line: http://localhost/very/very/long/url\n' + '...\n', conf, problem=(2, 21)) + + conf = 'line-length: {max: 20, allow-non-breakable-words: true}' + self.check('---\n' + '# http://www.verylongurlurlurlurlurlurlurlurl.com\n' + 'key:\n' + ' subkey: value\n', conf) + self.check('---\n' + '## http://www.verylongurlurlurlurlurlurlurlurl.com\n' + 'key:\n' + ' subkey: value\n', conf) + self.check('---\n' + '# # http://www.verylongurlurlurlurlurlurlurlurl.com\n' + 'key:\n' + ' subkey: value\n', conf, + problem=(2, 21)) + self.check('---\n' + '#A http://www.verylongurlurlurlurlurlurlurlurl.com\n' + 'key:\n' + ' subkey: value\n', conf, + problem1=(2, 2, 'comments'), + problem2=(2, 21, 'line-length')) + + conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n' + 'trailing-spaces: disable') + self.check('---\n' + 'loooooooooong+word+and+some+space+at+the+end \n', + conf, problem=(2, 21)) + + def test_non_breakable_inline_mappings(self): + conf = 'line-length: {max: 20, ' \ + 'allow-non-breakable-inline-mappings: true}' + self.check('---\n' + 'long_line: http://localhost/very/very/long/url\n' + 'long line: http://localhost/very/very/long/url\n', conf) + self.check('---\n' + '- long line: http://localhost/very/very/long/url\n', conf) + + self.check('---\n' + 'long_line: http://localhost/short/url + word\n' + 'long line: http://localhost/short/url + word\n', + conf, problem1=(2, 21), problem2=(3, 21)) + + conf = ('line-length: {max: 20,' + ' allow-non-breakable-inline-mappings: true}\n' + 'trailing-spaces: disable') + self.check('---\n' + 'long_line: and+some+space+at+the+end \n', + conf, problem=(2, 21)) + self.check('---\n' + 'long line: and+some+space+at+the+end \n', + conf, problem=(2, 21)) + self.check('---\n' + '- long line: and+some+space+at+the+end \n', + conf, problem=(2, 21)) + + # See https://github.com/adrienverge/yamllint/issues/21 + conf = 'line-length: {allow-non-breakable-inline-mappings: true}' + self.check('---\n' + 'content: |\n' + ' {% this line is' + 99 * ' really' + ' long %}\n', + conf, problem=(3, 81)) + + def test_unicode(self): + conf = 'line-length: {max: 53}' + self.check('---\n' + '# This is a test to check if “line-length” works nice\n' + 'with: “unicode characters” that span across bytes! ↺\n', + conf) + conf = 'line-length: {max: 51}' + self.check('---\n' + '# This is a test to check if “line-length” works nice\n' + 'with: “unicode characters” that span across bytes! ↺\n', + conf, problem1=(2, 52), problem2=(3, 52)) + + def test_with_dos_newlines(self): + conf = ('line-length: {max: 10}\n' + 'new-lines: {type: dos}\n' + 'new-line-at-end-of-file: disable\n') + self.check('---\r\nABCD EFGHI', conf) + self.check('---\r\nABCD EFGHI\r\n', conf) + self.check('---\r\nABCD EFGHIJ', conf, problem=(2, 11)) + self.check('---\r\nABCD EFGHIJ\r\n', conf, problem=(2, 11)) diff --git a/tests/rules/test_new_line_at_end_of_file.py b/tests/rules/test_new_line_at_end_of_file.py new file mode 100644 index 0000000..10a0bf0 --- /dev/null +++ b/tests/rules/test_new_line_at_end_of_file.py @@ -0,0 +1,41 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class NewLineAtEndOfFileTestCase(RuleTestCase): + rule_id = 'new-line-at-end-of-file' + + def test_disabled(self): + conf = ('new-line-at-end-of-file: disable\n' + 'empty-lines: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('word', conf) + self.check('Sentence.\n', conf) + + def test_enabled(self): + conf = ('new-line-at-end-of-file: enable\n' + 'empty-lines: disable\n' + 'document-start: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('word', conf, problem=(1, 5)) + self.check('Sentence.\n', conf) + self.check('---\n' + 'yaml: document\n' + '...', conf, problem=(3, 4)) diff --git a/tests/rules/test_new_lines.py b/tests/rules/test_new_lines.py new file mode 100644 index 0000000..80334ea --- /dev/null +++ b/tests/rules/test_new_lines.py @@ -0,0 +1,96 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 unittest import mock + +from tests.common import RuleTestCase + + +class NewLinesTestCase(RuleTestCase): + rule_id = 'new-lines' + + def test_disabled(self): + conf = ('new-line-at-end-of-file: disable\n' + 'new-lines: disable\n') + self.check('', conf) + self.check('\n', conf) + self.check('\r', conf) + self.check('\r\n', conf) + self.check('---\ntext\n', conf) + self.check('---\r\ntext\r\n', conf) + + def test_unix_type(self): + conf = ('new-line-at-end-of-file: disable\n' + 'new-lines: {type: unix}\n') + self.check('', conf) + self.check('\r', conf) + self.check('\n', conf) + self.check('\r\n', conf, problem=(1, 1)) + self.check('---\ntext\n', conf) + self.check('---\r\ntext\r\n', conf, problem=(1, 4)) + + def test_unix_type_required_st_sp(self): + # If we find a CRLF when looking for Unix newlines, yamllint + # should always raise, regardless of logic with + # require-starting-space. + conf = ('new-line-at-end-of-file: disable\n' + 'new-lines: {type: unix}\n' + 'comments:\n' + ' require-starting-space: true\n') + self.check('---\r\n#\r\n', conf, problem=(1, 4)) + + def test_dos_type(self): + conf = ('new-line-at-end-of-file: disable\n' + 'new-lines: {type: dos}\n') + self.check('', conf) + self.check('\r', conf) + self.check('\n', conf, problem=(1, 1)) + self.check('\r\n', conf) + self.check('---\ntext\n', conf, problem=(1, 4)) + self.check('---\r\ntext\r\n', conf) + + def test_platform_type(self): + conf = ('new-line-at-end-of-file: disable\n' + 'new-lines: {type: platform}\n') + + self.check('', conf) + + # mock the Linux new-line-character + with mock.patch('yamllint.rules.new_lines.linesep', '\n'): + self.check('\n', conf) + self.check('\r\n', conf, problem=(1, 1)) + self.check('---\ntext\n', conf) + self.check('---\r\ntext\r\n', conf, problem=(1, 4)) + self.check('---\r\ntext\n', conf, problem=(1, 4)) + # FIXME: the following tests currently don't work + # because only the first line is checked for line-endings + # see: issue #475 + # --- + # self.check('---\ntext\r\nfoo\n', conf, problem=(2, 4)) + # self.check('---\ntext\r\n', conf, problem=(2, 4)) + + # mock the Windows new-line-character + with mock.patch('yamllint.rules.new_lines.linesep', '\r\n'): + self.check('\r\n', conf) + self.check('\n', conf, problem=(1, 1)) + self.check('---\r\ntext\r\n', conf) + self.check('---\ntext\n', conf, problem=(1, 4)) + self.check('---\ntext\r\n', conf, problem=(1, 4)) + # FIXME: the following tests currently don't work + # because only the first line is checked for line-endings + # see: issue #475 + # --- + # self.check('---\r\ntext\nfoo\r\n', conf, problem=(2, 4)) + # self.check('---\r\ntext\n', conf, problem=(2, 4)) diff --git a/tests/rules/test_octal_values.py b/tests/rules/test_octal_values.py new file mode 100644 index 0000000..be5b039 --- /dev/null +++ b/tests/rules/test_octal_values.py @@ -0,0 +1,80 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class OctalValuesTestCase(RuleTestCase): + rule_id = 'octal-values' + + def test_disabled(self): + conf = ('octal-values: disable\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('user-city: 010', conf) + self.check('user-city: 0o10', conf) + + def test_implicit_octal_values(self): + conf = ('octal-values:\n' + ' forbid-implicit-octal: true\n' + ' 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) + self.check("user-city: '010'", conf) + self.check('user-city: "010"', conf) + self.check('user-city:\n' + ' - 010', conf, problem=(2, 8)) + self.check('user-city: [010]', conf, problem=(1, 16)) + self.check('user-city: {beijing: 010}', conf, problem=(1, 25)) + self.check('explicit-octal: 0o10', conf) + self.check('not-number: 0abc', conf) + self.check('zero: 0', conf) + self.check('hex-value: 0x10', conf) + self.check('number-values:\n' + ' - 0.10\n' + ' - .01\n' + ' - 0e3\n', conf) + self.check('with-decimal-digits: 012345678', conf) + self.check('with-decimal-digits: 012345679', conf) + + def test_explicit_octal_values(self): + conf = ('octal-values:\n' + ' forbid-implicit-octal: false\n' + ' forbid-explicit-octal: true\n' + 'new-line-at-end-of-file: disable\n' + 'document-start: disable\n') + self.check('user-city: 0o10', conf, problem=(1, 16)) + self.check('user-city: abc', conf) + self.check('user-city: 0o10,0571', conf) + self.check("user-city: '0o10'", conf) + self.check('user-city:\n' + ' - 0o10', conf, problem=(2, 9)) + self.check('user-city: [0o10]', conf, problem=(1, 17)) + self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26)) + self.check('implicit-octal: 010', conf) + self.check('not-number: 0oabc', conf) + self.check('zero: 0', conf) + self.check('hex-value: 0x10', conf) + self.check('number-values:\n' + ' - 0.10\n' + ' - .01\n' + ' - 0e3\n', conf) + self.check('user-city: "010"', conf) + self.check('with-decimal-digits: 0o012345678', conf) + self.check('with-decimal-digits: 0o012345679', conf) diff --git a/tests/rules/test_quoted_strings.py b/tests/rules/test_quoted_strings.py new file mode 100644 index 0000000..543cc0d --- /dev/null +++ b/tests/rules/test_quoted_strings.py @@ -0,0 +1,558 @@ +# Copyright (C) 2018 ClearScore +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + +from yamllint import config + + +class QuotedTestCase(RuleTestCase): + rule_id = 'quoted-strings' + + def test_disabled(self): + conf = 'quoted-strings: disable' + + self.check('---\n' + 'foo: bar\n', conf) + self.check('---\n' + 'foo: "bar"\n', conf) + self.check('---\n' + 'foo: \'bar\'\n', conf) + self.check('---\n' + 'bar: 123\n', conf) + self.check('---\n' + 'bar: "123"\n', conf) + + def test_quote_type_any(self): + conf = 'quoted-strings: {quote-type: any}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' # fails + 'string2: "foo"\n' + 'string3: "true"\n' + 'string4: "123"\n' + 'string5: \'bar\'\n' + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - 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)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' # fails + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' + ' word 2"\n', + conf, problem1=(9, 3)) + + def test_quote_type_single(self): + conf = 'quoted-strings: {quote-type: single}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' # fails + 'string2: "foo"\n' # fails + 'string3: "true"\n' # fails + 'string4: "123"\n' # fails + 'string5: \'bar\'\n' + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' # fails + ' - "foo"\n' # fails + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + 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)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' # fails + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' + ' word 2"\n', + conf, problem1=(9, 3), problem2=(12, 3)) + + def test_quote_type_double(self): + conf = 'quoted-strings: {quote-type: double}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' # fails + 'string2: "foo"\n' + 'string3: "true"\n' + 'string4: "123"\n' + 'string5: \'bar\'\n' # fails + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' # fails + ' - "foo"\n' + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5), + problem4=(19, 12), problem5=(20, 15)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' # fails + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' + ' word 2"\n', + conf, problem1=(9, 3)) + + def test_any_quotes_not_required(self): + conf = 'quoted-strings: {quote-type: any, required: false}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' + 'string2: "foo"\n' + 'string3: "true"\n' + 'string4: "123"\n' + 'string5: \'bar\'\n' + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' # fails + ' - "foo"\n' + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + conf) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' + ' word 2"\n', + conf) + + def test_single_quotes_not_required(self): + conf = 'quoted-strings: {quote-type: single, required: false}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' + 'string2: "foo"\n' # fails + 'string3: "true"\n' # fails + 'string4: "123"\n' # fails + 'string5: \'bar\'\n' + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' # fails + ' - "foo"\n' + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10), + problem4=(18, 5), problem5=(19, 17), problem6=(20, 23)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' # fails + ' word 2"\n', + conf, problem1=(12, 3)) + + def test_only_when_needed(self): + conf = 'quoted-strings: {required: only-when-needed}\n' + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' + 'string2: "foo"\n' # fails + 'string3: "true"\n' + 'string4: "123"\n' + 'string5: \'bar\'\n' # fails + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' + ' - "foo"\n' # fails + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5), + problem4=(19, 17), problem5=(20, 23)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' # fails + ' word 2"\n', + conf, problem1=(12, 3)) + + def test_only_when_needed_single_quotes(self): + conf = ('quoted-strings: {quote-type: single,\n' + ' required: only-when-needed}\n') + + self.check('---\n' + 'boolean1: true\n' + 'number1: 123\n' + 'string1: foo\n' + 'string2: "foo"\n' # fails + 'string3: "true"\n' # fails + 'string4: "123"\n' # fails + 'string5: \'bar\'\n' # fails + 'string6: !!str genericstring\n' + 'string7: !!str 456\n' + 'string8: !!str "quotedgenericstring"\n' + 'binary: !!binary binstring\n' + 'integer: !!int intstring\n' + 'boolean2: !!bool boolstring\n' + 'boolean3: !!bool "quotedboolstring"\n' + 'block-seq:\n' + ' - foo\n' + ' - "foo"\n' # fails + 'flow-seq: [foo, "foo"]\n' # fails + 'flow-map: {a: foo, b: "foo"}\n', # fails + conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10), + problem4=(8, 10), problem5=(18, 5), problem6=(19, 17), + problem7=(20, 23)) + self.check('---\n' + 'multiline string 1: |\n' + ' line 1\n' + ' line 2\n' + 'multiline string 2: >\n' + ' word 1\n' + ' word 2\n' + 'multiline string 3:\n' + ' word 1\n' + ' word 2\n' + 'multiline string 4:\n' + ' "word 1\\\n' # fails + ' word 2"\n', + conf, problem1=(12, 3)) + + def test_only_when_needed_corner_cases(self): + conf = 'quoted-strings: {required: only-when-needed}\n' + + self.check('---\n' + '- ""\n' + '- "- item"\n' + '- "key: value"\n' + '- "%H:%M:%S"\n' + '- "%wheel ALL=(ALL) NOPASSWD: ALL"\n' + '- \'"quoted"\'\n' + '- "\'foo\' == \'bar\'"\n' + '- "\'Mac\' in ansible_facts.product_name"\n' + '- \'foo # bar\'\n', + conf) + self.check('---\n' + 'k1: ""\n' + 'k2: "- item"\n' + 'k3: "key: value"\n' + 'k4: "%H:%M:%S"\n' + 'k5: "%wheel ALL=(ALL) NOPASSWD: ALL"\n' + 'k6: \'"quoted"\'\n' + 'k7: "\'foo\' == \'bar\'"\n' + 'k8: "\'Mac\' in ansible_facts.product_name"\n', + conf) + + self.check('---\n' + '- ---\n' + '- "---"\n' # fails + '- ----------\n' + '- "----------"\n' # fails + '- :wq\n' + '- ":wq"\n', # fails + conf, problem1=(3, 3), problem2=(5, 3), problem3=(7, 3)) + self.check('---\n' + 'k1: ---\n' + 'k2: "---"\n' # fails + 'k3: ----------\n' + 'k4: "----------"\n' # fails + 'k5: :wq\n' + 'k6: ":wq"\n', # fails + conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5)) + + def test_only_when_needed_extras(self): + conf = ('quoted-strings:\n' + ' required: true\n' + ' extra-allowed: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' required: true\n' + ' extra-required: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' required: false\n' + ' extra-allowed: [^http://]\n') + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = ('quoted-strings:\n' + ' required: true\n') + self.check('---\n' + '- 123\n' + '- "123"\n' + '- localhost\n' # fails + '- "localhost"\n' + '- http://localhost\n' # fails + '- "http://localhost"\n' + '- ftp://localhost\n' # fails + '- "ftp://localhost"\n', + conf, problem1=(4, 3), problem2=(6, 3), problem3=(8, 3)) + + conf = ('quoted-strings:\n' + ' required: only-when-needed\n' + ' extra-allowed: [^ftp://]\n' + ' extra-required: [^http://]\n') + self.check('---\n' + '- 123\n' + '- "123"\n' + '- localhost\n' + '- "localhost"\n' # fails + '- http://localhost\n' # fails + '- "http://localhost"\n' + '- ftp://localhost\n' + '- "ftp://localhost"\n', + conf, problem1=(5, 3), problem2=(6, 3)) + + conf = ('quoted-strings:\n' + ' required: false\n' + ' extra-required: [^http://, ^ftp://]\n') + self.check('---\n' + '- 123\n' + '- "123"\n' + '- localhost\n' + '- "localhost"\n' + '- http://localhost\n' # fails + '- "http://localhost"\n' + '- ftp://localhost\n' # fails + '- "ftp://localhost"\n', + conf, problem1=(6, 3), problem2=(8, 3)) + + conf = ('quoted-strings:\n' + ' required: only-when-needed\n' + ' extra-allowed: [^ftp://, ";$", " "]\n') + self.check('---\n' + '- localhost\n' + '- "localhost"\n' # fails + '- ftp://localhost\n' + '- "ftp://localhost"\n' + '- i=i+1\n' + '- "i=i+1"\n' # fails + '- i=i+2;\n' + '- "i=i+2;"\n' + '- foo\n' + '- "foo"\n' # fails + '- foo bar\n' + '- "foo bar"\n', + conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3)) + + def test_octal_values(self): + conf = 'quoted-strings: {required: true}\n' + + self.check('---\n' + '- 100\n' + '- 0100\n' + '- 0o100\n' + '- 777\n' + '- 0777\n' + '- 0o777\n' + '- 800\n' + '- 0800\n' + '- 0o800\n' + '- "0800"\n' + '- "0o800"\n', + conf, + problem1=(9, 3), problem2=(10, 3)) + + def test_allow_quoted_quotes(self): + conf = ('quoted-strings: {quote-type: single,\n' + ' required: false,\n' + ' allow-quoted-quotes: false}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: single,\n' + ' required: false,\n' + ' allow-quoted-quotes: true}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: single,\n' + ' required: true,\n' + ' allow-quoted-quotes: false}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: single,\n' + ' required: true,\n' + ' allow-quoted-quotes: true}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: single,\n' + ' required: only-when-needed,\n' + ' allow-quoted-quotes: false}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: single,\n' + ' required: only-when-needed,\n' + ' allow-quoted-quotes: true}\n') + self.check('---\n' + 'foo1: "[barbaz]"\n' # fails + 'foo2: "[bar\'baz]"\n', + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: false,\n' + ' allow-quoted-quotes: false}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: false,\n' + ' allow-quoted-quotes: true}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: true,\n' + ' allow-quoted-quotes: false}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: true,\n' + ' allow-quoted-quotes: true}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: only-when-needed,\n' + ' allow-quoted-quotes: false}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", # fails + conf, problem1=(2, 7), problem2=(3, 7)) + + conf = ('quoted-strings: {quote-type: double,\n' + ' required: only-when-needed,\n' + ' allow-quoted-quotes: true}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" # fails + "foo2: '[bar\"baz]'\n", + conf, problem1=(2, 7)) + + conf = ('quoted-strings: {quote-type: any}\n') + self.check("---\n" + "foo1: '[barbaz]'\n" + "foo2: '[bar\"baz]'\n", + conf) diff --git a/tests/rules/test_trailing_spaces.py b/tests/rules/test_trailing_spaces.py new file mode 100644 index 0000000..016f56e --- /dev/null +++ b/tests/rules/test_trailing_spaces.py @@ -0,0 +1,47 @@ +# Copyright (C) 2016 Adrien Vergé +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class TrailingSpacesTestCase(RuleTestCase): + rule_id = 'trailing-spaces' + + def test_disabled(self): + conf = 'trailing-spaces: disable' + self.check('', conf) + self.check('\n', conf) + self.check(' \n', conf) + self.check('---\n' + 'some: text \n', conf) + + def test_enabled(self): + conf = 'trailing-spaces: enable' + self.check('', conf) + self.check('\n', conf) + self.check(' \n', conf, problem=(1, 1)) + self.check('\t\t\t\n', conf, problem=(1, 1, 'syntax')) + self.check('---\n' + 'some: text \n', conf, problem=(2, 11)) + self.check('---\n' + 'some: text\t\n', conf, problem=(2, 11, 'syntax')) + + def test_with_dos_new_lines(self): + conf = ('trailing-spaces: enable\n' + 'new-lines: {type: dos}\n') + self.check('---\r\n' + 'some: text\r\n', conf) + self.check('---\r\n' + 'some: text \r\n', conf, problem=(2, 11)) diff --git a/tests/rules/test_truthy.py b/tests/rules/test_truthy.py new file mode 100644 index 0000000..c8c8b7a --- /dev/null +++ b/tests/rules/test_truthy.py @@ -0,0 +1,145 @@ +# Copyright (C) 2016 Peter Ericson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# 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 tests.common import RuleTestCase + + +class TruthyTestCase(RuleTestCase): + rule_id = 'truthy' + + def test_disabled(self): + conf = 'truthy: disable' + self.check('---\n' + '1: True\n', conf) + self.check('---\n' + 'True: 1\n', conf) + + def test_enabled(self): + conf = 'truthy: enable\n' + self.check('---\n' + '1: True\n' + 'True: 1\n', + conf, problem1=(2, 4), problem2=(3, 1)) + self.check('---\n' + '1: "True"\n' + '"True": 1\n', conf) + self.check('---\n' + '[\n' + ' true, false,\n' + ' "false", "FALSE",\n' + ' "true", "True",\n' + ' True, FALSE,\n' + ' 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)) + + def test_different_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: ["yes", "no"]\n') + self.check('---\n' + 'key1: foo\n' + 'key2: yes\n' + 'key3: bar\n' + 'key4: no\n', conf) + self.check('---\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)) + + def test_combined_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: ["yes", "no", "true", "false"]\n') + self.check('---\n' + 'key1: foo\n' + 'key2: yes\n' + 'key3: bar\n' + 'key4: no\n', conf) + self.check('---\n' + 'key1: true\n' + 'key2: Yes\n' + 'key3: false\n' + 'key4: no\n' + 'key5: yes\n', + conf, problem1=(3, 7)) + + def test_no_allowed_values(self): + conf = ('truthy:\n' + ' allowed-values: []\n') + self.check('---\n' + 'key1: foo\n' + 'key2: bar\n', conf) + self.check('---\n' + 'key1: true\n' + 'key2: yes\n' + 'key3: false\n' + 'key4: no\n', conf, + problem1=(2, 7), problem2=(3, 7), + problem3=(4, 7), problem4=(5, 7)) + + def test_explicit_types(self): + conf = 'truthy: enable\n' + self.check('---\n' + 'string1: !!str True\n' + 'string2: !!str yes\n' + 'string3: !!str off\n' + 'encoded: !!binary |\n' + ' True\n' + ' OFF\n' + ' pad==\n' # this decodes as 'N\xbb\x9e8Qii' + 'boolean1: !!bool true\n' + 'boolean2: !!bool "false"\n' + 'boolean3: !!bool FALSE\n' + 'boolean4: !!bool True\n' + 'boolean5: !!bool off\n' + 'boolean6: !!bool NO\n', + conf) + + def test_check_keys_disabled(self): + conf = ('truthy:\n' + ' allowed-values: []\n' + ' check-keys: false\n' + 'key-duplicates: disable\n') + self.check('---\n' + 'YES: 0\n' + 'Yes: 0\n' + 'yes: 0\n' + 'No: 0\n' + 'No: 0\n' + 'no: 0\n' + 'TRUE: 0\n' + 'True: 0\n' + 'true: 0\n' + 'FALSE: 0\n' + 'False: 0\n' + 'false: 0\n' + 'ON: 0\n' + 'On: 0\n' + 'on: 0\n' + 'OFF: 0\n' + 'Off: 0\n' + 'off: 0\n' + 'YES:\n' + ' Yes:\n' + ' yes:\n' + ' on: 0\n', + conf) |