Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix YAML error message when error is at the end of the file #73241

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,2 @@
bugfixes:
- display correct error information when an error exists in the last line of the file (https://github.com/ansible/ansible/issues/16456)
14 changes: 14 additions & 0 deletions lib/ansible/errors/__init__.py
Expand Up @@ -100,7 +100,21 @@ def _get_error_lines_from_file(self, file_name, line_number):
with open(file_name, 'r') as f:
lines = f.readlines()

# In case of a YAML loading error, PyYAML will report the very last line
# as the location of the error. Avoid an index error here in order to
# return a helpful message.
file_length = len(lines)
if line_number >= file_length:
line_number = file_length - 1

# If target_line contains only whitespace, move backwards until
# actual code is found. If there are several empty lines after target_line,
# the error lines would just be blank, which is not very helpful.
target_line = lines[line_number]
while not target_line.strip():
line_number -= 1
target_line = lines[line_number]

if line_number > 0:
prev_line = lines[line_number - 1]

Expand Down
46 changes: 38 additions & 8 deletions test/units/errors/test_errors.py
Expand Up @@ -97,14 +97,15 @@ def test_get_error_lines_from_file(self):
"the exact syntax problem.\n\nThe offending line appears to be:\n\n\nthis is line 1\n^ here\n")
)

# this line will not be found, as it is out of the index range
self.obj.ansible_pos = ('foo.yml', 2, 1)
e = AnsibleError(self.message, self.obj)
self.assertEqual(
e.message,
("This is the error message\n\nThe error appears to be in 'foo.yml': line 2, column 1, but may\nbe elsewhere in the file depending on "
"the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")
)
with patch('ansible.errors.to_text', side_effect=IndexError('Raised intentionally')):
# raise an IndexError
self.obj.ansible_pos = ('foo.yml', 2, 1)
e = AnsibleError(self.message, self.obj)
self.assertEqual(
e.message,
("This is the error message\n\nThe error appears to be in 'foo.yml': line 2, column 1, but may\nbe elsewhere in the file depending on "
"the exact syntax problem.\n\n(specified line no longer in file, maybe it changed?)")
)

m = mock_open()
m.return_value.readlines.return_value = ['this line has unicode \xf0\x9f\x98\xa8 in it!\n']
Expand All @@ -119,3 +120,32 @@ def test_get_error_lines_from_file(self):
"file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\nthis line has unicode \xf0\x9f\x98\xa8 in it!\n^ "
"here\n")
)

def test_get_error_lines_error_in_last_line(self):
m = mock_open()
m.return_value.readlines.return_value = ['this is line 1\n', 'this is line 2\n', 'this is line 3\n']

with patch('{0}.open'.format(BUILTINS), m):
# If the error occurs in the last line of the file, use the correct index to get the line
# and avoid the IndexError
self.obj.ansible_pos = ('foo.yml', 4, 1)
e = AnsibleError(self.message, self.obj)
self.assertEqual(
e.message,
("This is the error message\n\nThe error appears to be in 'foo.yml': line 4, column 1, but may\nbe elsewhere in the file depending on "
"the exact syntax problem.\n\nThe offending line appears to be:\n\nthis is line 2\nthis is line 3\n^ here\n")
)

def test_get_error_lines_error_empty_lines_around_error(self):
"""Test that trailing whitespace after the error is removed"""
m = mock_open()
m.return_value.readlines.return_value = ['this is line 1\n', 'this is line 2\n', 'this is line 3\n', ' \n', ' \n', ' ']

with patch('{0}.open'.format(BUILTINS), m):
self.obj.ansible_pos = ('foo.yml', 5, 1)
e = AnsibleError(self.message, self.obj)
self.assertEqual(
e.message,
("This is the error message\n\nThe error appears to be in 'foo.yml': line 5, column 1, but may\nbe elsewhere in the file depending on "
"the exact syntax problem.\n\nThe offending line appears to be:\n\nthis is line 2\nthis is line 3\n^ here\n")
)