Skip to content

Commit 3c82b1c

Browse files
committed
Refactor report serialization/deserialization code
Refactoring this in order to support chained exceptions more easily. Related to #5786
1 parent 0215bcd commit 3c82b1c

File tree

1 file changed

+116
-89
lines changed

1 file changed

+116
-89
lines changed

src/_pytest/reports.py

Lines changed: 116 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -160,46 +160,7 @@ def _to_json(self):
160160
161161
Experimental method.
162162
"""
163-
164-
def disassembled_report(rep):
165-
reprtraceback = rep.longrepr.reprtraceback.__dict__.copy()
166-
reprcrash = rep.longrepr.reprcrash.__dict__.copy()
167-
168-
new_entries = []
169-
for entry in reprtraceback["reprentries"]:
170-
entry_data = {
171-
"type": type(entry).__name__,
172-
"data": entry.__dict__.copy(),
173-
}
174-
for key, value in entry_data["data"].items():
175-
if hasattr(value, "__dict__"):
176-
entry_data["data"][key] = value.__dict__.copy()
177-
new_entries.append(entry_data)
178-
179-
reprtraceback["reprentries"] = new_entries
180-
181-
return {
182-
"reprcrash": reprcrash,
183-
"reprtraceback": reprtraceback,
184-
"sections": rep.longrepr.sections,
185-
}
186-
187-
d = self.__dict__.copy()
188-
if hasattr(self.longrepr, "toterminal"):
189-
if hasattr(self.longrepr, "reprtraceback") and hasattr(
190-
self.longrepr, "reprcrash"
191-
):
192-
d["longrepr"] = disassembled_report(self)
193-
else:
194-
d["longrepr"] = str(self.longrepr)
195-
else:
196-
d["longrepr"] = self.longrepr
197-
for name in d:
198-
if isinstance(d[name], (py.path.local, Path)):
199-
d[name] = str(d[name])
200-
elif name == "result":
201-
d[name] = None # for now
202-
return d
163+
return _test_report_to_json(self)
203164

204165
@classmethod
205166
def _from_json(cls, reportdict):
@@ -211,55 +172,8 @@ def _from_json(cls, reportdict):
211172
212173
Experimental method.
213174
"""
214-
if reportdict["longrepr"]:
215-
if (
216-
"reprcrash" in reportdict["longrepr"]
217-
and "reprtraceback" in reportdict["longrepr"]
218-
):
219-
220-
reprtraceback = reportdict["longrepr"]["reprtraceback"]
221-
reprcrash = reportdict["longrepr"]["reprcrash"]
222-
223-
unserialized_entries = []
224-
reprentry = None
225-
for entry_data in reprtraceback["reprentries"]:
226-
data = entry_data["data"]
227-
entry_type = entry_data["type"]
228-
if entry_type == "ReprEntry":
229-
reprfuncargs = None
230-
reprfileloc = None
231-
reprlocals = None
232-
if data["reprfuncargs"]:
233-
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
234-
if data["reprfileloc"]:
235-
reprfileloc = ReprFileLocation(**data["reprfileloc"])
236-
if data["reprlocals"]:
237-
reprlocals = ReprLocals(data["reprlocals"]["lines"])
238-
239-
reprentry = ReprEntry(
240-
lines=data["lines"],
241-
reprfuncargs=reprfuncargs,
242-
reprlocals=reprlocals,
243-
filelocrepr=reprfileloc,
244-
style=data["style"],
245-
)
246-
elif entry_type == "ReprEntryNative":
247-
reprentry = ReprEntryNative(data["lines"])
248-
else:
249-
_report_unserialization_failure(entry_type, cls, reportdict)
250-
unserialized_entries.append(reprentry)
251-
reprtraceback["reprentries"] = unserialized_entries
252-
253-
exception_info = ReprExceptionInfo(
254-
reprtraceback=ReprTraceback(**reprtraceback),
255-
reprcrash=ReprFileLocation(**reprcrash),
256-
)
257-
258-
for section in reportdict["longrepr"]["sections"]:
259-
exception_info.addsection(*section)
260-
reportdict["longrepr"] = exception_info
261-
262-
return cls(**reportdict)
175+
kwargs = _test_report_kwargs_from_json(reportdict)
176+
return cls(**kwargs)
263177

264178

