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