mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
6.2 KiB
Python
134 lines
6.2 KiB
Python
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import tempfile
|
|
|
|
from unittest.mock import call
|
|
|
|
import typing as t
|
|
|
|
import pytest
|
|
import pytest_mock
|
|
|
|
from ansible import constants as C
|
|
from ansible._internal._errors._utils import format_exception_message
|
|
from ansible._internal._datatag._tags import Origin
|
|
from ansible.parsing.utils.yaml import from_yaml
|
|
from ansible._internal._yaml._errors import AnsibleYAMLParserError
|
|
from ansible.utils.display import Display
|
|
|
|
|
|
@pytest.mark.parametrize("content, expected_message, expect_help_text, line, col", (
|
|
# cases with multiple permutations due to handling of multiple levels of list and up to one dict level
|
|
|
|
('{{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 1),
|
|
('foo: {{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 6),
|
|
('- {{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 3),
|
|
('- foo: {{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 8),
|
|
(' - - {{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 7),
|
|
(' - - foo: {{ bar }}', 'This may be an issue with missing quotes around a template block.', True, 1, 12),
|
|
|
|
('"foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 1),
|
|
('foo: "foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 6),
|
|
('- "foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 3),
|
|
('- foo: "foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 8),
|
|
(' - - "foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 7),
|
|
(' - - foo: "foo" foo', 'Values starting with a quote must end with the same quote.', True, 1, 12),
|
|
|
|
('"foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 1),
|
|
('foo: "foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 6),
|
|
('- "foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 3),
|
|
('- foo: "foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 8),
|
|
(' - - "foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 7),
|
|
(' - - foo: "foo" "foo"', 'Values starting with a quote must end with the same quote, and not contain that quote.', True, 1, 12),
|
|
|
|
# cases without list/dict handling
|
|
|
|
('aaa: bbb:', 'Colons in unquoted values must be followed by a non-space character.', True, 1, 9),
|
|
('aaa: bbb: ccc', 'Colons in unquoted values must be followed by a non-space character.', True, 1, 9),
|
|
('''- value == "x: 'x' x"''', 'Colons in unquoted values must be followed by a non-space character.', True, 1, 14),
|
|
|
|
('!!map 1\t2\t3', 'Tabs are usually invalid in YAML.', False, 1, 8),
|
|
('{a: 1, a: 1}', "Found duplicate mapping key 'a'.", False, 1, 8),
|
|
|
|
(1, 'a string or stream input is required', False, None, None), # wrong type (misuse of API)
|
|
|
|
# cases where the underling MarkedYAMLError is used
|
|
|
|
('k1: v1\n k2: v2', 'Mapping values are not allowed in this context.', False, 2, 5),
|
|
('k1: v1\n k2: ":"', 'Mapping values are not allowed in this context.', False, 2, 5),
|
|
|
|
(':', 'While parsing a block mapping did not find expected key.', False, 1, 1),
|
|
('!!map 1', 'Expected a mapping node, but found scalar.', False, 1, 1),
|
|
('[]: bad', 'While constructing a mapping found unhashable key.', False, 1, 1),
|
|
('{}: bad', 'While constructing a mapping found unhashable key.', False, 1, 1),
|
|
('"\\"', 'While scanning a quoted scalar found unexpected end of stream.', False, 1, 4),
|
|
|
|
# DTFIX-FUTURE: add tests that use comments
|
|
))
|
|
def test_yaml_parser_error(
|
|
content: t.Any,
|
|
expected_message: str,
|
|
expect_help_text: bool,
|
|
line: int | None,
|
|
col: int | None,
|
|
mocker: pytest_mock.MockerFixture,
|
|
) -> None:
|
|
set_duplicate_yaml_dict_key_config(mocker, 'error')
|
|
|
|
expected_message = f'YAML parsing failed: {expected_message}'
|
|
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
source_path = pathlib.Path(tempdir) / 'source.yml'
|
|
source_path.write_text(str(content))
|
|
|
|
with pytest.raises(AnsibleYAMLParserError) as error:
|
|
from_yaml(content, file_name=str(source_path))
|
|
|
|
assert error.value.message == expected_message
|
|
assert error.value._original_message == expected_message
|
|
assert format_exception_message(error.value) == expected_message
|
|
assert str(error.value) == expected_message
|
|
|
|
assert error.value.obj == Origin(path=str(source_path), line_num=line, col_num=col)
|
|
|
|
if expect_help_text:
|
|
assert error.value._help_text is not None # DTFIX-FUTURE: check the content later once it's less volatile
|
|
else:
|
|
assert error.value._help_text is None
|
|
|
|
|
|
def test_yaml_duplicate_key_warning(mocker: pytest_mock.MockerFixture) -> None:
|
|
set_duplicate_yaml_dict_key_config(mocker, 'warn')
|
|
|
|
patched_warning = mocker.patch.object(Display(), 'warning')
|
|
|
|
assert from_yaml('{a: 1, b: 2, c: 3, b: 4, d: 5, b: 6}', file_name='/hello.yml')
|
|
|
|
patched_warning.assert_has_calls((
|
|
call(msg="Found duplicate mapping key 'b'.", obj="b", help_text="Using last defined value only."),
|
|
call(msg="Found duplicate mapping key 'b'.", obj="b", help_text="Using last defined value only."),
|
|
))
|
|
|
|
|
|
def test_yaml_duplicate_key_ignore(mocker: pytest_mock.MockerFixture) -> None:
|
|
set_duplicate_yaml_dict_key_config(mocker, 'ignore')
|
|
|
|
warning_spy = mocker.spy(Display(), 'warning')
|
|
|
|
assert from_yaml('{a: 1, a: 2}', file_name='/hello.yml')
|
|
|
|
warning_spy.assert_not_called()
|
|
|
|
|
|
def set_duplicate_yaml_dict_key_config(mocker: pytest_mock.MockerFixture, value: str):
|
|
original_get_config_value = C.config.get_config_value
|
|
|
|
def mocked_get_config_value(config, *args, **kwargs):
|
|
if config == 'DUPLICATE_YAML_DICT_KEY':
|
|
return value
|
|
|
|
return original_get_config_value(config, *args, **kwargs)
|
|
|
|
mocker.patch.object(C.config, 'get_config_value', mocked_get_config_value)
|