canonical.py 8.1 KB

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