canonical.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import os
  2. import logging
  3. import shutil
  4. import tempfile
  5. import six
  6. from . import process
  7. from . import runtime
  8. from . import path
  9. yatest_logger = logging.getLogger("ya.test")
  10. def _copy(src, dst, universal_lines=False):
  11. if universal_lines:
  12. with open(dst, "wb") as f:
  13. mode = "rbU" if six.PY2 else "rb"
  14. for line in open(src, mode):
  15. f.write(line)
  16. return
  17. shutil.copy(src, dst)
  18. @runtime.default_arg0
  19. def canonical_file(
  20. path, diff_tool=None, local=False, universal_lines=False, diff_file_name=None, diff_tool_timeout=None
  21. ):
  22. """
  23. Create canonical file that can be returned from a test
  24. :param path: path to the file
  25. :param diff_tool: custom diff tool to use for comparison with the canonical one, if None - default will be used
  26. :param local: save file locally, otherwise move to sandbox
  27. :param universal_lines: normalize EOL
  28. :param diff_tool_timeout: timeout for running diff tool
  29. :return: object that can be canonized
  30. """
  31. abs_path = os.path.abspath(path)
  32. assert os.path.exists(abs_path), "Canonical path {} does not exist".format(path)
  33. tempdir = tempfile.mkdtemp(prefix="canon_tmp", dir=runtime.build_path())
  34. safe_path = os.path.join(tempdir, os.path.basename(abs_path))
  35. # if the created file is in output_path, we copy it, so that it will be available when the tests finishes
  36. _copy(path, safe_path, universal_lines=universal_lines)
  37. if diff_tool:
  38. if not isinstance(diff_tool, six.string_types):
  39. try: # check if iterable
  40. if not isinstance(diff_tool[0], six.string_types):
  41. raise Exception("Invalid custom diff-tool: not cmd")
  42. except Exception:
  43. raise Exception("Invalid custom diff-tool: not binary path")
  44. return runtime._get_ya_plugin_instance().file(
  45. safe_path, diff_tool=diff_tool, local=local, diff_file_name=diff_file_name, diff_tool_timeout=diff_tool_timeout
  46. )
  47. @runtime.default_arg0
  48. def canonical_dir(path, diff_tool=None, local=False, diff_file_name=None, diff_tool_timeout=None):
  49. abs_path = os.path.abspath(path)
  50. assert os.path.exists(abs_path), "Canonical path {} does not exist".format(path)
  51. assert os.path.isdir(abs_path), "Path {} is not a directory".format(path)
  52. if diff_file_name and not diff_tool:
  53. raise Exception("diff_file_name can be only be used with diff_tool for canonical_dir")
  54. tempdir = tempfile.mkdtemp()
  55. safe_path = os.path.join(tempdir, os.path.basename(abs_path))
  56. shutil.copytree(abs_path, safe_path)
  57. return runtime._get_ya_plugin_instance().file(
  58. safe_path, diff_tool=diff_tool, local=local, diff_file_name=diff_file_name, diff_tool_timeout=diff_tool_timeout
  59. )
  60. def canonical_execute(
  61. binary,
  62. args=None,
  63. check_exit_code=True,
  64. shell=False,
  65. timeout=None,
  66. cwd=None,
  67. env=None,
  68. stdin=None,
  69. stderr=None,
  70. creationflags=0,
  71. file_name=None,
  72. save_locally=False,
  73. close_fds=False,
  74. diff_tool=None,
  75. diff_file_name=None,
  76. diff_tool_timeout=None,
  77. data_transformer=None,
  78. ):
  79. """
  80. Shortcut to execute a binary and canonize its stdout
  81. :param binary: absolute path to the binary
  82. :param args: binary arguments
  83. :param check_exit_code: will raise ExecutionError if the command exits with non zero code
  84. :param shell: use shell to run the command
  85. :param timeout: execution timeout
  86. :param cwd: working directory
  87. :param env: command environment
  88. :param stdin: command stdin
  89. :param stderr: command stderr
  90. :param creationflags: command creation flags
  91. :param file_name: output file name. if not specified program name will be used
  92. :param diff_tool: path to custome diff tool
  93. :param diff_file_name: custom diff file name to create when diff is found
  94. :param diff_tool_timeout: timeout for running diff tool
  95. :param data_transformer: data modifier (before canonize)
  96. :return: object that can be canonized
  97. """
  98. if type(binary) == list:
  99. command = binary
  100. else:
  101. command = [binary]
  102. command += _prepare_args(args)
  103. if shell:
  104. command = " ".join(command)
  105. execute_args = locals()
  106. del execute_args["binary"]
  107. del execute_args["args"]
  108. del execute_args["file_name"]
  109. del execute_args["save_locally"]
  110. del execute_args["diff_tool"]
  111. del execute_args["diff_file_name"]
  112. del execute_args["diff_tool_timeout"]
  113. del execute_args["data_transformer"]
  114. if not file_name and stdin:
  115. file_name = os.path.basename(stdin.name)
  116. return _canonical_execute(
  117. process.execute,
  118. execute_args,
  119. file_name,
  120. save_locally,
  121. diff_tool,
  122. diff_file_name,
  123. diff_tool_timeout,
  124. data_transformer,
  125. )
  126. def canonical_py_execute(
  127. script_path,
  128. args=None,
  129. check_exit_code=True,
  130. shell=False,
  131. timeout=None,
  132. cwd=None,
  133. env=None,
  134. stdin=None,
  135. stderr=None,
  136. creationflags=0,
  137. file_name=None,
  138. save_locally=False,
  139. close_fds=False,
  140. diff_tool=None,
  141. diff_file_name=None,
  142. diff_tool_timeout=None,
  143. data_transformer=None,
  144. ):
  145. """
  146. Shortcut to execute a python script and canonize its stdout
  147. :param script_path: path to the script arcadia relative
  148. :param args: script arguments
  149. :param check_exit_code: will raise ExecutionError if the command exits with non zero code
  150. :param shell: use shell to run the command
  151. :param timeout: execution timeout
  152. :param cwd: working directory
  153. :param env: command environment
  154. :param stdin: command stdin
  155. :param stderr: command stderr
  156. :param creationflags: command creation flags
  157. :param file_name: output file name. if not specified program name will be used
  158. :param diff_tool: path to custome diff tool
  159. :param diff_file_name: custom diff file name to create when diff is found
  160. :param diff_tool_timeout: timeout for running diff tool
  161. :param data_transformer: data modifier (before canonize)
  162. :return: object that can be canonized
  163. """
  164. command = [runtime.source_path(script_path)] + _prepare_args(args)
  165. if shell:
  166. command = " ".join(command)
  167. execute_args = locals()
  168. del execute_args["script_path"]
  169. del execute_args["args"]
  170. del execute_args["file_name"]
  171. del execute_args["save_locally"]
  172. del execute_args["diff_tool"]
  173. del execute_args["diff_file_name"]
  174. del execute_args["diff_tool_timeout"]
  175. del execute_args["data_transformer"]
  176. return _canonical_execute(
  177. process.py_execute,
  178. execute_args,
  179. file_name,
  180. save_locally,
  181. diff_tool,
  182. diff_file_name,
  183. diff_tool_timeout,
  184. data_transformer,
  185. )
  186. def _prepare_args(args):
  187. if args is None:
  188. args = []
  189. if isinstance(args, six.string_types):
  190. args = list(map(lambda a: a.strip(), args.split()))
  191. return args
  192. def _canonical_execute(
  193. excutor, kwargs, file_name, save_locally, diff_tool, diff_file_name, diff_tool_timeout, data_transformer
  194. ):
  195. res = excutor(**kwargs)
  196. command = kwargs["command"]
  197. file_name = file_name or process.get_command_name(command)
  198. if file_name.endswith(".exe"):
  199. file_name = os.path.splitext(file_name)[0] # don't want to bring windows stuff in file names
  200. out_file_path = path.get_unique_file_path(runtime.output_path(), "{}.out.txt".format(file_name))
  201. err_file_path = path.get_unique_file_path(runtime.output_path(), "{}.err.txt".format(file_name))
  202. if not data_transformer:
  203. def data_transformer(x):
  204. return x
  205. try:
  206. os.makedirs(os.path.dirname(out_file_path))
  207. except OSError:
  208. pass
  209. with open(out_file_path, "wb") as out_file:
  210. yatest_logger.debug("Will store file in %s", out_file_path)
  211. out_file.write(data_transformer(res.std_out))
  212. if res.std_err:
  213. with open(err_file_path, "wb") as err_file:
  214. err_file.write(res.std_err)
  215. return canonical_file(
  216. out_file_path,
  217. local=save_locally,
  218. diff_tool=diff_tool,
  219. diff_file_name=diff_file_name,
  220. diff_tool_timeout=diff_tool_timeout,
  221. )