10 import json |
10 import json |
11 import os |
11 import os |
12 import sys |
12 import sys |
13 import time |
13 import time |
14 |
14 |
15 sys.path.insert( |
15 sys.path.insert(2, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) |
16 2, |
16 |
17 os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) |
17 |
18 ) |
18 class GetPluginVersionsPlugin: |
19 |
|
20 |
|
21 class GetPluginVersionsPlugin(): |
|
22 """ |
19 """ |
23 Class implementing a pytest plugin to extract the version info of all |
20 Class implementing a pytest plugin to extract the version info of all |
24 installed plugins. |
21 installed plugins. |
25 """ |
22 """ |
|
23 |
26 def __init__(self): |
24 def __init__(self): |
27 """ |
25 """ |
28 Constructor |
26 Constructor |
29 """ |
27 """ |
30 super().__init__() |
28 super().__init__() |
31 |
29 |
32 self.versions = [] |
30 self.versions = [] |
33 |
31 |
34 def pytest_cmdline_main(self, config): |
32 def pytest_cmdline_main(self, config): |
35 """ |
33 """ |
36 Public method called for performing the main command line action. |
34 Public method called for performing the main command line action. |
37 |
35 |
38 @param config pytest config object |
36 @param config pytest config object |
39 @type Config |
37 @type Config |
40 """ |
38 """ |
41 pluginInfo = config.pluginmanager.list_plugin_distinfo() |
39 pluginInfo = config.pluginmanager.list_plugin_distinfo() |
42 if pluginInfo: |
40 if pluginInfo: |
43 for _plugin, dist in pluginInfo: |
41 for _plugin, dist in pluginInfo: |
44 self.versions.append({ |
42 self.versions.append( |
45 "name": dist.project_name, |
43 {"name": dist.project_name, "version": dist.version} |
46 "version": dist.version |
44 ) |
47 }) |
45 |
48 |
|
49 def getVersions(self): |
46 def getVersions(self): |
50 """ |
47 """ |
51 Public method to get the assembled list of plugin versions. |
48 Public method to get the assembled list of plugin versions. |
52 |
49 |
53 @return list of collected plugin versions |
50 @return list of collected plugin versions |
54 @rtype list of dict |
51 @rtype list of dict |
55 """ |
52 """ |
56 return self.versions |
53 return self.versions |
57 |
54 |
58 |
55 |
59 class EricPlugin(): |
56 class EricPlugin: |
60 """ |
57 """ |
61 Class implementing a pytest plugin which reports the data in a format |
58 Class implementing a pytest plugin which reports the data in a format |
62 suitable for the PytestExecutor. |
59 suitable for the PytestExecutor. |
63 """ |
60 """ |
|
61 |
64 def __init__(self, writer): |
62 def __init__(self, writer): |
65 """ |
63 """ |
66 Constructor |
64 Constructor |
67 |
65 |
68 @param writer reference to the object to write the results to |
66 @param writer reference to the object to write the results to |
69 @type EricJsonWriter |
67 @type EricJsonWriter |
70 """ |
68 """ |
71 self.__writer = writer |
69 self.__writer = writer |
72 |
70 |
73 self.__testsRun = 0 |
71 self.__testsRun = 0 |
74 |
72 |
75 def __initializeReportData(self): |
73 def __initializeReportData(self): |
76 """ |
74 """ |
77 Private method to initialize attributes for data collection. |
75 Private method to initialize attributes for data collection. |
78 """ |
76 """ |
79 self.__status = '---' |
77 self.__status = "---" |
80 self.__duration = 0 |
78 self.__duration = 0 |
81 self.__report = [] |
79 self.__report = [] |
82 self.__reportPhase = "" |
80 self.__reportPhase = "" |
83 self.__sections = [] |
81 self.__sections = [] |
84 self.__hadError = False |
82 self.__hadError = False |
85 self.__wasSkipped = False |
83 self.__wasSkipped = False |
86 self.__wasXfail = False |
84 self.__wasXfail = False |
87 |
85 |
88 def pytest_report_header(self, config, startdir): |
86 def pytest_report_header(self, config, startdir): |
89 """ |
87 """ |
90 Public method called by pytest before any reporting. |
88 Public method called by pytest before any reporting. |
91 |
89 |
92 @param config reference to the configuration object |
90 @param config reference to the configuration object |
93 @type Config |
91 @type Config |
94 @param startdir starting directory |
92 @param startdir starting directory |
95 @type LocalPath |
93 @type LocalPath |
96 """ |
94 """ |
97 self.__writer.write({ |
95 self.__writer.write({"event": "config", "root": str(config.rootdir)}) |
98 "event": "config", |
96 |
99 "root": str(config.rootdir) |
|
100 }) |
|
101 |
|
102 def pytest_collectreport(self, report): |
97 def pytest_collectreport(self, report): |
103 """ |
98 """ |
104 Public method called by pytest after the tests have been collected. |
99 Public method called by pytest after the tests have been collected. |
105 |
100 |
106 @param report reference to the report object |
101 @param report reference to the report object |
107 @type CollectReport |
102 @type CollectReport |
108 """ |
103 """ |
109 if report.outcome == "failed": |
104 if report.outcome == "failed": |
110 self.__writer.write({ |
105 self.__writer.write( |
111 "event": "collecterror", |
106 { |
112 "nodeid": report.nodeid, |
107 "event": "collecterror", |
113 "report": str(report.longrepr), |
108 "nodeid": report.nodeid, |
114 }) |
109 "report": str(report.longrepr), |
115 |
110 } |
|
111 ) |
|
112 |
116 def pytest_itemcollected(self, item): |
113 def pytest_itemcollected(self, item): |
117 """ |
114 """ |
118 Public malled by pytest after a test item has been collected. |
115 Public malled by pytest after a test item has been collected. |
119 |
116 |
120 @param item reference to the collected test item |
117 @param item reference to the collected test item |
121 @type Item |
118 @type Item |
122 """ |
119 """ |
123 self.__writer.write({ |
120 self.__writer.write( |
124 "event": "collected", |
121 { |
125 "nodeid": item.nodeid, |
122 "event": "collected", |
126 "name": item.name, |
123 "nodeid": item.nodeid, |
127 }) |
124 "name": item.name, |
128 |
125 } |
|
126 ) |
|
127 |
129 def pytest_runtest_logstart(self, nodeid, location): |
128 def pytest_runtest_logstart(self, nodeid, location): |
130 """ |
129 """ |
131 Public method called by pytest before running a test. |
130 Public method called by pytest before running a test. |
132 |
131 |
133 @param nodeid node id of the test item |
132 @param nodeid node id of the test item |
134 @type str |
133 @type str |
135 @param location tuple containing the file name, the line number and |
134 @param location tuple containing the file name, the line number and |
136 the test name |
135 the test name |
137 @type tuple of (str, int, str) |
136 @type tuple of (str, int, str) |
138 """ |
137 """ |
139 self.__testsRun += 1 |
138 self.__testsRun += 1 |
140 |
139 |
141 self.__writer.write({ |
140 self.__writer.write( |
142 "event": "starttest", |
141 { |
143 "nodeid": nodeid, |
142 "event": "starttest", |
144 }) |
143 "nodeid": nodeid, |
145 |
144 } |
|
145 ) |
|
146 |
146 self.__initializeReportData() |
147 self.__initializeReportData() |
147 |
148 |
148 def pytest_runtest_logreport(self, report): |
149 def pytest_runtest_logreport(self, report): |
149 """ |
150 """ |
150 Public method called by pytest when a test phase (setup, call and |
151 Public method called by pytest when a test phase (setup, call and |
151 teardown) has been completed. |
152 teardown) has been completed. |
152 |
153 |
153 @param report reference to the test report object |
154 @param report reference to the test report object |
154 @type TestReport |
155 @type TestReport |
155 """ |
156 """ |
156 if report.when == "call": |
157 if report.when == "call": |
157 self.__status = report.outcome |
158 self.__status = report.outcome |
159 else: |
160 else: |
160 if report.outcome == "failed": |
161 if report.outcome == "failed": |
161 self.__hadError = True |
162 self.__hadError = True |
162 elif report.outcome == "skipped": |
163 elif report.outcome == "skipped": |
163 self.__wasSkipped = True |
164 self.__wasSkipped = True |
164 |
165 |
165 if hasattr(report, "wasxfail"): |
166 if hasattr(report, "wasxfail"): |
166 self.__wasXfail = True |
167 self.__wasXfail = True |
167 self.__report.append(report.wasxfail) |
168 self.__report.append(report.wasxfail) |
168 self.__reportPhase = report.when |
169 self.__reportPhase = report.when |
169 |
170 |
170 self.__sections = report.sections |
171 self.__sections = report.sections |
171 |
172 |
172 if report.longrepr: |
173 if report.longrepr: |
173 self.__reportPhase = report.when |
174 self.__reportPhase = report.when |
174 if ( |
175 if ( |
175 hasattr(report.longrepr, "reprcrash") and |
176 hasattr(report.longrepr, "reprcrash") |
176 report.longrepr.reprcrash is not None |
177 and report.longrepr.reprcrash is not None |
177 ): |
178 ): |
178 self.__report.append( |
179 self.__report.append(report.longrepr.reprcrash.message) |
179 report.longrepr.reprcrash.message) |
|
180 if isinstance(report.longrepr, tuple): |
180 if isinstance(report.longrepr, tuple): |
181 self.__report.append(report.longrepr[2]) |
181 self.__report.append(report.longrepr[2]) |
182 elif isinstance(report.longrepr, str): |
182 elif isinstance(report.longrepr, str): |
183 self.__report.append(report.longrepr) |
183 self.__report.append(report.longrepr) |
184 else: |
184 else: |
185 self.__report.append(str(report.longrepr)) |
185 self.__report.append(str(report.longrepr)) |
186 |
186 |
187 def pytest_runtest_logfinish(self, nodeid, location): |
187 def pytest_runtest_logfinish(self, nodeid, location): |
188 """ |
188 """ |
189 Public method called by pytest after a test has been completed. |
189 Public method called by pytest after a test has been completed. |
190 |
190 |
191 @param nodeid node id of the test item |
191 @param nodeid node id of the test item |
192 @type str |
192 @type str |
193 @param location tuple containing the file name, the line number and |
193 @param location tuple containing the file name, the line number and |
194 the test name |
194 the test name |
195 @type tuple of (str, int, str) |
195 @type tuple of (str, int, str) |
196 """ |
196 """ |
197 if self.__wasXfail: |
197 if self.__wasXfail: |
198 self.__status = ( |
198 self.__status = "xpassed" if self.__status == "passed" else "xfailed" |
199 "xpassed" |
|
200 if self.__status == "passed" else |
|
201 "xfailed" |
|
202 ) |
|
203 elif self.__wasSkipped: |
199 elif self.__wasSkipped: |
204 self.__status = "skipped" |
200 self.__status = "skipped" |
205 |
201 |
206 data = { |
202 data = { |
207 "event": "result", |
203 "event": "result", |
208 "status": self.__status, |
204 "status": self.__status, |
209 "with_error": self.__hadError, |
205 "with_error": self.__hadError, |
210 "sections": self.__sections, |
206 "sections": self.__sections, |
216 } |
212 } |
217 if self.__report: |
213 if self.__report: |
218 messageLines = self.__report[0].rstrip().splitlines() |
214 messageLines = self.__report[0].rstrip().splitlines() |
219 data["message"] = messageLines[0] |
215 data["message"] = messageLines[0] |
220 data["report"] = "\n".join(self.__report) |
216 data["report"] = "\n".join(self.__report) |
221 |
217 |
222 self.__writer.write(data) |
218 self.__writer.write(data) |
223 |
219 |
224 def pytest_sessionstart(self, session): |
220 def pytest_sessionstart(self, session): |
225 """ |
221 """ |
226 Public method called by pytest before performing collection and |
222 Public method called by pytest before performing collection and |
227 entering the run test loop. |
223 entering the run test loop. |
228 |
224 |
229 @param session reference to the session object |
225 @param session reference to the session object |
230 @type Session |
226 @type Session |
231 """ |
227 """ |
232 self.__totalStartTime = time.monotonic_ns() |
228 self.__totalStartTime = time.monotonic_ns() |
233 self.__testsRun = 0 |
229 self.__testsRun = 0 |
234 |
230 |
235 def pytest_sessionfinish(self, session, exitstatus): |
231 def pytest_sessionfinish(self, session, exitstatus): |
236 """ |
232 """ |
237 Public method called by pytest after the whole test run finished. |
233 Public method called by pytest after the whole test run finished. |
238 |
234 |
239 @param session reference to the session object |
235 @param session reference to the session object |
240 @type Session |
236 @type Session |
241 @param exitstatus exit status |
237 @param exitstatus exit status |
242 @type int or ExitCode |
238 @type int or ExitCode |
243 """ |
239 """ |
244 stopTime = time.monotonic_ns() |
240 stopTime = time.monotonic_ns() |
245 duration = (stopTime - self.__totalStartTime) / 1_000_000_000 # s |
241 duration = (stopTime - self.__totalStartTime) / 1_000_000_000 # s |
246 |
242 |
247 self.__writer.write({ |
243 self.__writer.write( |
248 "event": "finished", |
244 { |
249 "duration_s": duration, |
245 "event": "finished", |
250 "tests": self.__testsRun, |
246 "duration_s": duration, |
251 }) |
247 "tests": self.__testsRun, |
|
248 } |
|
249 ) |
252 |
250 |
253 |
251 |
254 def getVersions(): |
252 def getVersions(): |
255 """ |
253 """ |
256 Function to determine the framework version and versions of all available |
254 Function to determine the framework version and versions of all available |
257 plugins. |
255 plugins. |
258 """ |
256 """ |
259 try: |
257 try: |
260 import pytest |
258 import pytest |
|
259 |
261 versions = { |
260 versions = { |
262 "name": "pytest", |
261 "name": "pytest", |
263 "version": pytest.__version__, |
262 "version": pytest.__version__, |
264 "plugins": [], |
263 "plugins": [], |
265 } |
264 } |
266 |
265 |
267 # --capture=sys needed on Windows to avoid |
266 # --capture=sys needed on Windows to avoid |
268 # ValueError: saved filedescriptor not valid anymore |
267 # ValueError: saved filedescriptor not valid anymore |
269 plugin = GetPluginVersionsPlugin() |
268 plugin = GetPluginVersionsPlugin() |
270 pytest.main(['--version', '--capture=sys'], plugins=[plugin]) |
269 pytest.main(["--version", "--capture=sys"], plugins=[plugin]) |
271 versions["plugins"] = plugin.getVersions() |
270 versions["plugins"] = plugin.getVersions() |
272 except ImportError: |
271 except ImportError: |
273 versions = {} |
272 versions = {} |
274 |
273 |
275 print(json.dumps(versions)) |
274 print(json.dumps(versions)) |
276 sys.exit(0) |
275 sys.exit(0) |
277 |
276 |
278 |
277 |
279 if __name__ == '__main__': |
278 if __name__ == "__main__": |
280 command = sys.argv[1] |
279 command = sys.argv[1] |
281 if command == "installed": |
280 if command == "installed": |
282 try: |
281 try: |
283 import pytest # __IGNORE_WARNING__ |
282 import pytest # __IGNORE_WARNING__ |
|
283 |
284 sys.exit(0) |
284 sys.exit(0) |
285 except ImportError: |
285 except ImportError: |
286 sys.exit(1) |
286 sys.exit(1) |
287 |
287 |
288 elif command == "versions": |
288 elif command == "versions": |
289 getVersions() |
289 getVersions() |
290 |
290 |
291 elif command == "runtest": |
291 elif command == "runtest": |
292 import pytest |
292 import pytest |
293 from EricNetwork.EricJsonStreamWriter import EricJsonWriter |
293 from EricNetwork.EricJsonStreamWriter import EricJsonWriter |
|
294 |
294 writer = EricJsonWriter(sys.argv[2], int(sys.argv[3])) |
295 writer = EricJsonWriter(sys.argv[2], int(sys.argv[3])) |
295 pytest.main(sys.argv[4:], plugins=[EricPlugin(writer)]) |
296 pytest.main(sys.argv[4:], plugins=[EricPlugin(writer)]) |
296 writer.close() |
297 writer.close() |
297 sys.exit(0) |
298 sys.exit(0) |
298 |
299 |
299 sys.exit(42) |
300 sys.exit(42) |
300 |
301 |
301 # |
302 # |
302 # eflag: noqa = M801 |
303 # eflag: noqa = M801 |