diff mbox

[KVM-AUTOTEST,3/7,RFC] Introduce exception context strings

Message ID 1294079651-21631-3-git-send-email-mgoldish@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Michael Goldish Jan. 3, 2011, 6:34 p.m. UTC
None
diff mbox

Patch

diff --git a/client/common_lib/error.py b/client/common_lib/error.py
index f1ddaea..394f555 100644
--- a/client/common_lib/error.py
+++ b/client/common_lib/error.py
@@ -2,13 +2,13 @@ 
 Internal global error types
 """
 
-import sys, traceback
+import sys, traceback, threading, logging
 from traceback import format_exception
 
 # Add names you want to be imported by 'from errors import *' to this list.
 # This must be list not a tuple as we modify it to include all of our
 # the Exception classes we define below at the end of this file.
-__all__ = ['format_error']
+__all__ = ['format_error', 'context', 'get_context']
 
 
 def format_error():
@@ -21,6 +21,84 @@  def format_error():
     return ''.join(trace)
 
 
+# Exception context information:
+# ------------------------------
+# Every function can have some context string associated with it.
+# The context string can be changed by calling context(str) and cleared by
+# calling context() with no parameters.
+# get_context() joins the current context strings of all functions in the
+# provided traceback.  The result is a brief description of what the test was
+# doing in the provided traceback (which should be the traceback of a caught
+# exception).
+#
+# For example: assume a() calls b() and b() calls c().
+#
+# def a():
+#     error.context("hello")
+#     b()
+#     error.context("world")
+#     get_context() ----> 'world'
+#
+# def b():
+#     error.context("foo")
+#     c()
+#
+# def c():
+#     error.context("bar")
+#     get_context() ----> 'hello --> foo --> bar'
+
+context_data = threading.local()
+context_data.contexts = {}
+
+
+def context(c=None, log=logging.info):
+    """
+    Set the context for the currently executing function and log it as an INFO
+    message by default.
+
+    @param c: A string or None.  If c is None or an empty string, the context
+            for the current function is cleared.
+    @param log: A logging function to pass the string to.  If None, no function
+            will be called.
+    """
+    stack = traceback.extract_stack()
+    try:
+        key_parts = ["%s:%s:%s:" % (filename, func, lineno)
+                     for filename, lineno, func, text in stack[:-2]]
+        filename, lineno, func, text = stack[-2]
+        key_parts.append("%s:%s" % (filename, func))
+        key = "".join(key_parts)
+        if c:
+            context_data.contexts[key] = c
+            if log:
+                log("Context: %s" % c)
+        elif key in context_data.contexts:
+            del context_data.contexts[key]
+    finally:
+        # Prevent circular references
+        del stack
+
+
+def get_context(tb):
+    """
+    Construct a context string from the given traceback.
+
+    @param tb: A traceback object (usually sys.exc_info()[2]).
+    """
+    l = []
+    key = ""
+    for filename, lineno, func, text in traceback.extract_tb(tb):
+        key += "%s:%s" % (filename, func)
+        # An exception's traceback is relative to the function that handles it,
+        # so instead of an exact match, we expect the traceback entries to be
+        # suffixes of the stack entries recorded using extract_stack().
+        for key_ in context_data.contexts:
+            if key_.endswith(key):
+                l.append(context_data.contexts[key_])
+        key += ":%s:" % lineno
+    return " --> ".join(l)
+
+
 class JobContinue(SystemExit):
     """Allow us to bail out requesting continuance."""
     pass