from __future__ import annotations from typing import Optional, List class ExceptionContainer: ''' An exception container contains the contents of an exception. It's a standard frmat shared with Java so that we can exchange exceptions. @param classname the class name of the exception, eg eg "java.lang.NullPointerException", "AttributeError" @param message short human-readable message explaining what happened. null is used as "". @param stacktrace a list of strings, each a stack point. May contain filename, class name, line number, column number. Kept general for now as currently this is intended for human use only @param cause another {@link ExceptionContainer} holding the exception that caused this, or None if there was no other cause. ''' def __init__(self, classname:str, message:Optional[str], stacktrace:List[str],cause: Optional[ExceptionContainer] ): if classname == None or stacktrace == None: raise AttributeError("classname and stacktrace must be not None") self.__classname = classname self.__message:str = "" if message == None else message self.__stacktrace = stacktrace self.__cause = cause @staticmethod def create( trace:str) -> ExceptionContainer: ''' This creates a ExceptionContainer from a trace message. The trace message is found in the errors and failures fields of a TestResult object. The error field contains a list of tuples and the 2nd tuple is the trace message. It looks like a general error message, eg Traceback (...): File "blabla.py", line 22 in class1: method(...) File "blablabla.py" line 11 in class2: raise blabla(....) SomeErorr: errormessageblabla The above exception was the direct cause ..... Traceback (....): File .... This trace message needs to be decyphered into a ExceptionContainer format ''' return ExceptionContainer.create1(trace.split("\nThe above exception was the direct cause of the following exception:\n\n"),None) @staticmethod def create1(traces:List[str], cause:Optional[ExceptionContainer] ): if len(traces)==0: return cause # there are more causes. process the first trace = traces[0].split("\n") err = trace[-1].split(":",1) clazz = err[0] message=err[1] stacklist=trace[:-1] # pre-pend this new cause before the existing cause cause = ExceptionContainer(clazz, message, stacklist, cause) # and continue with rest of the traces return ExceptionContainer.create1(traces[1:],cause) @staticmethod def fromException(t:Exception) -> Optional[ExceptionContainer]: ''' creates container holding given exxception. Note that python exceptions do not contain a stacktrace. NOTE you may want to use create(stacktrace). ''' msg:str = t.args[0] if t.args else "" name:str = type(t).__name__; cause:Optional[ExceptionContainer] = None if not t.__cause__ else \ ExceptionContainer.fromException(t.__cause__) return ExceptionContainer(name, msg, [],cause); def getClassName(self) -> str: return self.__classname def getMessage(self)->str : return self.__message def getStackTrace(self) -> List[str]: return self.__stacktrace def getCause(self) ->Optional[ExceptionContainer]: return self.__cause def toJson(self): ''' bit hacky, avoid use of pyson here to keep the libraries independent ''' return {'classname':self.__classname, 'message':self.__message, 'stacktrace':self.__stacktrace, 'cause':self.__cause} def __repr__(self): mesg:str = self.__classname + ":" + self.__message + "\n"\ + "\n".join(self.__stacktrace) if self.__cause != None: mesg = mesg + "\n caused by:\n" + str(self.__cause) return mesg; def __hash__(self): return hash((self.__cause, self.__classname, self.__message, self.__stacktrace)) def __eq__(self, obj): if self == obj: return True if obj == None: return False if type(self) != type(obj): return False return self.__cause==obj.cause \ and self.__classname == obj.classname \ and self.__message == obj.message \ and self.__stacktrace == obj.stacktrace