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 |
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"]) |