src/eric7/Testing/Interfaces/UnittestExecutor.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9264
18a7312cfdb3
child 9313
6bac6775abb2
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
21 21
22 class UnittestExecutor(TestExecutorBase): 22 class UnittestExecutor(TestExecutorBase):
23 """ 23 """
24 Class implementing the executor for the standard 'unittest' framework. 24 Class implementing the executor for the standard 'unittest' framework.
25 """ 25 """
26
26 module = "unittest" 27 module = "unittest"
27 name = "unittest" 28 name = "unittest"
28 29
29 runner = os.path.join(os.path.dirname(__file__), "UnittestRunner.py") 30 runner = os.path.join(os.path.dirname(__file__), "UnittestRunner.py")
30 31
31 def __init__(self, testWidget): 32 def __init__(self, testWidget):
32 """ 33 """
33 Constructor 34 Constructor
34 35
35 @param testWidget reference to the unit test widget 36 @param testWidget reference to the unit test widget
36 @type TestingWidget 37 @type TestingWidget
37 """ 38 """
38 super().__init__(testWidget) 39 super().__init__(testWidget)
39 40
40 self.__statusCategoryMapping = { 41 self.__statusCategoryMapping = {
41 "failure": TestResultCategory.FAIL, 42 "failure": TestResultCategory.FAIL,
42 "error": TestResultCategory.FAIL, 43 "error": TestResultCategory.FAIL,
43 "skipped": TestResultCategory.SKIP, 44 "skipped": TestResultCategory.SKIP,
44 "expected failure": TestResultCategory.OK, 45 "expected failure": TestResultCategory.OK,
45 "unexpected success": TestResultCategory.FAIL, 46 "unexpected success": TestResultCategory.FAIL,
46 "success": TestResultCategory.OK, 47 "success": TestResultCategory.OK,
47 } 48 }
48 49
49 self.__statusDisplayMapping = { 50 self.__statusDisplayMapping = {
50 "failure": self.tr("Failure"), 51 "failure": self.tr("Failure"),
51 "error": self.tr("Error"), 52 "error": self.tr("Error"),
52 "skipped": self.tr("Skipped"), 53 "skipped": self.tr("Skipped"),
53 "expected failure": self.tr("Expected Failure"), 54 "expected failure": self.tr("Expected Failure"),
54 "unexpected success": self.tr("Unexpected Success"), 55 "unexpected success": self.tr("Unexpected Success"),
55 "success": self.tr("Success"), 56 "success": self.tr("Success"),
56 } 57 }
57 58
58 self.__testWidget = testWidget 59 self.__testWidget = testWidget
59 60
60 def getVersions(self, interpreter): 61 def getVersions(self, interpreter):
61 """ 62 """
62 Public method to get the test framework version and version information 63 Public method to get the test framework version and version information
63 of its installed plugins. 64 of its installed plugins.
64 65
65 @param interpreter interpreter to be used for the test 66 @param interpreter interpreter to be used for the test
66 @type str 67 @type str
67 @return dictionary containing the framework name and version and the 68 @return dictionary containing the framework name and version and the
68 list of available plugins with name and version each 69 list of available plugins with name and version each
69 @rtype dict 70 @rtype dict
74 exitCode = proc.exitCode() 75 exitCode = proc.exitCode()
75 if exitCode == 0: 76 if exitCode == 0:
76 versionsStr = self.readAllOutput(proc) 77 versionsStr = self.readAllOutput(proc)
77 with contextlib.suppress(json.JSONDecodeError): 78 with contextlib.suppress(json.JSONDecodeError):
78 return json.loads(versionsStr) 79 return json.loads(versionsStr)
79 80
80 return {} 81 return {}
81 82
82 def hasCoverage(self, interpreter): 83 def hasCoverage(self, interpreter):
83 """ 84 """
84 Public method to get the test framework version and version information 85 Public method to get the test framework version and version information
85 of its installed plugins. 86 of its installed plugins.
86 87
87 @param interpreter interpreter to be used for the test 88 @param interpreter interpreter to be used for the test
88 @type str 89 @type str
89 @return flag indicating the availability of coverage functionality 90 @return flag indicating the availability of coverage functionality
90 @rtype bool 91 @rtype bool
91 """ 92 """
92 return True 93 return True
93 94
94 def createArguments(self, config): 95 def createArguments(self, config):
95 """ 96 """
96 Public method to create the arguments needed to start the test process. 97 Public method to create the arguments needed to start the test process.
97 98
98 @param config configuration for the test execution 99 @param config configuration for the test execution
99 @type TestConfig 100 @type TestConfig
100 @return list of process arguments 101 @return list of process arguments
101 @rtype list of str 102 @rtype list of str
102 """ 103 """
104 UnittestExecutor.runner, 105 UnittestExecutor.runner,
105 "runtest", 106 "runtest",
106 self.reader.address(), 107 self.reader.address(),
107 str(self.reader.port()), 108 str(self.reader.port()),
108 ] 109 ]
109 110
110 if config.discover: 111 if config.discover:
111 args.extend([ 112 args.extend(
112 "discover", 113 [
113 "--start-directory", 114 "discover",
114 config.discoveryStart, 115 "--start-directory",
115 ]) 116 config.discoveryStart,
116 117 ]
118 )
119
117 if config.failFast: 120 if config.failFast:
118 args.append("--failfast") 121 args.append("--failfast")
119 122
120 if config.collectCoverage: 123 if config.collectCoverage:
121 args.append("--cover") 124 args.append("--cover")
122 if config.eraseCoverage: 125 if config.eraseCoverage:
123 args.append("--cover-erase") 126 args.append("--cover-erase")
124 if config.coverageFile: 127 if config.coverageFile:
125 args.append("--cover-file") 128 args.append("--cover-file")
126 args.append(config.coverageFile) 129 args.append(config.coverageFile)
127 130
128 if config.failedOnly: 131 if config.failedOnly:
129 args.append("--failed-only") 132 args.append("--failed-only")
130 if config.testFilename: 133 if config.testFilename:
131 args.append(config.testFilename) 134 args.append(config.testFilename)
132 args.extend(self.__testWidget.getFailedTests()) 135 args.extend(self.__testWidget.getFailedTests())
133 elif config.testFilename: 136 elif config.testFilename:
134 args.append(config.testFilename) 137 args.append(config.testFilename)
135 args.append(config.testName if config.testName else "suite") 138 args.append(config.testName if config.testName else "suite")
136 139
137 return args 140 return args
138 141
139 def start(self, config, pythonpath): 142 def start(self, config, pythonpath):
140 """ 143 """
141 Public method to start the testing process. 144 Public method to start the testing process.
142 145
143 @param config configuration for the test execution 146 @param config configuration for the test execution
144 @type TestConfig 147 @type TestConfig
145 @param pythonpath list of directories to be added to the Python path 148 @param pythonpath list of directories to be added to the Python path
146 @type list of str 149 @type list of str
147 """ 150 """
148 self.reader = EricJsonReader(name="Unittest Reader", parent=self) 151 self.reader = EricJsonReader(name="Unittest Reader", parent=self)
149 self.reader.dataReceived.connect(self.__processData) 152 self.reader.dataReceived.connect(self.__processData)
150 153
151 super().start(config, pythonpath) 154 super().start(config, pythonpath)
152 155
153 def finished(self): 156 def finished(self):
154 """ 157 """
155 Public method handling the unit test process been finished. 158 Public method handling the unit test process been finished.
156 159
157 This method should read the results (if necessary) and emit the signal 160 This method should read the results (if necessary) and emit the signal
158 testFinished. 161 testFinished.
159 """ 162 """
160 self.reader.close() 163 self.reader.close()
161 164
162 output = self.readAllOutput() 165 output = self.readAllOutput()
163 self.testFinished.emit([], output) 166 self.testFinished.emit([], output)
164 167
165 @pyqtSlot(object) 168 @pyqtSlot(object)
166 def __processData(self, data): 169 def __processData(self, data):
167 """ 170 """
168 Private slot to process the received data. 171 Private slot to process the received data.
169 172
170 @param data data object received 173 @param data data object received
171 @type dict 174 @type dict
172 """ 175 """
173 # error collecting tests 176 # error collecting tests
174 if data["event"] == "collecterror": 177 if data["event"] == "collecterror":
175 self.collectError.emit([("", data["error"])]) 178 self.collectError.emit([("", data["error"])])
176 179
177 # tests collected 180 # tests collected
178 elif data["event"] == "collected": 181 elif data["event"] == "collected":
179 self.collected.emit([ 182 self.collected.emit(
180 (t["id"], t["name"], t["description"]) for t in data["tests"] 183 [(t["id"], t["name"], t["description"]) for t in data["tests"]]
181 ]) 184 )
182 185
183 # test started 186 # test started
184 elif data["event"] == "started": 187 elif data["event"] == "started":
185 self.startTest.emit( 188 self.startTest.emit((data["id"], data["name"], data["description"]))
186 (data["id"], data["name"], data["description"]) 189
187 )
188
189 # test result 190 # test result
190 elif data["event"] == "result": 191 elif data["event"] == "result":
191 filename, lineno = None, None 192 filename, lineno = None, None
192 tracebackLines = data.get("traceback", "").splitlines() 193 tracebackLines = data.get("traceback", "").splitlines()
193 if tracebackLines: 194 if tracebackLines:
194 # find the last entry matching the pattern 195 # find the last entry matching the pattern
195 for index in range(len(tracebackLines) - 1, -1, -1): 196 for index in range(len(tracebackLines) - 1, -1, -1):
196 fmatch = re.search(r'File "(.*?)", line (\d*?),.*', 197 fmatch = re.search(
197 tracebackLines[index]) 198 r'File "(.*?)", line (\d*?),.*', tracebackLines[index]
199 )
198 if fmatch: 200 if fmatch:
199 break 201 break
200 if fmatch: 202 if fmatch:
201 filename = fmatch.group(1) 203 filename = fmatch.group(1)
202 lineno = int(fmatch.group(2)) 204 lineno = int(fmatch.group(2))
203 205
204 message = data.get("shortmsg", "") 206 message = data.get("shortmsg", "")
205 if not message and tracebackLines: 207 if not message and tracebackLines:
206 message = tracebackLines[-1].split(":", 1)[1].strip() 208 message = tracebackLines[-1].split(":", 1)[1].strip()
207 209
208 self.testResult.emit(TestResult( 210 self.testResult.emit(
209 category=self.__statusCategoryMapping[data["status"]], 211 TestResult(
210 status=self.__statusDisplayMapping[data["status"]], 212 category=self.__statusCategoryMapping[data["status"]],
211 name=data["name"], 213 status=self.__statusDisplayMapping[data["status"]],
212 id=data["id"], 214 name=data["name"],
213 description=data["description"], 215 id=data["id"],
214 message=message, 216 description=data["description"],
215 extra=tracebackLines, 217 message=message,
216 duration=data.get("duration_ms", None), 218 extra=tracebackLines,
217 filename=filename, 219 duration=data.get("duration_ms", None),
218 lineno=lineno, 220 filename=filename,
219 subtestResult=data.get("subtest", False) 221 lineno=lineno,
220 )) 222 subtestResult=data.get("subtest", False),
221 223 )
224 )
225
222 # test run finished 226 # test run finished
223 elif data["event"] == "finished": 227 elif data["event"] == "finished":
224 self.testRunFinished.emit(data["tests"], data["duration_s"]) 228 self.testRunFinished.emit(data["tests"], data["duration_s"])
225 229
226 # coverage data 230 # coverage data
227 elif data["event"] == "coverage": 231 elif data["event"] == "coverage":
228 self.coverageDataSaved.emit(data["file"]) 232 self.coverageDataSaved.emit(data["file"])

eric ide

mercurial