src/eric7/Testing/Interfaces/UnittestExecutor.py

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

eric ide

mercurial