From a50332fc8a4b6d68a3131d3a479f75b1cf76dcea Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Thu, 9 Oct 2014 12:52:09 -0500 Subject: [PATCH] Adding v2 error line support, and tests --- test/v2/errors/__init__.py | 18 +++++++++++++ test/v2/errors/test_errors.py | 33 +++++++++++++++++++++++ v2/ansible/errors/__init__.py | 43 +++++++++++++++++++++++++----- v2/ansible/parsing/yaml/objects.py | 3 +++ 4 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 test/v2/errors/__init__.py create mode 100644 test/v2/errors/test_errors.py diff --git a/test/v2/errors/__init__.py b/test/v2/errors/__init__.py new file mode 100644 index 00000000000..674334b15a3 --- /dev/null +++ b/test/v2/errors/__init__.py @@ -0,0 +1,18 @@ +# (c) 2012-2014, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible 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. +# +# Ansible 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 Ansible. If not, see . + + diff --git a/test/v2/errors/test_errors.py b/test/v2/errors/test_errors.py new file mode 100644 index 00000000000..874195e476a --- /dev/null +++ b/test/v2/errors/test_errors.py @@ -0,0 +1,33 @@ +# TODO: header + +import unittest + +from mock import mock_open, patch + +from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject +from ansible.errors import AnsibleError + +class TestErrors(unittest.TestCase): + + def setUp(self): + self.message = 'this is the error message' + + def tearDown(self): + pass + + def test_basic_error(self): + e = AnsibleError(self.message) + assert e.message == self.message + + def test_error_with_object(self): + obj = AnsibleBaseYAMLObject() + obj._data_source = 'foo.yml' + obj._line_number = 1 + obj._column_number = 1 + + 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('__builtin__.open', m): + e = AnsibleError(self.message, obj) + + assert e.message == 'this is the error message\nThe error occurred on line 1 of the file foo.yml:\nthis is line 1\n^' diff --git a/v2/ansible/errors/__init__.py b/v2/ansible/errors/__init__.py index ae629ba4bbe..54406ef6c25 100644 --- a/v2/ansible/errors/__init__.py +++ b/v2/ansible/errors/__init__.py @@ -15,15 +15,46 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +import os +from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject + class AnsibleError(Exception): - def __init__(self, message, object=None): - self.message = message - self.object = object + def __init__(self, message, obj=None): + self._obj = obj + if isinstance(self._obj, AnsibleBaseYAMLObject): + extended_error = self._get_extended_error() + if extended_error: + self.message = '%s\n%s' % (message, extended_error) + else: + self.message = message + + def __repr__(self): + return self.message + + def _get_line_from_file(self, filename, line_number): + with open(filename, 'r') as f: + lines = f.readlines() + if line_number < len(lines): + return lines[line_number] + return None + + def _get_extended_error(self): + error_message = '' - # TODO: nice __repr__ message that includes the line number if the object - # it was constructed with had the line number + try: + (src_file, line_number, col_number) = self._obj.get_position_info() + error_message += 'The error occurred on line %d of the file %s:\n' % (line_number, src_file) + if src_file not in ('', ''): + responsible_line = self._get_line_from_file(src_file, line_number - 1) + if responsible_line: + error_message += responsible_line + error_message += (' ' * (col_number-1)) + '^' + except IOError: + error_message += '\n(could not open file to display line)' + except IndexError: + error_message += '\n(specified line no longer in file, maybe it changed?)' - # TODO: tests for the line number functionality + return error_message class AnsibleParserError(AnsibleError): ''' something was detected early that is wrong about a playbook or data file ''' diff --git a/v2/ansible/parsing/yaml/objects.py b/v2/ansible/parsing/yaml/objects.py index cc9fc445d22..5870ea8cbe7 100644 --- a/v2/ansible/parsing/yaml/objects.py +++ b/v2/ansible/parsing/yaml/objects.py @@ -8,6 +8,9 @@ class AnsibleBaseYAMLObject(object): _line_number = None _column_number = None + def get_position_info(self): + return (self._data_source, self._line_number, self._column_number) + class AnsibleMapping(AnsibleBaseYAMLObject, dict): ''' sub class for dictionaries ''' pass