Merge "Refactor exceptions message formatting on top of format method"
This commit is contained in:
commit
001016c194
@ -39,7 +39,7 @@ class CreateUtil(base.TobikoCMD):
|
||||
|
||||
|
||||
class NoSuchTemplateError(tobiko.TobikoException):
|
||||
message = "No such template. Existing templates:\n%(templates)s"
|
||||
message = "no such template; existing templates are: {templates}"
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -19,7 +19,20 @@ import testtools
|
||||
FailureException = testtools.TestCase.failureException
|
||||
|
||||
|
||||
def fail(reason, *args, **kwargs):
|
||||
def fail(msg, *args, **kwargs):
|
||||
"""Fail immediately current test case execution, with the given message.
|
||||
|
||||
Unconditionally raises a tobiko.FailureException as in below equivalent
|
||||
code:
|
||||
|
||||
raise FailureException(msg.format(*args, **kwargs))
|
||||
|
||||
:param msg: string message used to create FailureException
|
||||
:param *args: positional arguments to be passed to str.format method
|
||||
:param **kwargs: key-word arguments to be passed to str.format method
|
||||
:returns: It never returns
|
||||
:raises FailureException:
|
||||
"""
|
||||
if args or kwargs:
|
||||
reason = reason.format(*args, **kwargs)
|
||||
raise FailureException(reason)
|
||||
msg = msg.format(*args, **kwargs)
|
||||
raise FailureException(msg)
|
||||
|
@ -17,22 +17,40 @@ from __future__ import absolute_import
|
||||
class TobikoException(Exception):
|
||||
"""Base Tobiko Exception.
|
||||
|
||||
To use this class, inherit from it and define a 'message' property.
|
||||
To use this class, inherit from it and define attribute 'message' string.
|
||||
If **properties parameters is given, then it will format message string
|
||||
using properties as key-word arguments.
|
||||
|
||||
Example:
|
||||
|
||||
class MyException(TobikoException):
|
||||
message = "This exception occurred because of {reason}"
|
||||
|
||||
try:
|
||||
raise MyException(reason="something went wrong")
|
||||
except MyException as ex:
|
||||
|
||||
# It should print:
|
||||
# This exception occurred because of something went wrong
|
||||
print(ex)
|
||||
|
||||
# It should print:
|
||||
# something went wrong
|
||||
print(ex.reason)
|
||||
|
||||
:attribute message: the message to be printed out.
|
||||
"""
|
||||
|
||||
message = None
|
||||
|
||||
def __init__(self, **properties):
|
||||
super(TobikoException, self).__init__()
|
||||
self._properties = properties
|
||||
message = self.message # pylint: disable=exception-message-attribute
|
||||
if message:
|
||||
if properties:
|
||||
message = message % properties
|
||||
self._message = message or "unknown reason"
|
||||
|
||||
def __str__(self):
|
||||
return self._message
|
||||
def __init__(self, message=None, **properties):
|
||||
# pylint: disable=exception-message-attribute
|
||||
message = message or self.message or "unknown reason"
|
||||
if properties:
|
||||
message = message.format(**properties)
|
||||
self.message = message
|
||||
self._properties = properties or {}
|
||||
super(TobikoException, self).__init__(message)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
@ -40,3 +58,8 @@ class TobikoException(Exception):
|
||||
except KeyError:
|
||||
msg = ("{!r} object has no attribute {!r}").format(self, name)
|
||||
raise AttributeError(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return "{class_name}({message!r})".format(
|
||||
class_name=type(self).__name__,
|
||||
message=self.message)
|
||||
|
@ -301,16 +301,16 @@ def check_stack_status(stack, expected):
|
||||
|
||||
|
||||
class InvalidHeatStackOutputKey(tobiko.TobikoException):
|
||||
message = "Output key %(key)r not found in stack %(name)r."
|
||||
message = "output key {key!r} not found in stack {name!r}"
|
||||
|
||||
|
||||
class HeatStackNotFound(tobiko.TobikoException):
|
||||
message = "Stack %(name)r not found"
|
||||
message = "stack {name!r} not found"
|
||||
|
||||
|
||||
class InvalidHeatStackStatus(tobiko.TobikoException):
|
||||
message = ("Stack %(name)r status %(observed)r not in %(expected)r.\n"
|
||||
"%(status_reason)s")
|
||||
message = ("stack {name!r} status {observed!r} not in {expected!r}\n"
|
||||
"{status_reason!s}")
|
||||
|
||||
|
||||
class HeatStackCreationFailed(InvalidHeatStackStatus):
|
||||
|
@ -74,7 +74,7 @@ def keystone_credentials(api_version=None, auth_url=None,
|
||||
|
||||
|
||||
class InvalidKeystoneCredentials(tobiko.TobikoException):
|
||||
message = "Invalid Keystone credentials (%(credentials)r): %(reason)s."
|
||||
message = "invalid Keystone credentials; {reason!s}; {credentials!r}"
|
||||
|
||||
|
||||
class EnvironKeystoneCredentialsFixture(tobiko.SharedFixture):
|
||||
|
@ -24,7 +24,7 @@ class PingException(tobiko.TobikoException):
|
||||
|
||||
class PingError(PingException):
|
||||
"""Base ping error"""
|
||||
message = ("%(details)s")
|
||||
message = "{details!s}"
|
||||
|
||||
|
||||
class LocalPingError(PingError):
|
||||
@ -41,13 +41,13 @@ class UnknowHostError(PingError):
|
||||
|
||||
class BadAddressPingError(PingError):
|
||||
"""Raised when passing wrong address to ping command"""
|
||||
message = ("Bad address: %(address)r")
|
||||
message = "bad address ({address!r})"
|
||||
|
||||
|
||||
class PingFailed(PingError, tobiko.FailureException):
|
||||
"""Raised when ping timeout expires before reaching expected message count
|
||||
|
||||
"""
|
||||
message = ("Timeout of %(timeout)d seconds expired after counting only "
|
||||
"%(count)d out of expected %(expected_count)d ICMP messages of "
|
||||
"type %(message_type)r.")
|
||||
message = ("timeout of {timeout} seconds expired after counting only "
|
||||
"{count} out of expected {expected_count} ICMP messages of "
|
||||
"type {message_type!r}")
|
||||
|
@ -25,14 +25,14 @@ class ShellError(tobiko.TobikoException):
|
||||
class ShellCommandFailed(ShellError):
|
||||
"""Raised when shell command exited with non-zero status
|
||||
"""
|
||||
message = ("Command %(command)r failed, exit status: %(exit_status)d, "
|
||||
"stderr:\n%(stderr)s\n"
|
||||
"stdout:\n%(stdout)s")
|
||||
message = ("command {command!r} failed (exit status is {exit_status}); "
|
||||
"stderr:\n{stderr!s}\n"
|
||||
"stdout:\n{stdout!s}")
|
||||
|
||||
|
||||
class ShellTimeoutExpired(ShellError):
|
||||
"""Raised when shell command timeouts and has been killed before exiting
|
||||
"""
|
||||
message = ("Command '%(command)s' timed out: %(timeout)d, "
|
||||
"stderr:\n%(stderr)s\n"
|
||||
"stdout:\n%(stdout)s")
|
||||
message = ("command {command!r} timed out after {timeout!s} seconds; "
|
||||
"stderr:\n{stderr!s}\n"
|
||||
"stdout:\n{stdout!s}")
|
||||
|
38
tobiko/tests/test_assert.py
Normal file
38
tobiko/tests/test_assert.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Copyright 2019 Red Hat
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import tobiko
|
||||
from tobiko.tests import unit
|
||||
|
||||
|
||||
class TestFail(unit.TobikoUnitTest):
|
||||
|
||||
def test_fail(self):
|
||||
self._test_fail('some_reason')
|
||||
|
||||
def test_fail_with_args(self):
|
||||
self._test_fail('some {1!r} {0!s}', 'reason', 'strange')
|
||||
|
||||
def test_fail_with_kwargs(self):
|
||||
self._test_fail('some {b!r} {a!s}', a='reason', b='strange')
|
||||
|
||||
def _test_fail(self, reason, *args, **kwargs):
|
||||
ex = self.assertRaises(
|
||||
tobiko.FailureException, tobiko.fail, reason, *args, **kwargs)
|
||||
if args or kwargs:
|
||||
expected_reason = reason.format(*args, **kwargs)
|
||||
else:
|
||||
expected_reason = reason
|
||||
self.assertEqual(expected_reason, str(ex))
|
73
tobiko/tests/test_exception.py
Normal file
73
tobiko/tests/test_exception.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright 2019 Red Hat
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from __future__ import absolute_import
|
||||
|
||||
import tobiko
|
||||
from tobiko.tests import unit
|
||||
|
||||
|
||||
class SomeException(tobiko.TobikoException):
|
||||
message = "message formatted with {b} and {a}"
|
||||
|
||||
|
||||
class TestException(unit.TobikoUnitTest):
|
||||
|
||||
def test_init(self, message=None, **properties):
|
||||
ex = SomeException(message, **properties)
|
||||
expected_str = message or SomeException.message
|
||||
if properties:
|
||||
expected_str = expected_str.format(**properties)
|
||||
self.assertEqual(expected_str, str(ex))
|
||||
for k, v in properties.items():
|
||||
self.assertEqual(v, getattr(ex, k))
|
||||
|
||||
def test_init_with_properties(self):
|
||||
self.test_init(a='a', b='b')
|
||||
|
||||
def test_init_with_message(self):
|
||||
self.test_init('{other} message', other='another')
|
||||
|
||||
def test_raise(self):
|
||||
def _raise_my_exception(**kwargs):
|
||||
raise SomeException(**kwargs)
|
||||
ex = self.assertRaises(SomeException, _raise_my_exception, b=1, a=2)
|
||||
self.assertEqual('message formatted with 1 and 2', str(ex))
|
||||
|
||||
def test_repr(self):
|
||||
ex = SomeException('some reason')
|
||||
self.assertEqual("SomeException('some reason')", repr(ex))
|
||||
|
||||
def test_get_invalid_property(self):
|
||||
ex = SomeException(a='1', b='2')
|
||||
|
||||
def _get_invalid_property():
|
||||
return ex.invalid_attribute_name
|
||||
|
||||
ex = self.assertRaises(AttributeError, _get_invalid_property)
|
||||
self.assertEqual(
|
||||
"SomeException('message formatted with 2 and 1') object has no "
|
||||
"attribute 'invalid_attribute_name'", str(ex))
|
||||
|
||||
def test_docstring_example(self):
|
||||
|
||||
class MyException(tobiko.TobikoException):
|
||||
message = "This exception occurred because of {reason}"
|
||||
|
||||
try:
|
||||
raise MyException(reason="something went wrong")
|
||||
except MyException as ex:
|
||||
self.assertEqual(
|
||||
'This exception occurred because of something went wrong',
|
||||
str(ex))
|
||||
self.assertEqual('something went wrong', ex.reason)
|
Loading…
Reference in New Issue
Block a user