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