ytest.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  1. import os
  2. import re
  3. import sys
  4. import json
  5. import copy
  6. import base64
  7. import shlex
  8. import _common
  9. import lib.test_const as consts
  10. import _requirements as reqs
  11. import StringIO
  12. import subprocess
  13. import collections
  14. import ymake
  15. MDS_URI_PREFIX = 'https://storage.yandex-team.ru/get-devtools/'
  16. MDS_SCHEME = 'mds'
  17. CANON_DATA_DIR_NAME = 'canondata'
  18. CANON_OUTPUT_STORAGE = 'canondata_storage'
  19. CANON_RESULT_FILE_NAME = 'result.json'
  20. CANON_MDS_RESOURCE_REGEX = re.compile(re.escape(MDS_URI_PREFIX) + r'(.*?)($|#)')
  21. CANON_SBR_RESOURCE_REGEX = re.compile(r'(sbr:/?/?(\d+))')
  22. BLOCK_SEPARATOR = '============================================================='
  23. SPLIT_FACTOR_MAX_VALUE = 1000
  24. SPLIT_FACTOR_TEST_FILES_MAX_VALUE = 4250
  25. PARTITION_MODS = ('SEQUENTIAL', 'MODULO')
  26. DEFAULT_TIDY_CONFIG = "build/config/tests/clang_tidy/config.yaml"
  27. DEFAULT_TIDY_CONFIG_MAP_PATH = "build/yandex_specific/config/clang_tidy/tidy_default_map.json"
  28. PROJECT_TIDY_CONFIG_MAP_PATH = "build/yandex_specific/config/clang_tidy/tidy_project_map.json"
  29. tidy_config_map = None
  30. def ontest_data(unit, *args):
  31. ymake.report_configure_error("TEST_DATA is removed in favour of DATA")
  32. def prepare_recipes(data):
  33. data = data.replace('"USE_RECIPE_DELIM"', "\n")
  34. data = data.replace("$TEST_RECIPES_VALUE", "")
  35. return base64.b64encode(data or "")
  36. def prepare_env(data):
  37. data = data.replace("$TEST_ENV_VALUE", "")
  38. return serialize_list(shlex.split(data))
  39. def is_yt_spec_contain_pool_info(filename): # XXX switch to yson in ymake + perf test for configure
  40. pool_re = re.compile(r"""['"]*pool['"]*\s*?=""")
  41. cypress_root_re = re.compile(r"""['"]*cypress_root['"]*\s*=""")
  42. with open(filename, 'r') as afile:
  43. yt_spec = afile.read()
  44. return pool_re.search(yt_spec) and cypress_root_re.search(yt_spec)
  45. def validate_test(unit, kw):
  46. def get_list(key):
  47. return deserialize_list(kw.get(key, ""))
  48. valid_kw = copy.deepcopy(kw)
  49. errors = []
  50. warnings = []
  51. if valid_kw.get('SCRIPT-REL-PATH') == 'boost.test':
  52. project_path = valid_kw.get('BUILD-FOLDER-PATH', "")
  53. if not project_path.startswith(
  54. ("contrib", "mail", "maps", "tools/idl", "metrika", "devtools", "mds", "yandex_io", "smart_devices")
  55. ):
  56. errors.append("BOOSTTEST is not allowed here")
  57. elif valid_kw.get('SCRIPT-REL-PATH') == 'gtest':
  58. project_path = valid_kw.get('BUILD-FOLDER-PATH', "")
  59. if not project_path.startswith(("contrib", "devtools", "mds")):
  60. errors.append("GTEST_UGLY is not allowed here, use GTEST instead")
  61. size_timeout = collections.OrderedDict(sorted(consts.TestSize.DefaultTimeouts.items(), key=lambda t: t[1]))
  62. size = valid_kw.get('SIZE', consts.TestSize.Small).lower()
  63. tags = set(get_list("TAG"))
  64. requirements_orig = get_list("REQUIREMENTS")
  65. in_autocheck = consts.YaTestTags.NotAutocheck not in tags and consts.YaTestTags.Manual not in tags
  66. is_fat = consts.YaTestTags.Fat in tags
  67. is_force_sandbox = consts.YaTestTags.ForceDistbuild not in tags and is_fat
  68. is_ytexec_run = consts.YaTestTags.YtRunner in tags
  69. is_fuzzing = valid_kw.get("FUZZING", False)
  70. is_kvm = 'kvm' in requirements_orig
  71. requirements = {}
  72. secret_requirements = ('sb_vault', 'yav')
  73. list_requirements = secret_requirements
  74. for req in requirements_orig:
  75. if req in ('kvm',):
  76. requirements[req] = str(True)
  77. continue
  78. if ":" in req:
  79. req_name, req_value = req.split(":", 1)
  80. if req_name in list_requirements:
  81. requirements[req_name] = ",".join(filter(None, [requirements.get(req_name), req_value]))
  82. else:
  83. if req_name in requirements:
  84. if req_value in ["0"]:
  85. warnings.append(
  86. "Requirement [[imp]]{}[[rst]] is dropped [[imp]]{}[[rst]] -> [[imp]]{}[[rst]]".format(
  87. req_name, requirements[req_name], req_value
  88. )
  89. )
  90. del requirements[req_name]
  91. elif requirements[req_name] != req_value:
  92. warnings.append(
  93. "Requirement [[imp]]{}[[rst]] is redefined [[imp]]{}[[rst]] -> [[imp]]{}[[rst]]".format(
  94. req_name, requirements[req_name], req_value
  95. )
  96. )
  97. requirements[req_name] = req_value
  98. else:
  99. requirements[req_name] = req_value
  100. else:
  101. errors.append("Invalid requirement syntax [[imp]]{}[[rst]]: expect <requirement>:<value>".format(req))
  102. if not errors:
  103. for req_name, req_value in requirements.items():
  104. error_msg = reqs.validate_requirement(
  105. req_name,
  106. req_value,
  107. size,
  108. is_force_sandbox,
  109. in_autocheck,
  110. is_fuzzing,
  111. is_kvm,
  112. is_ytexec_run,
  113. requirements,
  114. )
  115. if error_msg:
  116. errors += [error_msg]
  117. invalid_requirements_for_distbuild = [
  118. requirement for requirement in requirements.keys() if requirement not in ('ram', 'ram_disk', 'cpu', 'network')
  119. ]
  120. sb_tags = []
  121. # XXX Unfortunately, some users have already started using colons
  122. # in their tag names. Use skip set to avoid treating their tag as system ones.
  123. # Remove this check when all such user tags are removed.
  124. skip_set = ('ynmt_benchmark', 'bert_models', 'zeliboba_map')
  125. # Verify the prefixes of the system tags to avoid pointless use of the REQUIREMENTS macro parameters in the TAG macro.
  126. for tag in tags:
  127. if tag.startswith('sb:'):
  128. sb_tags.append(tag)
  129. elif ':' in tag and not tag.startswith('ya:') and tag.split(':')[0] not in skip_set:
  130. errors.append("Only [[imp]]sb:[[rst]] and [[imp]]ya:[[rst]] prefixes are allowed in system tags: {}".format(tag))
  131. if is_fat:
  132. if size != consts.TestSize.Large:
  133. errors.append("Only LARGE test may have ya:fat tag")
  134. if in_autocheck and not is_force_sandbox:
  135. if invalid_requirements_for_distbuild:
  136. errors.append(
  137. "'{}' REQUIREMENTS options can be used only for FAT tests without ya:force_distbuild tag. Remove TAG(ya:force_distbuild) or an option.".format(
  138. invalid_requirements_for_distbuild
  139. )
  140. )
  141. if sb_tags:
  142. errors.append(
  143. "You can set sandbox tags '{}' only for FAT tests without ya:force_distbuild. Remove TAG(ya:force_sandbox) or sandbox tags.".format(
  144. sb_tags
  145. )
  146. )
  147. if consts.YaTestTags.SandboxCoverage in tags:
  148. errors.append("You can set 'ya:sandbox_coverage' tag only for FAT tests without ya:force_distbuild.")
  149. if is_ytexec_run:
  150. errors.append(
  151. "Running LARGE tests over YT (ya:yt) on Distbuild (ya:force_distbuild) is forbidden. Consider removing TAG(ya:force_distbuild)."
  152. )
  153. else:
  154. if is_force_sandbox:
  155. errors.append('ya:force_sandbox can be used with LARGE tests only')
  156. if consts.YaTestTags.NoFuse in tags:
  157. errors.append('ya:nofuse can be used with LARGE tests only')
  158. if consts.YaTestTags.Privileged in tags:
  159. errors.append("ya:privileged can be used with LARGE tests only")
  160. if in_autocheck and size == consts.TestSize.Large:
  161. errors.append("LARGE test must have ya:fat tag")
  162. if consts.YaTestTags.Privileged in tags and 'container' not in requirements:
  163. errors.append("Only tests with 'container' requirement can have 'ya:privileged' tag")
  164. if size not in size_timeout:
  165. errors.append(
  166. "Unknown test size: [[imp]]{}[[rst]], choose from [[imp]]{}[[rst]]".format(
  167. size.upper(), ", ".join([sz.upper() for sz in size_timeout.keys()])
  168. )
  169. )
  170. else:
  171. try:
  172. timeout = int(valid_kw.get('TEST-TIMEOUT', size_timeout[size]) or size_timeout[size])
  173. script_rel_path = valid_kw.get('SCRIPT-REL-PATH')
  174. if timeout < 0:
  175. raise Exception("Timeout must be > 0")
  176. if size_timeout[size] < timeout and in_autocheck and script_rel_path != 'java.style':
  177. suggested_size = None
  178. for s, t in size_timeout.items():
  179. if timeout <= t:
  180. suggested_size = s
  181. break
  182. if suggested_size:
  183. suggested_size = ", suggested size: [[imp]]{}[[rst]]".format(suggested_size.upper())
  184. else:
  185. suggested_size = ""
  186. errors.append(
  187. "Max allowed timeout for test size [[imp]]{}[[rst]] is [[imp]]{} sec[[rst]]{}".format(
  188. size.upper(), size_timeout[size], suggested_size
  189. )
  190. )
  191. except Exception as e:
  192. errors.append("Error when parsing test timeout: [[bad]]{}[[rst]]".format(e))
  193. requirements_list = []
  194. for req_name, req_value in requirements.iteritems():
  195. requirements_list.append(req_name + ":" + req_value)
  196. valid_kw['REQUIREMENTS'] = serialize_list(requirements_list)
  197. # Mark test with ya:external tag if it requests any secret from external storages
  198. # It's not stable and nonreproducible by definition
  199. for x in secret_requirements:
  200. if x in requirements:
  201. tags.add(consts.YaTestTags.External)
  202. if valid_kw.get("FUZZ-OPTS"):
  203. for option in get_list("FUZZ-OPTS"):
  204. if not option.startswith("-"):
  205. errors.append(
  206. "Unrecognized fuzzer option '[[imp]]{}[[rst]]'. All fuzzer options should start with '-'".format(
  207. option
  208. )
  209. )
  210. break
  211. eqpos = option.find("=")
  212. if eqpos == -1 or len(option) == eqpos + 1:
  213. errors.append(
  214. "Unrecognized fuzzer option '[[imp]]{}[[rst]]'. All fuzzer options should obtain value specified after '='".format(
  215. option
  216. )
  217. )
  218. break
  219. if option[eqpos - 1] == " " or option[eqpos + 1] == " ":
  220. errors.append("Spaces are not allowed: '[[imp]]{}[[rst]]'".format(option))
  221. break
  222. if option[:eqpos] in ("-runs", "-dict", "-jobs", "-workers", "-artifact_prefix", "-print_final_stats"):
  223. errors.append(
  224. "You can't use '[[imp]]{}[[rst]]' - it will be automatically calculated or configured during run".format(
  225. option
  226. )
  227. )
  228. break
  229. if valid_kw.get("YT-SPEC"):
  230. if not is_ytexec_run:
  231. errors.append("You can use YT_SPEC macro only tests marked with ya:yt tag")
  232. else:
  233. for filename in get_list("YT-SPEC"):
  234. filename = unit.resolve('$S/' + filename)
  235. if not os.path.exists(filename):
  236. errors.append("File '{}' specified in the YT_SPEC macro doesn't exist".format(filename))
  237. continue
  238. if not is_yt_spec_contain_pool_info(filename):
  239. tags.add(consts.YaTestTags.External)
  240. tags.add("ya:yt_research_pool")
  241. if valid_kw.get("USE_ARCADIA_PYTHON") == "yes" and valid_kw.get("SCRIPT-REL-PATH") == "py.test":
  242. errors.append("PYTEST_SCRIPT is deprecated")
  243. partition = valid_kw.get('TEST_PARTITION', 'SEQUENTIAL')
  244. if partition not in PARTITION_MODS:
  245. raise ValueError('partition mode should be one of {}, detected: {}'.format(PARTITION_MODS, partition))
  246. if valid_kw.get('SPLIT-FACTOR'):
  247. if valid_kw.get('FORK-MODE') == 'none':
  248. errors.append('SPLIT_FACTOR must be use with FORK_TESTS() or FORK_SUBTESTS() macro')
  249. value = 1
  250. try:
  251. value = int(valid_kw.get('SPLIT-FACTOR'))
  252. if value <= 0:
  253. raise ValueError("must be > 0")
  254. if value > SPLIT_FACTOR_MAX_VALUE:
  255. raise ValueError("the maximum allowed value is {}".format(SPLIT_FACTOR_MAX_VALUE))
  256. except ValueError as e:
  257. errors.append('Incorrect SPLIT_FACTOR value: {}'.format(e))
  258. if valid_kw.get('FORK-TEST-FILES') and size != consts.TestSize.Large:
  259. nfiles = count_entries(valid_kw.get('TEST-FILES'))
  260. if nfiles * value > SPLIT_FACTOR_TEST_FILES_MAX_VALUE:
  261. errors.append(
  262. 'Too much chunks generated:{} (limit: {}). Remove FORK_TEST_FILES() macro or reduce SPLIT_FACTOR({}).'.format(
  263. nfiles * value, SPLIT_FACTOR_TEST_FILES_MAX_VALUE, value
  264. )
  265. )
  266. if tags:
  267. valid_kw['TAG'] = serialize_list(tags)
  268. unit_path = _common.get_norm_unit_path(unit)
  269. if (
  270. not is_fat
  271. and consts.YaTestTags.Noretries in tags
  272. and not is_ytexec_run
  273. and not unit_path.startswith("devtools/dummy_arcadia/test/noretries")
  274. ):
  275. errors.append("Only LARGE tests can have 'ya:noretries' tag")
  276. if errors:
  277. return None, warnings, errors
  278. return valid_kw, warnings, errors
  279. def dump_test(unit, kw):
  280. valid_kw, warnings, errors = validate_test(unit, kw)
  281. for w in warnings:
  282. unit.message(['warn', w])
  283. for e in errors:
  284. ymake.report_configure_error(e)
  285. if valid_kw is None:
  286. return None
  287. string_handler = StringIO.StringIO()
  288. for k, v in valid_kw.iteritems():
  289. print >> string_handler, k + ': ' + v
  290. print >> string_handler, BLOCK_SEPARATOR
  291. data = string_handler.getvalue()
  292. string_handler.close()
  293. return data
  294. def serialize_list(lst):
  295. lst = filter(None, lst)
  296. return '\"' + ';'.join(lst) + '\"' if lst else ''
  297. def deserialize_list(val):
  298. return filter(None, val.replace('"', "").split(";"))
  299. def get_correct_expression_for_group_var(varname):
  300. return "\"${join=\;:" + varname + "}\""
  301. def count_entries(x):
  302. # see (de)serialize_list
  303. assert x is None or isinstance(x, str), type(x)
  304. if not x:
  305. return 0
  306. return x.count(";") + 1
  307. def get_values_list(unit, key):
  308. res = map(str.strip, (unit.get(key) or '').replace('$' + key, '').strip().split())
  309. return [r for r in res if r and r not in ['""', "''"]]
  310. def get_norm_paths(unit, key):
  311. # return paths without trailing (back)slash
  312. return [x.rstrip('\\/').replace('${ARCADIA_ROOT}/', '') for x in get_values_list(unit, key)]
  313. def get_unit_list_variable(unit, name):
  314. items = unit.get(name)
  315. if items:
  316. items = items.split(' ')
  317. assert items[0] == "${}".format(name), (items, name)
  318. return items[1:]
  319. return []
  320. def implies(a, b):
  321. return bool((not a) or b)
  322. def match_coverage_extractor_requirements(unit):
  323. # we shouldn't add test if
  324. return all(
  325. [
  326. # tests are not requested
  327. unit.get("TESTS_REQUESTED") == "yes",
  328. # build doesn't imply clang coverage, which supports segment extraction from the binaries
  329. unit.get("CLANG_COVERAGE") == "yes",
  330. # contrib wasn't requested
  331. implies(
  332. _common.get_norm_unit_path(unit).startswith("contrib/"), unit.get("ENABLE_CONTRIB_COVERAGE") == "yes"
  333. ),
  334. ]
  335. )
  336. def get_tidy_config_map(unit, map_path):
  337. config_map_path = unit.resolve(os.path.join("$S", map_path))
  338. config_map = {}
  339. try:
  340. with open(config_map_path, 'r') as afile:
  341. config_map = json.load(afile)
  342. except ValueError:
  343. ymake.report_configure_error("{} is invalid json".format(map_path))
  344. except Exception as e:
  345. ymake.report_configure_error(str(e))
  346. return config_map
  347. def get_default_tidy_config(unit):
  348. unit_path = _common.get_norm_unit_path(unit)
  349. tidy_default_config_map = get_tidy_config_map(unit, DEFAULT_TIDY_CONFIG_MAP_PATH)
  350. for project_prefix, config_path in tidy_default_config_map.items():
  351. if unit_path.startswith(project_prefix):
  352. return config_path
  353. return DEFAULT_TIDY_CONFIG
  354. ordered_tidy_map = None
  355. def get_project_tidy_config(unit):
  356. global ordered_tidy_map
  357. if ordered_tidy_map is None:
  358. ordered_tidy_map = list(reversed(sorted(get_tidy_config_map(unit, PROJECT_TIDY_CONFIG_MAP_PATH).items())))
  359. unit_path = _common.get_norm_unit_path(unit)
  360. for project_prefix, config_path in ordered_tidy_map:
  361. if unit_path.startswith(project_prefix):
  362. return config_path
  363. else:
  364. return get_default_tidy_config(unit)
  365. def onadd_ytest(unit, *args):
  366. keywords = {
  367. "DEPENDS": -1,
  368. "DATA": -1,
  369. "TIMEOUT": 1,
  370. "FORK_MODE": 1,
  371. "SPLIT_FACTOR": 1,
  372. "FORK_SUBTESTS": 0,
  373. "FORK_TESTS": 0,
  374. }
  375. flat_args, spec_args = _common.sort_by_keywords(keywords, args)
  376. is_implicit_data_needed = flat_args[1] in ("unittest.py", "gunittest", "g_benchmark", "go.test", "boost.test", "fuzz.test")
  377. if is_implicit_data_needed and unit.get('ADD_SRCDIR_TO_TEST_DATA') == "yes":
  378. unit.ondata_files(_common.get_norm_unit_path(unit))
  379. if flat_args[1] == "fuzz.test":
  380. unit.ondata("arcadia/fuzzing/{}/corpus.json".format(_common.get_norm_unit_path(unit)))
  381. if not flat_args[1] in ("unittest.py", "gunittest", "g_benchmark"):
  382. unit.ondata_files(get_unit_list_variable(unit, 'TEST_YT_SPEC_VALUE'))
  383. test_data = sorted(
  384. _common.filter_out_by_keyword(
  385. spec_args.get('DATA', []) + get_norm_paths(unit, 'TEST_DATA_VALUE'), 'AUTOUPDATED'
  386. )
  387. )
  388. if flat_args[1] == "go.test":
  389. data, _ = get_canonical_test_resources(unit)
  390. test_data += data
  391. elif flat_args[1] == "coverage.extractor" and not match_coverage_extractor_requirements(unit):
  392. # XXX
  393. # Current ymake implementation doesn't allow to call macro inside the 'when' body
  394. # that's why we add ADD_YTEST(coverage.extractor) to every PROGRAM entry and check requirements later
  395. return
  396. elif flat_args[1] == "clang_tidy" and unit.get("TIDY_ENABLED") != "yes":
  397. # Graph is not prepared
  398. return
  399. elif unit.get("TIDY") == "yes" and unit.get("TIDY_ENABLED") != "yes":
  400. # clang_tidy disabled for module
  401. return
  402. elif flat_args[1] == "no.test":
  403. return
  404. test_size = ''.join(spec_args.get('SIZE', [])) or unit.get('TEST_SIZE_NAME') or ''
  405. test_tags = serialize_list(_get_test_tags(unit, spec_args))
  406. test_timeout = ''.join(spec_args.get('TIMEOUT', [])) or unit.get('TEST_TIMEOUT') or ''
  407. test_requirements = spec_args.get('REQUIREMENTS', []) + get_values_list(unit, 'TEST_REQUIREMENTS_VALUE')
  408. if flat_args[1] != "clang_tidy" and unit.get("TIDY_ENABLED") == "yes":
  409. # graph changed for clang_tidy tests
  410. if flat_args[1] in ("unittest.py", "gunittest", "g_benchmark"):
  411. flat_args[1] = "clang_tidy"
  412. test_size = 'SMALL'
  413. test_tags = ''
  414. test_timeout = "60"
  415. test_requirements = []
  416. unit.set(["TEST_YT_SPEC_VALUE", ""])
  417. else:
  418. return
  419. if flat_args[1] == "clang_tidy" and unit.get("TIDY_ENABLED") == "yes":
  420. if unit.get("TIDY_CONFIG"):
  421. default_config_path = unit.get("TIDY_CONFIG")
  422. project_config_path = unit.get("TIDY_CONFIG")
  423. else:
  424. default_config_path = get_default_tidy_config(unit)
  425. project_config_path = get_project_tidy_config(unit)
  426. unit.set(["DEFAULT_TIDY_CONFIG", default_config_path])
  427. unit.set(["PROJECT_TIDY_CONFIG", project_config_path])
  428. fork_mode = []
  429. if 'FORK_SUBTESTS' in spec_args:
  430. fork_mode.append('subtests')
  431. if 'FORK_TESTS' in spec_args:
  432. fork_mode.append('tests')
  433. fork_mode = fork_mode or spec_args.get('FORK_MODE', []) or unit.get('TEST_FORK_MODE').split()
  434. fork_mode = ' '.join(fork_mode) if fork_mode else ''
  435. unit_path = _common.get_norm_unit_path(unit)
  436. test_record = {
  437. 'TEST-NAME': flat_args[0],
  438. 'SCRIPT-REL-PATH': flat_args[1],
  439. 'TESTED-PROJECT-NAME': unit.name(),
  440. 'TESTED-PROJECT-FILENAME': unit.filename(),
  441. 'SOURCE-FOLDER-PATH': unit_path,
  442. # TODO get rid of BUILD-FOLDER-PATH
  443. 'BUILD-FOLDER-PATH': unit_path,
  444. 'BINARY-PATH': "{}/{}".format(unit_path, unit.filename()),
  445. 'GLOBAL-LIBRARY-PATH': unit.global_filename(),
  446. 'CUSTOM-DEPENDENCIES': ' '.join(spec_args.get('DEPENDS', []) + get_values_list(unit, 'TEST_DEPENDS_VALUE')),
  447. 'TEST-RECIPES': prepare_recipes(unit.get("TEST_RECIPES_VALUE")),
  448. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  449. # 'TEST-PRESERVE-ENV': 'da',
  450. 'TEST-DATA': serialize_list(test_data),
  451. 'TEST-TIMEOUT': test_timeout,
  452. 'FORK-MODE': fork_mode,
  453. 'SPLIT-FACTOR': ''.join(spec_args.get('SPLIT_FACTOR', [])) or unit.get('TEST_SPLIT_FACTOR') or '',
  454. 'SIZE': test_size,
  455. 'TAG': test_tags,
  456. 'REQUIREMENTS': serialize_list(test_requirements),
  457. 'TEST-CWD': unit.get('TEST_CWD_VALUE') or '',
  458. 'FUZZ-DICTS': serialize_list(
  459. spec_args.get('FUZZ_DICTS', []) + get_unit_list_variable(unit, 'FUZZ_DICTS_VALUE')
  460. ),
  461. 'FUZZ-OPTS': serialize_list(spec_args.get('FUZZ_OPTS', []) + get_unit_list_variable(unit, 'FUZZ_OPTS_VALUE')),
  462. 'YT-SPEC': serialize_list(spec_args.get('YT_SPEC', []) + get_unit_list_variable(unit, 'TEST_YT_SPEC_VALUE')),
  463. 'BLOB': unit.get('TEST_BLOB_DATA') or '',
  464. 'SKIP_TEST': unit.get('SKIP_TEST_VALUE') or '',
  465. 'TEST_IOS_DEVICE_TYPE': unit.get('TEST_IOS_DEVICE_TYPE_VALUE') or '',
  466. 'TEST_IOS_RUNTIME_TYPE': unit.get('TEST_IOS_RUNTIME_TYPE_VALUE') or '',
  467. 'ANDROID_APK_TEST_ACTIVITY': unit.get('ANDROID_APK_TEST_ACTIVITY_VALUE') or '',
  468. 'TEST_PARTITION': unit.get("TEST_PARTITION") or 'SEQUENTIAL',
  469. 'GO_BENCH_TIMEOUT': unit.get('GO_BENCH_TIMEOUT') or '',
  470. }
  471. if flat_args[1] == "go.bench":
  472. if "ya:run_go_benchmark" not in test_record["TAG"]:
  473. return
  474. else:
  475. test_record["TEST-NAME"] += "_bench"
  476. if flat_args[1] == 'fuzz.test' and unit.get('FUZZING') == 'yes':
  477. test_record['FUZZING'] = '1'
  478. # use all cores if fuzzing requested
  479. test_record['REQUIREMENTS'] = serialize_list(
  480. filter(None, deserialize_list(test_record['REQUIREMENTS']) + ["cpu:all", "ram:all"])
  481. )
  482. data = dump_test(unit, test_record)
  483. if data:
  484. unit.set_property(["DART_DATA", data])
  485. def java_srcdirs_to_data(unit, var):
  486. extra_data = []
  487. for srcdir in (unit.get(var) or '').replace('$' + var, '').split():
  488. if srcdir == '.':
  489. srcdir = unit.get('MODDIR')
  490. if srcdir.startswith('${ARCADIA_ROOT}/') or srcdir.startswith('$ARCADIA_ROOT/'):
  491. srcdir = srcdir.replace('${ARCADIA_ROOT}/', '$S/')
  492. srcdir = srcdir.replace('$ARCADIA_ROOT/', '$S/')
  493. if srcdir.startswith('${CURDIR}') or srcdir.startswith('$CURDIR'):
  494. srcdir = srcdir.replace('${CURDIR}', os.path.join('$S', unit.get('MODDIR')))
  495. srcdir = srcdir.replace('$CURDIR', os.path.join('$S', unit.get('MODDIR')))
  496. srcdir = unit.resolve_arc_path(srcdir)
  497. if not srcdir.startswith('$'):
  498. srcdir = os.path.join('$S', unit.get('MODDIR'), srcdir)
  499. if srcdir.startswith('$S'):
  500. extra_data.append(srcdir.replace('$S', 'arcadia'))
  501. return serialize_list(extra_data)
  502. def onadd_check(unit, *args):
  503. if unit.get("TIDY") == "yes":
  504. # graph changed for clang_tidy tests
  505. return
  506. flat_args, spec_args = _common.sort_by_keywords(
  507. {
  508. "DEPENDS": -1,
  509. "TIMEOUT": 1,
  510. "DATA": -1,
  511. "TAG": -1,
  512. "REQUIREMENTS": -1,
  513. "FORK_MODE": 1,
  514. "SPLIT_FACTOR": 1,
  515. "FORK_SUBTESTS": 0,
  516. "FORK_TESTS": 0,
  517. "SIZE": 1,
  518. },
  519. args,
  520. )
  521. check_type = flat_args[0]
  522. if check_type in ("check.data", "check.resource") and unit.get('VALIDATE_DATA') == "no":
  523. return
  524. test_dir = _common.get_norm_unit_path(unit)
  525. test_timeout = ''
  526. fork_mode = ''
  527. extra_test_data = ''
  528. extra_test_dart_data = {}
  529. ymake_java_test = unit.get('YMAKE_JAVA_TEST') == 'yes'
  530. use_arcadia_python = unit.get('USE_ARCADIA_PYTHON')
  531. uid_ext = ''
  532. script_rel_path = check_type
  533. test_files = flat_args[1:]
  534. if check_type in ["check.data", "check.resource"]:
  535. uid_ext = unit.get("SBR_UID_EXT").split(" ", 1)[-1] # strip variable name
  536. if check_type in ["flake8.py2", "flake8.py3", "black"]:
  537. fork_mode = unit.get('TEST_FORK_MODE') or ''
  538. elif check_type == "JAVA_STYLE":
  539. if ymake_java_test and not unit.get('ALL_SRCDIRS') or '':
  540. return
  541. if len(flat_args) < 2:
  542. raise Exception("Not enough arguments for JAVA_STYLE check")
  543. check_level = flat_args[1]
  544. allowed_levels = {
  545. 'base': '/yandex_checks.xml',
  546. 'strict': '/yandex_checks_strict.xml',
  547. 'extended': '/yandex_checks_extended.xml',
  548. 'library': '/yandex_checks_library.xml',
  549. }
  550. if check_level not in allowed_levels:
  551. raise Exception("'{}' is not allowed in LINT(), use one of {}".format(check_level, allowed_levels.keys()))
  552. test_files[0] = allowed_levels[check_level] # replace check_level with path to config file
  553. script_rel_path = "java.style"
  554. test_timeout = '240'
  555. fork_mode = unit.get('TEST_FORK_MODE') or ''
  556. if ymake_java_test:
  557. extra_test_data = java_srcdirs_to_data(unit, 'ALL_SRCDIRS')
  558. # jstyle should use the latest jdk
  559. unit.onpeerdir([unit.get('JDK_LATEST_PEERDIR')])
  560. extra_test_dart_data['JDK_LATEST_VERSION'] = unit.get('JDK_LATEST_VERSION')
  561. # TODO remove when ya-bin will be released (https://st.yandex-team.ru/DEVTOOLS-9611)
  562. extra_test_dart_data['JDK_RESOURCE'] = 'JDK' + (
  563. unit.get('JDK_VERSION') or unit.get('JDK_REAL_VERSION') or '_DEFAULT'
  564. )
  565. elif check_type == "gofmt":
  566. if test_files:
  567. test_dir = os.path.dirname(test_files[0]).lstrip("$S/")
  568. elif check_type == "check.data":
  569. data_re = re.compile(r"sbr:/?/?(\d+)=?.*")
  570. data = flat_args[1:]
  571. resources = []
  572. for f in data:
  573. matched = re.match(data_re, f)
  574. if matched:
  575. resources.append(matched.group(1))
  576. if resources:
  577. test_files = resources
  578. else:
  579. return
  580. serialized_test_files = serialize_list(test_files)
  581. test_record = {
  582. 'TEST-NAME': check_type.lower(),
  583. 'TEST-TIMEOUT': test_timeout,
  584. 'SCRIPT-REL-PATH': script_rel_path,
  585. 'TESTED-PROJECT-NAME': os.path.basename(test_dir),
  586. 'SOURCE-FOLDER-PATH': test_dir,
  587. 'CUSTOM-DEPENDENCIES': " ".join(spec_args.get('DEPENDS', [])),
  588. 'TEST-DATA': extra_test_data,
  589. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  590. 'SBR-UID-EXT': uid_ext,
  591. 'SPLIT-FACTOR': '',
  592. 'TEST_PARTITION': 'SEQUENTIAL',
  593. 'FORK-MODE': fork_mode,
  594. 'FORK-TEST-FILES': '',
  595. 'SIZE': 'SMALL',
  596. 'TAG': '',
  597. 'REQUIREMENTS': " ".join(spec_args.get('REQUIREMENTS', [])),
  598. 'USE_ARCADIA_PYTHON': use_arcadia_python or '',
  599. 'OLD_PYTEST': 'no',
  600. 'PYTHON-PATHS': '',
  601. # TODO remove FILES, see DEVTOOLS-7052
  602. 'FILES': serialized_test_files,
  603. 'TEST-FILES': serialized_test_files,
  604. }
  605. test_record.update(extra_test_dart_data)
  606. data = dump_test(unit, test_record)
  607. if data:
  608. unit.set_property(["DART_DATA", data])
  609. def on_register_no_check_imports(unit):
  610. s = unit.get('NO_CHECK_IMPORTS_FOR_VALUE')
  611. if s not in ('', 'None'):
  612. unit.onresource(['-', 'py/no_check_imports/{}="{}"'.format(_common.pathid(s), s)])
  613. def onadd_check_py_imports(unit, *args):
  614. if unit.get("TIDY") == "yes":
  615. # graph changed for clang_tidy tests
  616. return
  617. if unit.get('NO_CHECK_IMPORTS_FOR_VALUE').strip() == "":
  618. return
  619. unit.onpeerdir(['library/python/testing/import_test'])
  620. check_type = "py.imports"
  621. test_dir = _common.get_norm_unit_path(unit)
  622. use_arcadia_python = unit.get('USE_ARCADIA_PYTHON')
  623. test_files = serialize_list([_common.get_norm_unit_path(unit, unit.filename())])
  624. test_record = {
  625. 'TEST-NAME': "pyimports",
  626. 'TEST-TIMEOUT': '',
  627. 'SCRIPT-REL-PATH': check_type,
  628. 'TESTED-PROJECT-NAME': os.path.basename(test_dir),
  629. 'SOURCE-FOLDER-PATH': test_dir,
  630. 'CUSTOM-DEPENDENCIES': '',
  631. 'TEST-DATA': '',
  632. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  633. 'SPLIT-FACTOR': '',
  634. 'TEST_PARTITION': 'SEQUENTIAL',
  635. 'FORK-MODE': '',
  636. 'FORK-TEST-FILES': '',
  637. 'SIZE': 'SMALL',
  638. 'TAG': '',
  639. 'USE_ARCADIA_PYTHON': use_arcadia_python or '',
  640. 'OLD_PYTEST': 'no',
  641. 'PYTHON-PATHS': '',
  642. # TODO remove FILES, see DEVTOOLS-7052
  643. 'FILES': test_files,
  644. 'TEST-FILES': test_files,
  645. }
  646. if unit.get('NO_CHECK_IMPORTS_FOR_VALUE') != "None":
  647. test_record["NO-CHECK"] = serialize_list(get_values_list(unit, 'NO_CHECK_IMPORTS_FOR_VALUE') or ["*"])
  648. else:
  649. test_record["NO-CHECK"] = ''
  650. data = dump_test(unit, test_record)
  651. if data:
  652. unit.set_property(["DART_DATA", data])
  653. def onadd_pytest_script(unit, *args):
  654. if unit.get("TIDY") == "yes":
  655. # graph changed for clang_tidy tests
  656. return
  657. unit.set(["PYTEST_BIN", "no"])
  658. custom_deps = get_values_list(unit, 'TEST_DEPENDS_VALUE')
  659. timeout = filter(None, [unit.get(["TEST_TIMEOUT"])])
  660. if unit.get('ADD_SRCDIR_TO_TEST_DATA') == "yes":
  661. unit.ondata_files(_common.get_norm_unit_path(unit))
  662. if timeout:
  663. timeout = timeout[0]
  664. else:
  665. timeout = '0'
  666. test_type = args[0]
  667. fork_mode = unit.get('TEST_FORK_MODE').split() or ''
  668. split_factor = unit.get('TEST_SPLIT_FACTOR') or ''
  669. test_size = unit.get('TEST_SIZE_NAME') or ''
  670. test_files = get_values_list(unit, 'TEST_SRCS_VALUE')
  671. tags = _get_test_tags(unit)
  672. requirements = get_values_list(unit, 'TEST_REQUIREMENTS_VALUE')
  673. test_data = get_norm_paths(unit, 'TEST_DATA_VALUE')
  674. data, data_files = get_canonical_test_resources(unit)
  675. test_data += data
  676. python_paths = get_values_list(unit, 'TEST_PYTHON_PATH_VALUE')
  677. binary_path = os.path.join(_common.get_norm_unit_path(unit), unit.filename())
  678. test_cwd = unit.get('TEST_CWD_VALUE') or ''
  679. _dump_test(
  680. unit,
  681. test_type,
  682. test_files,
  683. timeout,
  684. _common.get_norm_unit_path(unit),
  685. custom_deps,
  686. test_data,
  687. python_paths,
  688. split_factor,
  689. fork_mode,
  690. test_size,
  691. tags,
  692. requirements,
  693. binary_path,
  694. test_cwd=test_cwd,
  695. data_files=data_files,
  696. )
  697. def onadd_pytest_bin(unit, *args):
  698. if unit.get("TIDY") == "yes":
  699. # graph changed for clang_tidy tests
  700. return
  701. flat, kws = _common.sort_by_keywords({'RUNNER_BIN': 1}, args)
  702. if flat:
  703. ymake.report_configure_error('Unknown arguments found while processing add_pytest_bin macro: {!r}'.format(flat))
  704. runner_bin = kws.get('RUNNER_BIN', [None])[0]
  705. test_type = 'py3test.bin' if (unit.get("PYTHON3") == 'yes') else "pytest.bin"
  706. add_test_to_dart(unit, test_type, runner_bin=runner_bin)
  707. def add_test_to_dart(unit, test_type, binary_path=None, runner_bin=None):
  708. if unit.get("TIDY") == "yes":
  709. # graph changed for clang_tidy tests
  710. return
  711. if unit.get('ADD_SRCDIR_TO_TEST_DATA') == "yes":
  712. unit.ondata_files(_common.get_norm_unit_path(unit))
  713. custom_deps = get_values_list(unit, 'TEST_DEPENDS_VALUE')
  714. timeout = filter(None, [unit.get(["TEST_TIMEOUT"])])
  715. if timeout:
  716. timeout = timeout[0]
  717. else:
  718. timeout = '0'
  719. fork_mode = unit.get('TEST_FORK_MODE').split() or ''
  720. split_factor = unit.get('TEST_SPLIT_FACTOR') or ''
  721. test_size = unit.get('TEST_SIZE_NAME') or ''
  722. test_cwd = unit.get('TEST_CWD_VALUE') or ''
  723. yt_spec = get_values_list(unit, 'TEST_YT_SPEC_VALUE')
  724. unit.ondata_files(yt_spec)
  725. unit_path = unit.path()
  726. test_files = get_values_list(unit, 'TEST_SRCS_VALUE')
  727. tags = _get_test_tags(unit)
  728. requirements = get_values_list(unit, 'TEST_REQUIREMENTS_VALUE')
  729. test_data = get_norm_paths(unit, 'TEST_DATA_VALUE')
  730. data, data_files = get_canonical_test_resources(unit)
  731. test_data += data
  732. python_paths = get_values_list(unit, 'TEST_PYTHON_PATH_VALUE')
  733. if not binary_path:
  734. binary_path = os.path.join(unit_path, unit.filename())
  735. _dump_test(
  736. unit,
  737. test_type,
  738. test_files,
  739. timeout,
  740. _common.get_norm_unit_path(unit),
  741. custom_deps,
  742. test_data,
  743. python_paths,
  744. split_factor,
  745. fork_mode,
  746. test_size,
  747. tags,
  748. requirements,
  749. binary_path,
  750. test_cwd=test_cwd,
  751. runner_bin=runner_bin,
  752. yt_spec=yt_spec,
  753. data_files=data_files,
  754. )
  755. def extract_java_system_properties(unit, args):
  756. if len(args) % 2:
  757. return [], 'Wrong use of SYSTEM_PROPERTIES in {}: odd number of arguments'.format(unit.path())
  758. props = []
  759. for x, y in zip(args[::2], args[1::2]):
  760. if x == 'FILE':
  761. if y.startswith('${BINDIR}') or y.startswith('${ARCADIA_BUILD_ROOT}') or y.startswith('/'):
  762. return [], 'Wrong use of SYSTEM_PROPERTIES in {}: absolute/build file path {}'.format(unit.path(), y)
  763. y = _common.rootrel_arc_src(y, unit)
  764. if not os.path.exists(unit.resolve('$S/' + y)):
  765. return [], 'Wrong use of SYSTEM_PROPERTIES in {}: can\'t resolve {}'.format(unit.path(), y)
  766. y = '${ARCADIA_ROOT}/' + y
  767. props.append({'type': 'file', 'path': y})
  768. else:
  769. props.append({'type': 'inline', 'key': x, 'value': y})
  770. return props, None
  771. def onjava_test(unit, *args):
  772. if unit.get("TIDY") == "yes":
  773. # graph changed for clang_tidy tests
  774. return
  775. assert unit.get('MODULE_TYPE') is not None
  776. if unit.get('MODULE_TYPE') == 'JTEST_FOR':
  777. if not unit.get('UNITTEST_DIR'):
  778. ymake.report_configure_error('skip JTEST_FOR in {}: no args provided'.format(unit.path()))
  779. return
  780. java_cp_arg_type = unit.get('JAVA_CLASSPATH_CMD_TYPE_VALUE') or 'MANIFEST'
  781. if java_cp_arg_type not in ('MANIFEST', 'COMMAND_FILE', 'LIST'):
  782. ymake.report_configure_error(
  783. '{}: TEST_JAVA_CLASSPATH_CMD_TYPE({}) are invalid. Choose argument from MANIFEST, COMMAND_FILE or LIST)'.format(
  784. unit.path(), java_cp_arg_type
  785. )
  786. )
  787. return
  788. unit_path = unit.path()
  789. path = _common.strip_roots(unit_path)
  790. if unit.get('ADD_SRCDIR_TO_TEST_DATA') == "yes":
  791. unit.ondata_files(_common.get_norm_unit_path(unit))
  792. yt_spec_values = get_unit_list_variable(unit, 'TEST_YT_SPEC_VALUE')
  793. unit.ondata_files(yt_spec_values)
  794. test_data = get_norm_paths(unit, 'TEST_DATA_VALUE')
  795. test_data.append('arcadia/build/scripts/run_junit.py')
  796. test_data.append('arcadia/build/scripts/unpacking_jtest_runner.py')
  797. data, data_files = get_canonical_test_resources(unit)
  798. test_data += data
  799. props, error_mgs = extract_java_system_properties(unit, get_values_list(unit, 'SYSTEM_PROPERTIES_VALUE'))
  800. if error_mgs:
  801. ymake.report_configure_error(error_mgs)
  802. return
  803. for prop in props:
  804. if prop['type'] == 'file':
  805. test_data.append(prop['path'].replace('${ARCADIA_ROOT}', 'arcadia'))
  806. props = base64.b64encode(json.dumps(props, encoding='utf-8'))
  807. test_cwd = unit.get('TEST_CWD_VALUE') or '' # TODO: validate test_cwd value
  808. if unit.get('MODULE_TYPE') == 'JUNIT5':
  809. script_rel_path = 'junit5.test'
  810. else:
  811. script_rel_path = 'junit.test'
  812. ymake_java_test = unit.get('YMAKE_JAVA_TEST') == 'yes'
  813. test_record = {
  814. 'SOURCE-FOLDER-PATH': path,
  815. 'TEST-NAME': '-'.join([os.path.basename(os.path.dirname(path)), os.path.basename(path)]),
  816. 'SCRIPT-REL-PATH': script_rel_path,
  817. 'TEST-TIMEOUT': unit.get('TEST_TIMEOUT') or '',
  818. 'TESTED-PROJECT-NAME': path,
  819. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  820. # 'TEST-PRESERVE-ENV': 'da',
  821. 'TEST-DATA': serialize_list(sorted(_common.filter_out_by_keyword(test_data, 'AUTOUPDATED'))),
  822. 'FORK-MODE': unit.get('TEST_FORK_MODE') or '',
  823. 'SPLIT-FACTOR': unit.get('TEST_SPLIT_FACTOR') or '',
  824. 'CUSTOM-DEPENDENCIES': ' '.join(get_values_list(unit, 'TEST_DEPENDS_VALUE')),
  825. 'TAG': serialize_list(_get_test_tags(unit)),
  826. 'SIZE': unit.get('TEST_SIZE_NAME') or '',
  827. 'REQUIREMENTS': serialize_list(get_values_list(unit, 'TEST_REQUIREMENTS_VALUE')),
  828. 'TEST-RECIPES': prepare_recipes(unit.get("TEST_RECIPES_VALUE")),
  829. # JTEST/JTEST_FOR only
  830. 'MODULE_TYPE': unit.get('MODULE_TYPE'),
  831. 'UNITTEST_DIR': unit.get('UNITTEST_DIR') or '',
  832. 'JVM_ARGS': serialize_list(get_values_list(unit, 'JVM_ARGS_VALUE')),
  833. 'SYSTEM_PROPERTIES': props,
  834. 'TEST-CWD': test_cwd,
  835. 'SKIP_TEST': unit.get('SKIP_TEST_VALUE') or '',
  836. 'JAVA_CLASSPATH_CMD_TYPE': java_cp_arg_type,
  837. 'JDK_RESOURCE': 'JDK' + (unit.get('JDK_VERSION') or unit.get('JDK_REAL_VERSION') or '_DEFAULT'),
  838. 'JDK_FOR_TESTS': 'JDK' + (unit.get('JDK_VERSION') or unit.get('JDK_REAL_VERSION') or '_DEFAULT') + '_FOR_TESTS',
  839. 'YT-SPEC': serialize_list(yt_spec_values),
  840. }
  841. test_classpath_origins = unit.get('TEST_CLASSPATH_VALUE')
  842. if test_classpath_origins:
  843. test_record['TEST_CLASSPATH_ORIGINS'] = test_classpath_origins
  844. test_record['TEST_CLASSPATH'] = '${TEST_CLASSPATH_MANAGED}'
  845. elif ymake_java_test:
  846. test_record['TEST_CLASSPATH'] = '${DART_CLASSPATH}'
  847. test_record['TEST_CLASSPATH_DEPS'] = '${DART_CLASSPATH_DEPS}'
  848. if unit.get('UNITTEST_DIR'):
  849. test_record['TEST_JAR'] = '${UNITTEST_MOD}'
  850. else:
  851. test_record['TEST_JAR'] = '{}/{}.jar'.format(unit.get('MODDIR'), unit.get('REALPRJNAME'))
  852. data = dump_test(unit, test_record)
  853. if data:
  854. unit.set_property(['DART_DATA', data])
  855. def onjava_test_deps(unit, *args):
  856. if unit.get("TIDY") == "yes":
  857. # graph changed for clang_tidy tests
  858. return
  859. assert unit.get('MODULE_TYPE') is not None
  860. assert len(args) == 1
  861. mode = args[0]
  862. path = _common.get_norm_unit_path(unit)
  863. ymake_java_test = unit.get('YMAKE_JAVA_TEST') == 'yes'
  864. test_record = {
  865. 'SOURCE-FOLDER-PATH': path,
  866. 'TEST-NAME': '-'.join([os.path.basename(os.path.dirname(path)), os.path.basename(path), 'dependencies']).strip(
  867. '-'
  868. ),
  869. 'SCRIPT-REL-PATH': 'java.dependency.test',
  870. 'TEST-TIMEOUT': '',
  871. 'TESTED-PROJECT-NAME': path,
  872. 'TEST-DATA': '',
  873. 'TEST_PARTITION': 'SEQUENTIAL',
  874. 'FORK-MODE': '',
  875. 'SPLIT-FACTOR': '',
  876. 'CUSTOM-DEPENDENCIES': ' '.join(get_values_list(unit, 'TEST_DEPENDS_VALUE')),
  877. 'TAG': '',
  878. 'SIZE': 'SMALL',
  879. 'IGNORE_CLASSPATH_CLASH': ' '.join(get_values_list(unit, 'JAVA_IGNORE_CLASSPATH_CLASH_VALUE')),
  880. # JTEST/JTEST_FOR only
  881. 'MODULE_TYPE': unit.get('MODULE_TYPE'),
  882. 'UNITTEST_DIR': '',
  883. 'SYSTEM_PROPERTIES': '',
  884. 'TEST-CWD': '',
  885. }
  886. if mode == 'strict':
  887. test_record['STRICT_CLASSPATH_CLASH'] = 'yes'
  888. if ymake_java_test:
  889. test_record['CLASSPATH'] = '$B/{}/{}.jar ${{DART_CLASSPATH}}'.format(
  890. unit.get('MODDIR'), unit.get('REALPRJNAME')
  891. )
  892. data = dump_test(unit, test_record)
  893. unit.set_property(['DART_DATA', data])
  894. def _get_test_tags(unit, spec_args=None):
  895. if spec_args is None:
  896. spec_args = {}
  897. tags = spec_args.get('TAG', []) + get_values_list(unit, 'TEST_TAGS_VALUE')
  898. tags = set(tags)
  899. if unit.get('EXPORT_SEM') == 'yes':
  900. filter_only_tags = sorted(t for t in tags if ':' not in t)
  901. unit.set(['FILTER_ONLY_TEST_TAGS', ' '.join(filter_only_tags)])
  902. # DEVTOOLS-7571
  903. if unit.get('SKIP_TEST_VALUE') and consts.YaTestTags.Fat in tags:
  904. tags.add(consts.YaTestTags.NotAutocheck)
  905. return tags
  906. def _dump_test(
  907. unit,
  908. test_type,
  909. test_files,
  910. timeout,
  911. test_dir,
  912. custom_deps,
  913. test_data,
  914. python_paths,
  915. split_factor,
  916. fork_mode,
  917. test_size,
  918. tags,
  919. requirements,
  920. binary_path='',
  921. old_pytest=False,
  922. test_cwd=None,
  923. runner_bin=None,
  924. yt_spec=None,
  925. data_files=None,
  926. ):
  927. if test_type == "PY_TEST":
  928. script_rel_path = "py.test"
  929. else:
  930. script_rel_path = test_type
  931. unit_path = unit.path()
  932. fork_test_files = unit.get('FORK_TEST_FILES_MODE')
  933. fork_mode = ' '.join(fork_mode) if fork_mode else ''
  934. use_arcadia_python = unit.get('USE_ARCADIA_PYTHON')
  935. if test_cwd:
  936. test_cwd = test_cwd.replace("$TEST_CWD_VALUE", "").replace('"MACRO_CALLS_DELIM"', "").strip()
  937. test_name = os.path.basename(binary_path)
  938. test_record = {
  939. 'TEST-NAME': os.path.splitext(test_name)[0],
  940. 'TEST-TIMEOUT': timeout,
  941. 'SCRIPT-REL-PATH': script_rel_path,
  942. 'TESTED-PROJECT-NAME': test_name,
  943. 'SOURCE-FOLDER-PATH': test_dir,
  944. 'CUSTOM-DEPENDENCIES': " ".join(custom_deps),
  945. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  946. # 'TEST-PRESERVE-ENV': 'da',
  947. 'TEST-DATA': serialize_list(sorted(_common.filter_out_by_keyword(test_data, 'AUTOUPDATED'))),
  948. 'TEST-RECIPES': prepare_recipes(unit.get("TEST_RECIPES_VALUE")),
  949. 'SPLIT-FACTOR': split_factor,
  950. 'TEST_PARTITION': unit.get('TEST_PARTITION') or 'SEQUENTIAL',
  951. 'FORK-MODE': fork_mode,
  952. 'FORK-TEST-FILES': fork_test_files,
  953. 'TEST-FILES': serialize_list(test_files),
  954. 'SIZE': test_size,
  955. 'TAG': serialize_list(tags),
  956. 'REQUIREMENTS': serialize_list(requirements),
  957. 'USE_ARCADIA_PYTHON': use_arcadia_python or '',
  958. 'OLD_PYTEST': 'yes' if old_pytest else 'no',
  959. 'PYTHON-PATHS': serialize_list(python_paths),
  960. 'TEST-CWD': test_cwd or '',
  961. 'SKIP_TEST': unit.get('SKIP_TEST_VALUE') or '',
  962. 'BUILD-FOLDER-PATH': _common.strip_roots(unit_path),
  963. 'BLOB': unit.get('TEST_BLOB_DATA') or '',
  964. 'CANONIZE_SUB_PATH': unit.get('CANONIZE_SUB_PATH') or '',
  965. }
  966. if binary_path:
  967. test_record['BINARY-PATH'] = _common.strip_roots(binary_path)
  968. if runner_bin:
  969. test_record['TEST-RUNNER-BIN'] = runner_bin
  970. if yt_spec:
  971. test_record['YT-SPEC'] = serialize_list(yt_spec)
  972. data = dump_test(unit, test_record)
  973. if data:
  974. unit.set_property(["DART_DATA", data])
  975. def onsetup_pytest_bin(unit, *args):
  976. use_arcadia_python = unit.get('USE_ARCADIA_PYTHON') == "yes"
  977. if use_arcadia_python:
  978. unit.onresource(['-', 'PY_MAIN={}'.format("library.python.pytest.main:main")]) # XXX
  979. unit.onadd_pytest_bin(list(args))
  980. else:
  981. unit.onno_platform()
  982. unit.onadd_pytest_script(["PY_TEST"])
  983. def onrun(unit, *args):
  984. exectest_cmd = unit.get(["EXECTEST_COMMAND_VALUE"]) or ''
  985. exectest_cmd += "\n" + subprocess.list2cmdline(args)
  986. unit.set(["EXECTEST_COMMAND_VALUE", exectest_cmd])
  987. def onsetup_exectest(unit, *args):
  988. command = unit.get(["EXECTEST_COMMAND_VALUE"])
  989. if command is None:
  990. ymake.report_configure_error("EXECTEST must have at least one RUN macro")
  991. return
  992. command = command.replace("$EXECTEST_COMMAND_VALUE", "")
  993. if "PYTHON_BIN" in command:
  994. unit.ondepends('contrib/tools/python')
  995. unit.set(["TEST_BLOB_DATA", base64.b64encode(command)])
  996. add_test_to_dart(unit, "exectest", binary_path=os.path.join(unit.path(), unit.filename()).replace(".pkg", ""))
  997. def onsetup_run_python(unit):
  998. if unit.get("USE_ARCADIA_PYTHON") == "yes":
  999. unit.ondepends('contrib/tools/python')
  1000. def get_canonical_test_resources(unit):
  1001. unit_path = unit.path()
  1002. canon_data_dir = os.path.join(unit.resolve(unit_path), CANON_DATA_DIR_NAME, unit.get('CANONIZE_SUB_PATH') or '')
  1003. try:
  1004. _, dirs, files = next(os.walk(canon_data_dir))
  1005. except StopIteration:
  1006. # path doesn't exist
  1007. return [], []
  1008. if CANON_RESULT_FILE_NAME in files:
  1009. return _get_canonical_data_resources_v2(os.path.join(canon_data_dir, CANON_RESULT_FILE_NAME), unit_path)
  1010. return [], []
  1011. def _load_canonical_file(filename, unit_path):
  1012. try:
  1013. with open(filename) as results_file:
  1014. return json.load(results_file)
  1015. except Exception as e:
  1016. print >> sys.stderr, "malformed canonical data in {}: {} ({})".format(unit_path, e, filename)
  1017. return {}
  1018. def _get_resource_from_uri(uri):
  1019. m = CANON_MDS_RESOURCE_REGEX.match(uri)
  1020. if m:
  1021. res_id = m.group(1)
  1022. return "{}:{}".format(MDS_SCHEME, res_id)
  1023. m = CANON_SBR_RESOURCE_REGEX.match(uri)
  1024. if m:
  1025. # There might be conflict between resources, because all resources in sandbox have 'resource.tar.gz' name
  1026. # That's why we use notation with '=' to specify specific path for resource
  1027. uri = m.group(1)
  1028. res_id = m.group(2)
  1029. return "{}={}".format(uri, '/'.join([CANON_OUTPUT_STORAGE, res_id]))
  1030. def _get_external_resources_from_canon_data(data):
  1031. # Method should work with both canonization versions:
  1032. # result.json: {'uri':X 'checksum':Y}
  1033. # result.json: {'testname': {'uri':X 'checksum':Y}}
  1034. # result.json: {'testname': [{'uri':X 'checksum':Y}]}
  1035. # Also there is a bug - if user returns {'uri': 1} from test - machinery will fail
  1036. # That's why we check 'uri' and 'checksum' fields presence
  1037. # (it's still a bug - user can return {'uri':X, 'checksum': Y}, we need to unify canonization format)
  1038. res = set()
  1039. if isinstance(data, dict):
  1040. if 'uri' in data and 'checksum' in data:
  1041. resource = _get_resource_from_uri(data['uri'])
  1042. if resource:
  1043. res.add(resource)
  1044. else:
  1045. for k, v in data.iteritems():
  1046. res.update(_get_external_resources_from_canon_data(v))
  1047. elif isinstance(data, list):
  1048. for e in data:
  1049. res.update(_get_external_resources_from_canon_data(e))
  1050. return res
  1051. def _get_canonical_data_resources_v2(filename, unit_path):
  1052. return (_get_external_resources_from_canon_data(_load_canonical_file(filename, unit_path)), [filename])
  1053. def on_add_linter_check(unit, *args):
  1054. if unit.get("TIDY") == "yes":
  1055. return
  1056. source_root_from_prefix = '${ARCADIA_ROOT}/'
  1057. source_root_to_prefix = '$S/'
  1058. unlimited = -1
  1059. no_lint_value = _common.get_no_lint_value(unit)
  1060. if no_lint_value in ("none", "none_internal"):
  1061. return
  1062. if unit.get("OPENSOURCE") == "yes":
  1063. return
  1064. keywords = {
  1065. "DEPENDS": unlimited,
  1066. "FILES": unlimited,
  1067. "CONFIGS": unlimited,
  1068. "GLOBAL_RESOURCES": unlimited,
  1069. "FILE_PROCESSING_TIME": 1,
  1070. "EXTRA_PARAMS": unlimited,
  1071. }
  1072. flat_args, spec_args = _common.sort_by_keywords(keywords, args)
  1073. if len(flat_args) != 2:
  1074. unit.message(['ERROR', '_ADD_LINTER_CHECK params: expected 2 free parameters'])
  1075. return
  1076. configs = []
  1077. for cfg in spec_args.get('CONFIGS', []):
  1078. filename = unit.resolve(source_root_to_prefix + cfg)
  1079. if not os.path.exists(filename):
  1080. unit.message(['ERROR', 'Configuration file {} is not found'.format(filename)])
  1081. return
  1082. configs.append(cfg)
  1083. deps = []
  1084. lint_name, linter = flat_args
  1085. deps.append(os.path.dirname(linter))
  1086. test_files = []
  1087. for path in spec_args.get('FILES', []):
  1088. if path.startswith(source_root_from_prefix):
  1089. test_files.append(path.replace(source_root_from_prefix, source_root_to_prefix, 1))
  1090. elif path.startswith(source_root_to_prefix):
  1091. test_files.append(path)
  1092. if not test_files:
  1093. unit.message(['WARN', 'No files to lint for {}'.format(lint_name)])
  1094. return
  1095. for arg in spec_args.get('EXTRA_PARAMS', []):
  1096. if '=' not in arg:
  1097. unit.message(['WARN', 'Wrong EXTRA_PARAMS value: "{}". Values must have format "name=value".'.format(arg)])
  1098. return
  1099. deps += spec_args.get('DEPENDS', [])
  1100. for dep in deps:
  1101. unit.ondepends(dep)
  1102. for resource in spec_args.get('GLOBAL_RESOURCES', []):
  1103. unit.onpeerdir(resource)
  1104. test_record = {
  1105. 'TEST-NAME': lint_name,
  1106. 'SCRIPT-REL-PATH': 'custom_lint',
  1107. 'TESTED-PROJECT-NAME': unit.name(),
  1108. 'SOURCE-FOLDER-PATH': _common.get_norm_unit_path(unit),
  1109. 'CUSTOM-DEPENDENCIES': " ".join(deps),
  1110. 'TEST-DATA': '',
  1111. 'TEST-ENV': prepare_env(unit.get("TEST_ENV_VALUE")),
  1112. 'TEST-TIMEOUT': '',
  1113. 'SPLIT-FACTOR': '',
  1114. 'TEST_PARTITION': 'SEQUENTIAL',
  1115. 'FORK-MODE': '',
  1116. 'FORK-TEST-FILES': '',
  1117. 'SIZE': 'SMALL',
  1118. 'TAG': '',
  1119. 'USE_ARCADIA_PYTHON': unit.get('USE_ARCADIA_PYTHON') or '',
  1120. 'OLD_PYTEST': 'no',
  1121. 'PYTHON-PATHS': '',
  1122. # TODO remove FILES, see DEVTOOLS-7052
  1123. 'FILES': serialize_list(test_files),
  1124. 'TEST-FILES': serialize_list(test_files),
  1125. # Linter specific parameters
  1126. # TODO Add configs to DATA. See YMAKE-427
  1127. 'LINT-CONFIGS': serialize_list(configs),
  1128. 'LINT-NAME': lint_name,
  1129. 'LINT-FILE-PROCESSING-TIME': spec_args.get('FILE_PROCESSING_TIME', [''])[0],
  1130. 'LINT-EXTRA-PARAMS': serialize_list(spec_args.get('EXTRA_PARAMS', [])),
  1131. 'LINTER': linter,
  1132. }
  1133. data = dump_test(unit, test_record)
  1134. if data:
  1135. unit.set_property(["DART_DATA", data])