In recent Python version, when exceptions are unpickled their __init__() method is called again. The parameters passed to __init__() upon unpickling are *self.args. Because the first execution of __init__() sets self.args to something different from the parameters passed to __init__() (by passing something to Exception.__init__()), upon unpickling __init__() is called with unexpected parameters. For example, if a NameError is raised in a test, UnhandledTestFail.__init__() will call Exception.__init__(self, "Unhandled NameError: ..."). Upon unpickling, UnhandledTestFail.__init__() will be called with "Unhandled NameError: ..." which is a string. It will then call Exception.__init__(self, "Unhandled str: Unhandled NameError: ...") because str is not an instance of TestFail. The resulting exception's __str__() method will return "Unhandled str: Unhandled NameError: ..." which isn't pretty. To fix this, this patch makes the Unhandled* exceptions rely on __str__() and attributes (self.unhandled_exception and self.traceback) instead of __init__() and self.args. An alternative solution could be to check whether the parameter passed to __init__() is a string, and if so, pass it to the parent exception's __init__() without modification. Signed-off-by: Michael Goldish <mgoldish@xxxxxxxxxx> --- client/bin/job.py | 6 ++-- client/common_lib/error.py | 51 ++++++++++++++++++++++++++++--------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/client/bin/job.py b/client/bin/job.py index 3d552ce..f4306fc 100644 --- a/client/bin/job.py +++ b/client/bin/job.py @@ -1182,13 +1182,13 @@ def runjob(control, drop_caches, options): sys.exit(1) except error.JobError, instance: - logging.error("JOB ERROR: " + instance.args[0]) + logging.error("JOB ERROR: " + str(instance)) if myjob: command = None if len(instance.args) > 1: command = instance.args[1] - myjob.record('ABORT', None, command, instance.args[0]) - myjob.record('END ABORT', None, None, instance.args[0]) + myjob.record('ABORT', None, command, str(instance)) + myjob.record('END ABORT', None, None, str(instance)) assert myjob._record_indent == 0 myjob.complete(1) else: diff --git a/client/common_lib/error.py b/client/common_lib/error.py index 42dfe2b..71ba4e5 100644 --- a/client/common_lib/error.py +++ b/client/common_lib/error.py @@ -44,14 +44,19 @@ class JobError(AutotestError): class UnhandledJobError(JobError): """Indicates an unhandled error in a job.""" def __init__(self, unhandled_exception): - if isinstance(unhandled_exception, JobError): - JobError.__init__(self, *unhandled_exception.args) + JobError.__init__(self, unhandled_exception) + self.unhandled_exception = unhandled_exception + self.traceback = traceback.format_exc() + + def __str__(self): + if isinstance(self.unhandled_exception, JobError): + return JobError.__str__(self.unhandled_exception) else: msg = "Unhandled %s: %s" - msg %= (unhandled_exception.__class__.__name__, - unhandled_exception) - msg += "\n" + traceback.format_exc() - JobError.__init__(self, msg) + msg %= (self.unhandled_exception.__class__.__name__, + self.unhandled_exception) + msg += "\n" + self.traceback + return msg class TestBaseException(AutotestError): @@ -85,27 +90,37 @@ class TestWarn(TestBaseException): class UnhandledTestError(TestError): """Indicates an unhandled error in a test.""" def __init__(self, unhandled_exception): - if isinstance(unhandled_exception, TestError): - TestError.__init__(self, *unhandled_exception.args) + TestError.__init__(self, unhandled_exception) + self.unhandled_exception = unhandled_exception + self.traceback = traceback.format_exc() + + def __str__(self): + if isinstance(self.unhandled_exception, TestError): + return TestError.__str__(self.unhandled_exception) else: msg = "Unhandled %s: %s" - msg %= (unhandled_exception.__class__.__name__, - unhandled_exception) - msg += "\n" + traceback.format_exc() - TestError.__init__(self, msg) + msg %= (self.unhandled_exception.__class__.__name__, + self.unhandled_exception) + msg += "\n" + self.traceback + return msg class UnhandledTestFail(TestFail): """Indicates an unhandled fail in a test.""" def __init__(self, unhandled_exception): - if isinstance(unhandled_exception, TestFail): - TestFail.__init__(self, *unhandled_exception.args) + TestFail.__init__(self, unhandled_exception) + self.unhandled_exception = unhandled_exception + self.traceback = traceback.format_exc() + + def __str__(self): + if isinstance(self.unhandled_exception, TestFail): + return TestFail.__str__(self.unhandled_exception) else: msg = "Unhandled %s: %s" - msg %= (unhandled_exception.__class__.__name__, - unhandled_exception) - msg += "\n" + traceback.format_exc() - TestFail.__init__(self, msg) + msg %= (self.unhandled_exception.__class__.__name__, + self.unhandled_exception) + msg += "\n" + self.traceback + return msg class CmdError(TestError): -- 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