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()
|