diff options
Diffstat (limited to 'src/buildstream/testing')
-rw-r--r-- | src/buildstream/testing/_forked.py | 94 |
1 files changed, 94 insertions, 0 deletions
diff --git a/src/buildstream/testing/_forked.py b/src/buildstream/testing/_forked.py new file mode 100644 index 000000000..af5e9c070 --- /dev/null +++ b/src/buildstream/testing/_forked.py @@ -0,0 +1,94 @@ +# This code was based on pytest-forked, commit 6098c1, found here: +# <https://github.com/pytest-dev/pytest-forked> +# Its copyright notice is included below. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import marshal + +import py +import pytest +# XXX Using pytest private internals here +from _pytest import runner + +EXITSTATUS_TESTEXIT = 4 + + +# copied from xdist remote +def serialize_report(rep): + d = rep.__dict__.copy() + if hasattr(rep.longrepr, 'toterminal'): + d['longrepr'] = str(rep.longrepr) + else: + d['longrepr'] = rep.longrepr + for name in d: + if isinstance(d[name], py.path.local): # pylint: disable=no-member + d[name] = str(d[name]) + elif name == "result": + d[name] = None # for now + return d + + +def forked_run_report(item): + def runforked(): + try: + reports = runner.runtestprotocol(item, log=False) + except KeyboardInterrupt: + os._exit(EXITSTATUS_TESTEXIT) + return marshal.dumps([serialize_report(x) for x in reports]) + + ff = py.process.ForkedFunc(runforked) # pylint: disable=no-member + result = ff.waitfinish() + if result.retval is not None: + report_dumps = marshal.loads(result.retval) + return [runner.TestReport(**x) for x in report_dumps] + else: + if result.exitstatus == EXITSTATUS_TESTEXIT: + pytest.exit("forked test item %s raised Exit" % (item,)) + return [report_process_crash(item, result)] + + +def report_process_crash(item, result): + try: + from _pytest.compat import getfslineno + except ImportError: + # pytest<4.2 + path, lineno = item._getfslineno() + else: + path, lineno = getfslineno(item) + info = ("%s:%s: running the test CRASHED with signal %d" % + (path, lineno, result.signal)) + + # We need to create a CallInfo instance that is pre-initialised to contain + # info about an exception. We do this by using a function which does + # 0/0. Also, the API varies between pytest versions. + has_from_call = getattr(runner.CallInfo, "from_call", None) is not None + if has_from_call: # pytest >= 4.1 + call = runner.CallInfo.from_call(lambda: 0 / 0, "???") + else: + call = runner.CallInfo(lambda: 0 / 0, "???") + call.excinfo = info + + rep = runner.pytest_runtest_makereport(item, call) + if result.out: + rep.sections.append(("captured stdout", result.out)) + if result.err: + rep.sections.append(("captured stderr", result.err)) + return rep |