|
| 1 | +from pprint import pprint |
| 2 | + |
1 | 3 | import py
|
| 4 | +import six |
2 | 5 |
|
3 | 6 | from _pytest._code.code import ExceptionInfo
|
| 7 | +from _pytest._code.code import ReprEntry |
| 8 | +from _pytest._code.code import ReprEntryNative |
| 9 | +from _pytest._code.code import ReprExceptionInfo |
| 10 | +from _pytest._code.code import ReprFileLocation |
| 11 | +from _pytest._code.code import ReprFuncArgs |
| 12 | +from _pytest._code.code import ReprLocals |
| 13 | +from _pytest._code.code import ReprTraceback |
4 | 14 | from _pytest._code.code import TerminalRepr
|
5 | 15 | from _pytest.outcomes import skip
|
| 16 | +from _pytest.pathlib import Path |
6 | 17 |
|
7 | 18 |
|
8 | 19 | def getslaveinfoline(node):
|
@@ -137,12 +148,136 @@ def head_line(self):
|
137 | 148 | fspath, lineno, domain = self.location
|
138 | 149 | return domain
|
139 | 150 |
|
| 151 | + def _to_json(self): |
| 152 | + """ |
| 153 | + This was originally the serialize_report() function from xdist (ca03269). |
| 154 | +
|
| 155 | + Returns the contents of this report as a dict of builtin entries, suitable for |
| 156 | + serialization. |
| 157 | +
|
| 158 | + Experimental method. |
| 159 | + """ |
| 160 | + |
| 161 | + def disassembled_report(rep): |
| 162 | + reprtraceback = rep.longrepr.reprtraceback.__dict__.copy() |
| 163 | + reprcrash = rep.longrepr.reprcrash.__dict__.copy() |
| 164 | + |
| 165 | + new_entries = [] |
| 166 | + for entry in reprtraceback["reprentries"]: |
| 167 | + entry_data = { |
| 168 | + "type": type(entry).__name__, |
| 169 | + "data": entry.__dict__.copy(), |
| 170 | + } |
| 171 | + for key, value in entry_data["data"].items(): |
| 172 | + if hasattr(value, "__dict__"): |
| 173 | + entry_data["data"][key] = value.__dict__.copy() |
| 174 | + new_entries.append(entry_data) |
| 175 | + |
| 176 | + reprtraceback["reprentries"] = new_entries |
| 177 | + |
| 178 | + return { |
| 179 | + "reprcrash": reprcrash, |
| 180 | + "reprtraceback": reprtraceback, |
| 181 | + "sections": rep.longrepr.sections, |
| 182 | + } |
| 183 | + |
| 184 | + d = self.__dict__.copy() |
| 185 | + if hasattr(self.longrepr, "toterminal"): |
| 186 | + if hasattr(self.longrepr, "reprtraceback") and hasattr( |
| 187 | + self.longrepr, "reprcrash" |
| 188 | + ): |
| 189 | + d["longrepr"] = disassembled_report(self) |
| 190 | + else: |
| 191 | + d["longrepr"] = six.text_type(self.longrepr) |
| 192 | + else: |
| 193 | + d["longrepr"] = self.longrepr |
| 194 | + for name in d: |
| 195 | + if isinstance(d[name], (py.path.local, Path)): |
| 196 | + d[name] = str(d[name]) |
| 197 | + elif name == "result": |
| 198 | + d[name] = None # for now |
| 199 | + return d |
| 200 | + |
| 201 | + @classmethod |
| 202 | + def _from_json(cls, reportdict): |
| 203 | + """ |
| 204 | + This was originally the serialize_report() function from xdist (ca03269). |
| 205 | +
|
| 206 | + Factory method that returns either a TestReport or CollectReport, depending on the calling |
| 207 | + class. It's the callers responsibility to know which class to pass here. |
| 208 | +
|
| 209 | + Experimental method. |
| 210 | + """ |
| 211 | + if reportdict["longrepr"]: |
| 212 | + if ( |
| 213 | + "reprcrash" in reportdict["longrepr"] |
| 214 | + and "reprtraceback" in reportdict["longrepr"] |
| 215 | + ): |
| 216 | + |
| 217 | + reprtraceback = reportdict["longrepr"]["reprtraceback"] |
| 218 | + reprcrash = reportdict["longrepr"]["reprcrash"] |
| 219 | + |
| 220 | + unserialized_entries = [] |
| 221 | + reprentry = None |
| 222 | + for entry_data in reprtraceback["reprentries"]: |
| 223 | + data = entry_data["data"] |
| 224 | + entry_type = entry_data["type"] |
| 225 | + if entry_type == "ReprEntry": |
| 226 | + reprfuncargs = None |
| 227 | + reprfileloc = None |
| 228 | + reprlocals = None |
| 229 | + if data["reprfuncargs"]: |
| 230 | + reprfuncargs = ReprFuncArgs(**data["reprfuncargs"]) |
| 231 | + if data["reprfileloc"]: |
| 232 | + reprfileloc = ReprFileLocation(**data["reprfileloc"]) |
| 233 | + if data["reprlocals"]: |
| 234 | + reprlocals = ReprLocals(data["reprlocals"]["lines"]) |
| 235 | + |
| 236 | + reprentry = ReprEntry( |
| 237 | + lines=data["lines"], |
| 238 | + reprfuncargs=reprfuncargs, |
| 239 | + reprlocals=reprlocals, |
| 240 | + filelocrepr=reprfileloc, |
| 241 | + style=data["style"], |
| 242 | + ) |
| 243 | + elif entry_type == "ReprEntryNative": |
| 244 | + reprentry = ReprEntryNative(data["lines"]) |
| 245 | + else: |
| 246 | + _report_unserialization_failure(entry_type, cls, reportdict) |
| 247 | + unserialized_entries.append(reprentry) |
| 248 | + reprtraceback["reprentries"] = unserialized_entries |
| 249 | + |
| 250 | + exception_info = ReprExceptionInfo( |
| 251 | + reprtraceback=ReprTraceback(**reprtraceback), |
| 252 | + reprcrash=ReprFileLocation(**reprcrash), |
| 253 | + ) |
| 254 | + |
| 255 | + for section in reportdict["longrepr"]["sections"]: |
| 256 | + exception_info.addsection(*section) |
| 257 | + reportdict["longrepr"] = exception_info |
| 258 | + |
| 259 | + return cls(**reportdict) |
| 260 | + |
| 261 | + |
| 262 | +def _report_unserialization_failure(type_name, report_class, reportdict): |
| 263 | + url = "https://github.com/pytest-dev/pytest/issues" |
| 264 | + stream = py.io.TextIO() |
| 265 | + pprint("-" * 100, stream=stream) |
| 266 | + pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream) |
| 267 | + pprint("report_name: %s" % report_class, stream=stream) |
| 268 | + pprint(reportdict, stream=stream) |
| 269 | + pprint("Please report this bug at %s" % url, stream=stream) |
| 270 | + pprint("-" * 100, stream=stream) |
| 271 | + raise RuntimeError(stream.getvalue()) |
| 272 | + |
140 | 273 |
|
141 | 274 | class TestReport(BaseReport):
|
142 | 275 | """ Basic test report object (also used for setup and teardown calls if
|
143 | 276 | they fail).
|
144 | 277 | """
|
145 | 278 |
|
| 279 | + __test__ = False |
| 280 | + |
146 | 281 | def __init__(
|
147 | 282 | self,
|
148 | 283 | nodeid,
|
@@ -272,3 +407,21 @@ def __init__(self, msg):
|
272 | 407 |
|
273 | 408 | def toterminal(self, out):
|
274 | 409 | out.line(self.longrepr, red=True)
|
| 410 | + |
| 411 | + |
| 412 | +def pytest_report_to_serializable(report): |
| 413 | + if isinstance(report, (TestReport, CollectReport)): |
| 414 | + data = report._to_json() |
| 415 | + data["_report_type"] = report.__class__.__name__ |
| 416 | + return data |
| 417 | + |
| 418 | + |
| 419 | +def pytest_report_from_serializable(data): |
| 420 | + if "_report_type" in data: |
| 421 | + if data["_report_type"] == "TestReport": |
| 422 | + return TestReport._from_json(data) |
| 423 | + elif data["_report_type"] == "CollectReport": |
| 424 | + return CollectReport._from_json(data) |
| 425 | + assert False, "Unknown report_type unserialize data: {}".format( |
| 426 | + data["_report_type"] |
| 427 | + ) |
0 commit comments