-
-
Notifications
You must be signed in to change notification settings - Fork 122
/
runfiles.py
336 lines (264 loc) · 13.3 KB
/
runfiles.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
"""
Entry point module (keep at root):
Used to run with tests with unittest/pytest/nose.
"""
import os
def main():
import sys
# Separate the nose params and the pydev params.
pydev_params = []
other_test_framework_params = []
found_other_test_framework_param = None
NOSE_PARAMS = "--nose-params"
PY_TEST_PARAMS = "--py-test-params"
for arg in sys.argv[1:]:
if not found_other_test_framework_param and arg != NOSE_PARAMS and arg != PY_TEST_PARAMS:
pydev_params.append(arg)
else:
if not found_other_test_framework_param:
found_other_test_framework_param = arg
else:
other_test_framework_params.append(arg)
try:
# Convert to the case stored in the filesystem
import win32api
def get_with_filesystem_case(f):
return win32api.GetLongPathName(win32api.GetShortPathName(f))
except:
def get_with_filesystem_case(f):
return f
# Here we'll run either with nose or with the pydev_runfiles.
from _pydev_runfiles import pydev_runfiles
from _pydev_runfiles import pydev_runfiles_xml_rpc
from _pydevd_bundle import pydevd_constants
from pydevd_file_utils import canonical_normalized_path
DEBUG = 0
if DEBUG:
sys.stdout.write("Received parameters: %s\n" % (sys.argv,))
sys.stdout.write("Params for pydev: %s\n" % (pydev_params,))
if found_other_test_framework_param:
sys.stdout.write("Params for test framework: %s, %s\n" % (found_other_test_framework_param, other_test_framework_params))
try:
configuration = pydev_runfiles.parse_cmdline([sys.argv[0]] + pydev_params)
except:
sys.stderr.write("Command line received: %s\n" % (sys.argv,))
raise
pydev_runfiles_xml_rpc.initialize_server(configuration.port) # Note that if the port is None, a Null server will be initialized.
NOSE_FRAMEWORK = "nose"
PY_TEST_FRAMEWORK = "py.test"
test_framework = None # Default (pydev)
try:
if found_other_test_framework_param:
if found_other_test_framework_param == NOSE_PARAMS:
test_framework = NOSE_FRAMEWORK
import nose
elif found_other_test_framework_param == PY_TEST_PARAMS:
test_framework = PY_TEST_FRAMEWORK
import pytest
else:
raise ImportError("Test framework: %s not supported." % (found_other_test_framework_param,))
else:
raise ImportError()
except ImportError:
if found_other_test_framework_param:
raise
test_framework = None
# Clear any exception that may be there so that clients don't see it.
# See: https://sourceforge.net/tracker/?func=detail&aid=3408057&group_id=85796&atid=577329
if hasattr(sys, "exc_clear"):
sys.exc_clear()
if not test_framework:
return pydev_runfiles.main(configuration) # Note: still doesn't return a proper value.
else:
# We'll convert the parameters to what nose or py.test expects.
# The supported parameters are:
# runfiles.py --config-file|-t|--tests <Test.test1,Test2> dirs|files --nose-params xxx yyy zzz
# (all after --nose-params should be passed directly to nose)
# In java:
# --tests = Constants.ATTR_UNITTEST_TESTS
# --config-file = Constants.ATTR_UNITTEST_CONFIGURATION_FILE
# The only thing actually handled here are the tests that we want to run, which we'll
# handle and pass as what the test framework expects.
py_test_accept_filter = {}
files_to_tests = configuration.files_to_tests
if files_to_tests:
# Handling through the file contents (file where each line is a test)
files_or_dirs = []
for file, tests in files_to_tests.items():
if test_framework == NOSE_FRAMEWORK:
for test in tests:
files_or_dirs.append(file + ":" + test)
elif test_framework == PY_TEST_FRAMEWORK:
py_test_accept_filter[file] = tests
py_test_accept_filter[canonical_normalized_path(file)] = tests
files_or_dirs.append(file)
else:
raise AssertionError("Cannot handle test framework: %s at this point." % (test_framework,))
else:
if configuration.tests:
# Tests passed (works together with the files_or_dirs)
files_or_dirs = []
for file in configuration.files_or_dirs:
if test_framework == NOSE_FRAMEWORK:
for t in configuration.tests:
files_or_dirs.append(file + ":" + t)
elif test_framework == PY_TEST_FRAMEWORK:
py_test_accept_filter[file] = configuration.tests
py_test_accept_filter[canonical_normalized_path(file)] = configuration.tests
files_or_dirs.append(file)
else:
raise AssertionError("Cannot handle test framework: %s at this point." % (test_framework,))
else:
# Only files or dirs passed (let it do the test-loading based on those paths)
files_or_dirs = configuration.files_or_dirs
argv = other_test_framework_params + files_or_dirs
if test_framework == NOSE_FRAMEWORK:
# Nose usage: http://somethingaboutorange.com/mrl/projects/nose/0.11.2/usage.html
# show_stdout_option = ['-s']
# processes_option = ['--processes=2']
argv.insert(0, sys.argv[0])
if DEBUG:
sys.stdout.write("Final test framework args: %s\n" % (argv[1:],))
from _pydev_runfiles import pydev_runfiles_nose
PYDEV_NOSE_PLUGIN_SINGLETON = pydev_runfiles_nose.start_pydev_nose_plugin_singleton(configuration)
argv.append("--with-pydevplugin")
# Return 'not' because it will return 'success' (so, exit == 0 if success)
return not nose.run(argv=argv, addplugins=[PYDEV_NOSE_PLUGIN_SINGLETON])
elif test_framework == PY_TEST_FRAMEWORK:
if "--coverage_output_dir" in pydev_params and "--coverage_include" in pydev_params:
coverage_output_dir = pydev_params[pydev_params.index("--coverage_output_dir") + 1]
coverage_include = pydev_params[pydev_params.index("--coverage_include") + 1]
try:
import pytest_cov
except ImportError:
sys.stderr.write(
"To do a coverage run with pytest the pytest-cov library is needed (i.e.: pip install pytest-cov).\n\n"
)
raise
argv.insert(0, "--cov-append")
argv.insert(1, "--cov-report=")
argv.insert(2, "--cov=%s" % (coverage_include,))
import time
os.environ["COVERAGE_FILE"] = os.path.join(coverage_output_dir, ".coverage.%s" % (time.time(),))
if DEBUG:
sys.stdout.write("Final test framework args: %s\n" % (argv,))
sys.stdout.write("py_test_accept_filter: %s\n" % (py_test_accept_filter,))
def dotted(p):
# Helper to convert path to have dots instead of slashes
return os.path.normpath(p).replace(os.sep, "/").replace("/", ".")
curr_dir = os.path.realpath(".")
curr_dotted = dotted(curr_dir) + "."
# Overcome limitation on py.test:
# When searching conftest if we have a structure as:
# /my_package
# /my_package/conftest.py
# /my_package/tests
# /my_package/tests/test_my_package.py
# The test_my_package won't have access to the conftest contents from the
# test_my_package.py file unless the working dir is set to /my_package.
#
# See related issue (for which we work-around below):
# https://bitbucket.org/hpk42/pytest/issue/639/conftest-being-loaded-twice-giving
found_conftest = False
curdir_abs = os.path.abspath(os.curdir)
while True:
if os.path.exists(os.path.join(curdir_abs, "conftest.py")):
os.chdir(curdir_abs)
found_conftest = True
break
parent = os.path.dirname(curdir_abs)
if curdir_abs == parent or not parent:
break
curdir_abs = parent
if not found_conftest:
for path in sys.path:
path_dotted = dotted(path)
if curr_dotted.startswith(path_dotted):
os.chdir(path)
break
remove = []
for i in range(len(argv)):
arg = argv[i]
# Workaround bug in py.test: if we pass the full path it ends up importing conftest
# more than once (so, always work with relative paths).
if os.path.isfile(arg) or os.path.isdir(arg):
# Args must be passed with the proper case in the filesystem (otherwise
# python itself may not recognize it).
arg = get_with_filesystem_case(arg)
argv[i] = arg
from os.path import relpath
try:
# May fail if on different drives
arg = relpath(arg)
except ValueError:
pass
else:
argv[i] = arg
elif "<unable to get>" in arg:
remove.append(i)
for i in reversed(remove):
del argv[i]
# To find our runfile helpers (i.e.: plugin)...
d = os.path.dirname(__file__)
if d not in sys.path:
sys.path.insert(0, d)
import pickle, zlib, base64
# Update environment PYTHONPATH so that it finds our plugin if using xdist.
os.environ["PYTHONPATH"] = os.pathsep.join(sys.path)
# Set what should be skipped in the plugin through an environment variable
s = base64.b64encode(zlib.compress(pickle.dumps(py_test_accept_filter)))
s = s.decode("ascii") # Must be str in py3.
os.environ["PYDEV_PYTEST_SKIP"] = s
# Identifies the main pid (i.e.: if it's not the main pid it has to connect back to the
# main pid to give xml-rpc notifications).
os.environ["PYDEV_MAIN_PID"] = str(os.getpid())
os.environ["PYDEV_PYTEST_SERVER"] = str(configuration.port)
argv.append("-p")
argv.append("_pydev_runfiles.pydev_runfiles_pytest2")
return pytest.main(argv)
else:
raise AssertionError("Cannot handle test framework: %s at this point." % (test_framework,))
if __name__ == "__main__":
try:
main()
finally:
try:
# The server is not a daemon thread, so, we have to ask for it to be killed!
from _pydev_runfiles import pydev_runfiles_xml_rpc
pydev_runfiles_xml_rpc.force_server_kill()
except:
pass # Ignore any errors here
import sys
import threading
if hasattr(sys, "_current_frames") and hasattr(threading, "enumerate"):
import time
import traceback
class DumpThreads(threading.Thread):
def run(self):
time.sleep(10)
thread_id_to_name = {}
try:
for t in threading.enumerate():
thread_id_to_name[t.ident] = "%s (daemon: %s)" % (t.name, t.daemon)
except:
pass
stack_trace = [
"===============================================================================",
"pydev pyunit runner: Threads still found running after tests finished",
"================================= Thread Dump =================================",
]
for thread_id, stack in sys._current_frames().items():
stack_trace.append("\n-------------------------------------------------------------------------------")
stack_trace.append(" Thread %s" % thread_id_to_name.get(thread_id, thread_id))
stack_trace.append("")
if "self" in stack.f_locals:
sys.stderr.write(str(stack.f_locals["self"]) + "\n")
for filename, lineno, name, line in traceback.extract_stack(stack):
stack_trace.append(' File "%s", line %d, in %s' % (filename, lineno, name))
if line:
stack_trace.append(" %s" % (line.strip()))
stack_trace.append("\n=============================== END Thread Dump ===============================")
sys.stderr.write("\n".join(stack_trace))
dump_current_frames_thread = DumpThreads()
dump_current_frames_thread.daemon = True # Daemon so that this thread doesn't halt it!
dump_current_frames_thread.start()