[1392] | 1 | from __future__ import annotations
|
---|
| 2 | from typing import Optional, List
|
---|
| 3 |
|
---|
| 4 | class ExceptionContainer:
|
---|
| 5 | '''
|
---|
| 6 | An exception container contains the contents of an exception.
|
---|
| 7 | It's a standard frmat shared with Java so that we can exchange exceptions.
|
---|
| 8 | @param classname the class name of the exception, eg eg
|
---|
| 9 | "java.lang.NullPointerException", "AttributeError"
|
---|
| 10 | @param message short human-readable message explaining what happened.
|
---|
| 11 | null is used as "".
|
---|
| 12 | @param stacktrace a list of strings, each a stack point. May contain
|
---|
| 13 | filename, class name, line number, column number. Kept
|
---|
| 14 | general for now as currently this is intended for human
|
---|
| 15 | use only
|
---|
| 16 | @param cause another {@link ExceptionContainer} holding the
|
---|
| 17 | exception that caused this, or None if there was no
|
---|
| 18 | other cause.
|
---|
| 19 | '''
|
---|
| 20 | def __init__(self, classname:str, message:Optional[str],
|
---|
| 21 | stacktrace:List[str],cause: Optional[ExceptionContainer] ):
|
---|
| 22 | if classname == None or stacktrace == None:
|
---|
| 23 | raise AttributeError("classname and stacktrace must be not None")
|
---|
| 24 | self.__classname = classname
|
---|
| 25 | self.__message:str = "" if message == None else message
|
---|
| 26 | self.__stacktrace = stacktrace
|
---|
| 27 | self.__cause = cause
|
---|
| 28 |
|
---|
| 29 |
|
---|
| 30 | @staticmethod
|
---|
| 31 | def create( trace:str) -> ExceptionContainer:
|
---|
| 32 | '''
|
---|
| 33 | This creates a ExceptionContainer from a trace message.
|
---|
| 34 | The trace message is found in the errors and failures fields
|
---|
| 35 | of a TestResult object. The error field contains a list of tuples
|
---|
| 36 | and the 2nd tuple is the trace message.
|
---|
| 37 | It looks like a general error message, eg
|
---|
| 38 | Traceback (...):
|
---|
| 39 | File "blabla.py", line 22 in class1:
|
---|
| 40 | method(...)
|
---|
| 41 | File "blablabla.py" line 11 in class2:
|
---|
| 42 | raise blabla(....)
|
---|
| 43 | SomeErorr: errormessageblabla
|
---|
| 44 |
|
---|
| 45 | The above exception was the direct cause .....
|
---|
| 46 |
|
---|
| 47 | Traceback (....):
|
---|
| 48 | File ....
|
---|
| 49 |
|
---|
| 50 |
|
---|
| 51 | This trace message needs to be decyphered into a ExceptionContainer format
|
---|
| 52 | '''
|
---|
| 53 | return ExceptionContainer.create1(trace.split("\nThe above exception was the direct cause of the following exception:\n\n"),None)
|
---|
| 54 |
|
---|
| 55 | @staticmethod
|
---|
| 56 | def create1(traces:List[str], cause:Optional[ExceptionContainer] ):
|
---|
| 57 | if len(traces)==0:
|
---|
| 58 | return cause
|
---|
| 59 | # there are more causes. process the first
|
---|
| 60 | trace = traces[0].split("\n")
|
---|
| 61 | err = trace[-1].split(":",1)
|
---|
| 62 | clazz = err[0]
|
---|
| 63 | message=err[1]
|
---|
| 64 | stacklist=trace[:-1]
|
---|
| 65 |
|
---|
| 66 | # pre-pend this new cause before the existing cause
|
---|
| 67 | cause = ExceptionContainer(clazz, message, stacklist, cause)
|
---|
| 68 | # and continue with rest of the traces
|
---|
| 69 | return ExceptionContainer.create1(traces[1:],cause)
|
---|
| 70 |
|
---|
[1409] | 71 | @staticmethod
|
---|
| 72 | def fromException(t:Exception) -> Optional[ExceptionContainer]:
|
---|
| 73 | '''
|
---|
| 74 | creates container holding given exxception.
|
---|
| 75 | Note that python exceptions do not contain a stacktrace.
|
---|
| 76 | NOTE you may want to use create(stacktrace).
|
---|
| 77 | '''
|
---|
| 78 | msg:str = t.args[0] if t.args else ""
|
---|
| 79 | name:str = type(t).__name__;
|
---|
| 80 | cause:Optional[ExceptionContainer] = None if not t.__cause__ else \
|
---|
| 81 | ExceptionContainer.fromException(t.__cause__)
|
---|
| 82 | return ExceptionContainer(name, msg, [],cause);
|
---|
[1392] | 83 |
|
---|
| 84 | def getClassName(self) -> str:
|
---|
| 85 | return self.__classname
|
---|
| 86 |
|
---|
| 87 | def getMessage(self)->str :
|
---|
| 88 | return self.__message
|
---|
| 89 |
|
---|
| 90 | def getStackTrace(self) -> List[str]:
|
---|
| 91 | return self.__stacktrace
|
---|
| 92 |
|
---|
| 93 | def getCause(self) ->Optional[ExceptionContainer]:
|
---|
| 94 | return self.__cause
|
---|
| 95 |
|
---|
| 96 | def toJson(self):
|
---|
| 97 | '''
|
---|
| 98 | bit hacky, avoid use of pyson here to keep the libraries independent
|
---|
| 99 | '''
|
---|
| 100 | return {'classname':self.__classname, 'message':self.__message,
|
---|
| 101 | 'stacktrace':self.__stacktrace, 'cause':self.__cause}
|
---|
| 102 |
|
---|
| 103 | def __repr__(self):
|
---|
| 104 | mesg:str = self.__classname + ":" + self.__message + "\n"\
|
---|
| 105 | + "\n".join(self.__stacktrace)
|
---|
| 106 | if self.__cause != None:
|
---|
| 107 | mesg = mesg + "\n caused by:\n" + str(self.__cause)
|
---|
| 108 | return mesg;
|
---|
| 109 |
|
---|
| 110 | def __hash__(self):
|
---|
| 111 | return hash((self.__cause, self.__classname, self.__message, self.__stacktrace))
|
---|
| 112 |
|
---|
| 113 | def __eq__(self, obj):
|
---|
| 114 | if self == obj:
|
---|
| 115 | return True
|
---|
| 116 | if obj == None:
|
---|
| 117 | return False
|
---|
| 118 | if type(self) != type(obj):
|
---|
| 119 | return False
|
---|
| 120 | return self.__cause==obj.cause \
|
---|
| 121 | and self.__classname == obj.classname \
|
---|
| 122 | and self.__message == obj.message \
|
---|
| 123 | and self.__stacktrace == obj.stacktrace
|
---|