Skip to content

Commit

Permalink
Merge branch 'pickle-exc-handler'
Browse files Browse the repository at this point in the history
  • Loading branch information
clebergnu committed Jul 1, 2016
2 parents 394d4d9 + c267ab4 commit 3a88697
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 2 deletions.
12 changes: 10 additions & 2 deletions avocado/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from ..utils import wait
from ..utils import runtime
from ..utils import process
from ..utils import stacktrace

TEST_LOG = logging.getLogger("avocado.test")
APP_LOG = logging.getLogger("avocado.app")
Expand Down Expand Up @@ -298,13 +299,20 @@ def sigterm_handler(signum, frame): # pylint: disable=W0613
runtime.CURRENT_TEST = instance
early_state = instance.get_state()
early_state['early_status'] = True
queue.put(early_state)
try:
queue.put(early_state)
except Exception:
instance.error(stacktrace.str_unpickable_object(early_state))

self.result.start_test(early_state)
try:
instance.run_avocado()
finally:
queue.put(instance.get_state())
try:
state = instance.get_state()
queue.put(state)
except Exception:
instance.error(stacktrace.str_unpickable_object(state))

def setup(self):
"""
Expand Down
63 changes: 63 additions & 0 deletions avocado/utils/stacktrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from traceback import format_exception
import logging
import inspect
import pickle
from pprint import pformat


def tb_info(exc_info):
Expand Down Expand Up @@ -53,3 +55,64 @@ def log_message(message, logger='root'):
log = logging.getLogger(logger)
for line in message.splitlines():
log.error(line)


def analyze_unpickable_item(path_prefix, obj):
"""
Recursive method to obtain unpickable objects along with location
:param path_prefix: Path to this object
:param obj: The sub-object under introspection
:return: [($path_to_the_object, $value), ...]
"""
_path_prefix = path_prefix
try:
if hasattr(obj, "iteritems"):
subitems = obj.iteritems()
path_prefix += "[%s]"
elif hasattr(obj, "items"):
subitems = obj.items()
path_prefix += "[%s]"
elif isinstance(obj, list):
subitems = enumerate(obj)
path_prefix += "[%s]"
elif hasattr(obj, "__iter__"):
subitems = enumerate(obj.__iter__())
path_prefix += "<%s>"
elif hasattr(obj, "__dict__"):
subitems = obj.__dict__.iteritems()
path_prefix += ".%s"
else:
return [(path_prefix, obj)]
except Exception:
return [(path_prefix, obj)]
unpickables = []
for key, value in subitems:
try:
pickle.dumps(value)
except Exception:
ret = analyze_unpickable_item(path_prefix % key, value)
if ret:
unpickables.extend(ret)
if not unpickables:
return [(_path_prefix, obj)]
return unpickables


def str_unpickable_object(obj):
"""
Return human readable string identifying the unpickable objects
:param obj: The object for analysis
:raise ValueError: In case the object is pickable
"""
try:
pickle.dumps(obj)
except Exception:
pass
else:
raise ValueError("This object is pickable:\n%s" % pformat(obj))
unpickables = analyze_unpickable_item("this", obj)
return ("Unpickable object in:\n %s\nItems causing troubles:\n "
% "\n ".join(pformat(obj).splitlines()) +
"\n ".join("%s => %s" % obj for obj in unpickables))
84 changes: 84 additions & 0 deletions selftests/unit/test_utils_stacktrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#! /usr/bin/env python
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 LICENSE for more details.
#
# Copyright: 2016 Red Hat, Inc.
# Author: Lukas Doktor <[email protected]>
"""
avocado.utils.stacktrace unittests
"""

import re
import unittest

from avocado.utils import stacktrace


class Unpickable(object):
"""
Dummy class which does not support pickling
"""

def __getstate__(self):
raise NotImplementedError()


class InClassUnpickable(object):
"""
Dummy class containing unpickable object inside itself
"""

def __init__(self):
self.troublemaker = Unpickable()


class ListWithUnpickableAttribute(list):
"""
Dummy list class containing also unpickable attribute
"""

def __init__(self, *args, **kwargs):
self.__troublemaker = Unpickable()
super(ListWithUnpickableAttribute, self).__init__(*args, **kwargs)


class TestUnpickableObject(unittest.TestCase):

"""
Basic selftests for `avocado.utils.stacktrace.str_unpickable_object
"""

def test_basic(self):
""" Basic usage """
def check(exps, obj):
""" Search exps in the output of str_unpickable_object(obj) """
act = stacktrace.str_unpickable_object(obj)
for exp in exps:
if not re.search(exp, act):
self.fail("%r no match in:\n%s" % (exp, act))
self.assertRaises(ValueError, stacktrace.str_unpickable_object,
([{"foo": set([])}]))
check(["this => .*Unpickable"], Unpickable())
check([r"this\[0\]\[0\]\[foo\]\.troublemaker => .*Unpickable"],
[[{"foo": InClassUnpickable()}]])
check([r"this\[foo\] => \[1, 2, 3\]"],
{"foo": ListWithUnpickableAttribute([1, 2, 3])})
check([r"this\[foo\]\[3\] => .*Unpickable"],
{"foo": ListWithUnpickableAttribute([1, 2, 3, Unpickable()])})
check([r"this\[2\] => .*Unpickable",
r"this\[3\]\[foo\].troublemaker => .*Unpickable",
r"this\[4\]\[0\].troublemaker => .*Unpickable"],
[1, 2, Unpickable(), {"foo": InClassUnpickable(), "bar": None},
ListWithUnpickableAttribute(ListWithUnpickableAttribute(
[InClassUnpickable()]))])

if __name__ == '__main__':
unittest.main()

0 comments on commit 3a88697

Please sign in to comment.