Merge "Add validation of base exception type(s) in failure type"
This commit is contained in:
@@ -168,6 +168,19 @@ class FailureObjectTestCase(test.TestCase):
|
|||||||
f2 = failure.Failure.from_dict(d_f)
|
f2 = failure.Failure.from_dict(d_f)
|
||||||
self.assertTrue(f.matches(f2))
|
self.assertTrue(f.matches(f2))
|
||||||
|
|
||||||
|
def test_bad_root_exception(self):
|
||||||
|
f = _captured_failure('Woot!')
|
||||||
|
d_f = f.to_dict()
|
||||||
|
d_f['exc_type_names'] = ['Junk']
|
||||||
|
self.assertRaises(exceptions.InvalidFormat,
|
||||||
|
failure.Failure.validate, d_f)
|
||||||
|
|
||||||
|
def test_valid_from_dict_to_dict_2(self):
|
||||||
|
f = _captured_failure('Woot!')
|
||||||
|
d_f = f.to_dict()
|
||||||
|
d_f['exc_type_names'] = ['RuntimeError', 'Exception', 'BaseException']
|
||||||
|
failure.Failure.validate(d_f)
|
||||||
|
|
||||||
def test_dont_catch_base_exception(self):
|
def test_dont_catch_base_exception(self):
|
||||||
try:
|
try:
|
||||||
raise SystemExit()
|
raise SystemExit()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import collections
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -28,6 +29,7 @@ from taskflow.utils import iter_utils
|
|||||||
from taskflow.utils import mixins
|
from taskflow.utils import mixins
|
||||||
from taskflow.utils import schema_utils as su
|
from taskflow.utils import schema_utils as su
|
||||||
|
|
||||||
|
|
||||||
_exception_message = encodeutils.exception_to_unicode
|
_exception_message = encodeutils.exception_to_unicode
|
||||||
|
|
||||||
|
|
||||||
@@ -121,6 +123,13 @@ class Failure(mixins.StrMixin):
|
|||||||
"""
|
"""
|
||||||
DICT_VERSION = 1
|
DICT_VERSION = 1
|
||||||
|
|
||||||
|
BASE_EXCEPTIONS = ('BaseException', 'Exception')
|
||||||
|
"""
|
||||||
|
Root exceptions of all other python exceptions.
|
||||||
|
|
||||||
|
See: https://docs.python.org/2/library/exceptions.html
|
||||||
|
"""
|
||||||
|
|
||||||
#: Expected failure schema (in json schema format).
|
#: Expected failure schema (in json schema format).
|
||||||
SCHEMA = {
|
SCHEMA = {
|
||||||
"$ref": "#/definitions/cause",
|
"$ref": "#/definitions/cause",
|
||||||
@@ -206,11 +215,29 @@ class Failure(mixins.StrMixin):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, data):
|
def validate(cls, data):
|
||||||
|
"""Validate input data matches expected failure ``dict`` format."""
|
||||||
try:
|
try:
|
||||||
su.schema_validate(data, cls.SCHEMA)
|
su.schema_validate(data, cls.SCHEMA)
|
||||||
except su.ValidationError as e:
|
except su.ValidationError as e:
|
||||||
raise exc.InvalidFormat("Failure data not of the"
|
raise exc.InvalidFormat("Failure data not of the"
|
||||||
" expected format: %s" % (e.message), e)
|
" expected format: %s" % (e.message), e)
|
||||||
|
else:
|
||||||
|
# Ensure that all 'exc_type_names' originate from one of
|
||||||
|
# BASE_EXCEPTIONS, because those are the root exceptions that
|
||||||
|
# python mandates/provides and anything else is invalid...
|
||||||
|
causes = collections.deque([data])
|
||||||
|
while causes:
|
||||||
|
cause = causes.popleft()
|
||||||
|
root_exc_type = cause['exc_type_names'][-1]
|
||||||
|
if root_exc_type not in cls.BASE_EXCEPTIONS:
|
||||||
|
raise exc.InvalidFormat(
|
||||||
|
"Failure data 'exc_type_names' must"
|
||||||
|
" have an initial exception type that is one"
|
||||||
|
" of %s types: '%s' is not one of those"
|
||||||
|
" types" % (cls.BASE_EXCEPTIONS, root_exc_type))
|
||||||
|
sub_causes = cause.get('causes')
|
||||||
|
if sub_causes:
|
||||||
|
causes.extend(sub_causes)
|
||||||
|
|
||||||
def _matches(self, other):
|
def _matches(self, other):
|
||||||
if self is other:
|
if self is other:
|
||||||
|
|||||||
Reference in New Issue
Block a user