Set lock_path correctly.
[openstack-build/neutron-build.git] / neutron / tests / post_mortem_debug.py
1 # Copyright 2013 Red Hat, Inc.
2 # All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
7 #
8 #      http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
14 # under the License.
15
16 import functools
17 import traceback
18
19
20 def get_exception_handler(debugger_name):
21     debugger = _get_debugger(debugger_name)
22     return functools.partial(_exception_handler, debugger)
23
24
25 def _get_debugger(debugger_name):
26     try:
27         debugger = __import__(debugger_name)
28     except ImportError:
29         raise ValueError("can't import %s module as a post mortem debugger" %
30                          debugger_name)
31     if 'post_mortem' in dir(debugger):
32         return debugger
33     else:
34         raise ValueError("%s is not a supported post mortem debugger" %
35                          debugger_name)
36
37
38 def _exception_handler(debugger, exc_info):
39     """Exception handler enabling post-mortem debugging.
40
41     A class extending testtools.TestCase can add this handler in setUp():
42
43         self.addOnException(post_mortem_debug.exception_handler)
44
45     When an exception occurs, the user will be dropped into a debugger
46     session in the execution environment of the failure.
47
48     Frames associated with the testing framework are excluded so that
49     the post-mortem session for an assertion failure will start at the
50     assertion call (e.g. self.assertTrue) rather than the framework code
51     that raises the failure exception (e.g. the assertTrue method).
52     """
53     tb = exc_info[2]
54     ignored_traceback = get_ignored_traceback(tb)
55     if ignored_traceback:
56         tb = FilteredTraceback(tb, ignored_traceback)
57     traceback.print_exception(exc_info[0], exc_info[1], tb)
58     debugger.post_mortem(tb)
59
60
61 def get_ignored_traceback(tb):
62     """Retrieve the first traceback of an ignored trailing chain.
63
64     Given an initial traceback, find the first traceback of a trailing
65     chain of tracebacks that should be ignored.  The criteria for
66     whether a traceback should be ignored is whether its frame's
67     globals include the __unittest marker variable. This criteria is
68     culled from:
69
70         unittest.TestResult._is_relevant_tb_level
71
72     For example:
73
74        tb.tb_next => tb0.tb_next => tb1.tb_next
75
76     - If no tracebacks were to be ignored, None would be returned.
77     - If only tb1 was to be ignored, tb1 would be returned.
78     - If tb0 and tb1 were to be ignored, tb0 would be returned.
79     - If either of only tb or only tb0 was to be ignored, None would
80       be returned because neither tb or tb0 would be part of a
81       trailing chain of ignored tracebacks.
82     """
83     # Turn the traceback chain into a list
84     tb_list = []
85     while tb:
86         tb_list.append(tb)
87         tb = tb.tb_next
88
89     # Find all members of an ignored trailing chain
90     ignored_tracebacks = []
91     for tb in reversed(tb_list):
92         if '__unittest' in tb.tb_frame.f_globals:
93             ignored_tracebacks.append(tb)
94         else:
95             break
96
97     # Return the first member of the ignored trailing chain
98     if ignored_tracebacks:
99         return ignored_tracebacks[-1]
100
101
102 class FilteredTraceback(object):
103     """Wraps a traceback to filter unwanted frames."""
104
105     def __init__(self, tb, filtered_traceback):
106         """Constructor.
107
108         :param tb: The start of the traceback chain to filter.
109         :param filtered_traceback: The first traceback of a trailing
110                chain that is to be filtered.
111         """
112         self._tb = tb
113         self.tb_lasti = self._tb.tb_lasti
114         self.tb_lineno = self._tb.tb_lineno
115         self.tb_frame = self._tb.tb_frame
116         self._filtered_traceback = filtered_traceback
117
118     @property
119     def tb_next(self):
120         tb_next = self._tb.tb_next
121         if tb_next and tb_next != self._filtered_traceback:
122             return FilteredTraceback(tb_next, self._filtered_traceback)