runtime.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import errno
  2. import functools
  3. import json
  4. import os
  5. import sys
  6. import threading
  7. import six
  8. _lock = threading.Lock()
  9. _config = None
  10. _relaxed_runtime_allowed = False
  11. class NoRuntimeFormed(NotImplementedError):
  12. pass
  13. def _set_ya_config(config=None, ya=None):
  14. global _config
  15. if config:
  16. _config = config
  17. elif ya:
  18. class Config:
  19. def __init__(self):
  20. self.ya = None
  21. _config = Config()
  22. _config.ya = ya
  23. def _get_ya_config():
  24. if _config:
  25. return _config
  26. else:
  27. try:
  28. import pytest
  29. return pytest.config
  30. except (ImportError, AttributeError):
  31. raise NoRuntimeFormed("yatest.common.* is only available from the testing runtime")
  32. def _get_ya_plugin_instance():
  33. return _get_ya_config().ya
  34. def _norm_path(path):
  35. if path is None:
  36. return None
  37. assert isinstance(path, six.string_types)
  38. if "\\" in path:
  39. raise AssertionError("path {} contains Windows seprators \\ - replace them with '/'".format(path))
  40. return os.path.normpath(path)
  41. def _is_binary():
  42. return getattr(sys, 'is_standalone_binary', False)
  43. def _is_relaxed_runtime_allowed():
  44. global _relaxed_runtime_allowed
  45. if _relaxed_runtime_allowed:
  46. return True
  47. return not _is_binary()
  48. def default_arg0(func):
  49. return default_arg(func, 0)
  50. def default_arg1(func):
  51. return default_arg(func, 1)
  52. def default_arg(func, narg):
  53. # Always try to call func, before checking standaloneness.
  54. # The context file might be provided and func might return
  55. # result even if it's not a standalone binary run.
  56. @functools.wraps(func)
  57. def wrapper(*args, **kw):
  58. try:
  59. return func(*args, **kw)
  60. except NoRuntimeFormed:
  61. if _is_relaxed_runtime_allowed():
  62. if len(args) > narg:
  63. return args[narg]
  64. return None
  65. raise
  66. return wrapper
  67. def default_value(value):
  68. def decorator(func):
  69. @functools.wraps(func)
  70. def wrapper(*args, **kw):
  71. try:
  72. return func(*args, **kw)
  73. except NoRuntimeFormed:
  74. if _is_relaxed_runtime_allowed():
  75. return value
  76. raise
  77. return wrapper
  78. return decorator
  79. def _join_path(main_path, path):
  80. if not path:
  81. return main_path
  82. return os.path.join(main_path, _norm_path(path))
  83. def not_test(func):
  84. """
  85. Mark any function as not a test for py.test
  86. :param func:
  87. :return:
  88. """
  89. @functools.wraps(func)
  90. def wrapper(*args, **kwds):
  91. return func(*args, **kwds)
  92. setattr(wrapper, '__test__', False)
  93. return wrapper
  94. @default_arg0
  95. def source_path(path=None):
  96. """
  97. Get source path inside arcadia
  98. :param path: path arcadia relative, e.g. yatest.common.source_path('devtools/ya')
  99. :return: absolute path to the source folder
  100. """
  101. return _join_path(_get_ya_plugin_instance().source_root, path)
  102. @default_arg0
  103. def build_path(path=None):
  104. """
  105. Get path inside build directory
  106. :param path: path relative to the build directory, e.g. yatest.common.build_path('devtools/ya/bin')
  107. :return: absolute path inside build directory
  108. """
  109. return _join_path(_get_ya_plugin_instance().build_root, path)
  110. def java_path():
  111. """
  112. [DEPRECATED] Get path to java
  113. :return: absolute path to java
  114. """
  115. from . import runtime_java
  116. return runtime_java.get_java_path(binary_path(os.path.join('build', 'platform', 'java', 'jdk', 'testing')))
  117. def java_home():
  118. """
  119. Get jdk directory path
  120. """
  121. from . import runtime_java
  122. jdk_dir = runtime_java.get_build_java_dir(binary_path('jdk'))
  123. if not jdk_dir:
  124. raise Exception(
  125. "Cannot find jdk - make sure 'jdk' is added to the DEPENDS section and exists for the current platform"
  126. )
  127. return jdk_dir
  128. def java_bin():
  129. """
  130. Get path to the java binary
  131. Requires DEPENDS(jdk)
  132. """
  133. return os.path.join(java_home(), "bin", "java")
  134. @default_arg0
  135. def data_path(path=None):
  136. """
  137. Get path inside arcadia_tests_data directory
  138. :param path: path relative to the arcadia_tests_data directory, e.g. yatest.common.data_path("pers/rerank_service")
  139. :return: absolute path inside arcadia_tests_data
  140. """
  141. if "USE_ATD_FROM_ATD" in os.environ:
  142. return _join_path(_get_ya_plugin_instance().data_root, path)
  143. return _join_path(source_path("atd_ro_snapshot"), path)
  144. @default_arg0
  145. def output_path(path=None):
  146. """
  147. Get path inside the current test suite output dir.
  148. Placing files to this dir guarantees that files will be accessible after the test suite execution.
  149. :param path: path relative to the test suite output dir
  150. :return: absolute path inside the test suite output dir
  151. """
  152. return _join_path(_get_ya_plugin_instance().output_dir, path)
  153. @default_arg0
  154. def ram_drive_path(path=None):
  155. """
  156. :param path: path relative to the ram drive.
  157. :return: absolute path inside the ram drive directory or None if no ram drive was provided by environment.
  158. """
  159. if 'YA_TEST_RAM_DRIVE_PATH' in os.environ:
  160. return _join_path(os.environ['YA_TEST_RAM_DRIVE_PATH'], path)
  161. elif get_param("ram_drive_path"):
  162. return _join_path(get_param("ram_drive_path"), path)
  163. @default_arg0
  164. def output_ram_drive_path(path=None):
  165. """
  166. Returns path inside ram drive directory which will be saved in the testing_out_stuff directory after testing.
  167. Returns None if no ram drive was provided by environment.
  168. :param path: path relative to the output ram drive directory
  169. """
  170. if 'YA_TEST_OUTPUT_RAM_DRIVE_PATH' in os.environ:
  171. return _join_path(os.environ['YA_TEST_OUTPUT_RAM_DRIVE_PATH'], path)
  172. elif _get_ya_plugin_instance().get_context("test_output_ram_drive_path"):
  173. return _join_path(_get_ya_plugin_instance().get_context("test_output_ram_drive_path"), path)
  174. @default_arg0
  175. def binary_path(path=None):
  176. """
  177. Get path to the built binary
  178. :param path: path to the binary relative to the build directory e.g. yatest.common.binary_path('devtools/ya/bin/ya-bin')
  179. :return: absolute path to the binary
  180. """
  181. path = _norm_path(path)
  182. return _get_ya_plugin_instance().get_binary(path)
  183. @default_arg0
  184. def work_path(path=None):
  185. """
  186. Get path inside the current test suite working directory. Creating files in the work directory does not guarantee
  187. that files will be accessible after the test suite execution
  188. :param path: path relative to the test suite working dir
  189. :return: absolute path inside the test suite working dir
  190. """
  191. return _join_path(
  192. os.environ.get("TEST_WORK_PATH") or _get_ya_plugin_instance().get_context("work_path") or os.getcwd(), path
  193. )
  194. @default_value("python")
  195. def python_path():
  196. """
  197. Get path to the arcadia python.
  198. Warn: if you are using build with system python (-DUSE_SYSTEM_PYTHON=X) beware that some python bundles
  199. are built in a stripped-down form that is needed for building, not running tests.
  200. See comments in the file below to find out which version of python is compatible with tests.
  201. https://a.yandex-team.ru/arc/trunk/arcadia/build/platform/python/resources.inc
  202. :return: absolute path to python
  203. """
  204. return _get_ya_plugin_instance().python_path
  205. @default_value("valgrind")
  206. def valgrind_path():
  207. """
  208. Get path to valgrind
  209. :return: absolute path to valgrind
  210. """
  211. return _get_ya_plugin_instance().valgrind_path
  212. @default_arg1
  213. def get_param(key, default=None):
  214. """
  215. Get arbitrary parameter passed via command line
  216. :param key: key
  217. :param default: default value
  218. :return: parameter value or the default
  219. """
  220. return _get_ya_plugin_instance().get_param(key, default)
  221. def set_metric_value(name, val):
  222. """
  223. Use this method only when your test environment does not support pytest fixtures,
  224. otherwise you should prefer using https://docs.yandex-team.ru/ya-make/manual/tests/#python
  225. :param name: name
  226. :param val: value
  227. """
  228. _get_ya_plugin_instance().set_metric_value(name, val)
  229. @default_arg1
  230. def get_metric_value(name, default=None):
  231. """
  232. Use this method only when your test environment does not support pytest fixtures,
  233. otherwise you should prefer using https://docs.yandex-team.ru/ya-make/manual/tests/#python
  234. :param name: name
  235. :param default: default
  236. :return: parameter value or the default
  237. """
  238. return _get_ya_plugin_instance().get_metric_value(name, default)
  239. @default_value(lambda _: {})
  240. def get_param_dict_copy():
  241. """
  242. Return copy of dictionary with all parameters. Changes to this dictionary do *not* change parameters.
  243. :return: copy of dictionary with all parameters
  244. """
  245. return _get_ya_plugin_instance().get_param_dict_copy()
  246. @not_test
  247. def test_output_path(path=None):
  248. """
  249. Get dir in the suite output_path for the current test case
  250. """
  251. test_log_path = _get_ya_config().current_test_log_path
  252. test_out_dir, log_ext = os.path.splitext(test_log_path)
  253. log_ext = log_ext.strip(".")
  254. if log_ext.isdigit():
  255. test_out_dir = os.path.splitext(test_out_dir)[0]
  256. test_out_dir = test_out_dir + "_" + log_ext
  257. try:
  258. os.makedirs(test_out_dir)
  259. except OSError as e:
  260. if e.errno != errno.EEXIST:
  261. raise
  262. return _join_path(test_out_dir, path)
  263. def project_path(path=None):
  264. """
  265. Get path in build root relating to build_root/project path
  266. """
  267. return _join_path(os.path.join(build_path(), context.project_path), path)
  268. @default_value("gdb")
  269. def gdb_path():
  270. """
  271. Get path to the gdb
  272. """
  273. if _is_relaxed_runtime_allowed():
  274. return "gdb"
  275. return _get_ya_plugin_instance().gdb_path
  276. def c_compiler_path():
  277. """
  278. Get path to the gdb
  279. """
  280. return os.environ.get("YA_CC")
  281. def c_compiler_cmd():
  282. p = c_compiler_path()
  283. return [p, '-isystem' + os.path.dirname(os.path.dirname(p)) + '/share/include']
  284. def get_yt_hdd_path(path=None):
  285. if 'HDD_PATH' in os.environ:
  286. return _join_path(os.environ['HDD_PATH'], path)
  287. def cxx_compiler_path():
  288. """
  289. Get path to the gdb
  290. """
  291. return os.environ.get("YA_CXX")
  292. def cxx_compiler_cmd():
  293. p = cxx_compiler_path()
  294. return [p, '-isystem' + os.path.dirname(os.path.dirname(p)) + '/share/include']
  295. def global_resources():
  296. try:
  297. if "YA_GLOBAL_RESOURCES" in os.environ:
  298. return json.loads(os.environ.get("YA_GLOBAL_RESOURCES"))
  299. else:
  300. return _get_ya_plugin_instance().get_context("ya_global_resources")
  301. except (TypeError, ValueError):
  302. return {}
  303. def _register_core(name, binary_path, core_path, bt_path, pbt_path):
  304. config = _get_ya_config()
  305. with _lock:
  306. if not hasattr(config, 'test_cores_count'):
  307. config.test_cores_count = 0
  308. config.test_cores_count += 1
  309. count_str = '' if config.test_cores_count == 1 else str(config.test_cores_count)
  310. log_entry = config.test_logs[config.current_item_nodeid]
  311. if binary_path:
  312. log_entry['{} binary{}'.format(name, count_str)] = binary_path
  313. if core_path:
  314. log_entry['{} core{}'.format(name, count_str)] = core_path
  315. if bt_path:
  316. log_entry['{} backtrace{}'.format(name, count_str)] = bt_path
  317. if pbt_path:
  318. log_entry['{} backtrace html{}'.format(name, count_str)] = pbt_path
  319. @not_test
  320. def test_source_path(path=None):
  321. return _join_path(os.path.join(source_path(), context.project_path), path)
  322. class Context(object):
  323. """
  324. Runtime context
  325. """
  326. @property
  327. @default_value(None)
  328. def build_type(self):
  329. return _get_ya_plugin_instance().get_context("build_type")
  330. @property
  331. @default_value(None)
  332. def project_path(self):
  333. return _get_ya_plugin_instance().get_context("project_path")
  334. @property
  335. @default_value(False)
  336. def test_stderr(self):
  337. return _get_ya_plugin_instance().get_context("test_stderr")
  338. @property
  339. @default_value(False)
  340. def test_debug(self):
  341. return _get_ya_plugin_instance().get_context("test_debug")
  342. @property
  343. @default_value(None)
  344. def test_traceback(self):
  345. return _get_ya_plugin_instance().get_context("test_traceback")
  346. @property
  347. @default_value(None)
  348. def test_name(self):
  349. return _get_ya_config().current_test_name
  350. @property
  351. @default_value("test_tool")
  352. def test_tool_path(self):
  353. return _get_ya_plugin_instance().get_context("test_tool_path")
  354. @property
  355. @default_value(None)
  356. def retry_index(self):
  357. return _get_ya_plugin_instance().get_context("retry_index")
  358. @property
  359. @default_value(False)
  360. def sanitize(self):
  361. """
  362. Detect if current test run is under sanitizer
  363. :return: one of `None`, 'address', 'memory', 'thread', 'undefined'
  364. """
  365. return _get_ya_plugin_instance().get_context("sanitize")
  366. @property
  367. @default_value(lambda _: {})
  368. def flags(self):
  369. _flags = _get_ya_plugin_instance().get_context("flags")
  370. if _flags:
  371. _flags_dict = dict()
  372. for f in _flags:
  373. key, value = f.split('=', 1)
  374. _flags_dict[key] = value
  375. return _flags_dict
  376. else:
  377. return dict()
  378. def get_context_key(self, key):
  379. return _get_ya_plugin_instance().get_context(key)
  380. context = Context()