process.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. # coding: utf-8
  2. import io
  3. import os
  4. import re
  5. import time
  6. import signal
  7. import shutil
  8. import inspect
  9. import logging
  10. import tempfile
  11. import subprocess
  12. import errno
  13. import packaging.version
  14. import six
  15. try:
  16. # yatest.common should try to be hermetic, otherwise, PYTEST_SCRIPT (aka USE_ARCADIA_PYTHON=no) won't work.
  17. import library.python.cores as cores
  18. except ImportError:
  19. cores = None
  20. from . import runtime
  21. from . import path
  22. from . import environment
  23. MAX_OUT_LEN = 64 * 1024 # 64K
  24. MAX_MESSAGE_LEN = 1500
  25. SANITIZER_ERROR_PATTERN = br": ([A-Z][\w]+Sanitizer)"
  26. GLIBC_PATTERN = re.compile(r"\S+@GLIBC_([0-9.]+)")
  27. yatest_logger = logging.getLogger("ya.test")
  28. def truncate(s, size):
  29. if s is None:
  30. return None
  31. elif len(s) <= size:
  32. return s
  33. else:
  34. return (b'...' if isinstance(s, bytes) else '...') + s[-(size - 3) :]
  35. def get_command_name(command):
  36. return os.path.basename(command.split()[0] if isinstance(command, six.string_types) else command[0])
  37. class ExecutionError(Exception):
  38. def __init__(self, execution_result):
  39. if not isinstance(execution_result.command, six.string_types):
  40. command = " ".join(str(arg) for arg in execution_result.command)
  41. else:
  42. command = execution_result.command
  43. message = "Command '{command}' has failed with code {code}.\nErrors:\n{err}\n".format(
  44. command=command, code=execution_result.exit_code, err=_format_error(execution_result.std_err)
  45. )
  46. if cores:
  47. if execution_result.backtrace:
  48. message += "Backtrace:\n[[rst]]{}[[bad]]\n".format(
  49. cores.colorize_backtrace(execution_result._backtrace)
  50. )
  51. else:
  52. message += "Backtrace is not available: module cores isn't available"
  53. super(ExecutionError, self).__init__(message)
  54. self.execution_result = execution_result
  55. class TimeoutError(Exception):
  56. pass
  57. class ExecutionTimeoutError(TimeoutError):
  58. def __init__(self, execution_result, *args, **kwargs):
  59. super(ExecutionTimeoutError, self).__init__(args, kwargs)
  60. self.execution_result = execution_result
  61. class InvalidExecutionStateError(Exception):
  62. pass
  63. class SignalInterruptionError(Exception):
  64. def __init__(self, message=None):
  65. super(SignalInterruptionError, self).__init__(message)
  66. self.res = None
  67. class InvalidCommandError(Exception):
  68. pass
  69. class _Execution(object):
  70. def __init__(
  71. self,
  72. command,
  73. process,
  74. out_file,
  75. err_file,
  76. process_progress_listener=None,
  77. cwd=None,
  78. collect_cores=True,
  79. check_sanitizer=True,
  80. started=0,
  81. user_stdout=False,
  82. user_stderr=False,
  83. core_pattern=None,
  84. ):
  85. self._command = command
  86. self._process = process
  87. self._out_file = out_file
  88. self._err_file = err_file
  89. self._std_out = None
  90. self._std_err = None
  91. self._elapsed = None
  92. self._start = time.time()
  93. self._process_progress_listener = process_progress_listener
  94. self._cwd = cwd or os.getcwd()
  95. self._collect_cores = collect_cores
  96. self._backtrace = ''
  97. self._check_sanitizer = check_sanitizer
  98. self._metrics = {}
  99. self._started = started
  100. self._user_stdout = bool(user_stdout)
  101. self._user_stderr = bool(user_stderr)
  102. self._exit_code = None
  103. self._core_pattern = core_pattern
  104. if process_progress_listener:
  105. process_progress_listener.open(command, process, out_file, err_file)
  106. @property
  107. def running(self):
  108. return self._process.poll() is None
  109. def kill(self):
  110. if self.running:
  111. self._save_outputs(False)
  112. _kill_process_tree(self._process.pid)
  113. self._clean_files()
  114. # DEVTOOLS-2347
  115. yatest_logger.debug("Process status before wait_for: %s", self.running)
  116. try:
  117. wait_for(
  118. lambda: not self.running,
  119. timeout=5,
  120. fail_message="Could not kill process {}".format(self._process.pid),
  121. sleep_time=0.1,
  122. )
  123. except TimeoutError:
  124. yatest_logger.debug("Process status after wait_for: %s", self.running)
  125. yatest_logger.debug("Process %d info: %s", self._process.pid, _get_proc_tree_info([self._process.pid]))
  126. raise
  127. else:
  128. raise InvalidExecutionStateError("Cannot kill a stopped process")
  129. def terminate(self):
  130. if self.running:
  131. self._process.terminate()
  132. @property
  133. def process(self):
  134. return self._process
  135. @property
  136. def command(self):
  137. return self._command
  138. @property
  139. def core_pattern(self):
  140. return self._core_pattern
  141. @property
  142. def returncode(self):
  143. return self.exit_code
  144. @property
  145. def exit_code(self):
  146. """
  147. Deprecated, use returncode
  148. """
  149. if self._exit_code is None:
  150. self._exit_code = self._process.returncode
  151. return self._exit_code
  152. @property
  153. def stdout(self):
  154. return self.std_out
  155. @property
  156. def std_out(self):
  157. """
  158. Deprecated, use stdout
  159. """
  160. if self._std_out is not None:
  161. return self._std_out
  162. if self._process.stdout and not self._user_stdout:
  163. self._std_out = six.ensure_str(self._process.stdout.read())
  164. return self._std_out
  165. @property
  166. def stderr(self):
  167. return self.std_err
  168. @property
  169. def std_err(self):
  170. """
  171. Deprecated, use stderr
  172. """
  173. # TODO: Fix bytes/str, maybe need to change a lot of tests
  174. if self._std_err is not None:
  175. return self._std_err
  176. if self._process.stderr and not self._user_stderr:
  177. self._std_err = six.ensure_str(self._process.stderr.read())
  178. return self._std_err
  179. @property
  180. def elapsed(self):
  181. return self._elapsed
  182. @property
  183. def backtrace(self):
  184. return self._backtrace
  185. @property
  186. def metrics(self):
  187. return self._metrics
  188. def _save_outputs(self, clean_files=True):
  189. if self._process_progress_listener:
  190. self._process_progress_listener()
  191. self._process_progress_listener.close()
  192. if not self._user_stdout:
  193. if self._out_file is None:
  194. pass
  195. elif self._out_file != subprocess.PIPE:
  196. self._out_file.flush()
  197. self._out_file.seek(0, os.SEEK_SET)
  198. self._std_out = self._out_file.read()
  199. self._out_file.close()
  200. else:
  201. self._std_out = self._process.stdout.read()
  202. if not self._user_stderr:
  203. if self._err_file is None:
  204. pass
  205. elif self._err_file != subprocess.PIPE:
  206. self._err_file.flush()
  207. self._err_file.seek(0, os.SEEK_SET)
  208. self._std_err = self._err_file.read()
  209. self._err_file.close()
  210. else:
  211. self._std_err = self._process.stderr.read()
  212. if clean_files:
  213. self._clean_files()
  214. yatest_logger.debug("Command (pid %s) rc: %s", self._process.pid, self.exit_code)
  215. yatest_logger.debug("Command (pid %s) elapsed time (sec): %s", self._process.pid, self.elapsed)
  216. if self._metrics:
  217. for key, value in six.iteritems(self._metrics):
  218. yatest_logger.debug("Command (pid %s) %s: %s", self._process.pid, key, value)
  219. # Since this code is Python2/3 compatible, we don't know is _std_out/_std_err is real bytes or bytes-str.
  220. printable_std_out, err = _try_convert_bytes_to_string(self._std_out)
  221. if err:
  222. yatest_logger.debug("Got error during parse process stdout: %s", err)
  223. yatest_logger.debug("stdout will be displayed as raw bytes.")
  224. printable_std_err, err = _try_convert_bytes_to_string(self._std_err)
  225. if err:
  226. yatest_logger.debug("Got error during parse process stderr: %s", err)
  227. yatest_logger.debug("stderr will be displayed as raw bytes.")
  228. yatest_logger.debug("Command (pid %s) output:\n%s", self._process.pid, truncate(printable_std_out, MAX_OUT_LEN))
  229. yatest_logger.debug("Command (pid %s) errors:\n%s", self._process.pid, truncate(printable_std_err, MAX_OUT_LEN))
  230. def _clean_files(self):
  231. if self._err_file and not self._user_stderr and self._err_file != subprocess.PIPE:
  232. self._err_file.close()
  233. self._err_file = None
  234. if self._out_file and not self._user_stdout and self._out_file != subprocess.PIPE:
  235. self._out_file.close()
  236. self._out_file = None
  237. def _recover_core(self):
  238. core_path = cores.recover_core_dump_file(self.command[0], self._cwd, self.process.pid, self.core_pattern)
  239. if core_path:
  240. # Core dump file recovering may be disabled (for distbuild for example) - produce only bt
  241. store_cores = runtime._get_ya_config().collect_cores
  242. if store_cores:
  243. new_core_path = path.get_unique_file_path(
  244. runtime.output_path(), "{}.{}.core".format(os.path.basename(self.command[0]), self._process.pid)
  245. )
  246. # Copy core dump file, because it may be overwritten
  247. yatest_logger.debug("Coping core dump file from '%s' to the '%s'", core_path, new_core_path)
  248. shutil.copyfile(core_path, new_core_path)
  249. core_path = new_core_path
  250. bt_filename = None
  251. pbt_filename = None
  252. if os.path.exists(runtime.gdb_path()):
  253. yatest_logger.debug("Getting full backtrace from core file")
  254. self._backtrace = cores.get_gdb_full_backtrace(self.command[0], core_path, runtime.gdb_path())
  255. bt_filename = path.get_unique_file_path(
  256. runtime.output_path(),
  257. "{}.{}.backtrace".format(os.path.basename(self.command[0]), self._process.pid),
  258. )
  259. with open(bt_filename, "wb") as afile:
  260. afile.write(six.ensure_binary(self._backtrace))
  261. # generate pretty html version of backtrace aka Tri Korochki
  262. pbt_filename = bt_filename + ".html"
  263. backtrace_to_html(bt_filename, pbt_filename)
  264. yatest_logger.debug("Register coredump")
  265. if store_cores:
  266. runtime._register_core(
  267. os.path.basename(self.command[0]), self.command[0], core_path, bt_filename, pbt_filename
  268. )
  269. else:
  270. runtime._register_core(os.path.basename(self.command[0]), None, None, bt_filename, pbt_filename)
  271. def wait(self, check_exit_code=True, timeout=None, on_timeout=None):
  272. def _wait():
  273. finished = None
  274. interrupted = False
  275. try:
  276. if hasattr(os, "wait4"):
  277. try:
  278. if hasattr(subprocess, "_eintr_retry_call"):
  279. pid, sts, rusage = subprocess._eintr_retry_call(os.wait4, self._process.pid, 0)
  280. else:
  281. # PEP 475
  282. pid, sts, rusage = os.wait4(self._process.pid, 0)
  283. finished = time.time()
  284. self._process._handle_exitstatus(sts)
  285. for field in [
  286. "ru_idrss",
  287. "ru_inblock",
  288. "ru_isrss",
  289. "ru_ixrss",
  290. "ru_majflt",
  291. "ru_maxrss",
  292. "ru_minflt",
  293. "ru_msgrcv",
  294. "ru_msgsnd",
  295. "ru_nivcsw",
  296. "ru_nsignals",
  297. "ru_nswap",
  298. "ru_nvcsw",
  299. "ru_oublock",
  300. "ru_stime",
  301. "ru_utime",
  302. ]:
  303. if hasattr(rusage, field):
  304. self._metrics[field.replace("ru_", "")] = getattr(rusage, field)
  305. except OSError as exc:
  306. if exc.errno == errno.ECHILD:
  307. yatest_logger.debug(
  308. "Process resource usage is not available as process finished before wait4 was called"
  309. )
  310. else:
  311. raise
  312. except SignalInterruptionError:
  313. interrupted = True
  314. raise
  315. finally:
  316. if not interrupted:
  317. self._process.wait() # this has to be here unconditionally, so that all process properties are set
  318. if not finished:
  319. finished = time.time()
  320. self._metrics["wtime"] = round(finished - self._started, 3)
  321. try:
  322. if timeout:
  323. def process_is_finished():
  324. return not self.running
  325. fail_message = "Command '%s' stopped by %d seconds timeout" % (self._command, timeout)
  326. try:
  327. wait_for(
  328. process_is_finished,
  329. timeout,
  330. fail_message,
  331. sleep_time=0.1,
  332. on_check_condition=self._process_progress_listener,
  333. )
  334. except TimeoutError as e:
  335. if on_timeout:
  336. yatest_logger.debug("Calling user specified on_timeout function")
  337. try:
  338. on_timeout(self, timeout)
  339. except Exception:
  340. yatest_logger.exception("Exception while calling on_timeout")
  341. raise ExecutionTimeoutError(self, str(e))
  342. # Wait should be always called here, it finalizes internal states of its process and sets up return code
  343. _wait()
  344. except BaseException as e:
  345. _kill_process_tree(self._process.pid)
  346. _wait()
  347. yatest_logger.debug("Command exception: %s", e)
  348. raise
  349. finally:
  350. self._elapsed = time.time() - self._start
  351. self._save_outputs()
  352. self.verify_no_coredumps()
  353. self._finalise(check_exit_code)
  354. def _finalise(self, check_exit_code):
  355. # Set the signal (negative number) which caused the process to exit
  356. if check_exit_code and self.exit_code != 0:
  357. yatest_logger.error(
  358. "Execution failed with exit code: %s\n\t,std_out:%s\n\tstd_err:%s\n",
  359. self.exit_code,
  360. truncate(self.std_out, MAX_OUT_LEN),
  361. truncate(self.std_err, MAX_OUT_LEN),
  362. )
  363. raise ExecutionError(self)
  364. # Don't search for sanitize errors if stderr was redirected
  365. self.verify_sanitize_errors()
  366. def verify_no_coredumps(self):
  367. """
  368. Verify there is no coredump from this binary. If there is then report backtrace.
  369. """
  370. if self.exit_code < 0 and self._collect_cores:
  371. if cores:
  372. try:
  373. self._recover_core()
  374. except Exception:
  375. yatest_logger.exception("Exception while recovering core")
  376. else:
  377. yatest_logger.warning("Core dump file recovering is skipped: module cores isn't available")
  378. def verify_sanitize_errors(self):
  379. """
  380. Verify there are no sanitizer (ASAN, MSAN, TSAN, etc) errors for this binary. If there are any report them.
  381. """
  382. if self._std_err and self._check_sanitizer and runtime._get_ya_config().sanitizer_extra_checks:
  383. build_path = runtime.build_path()
  384. if self.command[0].startswith(build_path):
  385. match = re.search(SANITIZER_ERROR_PATTERN, six.ensure_binary(self._std_err))
  386. if match:
  387. yatest_logger.error(
  388. "%s sanitizer found errors:\n\tstd_err:%s\n",
  389. match.group(1),
  390. truncate(self.std_err, MAX_OUT_LEN),
  391. )
  392. raise ExecutionError(self)
  393. else:
  394. yatest_logger.debug("No sanitizer errors found")
  395. else:
  396. yatest_logger.debug(
  397. "'%s' doesn't belong to '%s' - no check for sanitize errors", self.command[0], build_path
  398. )
  399. def on_timeout_gen_coredump(exec_obj, _):
  400. """
  401. Function can be passed to the execute(..., timeout=X, on_timeout=on_timeout_gen_coredump)
  402. to generate core dump file, backtrace ahd html-version of the backtrace in case of timeout.
  403. All files will be available in the testing_out_stuff and via links.
  404. """
  405. try:
  406. os.kill(exec_obj.process.pid, signal.SIGQUIT)
  407. exec_obj.process.wait()
  408. except OSError:
  409. # process might be already terminated
  410. pass
  411. def execute(
  412. command,
  413. check_exit_code=True,
  414. shell=False,
  415. timeout=None,
  416. cwd=None,
  417. env=None,
  418. stdin=None,
  419. stdout=None,
  420. stderr=None,
  421. text=False,
  422. creationflags=0,
  423. wait=True,
  424. process_progress_listener=None,
  425. close_fds=False,
  426. collect_cores=True,
  427. check_sanitizer=True,
  428. preexec_fn=None,
  429. on_timeout=None,
  430. executor=_Execution,
  431. core_pattern=None,
  432. popen_kwargs=None,
  433. ):
  434. """
  435. Executes a command
  436. :param command: command: can be a list of arguments or a string
  437. :param check_exit_code: will raise ExecutionError if the command exits with non zero code
  438. :param shell: use shell to run the command
  439. :param timeout: execution timeout
  440. :param cwd: working directory
  441. :param env: command environment
  442. :param stdin: command stdin
  443. :param stdout: command stdout
  444. :param stderr: command stderr
  445. :param text: 'subprocess.Popen'-specific argument, specifies the type of returned data https://docs.python.org/3/library/subprocess.html#subprocess.run
  446. :type text: bool
  447. :param creationflags: command creation flags
  448. :param wait: should wait until the command finishes
  449. :param process_progress_listener=object that is polled while execution is in progress
  450. :param close_fds: subrpocess.Popen close_fds args
  451. :param collect_cores: recover core dump files if shell == False
  452. :param check_sanitizer: raise ExecutionError if stderr contains sanitize errors
  453. :param preexec_fn: subrpocess.Popen preexec_fn arg
  454. :param on_timeout: on_timeout(<execution object>, <timeout value>) callback
  455. :param popen_kwargs: subrpocess.Popen args dictionary. Useful for python3-only arguments
  456. :return _Execution: Execution object
  457. """
  458. if env is None:
  459. env = os.environ.copy()
  460. else:
  461. # Certain environment variables must be present for programs to work properly.
  462. # For more info see DEVTOOLSSUPPORT-4907
  463. mandatory_env_name = 'YA_MANDATORY_ENV_VARS'
  464. mandatory_vars = env.get(mandatory_env_name, os.environ.get(mandatory_env_name)) or ''
  465. if mandatory_vars:
  466. env[mandatory_env_name] = mandatory_vars
  467. mandatory_system_vars = filter(None, mandatory_vars.split(':'))
  468. else:
  469. mandatory_system_vars = ['TMPDIR']
  470. for var in mandatory_system_vars:
  471. if var not in env and var in os.environ:
  472. env[var] = os.environ[var]
  473. if not wait and timeout is not None:
  474. raise ValueError("Incompatible arguments 'timeout' and wait=False")
  475. if popen_kwargs is None:
  476. popen_kwargs = {}
  477. # if subprocess.PIPE in [stdout, stderr]:
  478. # raise ValueError("Don't use pipe to obtain stream data - it may leads to the deadlock")
  479. def get_out_stream(stream, default_name):
  480. mode = 'w+t' if text else 'w+b'
  481. open_kwargs = {'errors': 'ignore', 'encoding': 'utf-8'} if text else {'buffering': 0}
  482. if stream is None:
  483. # No stream is supplied: open new temp file
  484. return _get_command_output_file(command, default_name, mode, open_kwargs), False
  485. if isinstance(stream, six.string_types):
  486. is_block = stream.startswith('/dev/')
  487. if is_block:
  488. mode = 'w+b'
  489. open_kwargs = {'buffering': 0}
  490. # User filename is supplied: open file for writing
  491. return io.open(stream, mode, **open_kwargs), is_block
  492. # Open file or PIPE sentinel is supplied
  493. is_pipe = stream == subprocess.PIPE
  494. return stream, not is_pipe
  495. # to be able to have stdout/stderr and track the process time execution, we don't use subprocess.PIPE,
  496. # as it can cause processes hangs, but use tempfiles instead
  497. out_file, user_stdout = get_out_stream(stdout, 'out')
  498. err_file, user_stderr = get_out_stream(stderr, 'err')
  499. in_file = stdin
  500. if shell and type(command) == list:
  501. command = " ".join(command)
  502. if shell:
  503. collect_cores = False
  504. check_sanitizer = False
  505. else:
  506. if isinstance(command, (list, tuple)):
  507. executable = command[0]
  508. else:
  509. executable = command
  510. if not executable:
  511. raise InvalidCommandError("Target program is invalid: {}".format(command))
  512. elif os.path.isabs(executable):
  513. if not os.path.isfile(executable) and not os.path.isfile(executable + ".exe"):
  514. exists = os.path.exists(executable)
  515. if exists:
  516. stat = os.stat(executable)
  517. else:
  518. stat = None
  519. raise InvalidCommandError(
  520. "Target program is not a file: {} (exists: {} stat: {})".format(executable, exists, stat)
  521. )
  522. if not os.access(executable, os.X_OK) and not os.access(executable + ".exe", os.X_OK):
  523. raise InvalidCommandError("Target program is not executable: {}".format(executable))
  524. if check_sanitizer:
  525. env["LSAN_OPTIONS"] = environment.extend_env_var(os.environ, "LSAN_OPTIONS", "exitcode=100")
  526. if stdin:
  527. name = "PIPE" if stdin == subprocess.PIPE else stdin.name
  528. yatest_logger.debug(
  529. "Executing '%s' with input '%s' in '%s' (%s)", command, name, cwd, 'waiting' if wait else 'no wait'
  530. )
  531. else:
  532. yatest_logger.debug("Executing '%s' in '%s' (%s)", command, cwd, 'waiting' if wait else 'no wait')
  533. # XXX
  534. started = time.time()
  535. process = subprocess.Popen(
  536. command,
  537. shell=shell,
  538. universal_newlines=text,
  539. stdout=out_file,
  540. stderr=err_file,
  541. stdin=in_file,
  542. cwd=cwd,
  543. env=env,
  544. creationflags=creationflags,
  545. close_fds=close_fds,
  546. preexec_fn=preexec_fn,
  547. **popen_kwargs
  548. )
  549. yatest_logger.debug("Command pid: %s", process.pid)
  550. kwargs = {
  551. 'user_stdout': user_stdout,
  552. 'user_stderr': user_stderr,
  553. }
  554. if six.PY2:
  555. executor_args = inspect.getargspec(executor.__init__).args
  556. else:
  557. executor_args = inspect.getfullargspec(executor.__init__).args
  558. if 'core_pattern' in executor_args:
  559. kwargs.update([('core_pattern', core_pattern)])
  560. res = executor(
  561. command,
  562. process,
  563. out_file,
  564. err_file,
  565. process_progress_listener,
  566. cwd,
  567. collect_cores,
  568. check_sanitizer,
  569. started,
  570. **kwargs
  571. )
  572. if wait:
  573. res.wait(check_exit_code, timeout, on_timeout)
  574. return res
  575. def _get_command_output_file(cmd, ext, mode, open_kwargs=None):
  576. if open_kwargs is None:
  577. open_kwargs = {}
  578. parts = [get_command_name(cmd)]
  579. if 'YA_RETRY_INDEX' in os.environ:
  580. parts.append('retry{}'.format(os.environ.get('YA_RETRY_INDEX')))
  581. if int(os.environ.get('YA_SPLIT_COUNT', '0')) > 1:
  582. parts.append('chunk{}'.format(os.environ.get('YA_SPLIT_INDEX', '0')))
  583. filename = '.'.join(parts + [ext])
  584. try:
  585. # if execution is performed from test, save out / err to the test logs dir
  586. import yatest.common
  587. import library.python.pytest.plugins.ya
  588. if getattr(library.python.pytest.plugins.ya, 'pytest_config', None) is None:
  589. raise ImportError("not in test")
  590. filename = path.get_unique_file_path(yatest.common.output_path(), filename)
  591. yatest_logger.debug("Command %s will be placed to %s", ext, os.path.basename(filename))
  592. return io.open(filename, mode, **open_kwargs)
  593. except ImportError:
  594. return tempfile.NamedTemporaryFile(mode=mode, delete=False, suffix=filename, **(open_kwargs if six.PY3 else {}))
  595. def _get_proc_tree_info(pids):
  596. if os.name == 'nt':
  597. return 'Not supported'
  598. else:
  599. stdout, _ = subprocess.Popen(
  600. ["/bin/ps", "-wufp"] + [str(p) for p in pids], stdout=subprocess.PIPE, stderr=subprocess.PIPE
  601. ).communicate()
  602. return stdout
  603. def py_execute(
  604. command,
  605. check_exit_code=True,
  606. shell=False,
  607. timeout=None,
  608. cwd=None,
  609. env=None,
  610. stdin=None,
  611. stdout=None,
  612. stderr=None,
  613. creationflags=0,
  614. wait=True,
  615. process_progress_listener=None,
  616. close_fds=False,
  617. text=False,
  618. ):
  619. """
  620. Executes a command with the arcadia python
  621. :param command: command to pass to python
  622. :param check_exit_code: will raise ExecutionError if the command exits with non zero code
  623. :param shell: use shell to run the command
  624. :param timeout: execution timeout
  625. :param cwd: working directory
  626. :param env: command environment
  627. :param stdin: command stdin
  628. :param stdout: command stdout
  629. :param stderr: command stderr
  630. :param creationflags: command creation flags
  631. :param wait: should wait until the command finishes
  632. :param process_progress_listener=object that is polled while execution is in progress
  633. :param text: Return original str
  634. :return _Execution: Execution object
  635. """
  636. if isinstance(command, six.string_types):
  637. command = [command]
  638. command = [runtime.python_path()] + command
  639. if shell:
  640. command = " ".join(command)
  641. return execute(**locals())
  642. def _format_error(error):
  643. return truncate(error, MAX_MESSAGE_LEN)
  644. def wait_for(check_function, timeout, fail_message="", sleep_time=1.0, on_check_condition=None):
  645. """
  646. Tries to execute `check_function` for `timeout` seconds.
  647. Continue until function returns nonfalse value.
  648. If function doesn't return nonfalse value for `timeout` seconds
  649. OperationTimeoutException is raised.
  650. Return first nonfalse result returned by `checkFunction`.
  651. """
  652. if sleep_time <= 0:
  653. raise ValueError("Incorrect sleep time value {}".format(sleep_time))
  654. if timeout < 0:
  655. raise ValueError("Incorrect timeout value {}".format(timeout))
  656. start = time.time()
  657. while start + timeout > time.time():
  658. if on_check_condition:
  659. on_check_condition()
  660. res = check_function()
  661. if res:
  662. return res
  663. time.sleep(sleep_time)
  664. message = "{} second(s) wait timeout has expired".format(timeout)
  665. if fail_message:
  666. message += ": {}".format(fail_message)
  667. raise TimeoutError(truncate(message, MAX_MESSAGE_LEN))
  668. def _kill_process_tree(process_pid, target_pid_signal=None):
  669. """
  670. Kills child processes, req. Note that psutil should be installed
  671. @param process_pid: parent id to search for descendants
  672. """
  673. yatest_logger.debug("Killing process %s", process_pid)
  674. if os.name == 'nt':
  675. _win_kill_process_tree(process_pid)
  676. else:
  677. _nix_kill_process_tree(process_pid, target_pid_signal)
  678. def _nix_get_proc_children(pid):
  679. try:
  680. cmd = ["pgrep", "-P", str(pid)]
  681. return [int(p) for p in subprocess.check_output(cmd).split()]
  682. except Exception:
  683. return []
  684. def _get_binname(pid):
  685. try:
  686. return os.path.basename(os.readlink('/proc/{}/exe'.format(pid)))
  687. except Exception as e:
  688. return "error({})".format(e)
  689. def _nix_kill_process_tree(pid, target_pid_signal=None):
  690. """
  691. Kills the process tree.
  692. """
  693. yatest_logger.debug("Killing process tree for pid {} (bin:'{}')".format(pid, _get_binname(pid)))
  694. def try_to_send_signal(pid, sig):
  695. try:
  696. os.kill(pid, sig)
  697. yatest_logger.debug("Sent signal %d to the pid %d", sig, pid)
  698. except Exception as exc:
  699. yatest_logger.debug(
  700. "Error while sending signal {sig} to pid {pid}: {error}".format(sig=sig, pid=pid, error=str(exc))
  701. )
  702. try_to_send_signal(pid, signal.SIGSTOP) # Stop the process to prevent it from starting any child processes.
  703. # Get the child process PID list.
  704. child_pids = _nix_get_proc_children(pid)
  705. # Stop the child processes.
  706. for child_pid in child_pids:
  707. try:
  708. # Kill the child recursively.
  709. _kill_process_tree(int(child_pid))
  710. except Exception as e:
  711. # Skip the error and continue killing.
  712. yatest_logger.debug("Killing child pid {pid} failed: {error}".format(pid=child_pid, error=e))
  713. continue
  714. try_to_send_signal(pid, target_pid_signal or signal.SIGKILL) # Kill the root process.
  715. # sometimes on freebsd sigkill cannot kill the process and either sigkill or sigcont should be sent
  716. # https://www.mail-archive.com/freebsd-hackers@freebsd.org/msg159646.html
  717. try_to_send_signal(pid, signal.SIGCONT)
  718. def _win_kill_process_tree(pid):
  719. subprocess.call(['taskkill', '/F', '/T', '/PID', str(pid)])
  720. def _run_readelf(binary_path):
  721. return str(
  722. subprocess.check_output(
  723. [runtime.binary_path('contrib/python/pyelftools/readelf/readelf'), '-s', runtime.binary_path(binary_path)]
  724. )
  725. )
  726. def check_glibc_version(binary_path):
  727. lucid_glibc_version = packaging.version.parse("2.11")
  728. for line in _run_readelf(binary_path).split('\n'):
  729. match = GLIBC_PATTERN.search(line)
  730. if not match:
  731. continue
  732. assert packaging.version.parse(match.group(1)) <= lucid_glibc_version, match.group(0)
  733. def backtrace_to_html(bt_filename, output):
  734. try:
  735. from library.python import coredump_filter
  736. # XXX reduce noise from core_dumpfilter
  737. logging.getLogger("sandbox.sdk2.helpers.coredump_filter").setLevel(logging.ERROR)
  738. with open(output, "w") as afile:
  739. coredump_filter.filter_stackdump(bt_filename, stream=afile)
  740. except ImportError as e:
  741. yatest_logger.debug("Failed to import coredump_filter: %s", e)
  742. with open(output, "w") as afile:
  743. afile.write("<html>Failed to import coredump_filter in USE_ARCADIA_PYTHON=no mode</html>")
  744. def _try_convert_bytes_to_string(source):
  745. """Function is necessary while this code Python2/3 compatible, because bytes in Python3 is a real bytes and in Python2 is not"""
  746. # Bit ugly typecheck, because in Python2 isinstance(str(), bytes) and "type(str()) is bytes" working as True as well
  747. if 'bytes' not in str(type(source)):
  748. # We already got not bytes. Nothing to do here.
  749. return source, False
  750. result = source
  751. error = False
  752. try:
  753. result = source.decode(encoding='utf-8')
  754. except ValueError as e:
  755. error = e
  756. return result, error