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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



In complex tests (KVM) an exception string is often not informative enough and
the traceback and source code have to be examined in order to figure out what
caused the exception.  Context strings are a way for tests to provide
information about what they're doing, so that when an exception is raised, this
information will be embedded in the exception string.  The result is a concise
yet highly informative exception string, which should make it very easy to
figure out where/when the exception was raised.

A typical example for a test where this may be useful is KVM's reboot test.
Some exceptions can be raised either before or after the VM is rebooted (e.g.
logging into the guest can fail) and whether they are raised before or after
is critical to the understanding of the failure.  Normally the traceback would
have to be examined, but the proposed method makes it easy to know where the
exception is raised without doing so.  To achieve this, the reboot test should
place calls to error.context() as follows:

error.context("before reboot")
<carry out pre-reboot actions>
error.context("sending reboot command")
<send the reboot command>
error.context("after reboot")
<carry out post-reboot actions>

If login fails in the pre-reboot section, the resulting exception string can
can have something like "context: before reboot" embedded in it.  (The actual
embedding is done in the next patch in the series.)

Signed-off-by: Michael Goldish <mgoldish@xxxxxxxxxx>
---
 client/common_lib/error.py |   82 ++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 80 insertions(+), 2 deletions(-)

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
-- 
1.7.3.4

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux