Skip to content

Commit 935a6bd

Browse files
committed
Merge remote-tracking branch 'upstream/master' into MagicPro1994/master
2 parents aca6845 + 695bfa3 commit 935a6bd

File tree

9 files changed

+71
-18
lines changed

9 files changed

+71
-18
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
1.6.3 (08-08-2019)
2+
------------------
3+
4+
* `#102 <https://github.com/pytest-dev/execnet/pull/102>`__: Show paths in stack traces
5+
generated by ``remote_exec()``.
6+
17
1.6.1 (2019-07-22)
28
------------------
39

execnet/gateway.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import os
88
import inspect
99
import types
10-
import linecache
1110
import textwrap
1211
import execnet
1312
from execnet.gateway_base import Message
@@ -111,22 +110,27 @@ def remote_exec(self, source, **kwargs):
111110
executing code.
112111
"""
113112
call_name = None
113+
file_name = None
114114
if isinstance(source, types.ModuleType):
115-
linecache.updatecache(inspect.getsourcefile(source))
116-
source = inspect.getsource(source)
115+
file_name = inspect.getsourcefile(source)
116+
if not file_name:
117+
source = inspect.getsource(source)
118+
else:
119+
source = None
117120
elif isinstance(source, types.FunctionType):
118121
call_name = source.__name__
122+
file_name = inspect.getsourcefile(source)
119123
source = _source_of_function(source)
120124
else:
121125
source = textwrap.dedent(str(source))
122126

123-
if call_name is None and kwargs:
127+
if not call_name and kwargs:
124128
raise TypeError("can't pass kwargs to non-function remote_exec")
125129

126130
channel = self.newchannel()
127131
self._send(Message.CHANNEL_EXEC,
128132
channel.id,
129-
gateway_base.dumps_internal((source, call_name, kwargs)))
133+
gateway_base.dumps_internal((source, file_name, call_name, kwargs)))
130134
return channel
131135

132136
def remote_init_threads(self, num=None):
@@ -186,7 +190,7 @@ def _source_of_function(function):
186190
args = inspect.getargspec(function)[0]
187191
else:
188192
args = sig.args
189-
if args[0] != 'channel':
193+
if not args or args[0] != 'channel':
190194
raise ValueError('expected first function argument to be `channel`')
191195

192196
if gateway_base.ISPY3:
@@ -213,4 +217,5 @@ def _source_of_function(function):
213217
used_globals,
214218
)
215219

216-
return source
220+
leading_ws = "\n" * (codeobj.co_firstlineno - 1)
221+
return leading_ws + source

execnet/gateway_base.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""
1414
from __future__ import with_statement
1515
import sys
16+
import linecache
1617
import os
1718
import weakref
1819
import traceback
@@ -25,8 +26,9 @@
2526
ISPY3 = sys.version_info >= (3, 0)
2627
if ISPY3:
2728
from io import BytesIO
28-
exec("def do_exec(co, loc): exec(co, loc)\n"
29-
"def reraise(cls, val, tb): raise val\n")
29+
exec("do_exec = exec")
30+
def reraise(cls, val, tb):
31+
raise val.with_traceback(tb)
3032
unicode = str
3133
_long_type = int
3234
from _thread import interrupt_main
@@ -1044,7 +1046,7 @@ def trace(msg):
10441046

10451047
def executetask(self, item):
10461048
try:
1047-
channel, (source, call_name, kwargs) = item
1049+
channel, (source, file_name, call_name, kwargs) = item
10481050
if not ISPY3 and kwargs:
10491051
# some python2 versions do not accept unicode keyword params
10501052
# note: Unserializer generally turns py2-str to py3-str objects
@@ -1054,12 +1056,15 @@ def executetask(self, item):
10541056
name = name.encode('ascii')
10551057
newkwargs[name] = value
10561058
kwargs = newkwargs
1059+
if source is None:
1060+
assert file_name, file_name
1061+
source = "".join(linecache.updatecache(file_name))
10571062
loc = {'channel': channel, '__name__': '__channelexec__'}
10581063
self._trace("execution starts[%s]: %s" %
10591064
(channel.id, repr(source)[:50]))
10601065
channel._executing = True
10611066
try:
1062-
co = compile(source+'\n', '<remote exec>', 'exec')
1067+
co = compile(source+'\n', file_name or '<remote exec>', 'exec')
10631068
do_exec(co, loc) # noqa
10641069
if call_name:
10651070
self._trace('calling %s(**%60r)' % (call_name, kwargs))

execnet/script/socketserver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def exec_from_one_connection(serversock):
6363
}
6464
source = eval(source)
6565
if source:
66-
co = compile(source+'\n', source, 'exec')
66+
co = compile(source+'\n', '<socket server>', 'exec')
6767
print_(progname, 'compiled source, executing')
6868
try:
6969
exec_(co, g) # noqa

testing/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,8 @@ def gw(request, execmodel, group):
186186
def execmodel(request):
187187
if request.param != "thread":
188188
pytest.importorskip(request.param)
189-
if sys.platform == "win32":
190-
pytest.xfail("eventlet/gevent do not work onwin32")
189+
if request.param in ("eventlet", "gevent") and sys.platform == "win32":
190+
pytest.xfail(request.param + " does not work on win32")
191191
return get_execmodel(request.param)
192192

193193

testing/test_basics.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,6 @@ def test_stdouterrin_setnull(execmodel):
218218
cap = py.io.StdCaptureFD()
219219
gateway_base.init_popen_io(execmodel)
220220
os.write(1, "hello".encode('ascii'))
221-
if os.name == "nt":
222-
os.write(2, "world")
223221
os.read(0, 1)
224222
out, err = cap.reset()
225223
assert not out
@@ -326,7 +324,7 @@ def test_source_of_nested_function(self):
326324
def working(channel):
327325
pass
328326

329-
send_source = gateway._source_of_function(working)
327+
send_source = gateway._source_of_function(working).lstrip("\r\n")
330328
expected = 'def working(channel):\n pass\n'
331329
assert send_source == expected
332330

testing/test_gateway.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
mostly functional tests of gateways.
33
"""
44
import os
5+
from textwrap import dedent
6+
57
import py
68
import pytest
79
import socket
@@ -15,6 +17,7 @@
1517
needs_osdup = py.test.mark.skipif("not hasattr(os, 'dup')")
1618

1719

20+
flakytest = pytest.mark.xfail(reason='on some systems this test fails due to timing problems')
1821
skip_win_pypy = pytest.mark.xfail(condition=hasattr(sys, 'pypy_version_info') and sys.platform.startswith('win'),
1922
reason='failing on Windows on PyPy (#63)')
2023

@@ -76,6 +79,7 @@ def test_gateway_status_no_real_channel(self, gw):
7679
# closure of temporary channels
7780
assert numchan2 == numchan
7881

82+
@flakytest
7983
def test_gateway_status_busy(self, gw):
8084
numchannels = gw.remote_status().numchannels
8185
ch1 = gw.remote_exec("channel.send(1); channel.receive()")
@@ -110,6 +114,38 @@ def test_remote_exec_module(self, tmpdir, gw):
110114
name = channel.receive()
111115
assert name == 2
112116

117+
def test_remote_exec_module_with_traceback(self, gw, tmpdir, monkeypatch):
118+
remotetest = tmpdir.join("remotetest.py")
119+
remotetest.write(dedent("""
120+
def run_me(channel=None):
121+
raise ValueError('me')
122+
123+
if __name__ == '__channelexec__':
124+
run_me()
125+
""")
126+
)
127+
128+
monkeypatch.syspath_prepend(tmpdir)
129+
import remotetest
130+
131+
ch = gw.remote_exec(remotetest)
132+
try:
133+
ch.receive()
134+
except execnet.gateway_base.RemoteError as e:
135+
assert 'remotetest.py", line 3, in run_me' in str(e)
136+
assert "ValueError: me" in str(e)
137+
finally:
138+
ch.close()
139+
140+
ch = gw.remote_exec(remotetest.run_me)
141+
try:
142+
ch.receive()
143+
except execnet.gateway_base.RemoteError as e:
144+
assert 'remotetest.py", line 3, in run_me' in str(e)
145+
assert "ValueError: me" in str(e)
146+
finally:
147+
ch.close()
148+
113149
def test_correct_setup_no_py(self, gw):
114150
channel = gw.remote_exec("""
115151
import sys
@@ -347,6 +383,7 @@ def test_threads_race_sending(self, makegateway):
347383
for ch in channels:
348384
ch.waitclose(TESTTIMEOUT)
349385

386+
@flakytest
350387
def test_status_with_threads(self, makegateway):
351388
gw = makegateway('popen')
352389
c1 = gw.remote_exec("channel.send(1) ; channel.receive()")
@@ -391,6 +428,7 @@ def test_popen_filetracing(self, testdir, monkeypatch, makegateway):
391428
gw.exit()
392429

393430
@skip_win_pypy
431+
@flakytest
394432
def test_popen_stderr_tracing(self, capfd, monkeypatch, makegateway):
395433
monkeypatch.setenv('EXECNET_DEBUG', "2")
396434
gw = makegateway("popen")

testing/test_rsync.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def filter(self, x):
232232
assert len(dest.listdir()) == 1
233233
assert len(source.listdir()) == 1
234234

235-
@py.test.mark.skip_if('sys.version_info >= (3)')
235+
@py.test.mark.skipif('sys.version_info >= (3,)')
236236
def test_2_to_3_bridge_can_send_binary_files(self, tmpdir, makegateway):
237237
python = _find_version('3')
238238
gw = makegateway('popen//python=%s' % python)

testing/test_xspec.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def test_popen_default(self, makegateway):
125125
assert rinfo.version_info == sys.version_info
126126

127127
@pytest.mark.skipif("not hasattr(os, 'nice')")
128+
@pytest.mark.xfail(reason='fails due to timing problems on busy single-core VMs')
128129
def test_popen_nice(self, makegateway):
129130
gw = makegateway("popen")
130131

0 commit comments

Comments
 (0)