265179
def _report_unserialization_failure(type_name, report_class, reportdict):
@@ -424,3 +338,116 @@ def pytest_report_from_serializable(data):
424338
assert False, "Unknown report_type unserialize data: {}".format(
425339
data["_report_type"]
426340
)
341+
342+
343+
def _test_report_to_json(test_report):
344+
"""
345+
This was originally the serialize_report() function from xdist (ca03269).
346+
347+
Returns the contents of this report as a dict of builtin entries, suitable for
348+
serialization.
349+
"""
350+
351+
def serialize_repr_entry(entry):
352+
entry_data = {"type": type(entry).__name__, "data": entry.__dict__.copy()}
353+
for key, value in entry_data["data"].items():
354+
if hasattr(value, "__dict__"):
355+
entry_data["data"][key] = value.__dict__.copy()
356+
return entry_data
357+
358+
def serialize_repr_traceback(reprtraceback):
359+
result = reprtraceback.__dict__.copy()
360+
result["reprentries"] = [
361+
serialize_repr_entry(x) for x in reprtraceback.reprentries
362+
]
363+
return result
364+
365+
def serialize_repr_crash(reprcrash):
366+
return reprcrash.__dict__.copy()
367+
368+
def serialize_longrepr(rep):
369+
return {
370+
"reprcrash": serialize_repr_crash(rep.longrepr.reprcrash),
371+
"reprtraceback": serialize_repr_traceback(rep.longrepr.reprtraceback),
372+
"sections": rep.longrepr.sections,
373+
}
374+
375+
d = test_report.__dict__.copy()
376+
if hasattr(test_report.longrepr, "toterminal"):
377+
if hasattr(test_report.longrepr, "reprtraceback") and hasattr(
378+
test_report.longrepr, "reprcrash"
379+
):
380+
d["longrepr"] = serialize_longrepr(test_report)
381+
else:
382+
d["longrepr"] = str(test_report.longrepr)
383+
else:
384+
d["longrepr"] = test_report.longrepr
385+
for name in d:
386+
if isinstance(d[name], (py.path.local, Path)):
387+
d[name] = str(d[name])
388+
elif name == "result":
389+
d[name] = None # for now
390+
return d
391+
392+
393+
def _test_report_kwargs_from_json(reportdict):
394+
"""
395+
This was originally the serialize_report() function from xdist (ca03269).
396+
397+
Factory method that returns either a TestReport or CollectReport, depending on the calling
398+
class. It's the callers responsibility to know which class to pass here.
399+
"""
400+
401+
def deserialize_repr_entry(entry_data):
402+
data = entry_data["data"]
403+
entry_type = entry_data["type"]
404+
if entry_type == "ReprEntry":
405+
reprfuncargs = None
406+
reprfileloc = None
407+
reprlocals = None
408+
if data["reprfuncargs"]:
409+
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
410+
if data["reprfileloc"]:
411+
reprfileloc = ReprFileLocation(**data["reprfileloc"])
412+
if data["reprlocals"]:
413+
reprlocals = ReprLocals(data["reprlocals"]["lines"])
414+
415+
reprentry = ReprEntry(
416+
lines=data["lines"],
417+
reprfuncargs=reprfuncargs,
418+
reprlocals=reprlocals,
419+
filelocrepr=reprfileloc,
420+
style=data["style"],
421+
)
422+
elif entry_type == "ReprEntryNative":
423+
reprentry = ReprEntryNative(data["lines"])
424+
else:
425+
_report_unserialization_failure(entry_type, TestReport, reportdict)
426+
return reprentry
427+
428+
def deserialize_repr_traceback(repr_traceback_dict):
429+
repr_traceback_dict["reprentries"] = [
430+
deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"]
431+
]
432+
return ReprTraceback(**repr_traceback_dict)
433+
434+
def deserialize_repr_crash(repr_crash_dict):
435+
return ReprFileLocation(**repr_crash_dict)
436+
437+
if (
438+
reportdict["longrepr"]
439+
and "reprcrash" in reportdict["longrepr"]
440+
and "reprtraceback" in reportdict["longrepr"]
441+
):
442+
exception_info = ReprExceptionInfo(
443+
reprtraceback=deserialize_repr_traceback(
444+
reportdict["longrepr"]["reprtraceback"]
445+
),
446+
reprcrash=deserialize_repr_crash(reportdict["longrepr"]["reprcrash"]),
447+
)
448+
449+
for section in reportdict["longrepr"]["sections"]:
450+
exception_info.addsection(*section)
451+
reportdict["longrepr"] = exception_info
452+
453+
return reportdict

0 commit comments

Comments
 (0)