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 |
|
---|
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);
|
---|
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
|
---|