run_tests.py 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775
  1. #!/usr/bin/env python3
  2. # Copyright 2015 gRPC authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """Run tests in parallel."""
  16. from __future__ import print_function
  17. import argparse
  18. import ast
  19. import collections
  20. import glob
  21. import itertools
  22. import json
  23. import logging
  24. import multiprocessing
  25. import os
  26. import os.path
  27. import pipes
  28. import platform
  29. import random
  30. import re
  31. import socket
  32. import subprocess
  33. import sys
  34. import tempfile
  35. import time
  36. import traceback
  37. import uuid
  38. import six
  39. from six.moves import urllib
  40. import python_utils.jobset as jobset
  41. import python_utils.report_utils as report_utils
  42. import python_utils.start_port_server as start_port_server
  43. import python_utils.watch_dirs as watch_dirs
  44. try:
  45. from python_utils.upload_test_results import upload_results_to_bq
  46. except (ImportError):
  47. pass # It's ok to not import because this is only necessary to upload results to BQ.
  48. gcp_utils_dir = os.path.abspath(
  49. os.path.join(os.path.dirname(__file__), '../gcp/utils'))
  50. sys.path.append(gcp_utils_dir)
  51. _ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
  52. os.chdir(_ROOT)
  53. _FORCE_ENVIRON_FOR_WRAPPERS = {
  54. 'GRPC_VERBOSITY': 'DEBUG',
  55. }
  56. _POLLING_STRATEGIES = {
  57. 'linux': ['epollex', 'epoll1', 'poll'],
  58. 'mac': ['poll'],
  59. }
  60. def platform_string():
  61. return jobset.platform_string()
  62. _DEFAULT_TIMEOUT_SECONDS = 5 * 60
  63. _PRE_BUILD_STEP_TIMEOUT_SECONDS = 10 * 60
  64. def run_shell_command(cmd, env=None, cwd=None):
  65. try:
  66. subprocess.check_output(cmd, shell=True, env=env, cwd=cwd)
  67. except subprocess.CalledProcessError as e:
  68. logging.exception(
  69. "Error while running command '%s'. Exit status %d. Output:\n%s",
  70. e.cmd, e.returncode, e.output)
  71. raise
  72. def max_parallel_tests_for_current_platform():
  73. # Too much test parallelization has only been seen to be a problem
  74. # so far on windows.
  75. if jobset.platform_string() == 'windows':
  76. return 64
  77. return 1024
  78. def _print_debug_info_epilogue(dockerfile_dir=None):
  79. """Use to print useful info for debug/repro just before exiting."""
  80. print('')
  81. print('=== run_tests.py DEBUG INFO ===')
  82. print('command: \"%s\"' % ' '.join(sys.argv))
  83. if dockerfile_dir:
  84. print('dockerfile: %s' % dockerfile_dir)
  85. kokoro_job_name = os.getenv('KOKORO_JOB_NAME')
  86. if kokoro_job_name:
  87. print('kokoro job name: %s' % kokoro_job_name)
  88. print('===============================')
  89. # SimpleConfig: just compile with CONFIG=config, and run the binary to test
  90. class Config(object):
  91. def __init__(self,
  92. config,
  93. environ=None,
  94. timeout_multiplier=1,
  95. tool_prefix=[],
  96. iomgr_platform='native'):
  97. if environ is None:
  98. environ = {}
  99. self.build_config = config
  100. self.environ = environ
  101. self.environ['CONFIG'] = config
  102. self.tool_prefix = tool_prefix
  103. self.timeout_multiplier = timeout_multiplier
  104. self.iomgr_platform = iomgr_platform
  105. def job_spec(self,
  106. cmdline,
  107. timeout_seconds=_DEFAULT_TIMEOUT_SECONDS,
  108. shortname=None,
  109. environ={},
  110. cpu_cost=1.0,
  111. flaky=False):
  112. """Construct a jobset.JobSpec for a test under this config
  113. Args:
  114. cmdline: a list of strings specifying the command line the test
  115. would like to run
  116. """
  117. actual_environ = self.environ.copy()
  118. for k, v in environ.items():
  119. actual_environ[k] = v
  120. if not flaky and shortname and shortname in flaky_tests:
  121. flaky = True
  122. if shortname in shortname_to_cpu:
  123. cpu_cost = shortname_to_cpu[shortname]
  124. return jobset.JobSpec(
  125. cmdline=self.tool_prefix + cmdline,
  126. shortname=shortname,
  127. environ=actual_environ,
  128. cpu_cost=cpu_cost,
  129. timeout_seconds=(self.timeout_multiplier *
  130. timeout_seconds if timeout_seconds else None),
  131. flake_retries=4 if flaky or args.allow_flakes else 0,
  132. timeout_retries=1 if flaky or args.allow_flakes else 0)
  133. def get_c_tests(travis, test_lang):
  134. out = []
  135. platforms_str = 'ci_platforms' if travis else 'platforms'
  136. with open('tools/run_tests/generated/tests.json') as f:
  137. js = json.load(f)
  138. return [
  139. tgt for tgt in js
  140. if tgt['language'] == test_lang and platform_string() in
  141. tgt[platforms_str] and not (travis and tgt['flaky'])
  142. ]
  143. def _check_compiler(compiler, supported_compilers):
  144. if compiler not in supported_compilers:
  145. raise Exception('Compiler %s not supported (on this platform).' %
  146. compiler)
  147. def _check_arch(arch, supported_archs):
  148. if arch not in supported_archs:
  149. raise Exception('Architecture %s not supported.' % arch)
  150. def _is_use_docker_child():
  151. """Returns True if running running as a --use_docker child."""
  152. return True if os.getenv('DOCKER_RUN_SCRIPT_COMMAND') else False
  153. _PythonConfigVars = collections.namedtuple('_ConfigVars', [
  154. 'shell',
  155. 'builder',
  156. 'builder_prefix_arguments',
  157. 'venv_relative_python',
  158. 'toolchain',
  159. 'runner',
  160. ])
  161. def _python_config_generator(name, major, minor, bits, config_vars):
  162. build = (config_vars.shell + config_vars.builder +
  163. config_vars.builder_prefix_arguments +
  164. [_python_pattern_function(major=major, minor=minor, bits=bits)] +
  165. [name] + config_vars.venv_relative_python + config_vars.toolchain)
  166. run = (config_vars.shell + config_vars.runner + [
  167. os.path.join(name, config_vars.venv_relative_python[0]),
  168. ])
  169. return PythonConfig(name, build, run)
  170. def _pypy_config_generator(name, major, config_vars):
  171. return PythonConfig(
  172. name, config_vars.shell + config_vars.builder +
  173. config_vars.builder_prefix_arguments +
  174. [_pypy_pattern_function(major=major)] + [name] +
  175. config_vars.venv_relative_python + config_vars.toolchain,
  176. config_vars.shell + config_vars.runner +
  177. [os.path.join(name, config_vars.venv_relative_python[0])])
  178. def _python_pattern_function(major, minor, bits):
  179. # Bit-ness is handled by the test machine's environment
  180. if os.name == "nt":
  181. if bits == "64":
  182. return '/c/Python{major}{minor}/python.exe'.format(major=major,
  183. minor=minor,
  184. bits=bits)
  185. else:
  186. return '/c/Python{major}{minor}_{bits}bits/python.exe'.format(
  187. major=major, minor=minor, bits=bits)
  188. else:
  189. return 'python{major}.{minor}'.format(major=major, minor=minor)
  190. def _pypy_pattern_function(major):
  191. if major == '2':
  192. return 'pypy'
  193. elif major == '3':
  194. return 'pypy3'
  195. else:
  196. raise ValueError("Unknown PyPy major version")
  197. class CLanguage(object):
  198. def __init__(self, lang_suffix, test_lang):
  199. self.lang_suffix = lang_suffix
  200. self.platform = platform_string()
  201. self.test_lang = test_lang
  202. def configure(self, config, args):
  203. self.config = config
  204. self.args = args
  205. if self.platform == 'windows':
  206. _check_compiler(self.args.compiler, [
  207. 'default',
  208. 'cmake',
  209. 'cmake_ninja_vs2015',
  210. 'cmake_ninja_vs2017',
  211. 'cmake_vs2015',
  212. 'cmake_vs2017',
  213. 'cmake_vs2019',
  214. ])
  215. _check_arch(self.args.arch, ['default', 'x64', 'x86'])
  216. activate_vs_tools = ''
  217. if self.args.compiler == 'cmake_ninja_vs2015' or self.args.compiler == 'cmake' or self.args.compiler == 'default':
  218. # cmake + ninja build is the default because it is faster and supports boringssl assembly optimizations
  219. # the compiler used is exactly the same as for cmake_vs2015
  220. cmake_generator = 'Ninja'
  221. activate_vs_tools = '2015'
  222. elif self.args.compiler == 'cmake_ninja_vs2017':
  223. cmake_generator = 'Ninja'
  224. activate_vs_tools = '2017'
  225. elif self.args.compiler == 'cmake_vs2015':
  226. cmake_generator = 'Visual Studio 14 2015'
  227. elif self.args.compiler == 'cmake_vs2017':
  228. cmake_generator = 'Visual Studio 15 2017'
  229. elif self.args.compiler == 'cmake_vs2019':
  230. cmake_generator = 'Visual Studio 16 2019'
  231. else:
  232. print('should never reach here.')
  233. sys.exit(1)
  234. self._cmake_configure_extra_args = []
  235. self._cmake_generator_windows = cmake_generator
  236. # required to pass as cmake "-A" configuration for VS builds (but not for Ninja)
  237. self._cmake_architecture_windows = 'x64' if self.args.arch == 'x64' else 'Win32'
  238. # when builing with Ninja, the VS common tools need to be activated first
  239. self._activate_vs_tools_windows = activate_vs_tools
  240. self._vs_tools_architecture_windows = 'x64' if self.args.arch == 'x64' else 'x86'
  241. else:
  242. if self.platform == 'linux':
  243. # Allow all the known architectures. _check_arch_option has already checked that we're not doing
  244. # something illegal when not running under docker.
  245. _check_arch(self.args.arch, ['default', 'x64', 'x86'])
  246. else:
  247. _check_arch(self.args.arch, ['default'])
  248. self._docker_distro, self._cmake_configure_extra_args = self._compiler_options(
  249. self.args.use_docker, self.args.compiler)
  250. if self.args.arch == 'x86':
  251. # disable boringssl asm optimizations when on x86
  252. # see https://github.com/grpc/grpc/blob/b5b8578b3f8b4a9ce61ed6677e19d546e43c5c68/tools/run_tests/artifacts/artifact_targets.py#L253
  253. self._cmake_configure_extra_args.append('-DOPENSSL_NO_ASM=ON')
  254. def test_specs(self):
  255. out = []
  256. binaries = get_c_tests(self.args.travis, self.test_lang)
  257. for target in binaries:
  258. if target.get('boringssl', False):
  259. # cmake doesn't build boringssl tests
  260. continue
  261. auto_timeout_scaling = target.get('auto_timeout_scaling', True)
  262. polling_strategies = (_POLLING_STRATEGIES.get(
  263. self.platform, ['all']) if target.get('uses_polling', True) else
  264. ['none'])
  265. for polling_strategy in polling_strategies:
  266. env = {
  267. 'GRPC_DEFAULT_SSL_ROOTS_FILE_PATH':
  268. _ROOT + '/src/core/tsi/test_creds/ca.pem',
  269. 'GRPC_POLL_STRATEGY':
  270. polling_strategy,
  271. 'GRPC_VERBOSITY':
  272. 'DEBUG'
  273. }
  274. resolver = os.environ.get('GRPC_DNS_RESOLVER', None)
  275. if resolver:
  276. env['GRPC_DNS_RESOLVER'] = resolver
  277. shortname_ext = '' if polling_strategy == 'all' else ' GRPC_POLL_STRATEGY=%s' % polling_strategy
  278. if polling_strategy in target.get('excluded_poll_engines', []):
  279. continue
  280. timeout_scaling = 1
  281. if auto_timeout_scaling:
  282. config = self.args.config
  283. if ('asan' in config or config == 'msan' or
  284. config == 'tsan' or config == 'ubsan' or
  285. config == 'helgrind' or config == 'memcheck'):
  286. # Scale overall test timeout if running under various sanitizers.
  287. # scaling value is based on historical data analysis
  288. timeout_scaling *= 3
  289. if self.config.build_config in target['exclude_configs']:
  290. continue
  291. if self.args.iomgr_platform in target.get('exclude_iomgrs', []):
  292. continue
  293. if self.platform == 'windows':
  294. binary = 'cmake/build/%s/%s.exe' % (_MSBUILD_CONFIG[
  295. self.config.build_config], target['name'])
  296. else:
  297. binary = 'cmake/build/%s' % target['name']
  298. cpu_cost = target['cpu_cost']
  299. if cpu_cost == 'capacity':
  300. cpu_cost = multiprocessing.cpu_count()
  301. if os.path.isfile(binary):
  302. list_test_command = None
  303. filter_test_command = None
  304. # these are the flag defined by gtest and benchmark framework to list
  305. # and filter test runs. We use them to split each individual test
  306. # into its own JobSpec, and thus into its own process.
  307. if 'benchmark' in target and target['benchmark']:
  308. with open(os.devnull, 'w') as fnull:
  309. tests = subprocess.check_output(
  310. [binary, '--benchmark_list_tests'],
  311. stderr=fnull)
  312. for line in tests.decode().split('\n'):
  313. test = line.strip()
  314. if not test:
  315. continue
  316. cmdline = [binary,
  317. '--benchmark_filter=%s$' % test
  318. ] + target['args']
  319. out.append(
  320. self.config.job_spec(
  321. cmdline,
  322. shortname='%s %s' %
  323. (' '.join(cmdline), shortname_ext),
  324. cpu_cost=cpu_cost,
  325. timeout_seconds=target.get(
  326. 'timeout_seconds',
  327. _DEFAULT_TIMEOUT_SECONDS) *
  328. timeout_scaling,
  329. environ=env))
  330. elif 'gtest' in target and target['gtest']:
  331. # here we parse the output of --gtest_list_tests to build up a complete
  332. # list of the tests contained in a binary for each test, we then
  333. # add a job to run, filtering for just that test.
  334. with open(os.devnull, 'w') as fnull:
  335. tests = subprocess.check_output(
  336. [binary, '--gtest_list_tests'], stderr=fnull)
  337. base = None
  338. for line in tests.decode().split('\n'):
  339. i = line.find('#')
  340. if i >= 0:
  341. line = line[:i]
  342. if not line:
  343. continue
  344. if line[0] != ' ':
  345. base = line.strip()
  346. else:
  347. assert base is not None
  348. assert line[1] == ' '
  349. test = base + line.strip()
  350. cmdline = [binary,
  351. '--gtest_filter=%s' % test
  352. ] + target['args']
  353. out.append(
  354. self.config.job_spec(
  355. cmdline,
  356. shortname='%s %s' %
  357. (' '.join(cmdline), shortname_ext),
  358. cpu_cost=cpu_cost,
  359. timeout_seconds=target.get(
  360. 'timeout_seconds',
  361. _DEFAULT_TIMEOUT_SECONDS) *
  362. timeout_scaling,
  363. environ=env))
  364. else:
  365. cmdline = [binary] + target['args']
  366. shortname = target.get(
  367. 'shortname',
  368. ' '.join(pipes.quote(arg) for arg in cmdline))
  369. shortname += shortname_ext
  370. out.append(
  371. self.config.job_spec(
  372. cmdline,
  373. shortname=shortname,
  374. cpu_cost=cpu_cost,
  375. flaky=target.get('flaky', False),
  376. timeout_seconds=target.get(
  377. 'timeout_seconds',
  378. _DEFAULT_TIMEOUT_SECONDS) * timeout_scaling,
  379. environ=env))
  380. elif self.args.regex == '.*' or self.platform == 'windows':
  381. print('\nWARNING: binary not found, skipping', binary)
  382. return sorted(out)
  383. def pre_build_steps(self):
  384. return []
  385. def build_steps(self):
  386. if self.platform == 'windows':
  387. return [[
  388. 'tools\\run_tests\\helper_scripts\\build_cxx.bat',
  389. '-DgRPC_BUILD_MSVC_MP_COUNT=%d' % self.args.jobs
  390. ] + self._cmake_configure_extra_args]
  391. else:
  392. return [['tools/run_tests/helper_scripts/build_cxx.sh'] +
  393. self._cmake_configure_extra_args]
  394. def build_steps_environ(self):
  395. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  396. environ = {'GRPC_RUN_TESTS_CXX_LANGUAGE_SUFFIX': self.lang_suffix}
  397. if self.platform == 'windows':
  398. environ['GRPC_CMAKE_GENERATOR'] = self._cmake_generator_windows
  399. environ[
  400. 'GRPC_CMAKE_ARCHITECTURE'] = self._cmake_architecture_windows
  401. environ[
  402. 'GRPC_BUILD_ACTIVATE_VS_TOOLS'] = self._activate_vs_tools_windows
  403. environ[
  404. 'GRPC_BUILD_VS_TOOLS_ARCHITECTURE'] = self._vs_tools_architecture_windows
  405. return environ
  406. def post_tests_steps(self):
  407. if self.platform == 'windows':
  408. return []
  409. else:
  410. return [['tools/run_tests/helper_scripts/post_tests_c.sh']]
  411. def _clang_cmake_configure_extra_args(self, version_suffix=''):
  412. return [
  413. '-DCMAKE_C_COMPILER=clang%s' % version_suffix,
  414. '-DCMAKE_CXX_COMPILER=clang++%s' % version_suffix,
  415. ]
  416. def _compiler_options(self, use_docker, compiler):
  417. """Returns docker distro and cmake configure args to use for given compiler."""
  418. if not use_docker and not _is_use_docker_child():
  419. # if not running under docker, we cannot ensure the right compiler version will be used,
  420. # so we only allow the non-specific choices.
  421. _check_compiler(compiler, ['default', 'cmake'])
  422. if compiler == 'default' or compiler == 'cmake':
  423. return ('debian11', [])
  424. elif compiler == 'gcc5':
  425. return ('gcc_5', [])
  426. elif compiler == 'gcc10.2':
  427. return ('debian11', [])
  428. elif compiler == 'gcc10.2_openssl102':
  429. return ('debian11_openssl102', [
  430. "-DgRPC_SSL_PROVIDER=package",
  431. ])
  432. elif compiler == 'gcc11':
  433. return ('gcc_11', [])
  434. elif compiler == 'gcc_musl':
  435. return ('alpine', [])
  436. elif compiler == 'clang4':
  437. return ('clang_4', self._clang_cmake_configure_extra_args())
  438. elif compiler == 'clang13':
  439. return ('clang_13', self._clang_cmake_configure_extra_args())
  440. else:
  441. raise Exception('Compiler %s not supported.' % compiler)
  442. def dockerfile_dir(self):
  443. return 'tools/dockerfile/test/cxx_%s_%s' % (
  444. self._docker_distro, _docker_arch_suffix(self.args.arch))
  445. def __str__(self):
  446. return self.lang_suffix
  447. # This tests Node on grpc/grpc-node and will become the standard for Node testing
  448. class RemoteNodeLanguage(object):
  449. def __init__(self):
  450. self.platform = platform_string()
  451. def configure(self, config, args):
  452. self.config = config
  453. self.args = args
  454. # Note: electron ABI only depends on major and minor version, so that's all
  455. # we should specify in the compiler argument
  456. _check_compiler(self.args.compiler, [
  457. 'default', 'node0.12', 'node4', 'node5', 'node6', 'node7', 'node8',
  458. 'electron1.3', 'electron1.6'
  459. ])
  460. if self.args.compiler == 'default':
  461. self.runtime = 'node'
  462. self.node_version = '8'
  463. else:
  464. if self.args.compiler.startswith('electron'):
  465. self.runtime = 'electron'
  466. self.node_version = self.args.compiler[8:]
  467. else:
  468. self.runtime = 'node'
  469. # Take off the word "node"
  470. self.node_version = self.args.compiler[4:]
  471. # TODO: update with Windows/electron scripts when available for grpc/grpc-node
  472. def test_specs(self):
  473. if self.platform == 'windows':
  474. return [
  475. self.config.job_spec(
  476. ['tools\\run_tests\\helper_scripts\\run_node.bat'])
  477. ]
  478. else:
  479. return [
  480. self.config.job_spec(
  481. ['tools/run_tests/helper_scripts/run_grpc-node.sh'],
  482. None,
  483. environ=_FORCE_ENVIRON_FOR_WRAPPERS)
  484. ]
  485. def pre_build_steps(self):
  486. return []
  487. def build_steps(self):
  488. return []
  489. def build_steps_environ(self):
  490. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  491. return {}
  492. def post_tests_steps(self):
  493. return []
  494. def dockerfile_dir(self):
  495. return 'tools/dockerfile/test/node_jessie_%s' % _docker_arch_suffix(
  496. self.args.arch)
  497. def __str__(self):
  498. return 'grpc-node'
  499. class Php7Language(object):
  500. def configure(self, config, args):
  501. self.config = config
  502. self.args = args
  503. _check_compiler(self.args.compiler, ['default'])
  504. def test_specs(self):
  505. return [
  506. self.config.job_spec(['src/php/bin/run_tests.sh'],
  507. environ=_FORCE_ENVIRON_FOR_WRAPPERS)
  508. ]
  509. def pre_build_steps(self):
  510. return []
  511. def build_steps(self):
  512. return [['tools/run_tests/helper_scripts/build_php.sh']]
  513. def build_steps_environ(self):
  514. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  515. return {}
  516. def post_tests_steps(self):
  517. return [['tools/run_tests/helper_scripts/post_tests_php.sh']]
  518. def dockerfile_dir(self):
  519. return 'tools/dockerfile/test/php7_debian11_%s' % _docker_arch_suffix(
  520. self.args.arch)
  521. def __str__(self):
  522. return 'php7'
  523. class PythonConfig(
  524. collections.namedtuple('PythonConfig', ['name', 'build', 'run'])):
  525. """Tuple of commands (named s.t. 'what it says on the tin' applies)"""
  526. class PythonLanguage(object):
  527. _TEST_SPECS_FILE = {
  528. 'native': ['src/python/grpcio_tests/tests/tests.json'],
  529. 'gevent': [
  530. 'src/python/grpcio_tests/tests/tests.json',
  531. 'src/python/grpcio_tests/tests_gevent/tests.json',
  532. ],
  533. 'asyncio': ['src/python/grpcio_tests/tests_aio/tests.json'],
  534. }
  535. _TEST_COMMAND = {
  536. 'native': 'test_lite',
  537. 'gevent': 'test_gevent',
  538. 'asyncio': 'test_aio',
  539. }
  540. def configure(self, config, args):
  541. self.config = config
  542. self.args = args
  543. self.pythons = self._get_pythons(self.args)
  544. def test_specs(self):
  545. # load list of known test suites
  546. jobs = []
  547. for io_platform in self._TEST_SPECS_FILE:
  548. test_cases = []
  549. for tests_json_file_name in self._TEST_SPECS_FILE[io_platform]:
  550. with open(tests_json_file_name) as tests_json_file:
  551. test_cases.extend(json.load(tests_json_file))
  552. environment = dict(_FORCE_ENVIRON_FOR_WRAPPERS)
  553. # TODO(https://github.com/grpc/grpc/issues/21401) Fork handlers is not
  554. # designed for non-native IO manager. It has a side-effect that
  555. # overrides threading settings in C-Core.
  556. if io_platform != 'native':
  557. environment['GRPC_ENABLE_FORK_SUPPORT'] = '0'
  558. for python_config in self.pythons:
  559. # TODO(https://github.com/grpc/grpc/issues/23784) allow gevent
  560. # to run on later version once issue solved.
  561. if io_platform == 'gevent' and python_config.name != 'py36':
  562. continue
  563. jobs.extend([
  564. self.config.job_spec(
  565. python_config.run + [self._TEST_COMMAND[io_platform]],
  566. timeout_seconds=8 * 60,
  567. environ=dict(
  568. GRPC_PYTHON_TESTRUNNER_FILTER=str(test_case),
  569. **environment),
  570. shortname='%s.%s.%s' %
  571. (python_config.name, io_platform, test_case),
  572. ) for test_case in test_cases
  573. ])
  574. return jobs
  575. def pre_build_steps(self):
  576. return []
  577. def build_steps(self):
  578. return [config.build for config in self.pythons]
  579. def build_steps_environ(self):
  580. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  581. return {}
  582. def post_tests_steps(self):
  583. if self.config.build_config != 'gcov':
  584. return []
  585. else:
  586. return [['tools/run_tests/helper_scripts/post_tests_python.sh']]
  587. def dockerfile_dir(self):
  588. return 'tools/dockerfile/test/python_%s_%s' % (
  589. self._python_docker_distro_name(),
  590. _docker_arch_suffix(self.args.arch))
  591. def _python_docker_distro_name(self):
  592. """Choose the docker image to use based on python version."""
  593. if self.args.compiler == 'python_alpine':
  594. return 'alpine'
  595. else:
  596. return 'debian11_default'
  597. def _get_pythons(self, args):
  598. """Get python runtimes to test with, based on current platform, architecture, compiler etc."""
  599. if args.iomgr_platform != 'native':
  600. raise ValueError(
  601. 'Python builds no longer differentiate IO Manager platforms, please use "native"'
  602. )
  603. if args.arch == 'x86':
  604. bits = '32'
  605. else:
  606. bits = '64'
  607. if os.name == 'nt':
  608. shell = ['bash']
  609. builder = [
  610. os.path.abspath(
  611. 'tools/run_tests/helper_scripts/build_python_msys2.sh')
  612. ]
  613. builder_prefix_arguments = ['MINGW{}'.format(bits)]
  614. venv_relative_python = ['Scripts/python.exe']
  615. toolchain = ['mingw32']
  616. else:
  617. shell = []
  618. builder = [
  619. os.path.abspath(
  620. 'tools/run_tests/helper_scripts/build_python.sh')
  621. ]
  622. builder_prefix_arguments = []
  623. venv_relative_python = ['bin/python']
  624. toolchain = ['unix']
  625. runner = [
  626. os.path.abspath('tools/run_tests/helper_scripts/run_python.sh')
  627. ]
  628. config_vars = _PythonConfigVars(shell, builder,
  629. builder_prefix_arguments,
  630. venv_relative_python, toolchain, runner)
  631. python36_config = _python_config_generator(name='py36',
  632. major='3',
  633. minor='6',
  634. bits=bits,
  635. config_vars=config_vars)
  636. python37_config = _python_config_generator(name='py37',
  637. major='3',
  638. minor='7',
  639. bits=bits,
  640. config_vars=config_vars)
  641. python38_config = _python_config_generator(name='py38',
  642. major='3',
  643. minor='8',
  644. bits=bits,
  645. config_vars=config_vars)
  646. python39_config = _python_config_generator(name='py39',
  647. major='3',
  648. minor='9',
  649. bits=bits,
  650. config_vars=config_vars)
  651. python310_config = _python_config_generator(name='py310',
  652. major='3',
  653. minor='10',
  654. bits=bits,
  655. config_vars=config_vars)
  656. pypy27_config = _pypy_config_generator(name='pypy',
  657. major='2',
  658. config_vars=config_vars)
  659. pypy32_config = _pypy_config_generator(name='pypy3',
  660. major='3',
  661. config_vars=config_vars)
  662. if args.compiler == 'default':
  663. if os.name == 'nt':
  664. return (python38_config,)
  665. elif os.uname()[0] == 'Darwin':
  666. # NOTE(rbellevi): Testing takes significantly longer on
  667. # MacOS, so we restrict the number of interpreter versions
  668. # tested.
  669. return (python38_config,)
  670. else:
  671. return (
  672. python36_config,
  673. python38_config,
  674. )
  675. elif args.compiler == 'python3.6':
  676. return (python36_config,)
  677. elif args.compiler == 'python3.7':
  678. return (python37_config,)
  679. elif args.compiler == 'python3.8':
  680. return (python38_config,)
  681. elif args.compiler == 'python3.9':
  682. return (python39_config,)
  683. elif args.compiler == 'python3.10':
  684. return (python310_config,)
  685. elif args.compiler == 'pypy':
  686. return (pypy27_config,)
  687. elif args.compiler == 'pypy3':
  688. return (pypy32_config,)
  689. elif args.compiler == 'python_alpine':
  690. return (python38_config,)
  691. elif args.compiler == 'all_the_cpythons':
  692. return (
  693. python36_config,
  694. python37_config,
  695. python38_config,
  696. python39_config,
  697. python310_config,
  698. )
  699. else:
  700. raise Exception('Compiler %s not supported.' % args.compiler)
  701. def __str__(self):
  702. return 'python'
  703. class RubyLanguage(object):
  704. def configure(self, config, args):
  705. self.config = config
  706. self.args = args
  707. _check_compiler(self.args.compiler, ['default'])
  708. def test_specs(self):
  709. tests = [
  710. self.config.job_spec(['tools/run_tests/helper_scripts/run_ruby.sh'],
  711. timeout_seconds=10 * 60,
  712. environ=_FORCE_ENVIRON_FOR_WRAPPERS)
  713. ]
  714. for test in [
  715. 'src/ruby/end2end/sig_handling_test.rb',
  716. 'src/ruby/end2end/channel_state_test.rb',
  717. 'src/ruby/end2end/channel_closing_test.rb',
  718. 'src/ruby/end2end/sig_int_during_channel_watch_test.rb',
  719. 'src/ruby/end2end/killed_client_thread_test.rb',
  720. 'src/ruby/end2end/forking_client_test.rb',
  721. 'src/ruby/end2end/grpc_class_init_test.rb',
  722. 'src/ruby/end2end/multiple_killed_watching_threads_test.rb',
  723. 'src/ruby/end2end/load_grpc_with_gc_stress_test.rb',
  724. 'src/ruby/end2end/client_memory_usage_test.rb',
  725. 'src/ruby/end2end/package_with_underscore_test.rb',
  726. 'src/ruby/end2end/graceful_sig_handling_test.rb',
  727. 'src/ruby/end2end/graceful_sig_stop_test.rb',
  728. 'src/ruby/end2end/errors_load_before_grpc_lib_test.rb',
  729. 'src/ruby/end2end/logger_load_before_grpc_lib_test.rb',
  730. 'src/ruby/end2end/status_codes_load_before_grpc_lib_test.rb',
  731. 'src/ruby/end2end/call_credentials_timeout_test.rb',
  732. 'src/ruby/end2end/call_credentials_returning_bad_metadata_doesnt_kill_background_thread_test.rb'
  733. ]:
  734. tests.append(
  735. self.config.job_spec(['ruby', test],
  736. shortname=test,
  737. timeout_seconds=20 * 60,
  738. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  739. return tests
  740. def pre_build_steps(self):
  741. return [['tools/run_tests/helper_scripts/pre_build_ruby.sh']]
  742. def build_steps(self):
  743. return [['tools/run_tests/helper_scripts/build_ruby.sh']]
  744. def build_steps_environ(self):
  745. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  746. return {}
  747. def post_tests_steps(self):
  748. return [['tools/run_tests/helper_scripts/post_tests_ruby.sh']]
  749. def dockerfile_dir(self):
  750. return 'tools/dockerfile/test/ruby_debian11_%s' % _docker_arch_suffix(
  751. self.args.arch)
  752. def __str__(self):
  753. return 'ruby'
  754. class CSharpLanguage(object):
  755. def __init__(self):
  756. self.platform = platform_string()
  757. def configure(self, config, args):
  758. self.config = config
  759. self.args = args
  760. _check_compiler(self.args.compiler, ['default', 'coreclr', 'mono'])
  761. if self.args.compiler == 'default':
  762. # test both runtimes by default
  763. self.test_runtimes = ['coreclr', 'mono']
  764. else:
  765. # only test the specified runtime
  766. self.test_runtimes = [self.args.compiler]
  767. if self.platform == 'windows':
  768. _check_arch(self.args.arch, ['default'])
  769. self._cmake_arch_option = 'x64'
  770. else:
  771. self._docker_distro = 'debian11'
  772. def test_specs(self):
  773. with open('src/csharp/tests.json') as f:
  774. tests_by_assembly = json.load(f)
  775. msbuild_config = _MSBUILD_CONFIG[self.config.build_config]
  776. nunit_args = ['--labels=All', '--noresult', '--workers=1']
  777. specs = []
  778. for test_runtime in self.test_runtimes:
  779. if self.args.compiler == 'coreclr':
  780. assembly_extension = '.dll'
  781. assembly_subdir = 'bin/%s/netcoreapp3.1' % msbuild_config
  782. runtime_cmd = ['dotnet', 'exec']
  783. else:
  784. assembly_extension = '.exe'
  785. assembly_subdir = 'bin/%s/net45' % msbuild_config
  786. if self.platform == 'windows':
  787. runtime_cmd = []
  788. elif self.platform == 'mac':
  789. # mono before version 5.2 on MacOS defaults to 32bit runtime
  790. runtime_cmd = ['mono', '--arch=64']
  791. else:
  792. runtime_cmd = ['mono']
  793. for assembly in six.iterkeys(tests_by_assembly):
  794. assembly_file = 'src/csharp/%s/%s/%s%s' % (
  795. assembly, assembly_subdir, assembly, assembly_extension)
  796. # normally, run each test as a separate process
  797. for test in tests_by_assembly[assembly]:
  798. cmdline = runtime_cmd + [assembly_file,
  799. '--test=%s' % test] + nunit_args
  800. specs.append(
  801. self.config.job_spec(
  802. cmdline,
  803. shortname='csharp.%s.%s' % (test_runtime, test),
  804. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  805. return specs
  806. def pre_build_steps(self):
  807. if self.platform == 'windows':
  808. return [['tools\\run_tests\\helper_scripts\\pre_build_csharp.bat']]
  809. else:
  810. return [['tools/run_tests/helper_scripts/pre_build_csharp.sh']]
  811. def build_steps(self):
  812. if self.platform == 'windows':
  813. return [['tools\\run_tests\\helper_scripts\\build_csharp.bat']]
  814. else:
  815. return [['tools/run_tests/helper_scripts/build_csharp.sh']]
  816. def build_steps_environ(self):
  817. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  818. if self.platform == 'windows':
  819. return {'ARCHITECTURE': self._cmake_arch_option}
  820. else:
  821. return {}
  822. def post_tests_steps(self):
  823. if self.platform == 'windows':
  824. return [['tools\\run_tests\\helper_scripts\\post_tests_csharp.bat']]
  825. else:
  826. return [['tools/run_tests/helper_scripts/post_tests_csharp.sh']]
  827. def dockerfile_dir(self):
  828. return 'tools/dockerfile/test/csharp_%s_%s' % (
  829. self._docker_distro, _docker_arch_suffix(self.args.arch))
  830. def __str__(self):
  831. return 'csharp'
  832. class ObjCLanguage(object):
  833. def configure(self, config, args):
  834. self.config = config
  835. self.args = args
  836. _check_compiler(self.args.compiler, ['default'])
  837. def test_specs(self):
  838. out = []
  839. out.append(
  840. self.config.job_spec(
  841. ['src/objective-c/tests/build_one_example_bazel.sh'],
  842. timeout_seconds=10 * 60,
  843. shortname='ios-buildtest-example-sample',
  844. cpu_cost=1e6,
  845. environ={
  846. 'SCHEME': 'Sample',
  847. 'EXAMPLE_PATH': 'src/objective-c/examples/Sample',
  848. 'FRAMEWORKS': 'NO'
  849. }))
  850. # Currently not supporting compiling as frameworks in Bazel
  851. out.append(
  852. self.config.job_spec(
  853. ['src/objective-c/tests/build_one_example.sh'],
  854. timeout_seconds=20 * 60,
  855. shortname='ios-buildtest-example-sample-frameworks',
  856. cpu_cost=1e6,
  857. environ={
  858. 'SCHEME': 'Sample',
  859. 'EXAMPLE_PATH': 'src/objective-c/examples/Sample',
  860. 'FRAMEWORKS': 'YES'
  861. }))
  862. out.append(
  863. self.config.job_spec(
  864. ['src/objective-c/tests/build_one_example.sh'],
  865. timeout_seconds=20 * 60,
  866. shortname='ios-buildtest-example-switftsample',
  867. cpu_cost=1e6,
  868. environ={
  869. 'SCHEME': 'SwiftSample',
  870. 'EXAMPLE_PATH': 'src/objective-c/examples/SwiftSample'
  871. }))
  872. out.append(
  873. self.config.job_spec(
  874. ['src/objective-c/tests/build_one_example_bazel.sh'],
  875. timeout_seconds=10 * 60,
  876. shortname='ios-buildtest-example-tvOS-sample',
  877. cpu_cost=1e6,
  878. environ={
  879. 'SCHEME': 'tvOS-sample',
  880. 'EXAMPLE_PATH': 'src/objective-c/examples/tvOS-sample',
  881. 'FRAMEWORKS': 'NO'
  882. }))
  883. # Disabled due to #20258
  884. # TODO (mxyan): Reenable this test when #20258 is resolved.
  885. # out.append(
  886. # self.config.job_spec(
  887. # ['src/objective-c/tests/build_one_example_bazel.sh'],
  888. # timeout_seconds=20 * 60,
  889. # shortname='ios-buildtest-example-watchOS-sample',
  890. # cpu_cost=1e6,
  891. # environ={
  892. # 'SCHEME': 'watchOS-sample-WatchKit-App',
  893. # 'EXAMPLE_PATH': 'src/objective-c/examples/watchOS-sample',
  894. # 'FRAMEWORKS': 'NO'
  895. # }))
  896. out.append(
  897. self.config.job_spec(['src/objective-c/tests/run_plugin_tests.sh'],
  898. timeout_seconds=60 * 60,
  899. shortname='ios-test-plugintest',
  900. cpu_cost=1e6,
  901. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  902. out.append(
  903. self.config.job_spec(
  904. ['src/objective-c/tests/run_plugin_option_tests.sh'],
  905. timeout_seconds=60 * 60,
  906. shortname='ios-test-plugin-option-test',
  907. cpu_cost=1e6,
  908. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  909. out.append(
  910. self.config.job_spec(
  911. ['test/core/iomgr/ios/CFStreamTests/build_and_run_tests.sh'],
  912. timeout_seconds=60 * 60,
  913. shortname='ios-test-cfstream-tests',
  914. cpu_cost=1e6,
  915. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  916. out.append(
  917. self.config.job_spec(
  918. ['src/objective-c/tests/CoreTests/build_and_run_tests.sh'],
  919. timeout_seconds=60 * 60,
  920. shortname='ios-test-core-tests',
  921. cpu_cost=1e6,
  922. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  923. # TODO: replace with run_one_test_bazel.sh when Bazel-Xcode is stable
  924. out.append(
  925. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  926. timeout_seconds=60 * 60,
  927. shortname='ios-test-unittests',
  928. cpu_cost=1e6,
  929. environ={'SCHEME': 'UnitTests'}))
  930. out.append(
  931. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  932. timeout_seconds=60 * 60,
  933. shortname='ios-test-interoptests',
  934. cpu_cost=1e6,
  935. environ={'SCHEME': 'InteropTests'}))
  936. out.append(
  937. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  938. timeout_seconds=60 * 60,
  939. shortname='ios-test-cronettests',
  940. cpu_cost=1e6,
  941. environ={'SCHEME': 'CronetTests'}))
  942. out.append(
  943. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  944. timeout_seconds=30 * 60,
  945. shortname='ios-perf-test',
  946. cpu_cost=1e6,
  947. environ={'SCHEME': 'PerfTests'}))
  948. out.append(
  949. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  950. timeout_seconds=30 * 60,
  951. shortname='ios-perf-test-posix',
  952. cpu_cost=1e6,
  953. environ={'SCHEME': 'PerfTestsPosix'}))
  954. out.append(
  955. self.config.job_spec(['test/cpp/ios/build_and_run_tests.sh'],
  956. timeout_seconds=60 * 60,
  957. shortname='ios-cpp-test-cronet',
  958. cpu_cost=1e6,
  959. environ=_FORCE_ENVIRON_FOR_WRAPPERS))
  960. out.append(
  961. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  962. timeout_seconds=60 * 60,
  963. shortname='mac-test-basictests',
  964. cpu_cost=1e6,
  965. environ={
  966. 'SCHEME': 'MacTests',
  967. 'PLATFORM': 'macos'
  968. }))
  969. out.append(
  970. self.config.job_spec(['src/objective-c/tests/run_one_test.sh'],
  971. timeout_seconds=30 * 60,
  972. shortname='tvos-test-basictests',
  973. cpu_cost=1e6,
  974. environ={
  975. 'SCHEME': 'TvTests',
  976. 'PLATFORM': 'tvos'
  977. }))
  978. return sorted(out)
  979. def pre_build_steps(self):
  980. return []
  981. def build_steps(self):
  982. return []
  983. def build_steps_environ(self):
  984. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  985. return {}
  986. def post_tests_steps(self):
  987. return []
  988. def dockerfile_dir(self):
  989. return None
  990. def __str__(self):
  991. return 'objc'
  992. class Sanity(object):
  993. def configure(self, config, args):
  994. self.config = config
  995. self.args = args
  996. _check_compiler(self.args.compiler, ['default'])
  997. def test_specs(self):
  998. import yaml
  999. with open('tools/run_tests/sanity/sanity_tests.yaml', 'r') as f:
  1000. environ = {'TEST': 'true'}
  1001. if _is_use_docker_child():
  1002. environ['CLANG_FORMAT_SKIP_DOCKER'] = 'true'
  1003. environ['CLANG_TIDY_SKIP_DOCKER'] = 'true'
  1004. # sanity tests run tools/bazel wrapper concurrently
  1005. # and that can result in a download/run race in the wrapper.
  1006. # under docker we already have the right version of bazel
  1007. # so we can just disable the wrapper.
  1008. environ['DISABLE_BAZEL_WRAPPER'] = 'true'
  1009. return [
  1010. self.config.job_spec(cmd['script'].split(),
  1011. timeout_seconds=30 * 60,
  1012. environ=environ,
  1013. cpu_cost=cmd.get('cpu_cost', 1))
  1014. for cmd in yaml.load(f)
  1015. ]
  1016. def pre_build_steps(self):
  1017. return []
  1018. def build_steps(self):
  1019. return []
  1020. def build_steps_environ(self):
  1021. """Extra environment variables set for pre_build_steps and build_steps jobs."""
  1022. return {}
  1023. def post_tests_steps(self):
  1024. return []
  1025. def dockerfile_dir(self):
  1026. return 'tools/dockerfile/test/sanity'
  1027. def __str__(self):
  1028. return 'sanity'
  1029. # different configurations we can run under
  1030. with open('tools/run_tests/generated/configs.json') as f:
  1031. _CONFIGS = dict(
  1032. (cfg['config'], Config(**cfg)) for cfg in ast.literal_eval(f.read()))
  1033. _LANGUAGES = {
  1034. 'c++': CLanguage('cxx', 'c++'),
  1035. 'c': CLanguage('c', 'c'),
  1036. 'grpc-node': RemoteNodeLanguage(),
  1037. 'php7': Php7Language(),
  1038. 'python': PythonLanguage(),
  1039. 'ruby': RubyLanguage(),
  1040. 'csharp': CSharpLanguage(),
  1041. 'objc': ObjCLanguage(),
  1042. 'sanity': Sanity()
  1043. }
  1044. _MSBUILD_CONFIG = {
  1045. 'dbg': 'Debug',
  1046. 'opt': 'Release',
  1047. 'gcov': 'Debug',
  1048. }
  1049. def _build_step_environ(cfg, extra_env={}):
  1050. """Environment variables set for each build step."""
  1051. environ = {'CONFIG': cfg, 'GRPC_RUN_TESTS_JOBS': str(args.jobs)}
  1052. msbuild_cfg = _MSBUILD_CONFIG.get(cfg)
  1053. if msbuild_cfg:
  1054. environ['MSBUILD_CONFIG'] = msbuild_cfg
  1055. environ.update(extra_env)
  1056. return environ
  1057. def _windows_arch_option(arch):
  1058. """Returns msbuild cmdline option for selected architecture."""
  1059. if arch == 'default' or arch == 'x86':
  1060. return '/p:Platform=Win32'
  1061. elif arch == 'x64':
  1062. return '/p:Platform=x64'
  1063. else:
  1064. print('Architecture %s not supported.' % arch)
  1065. sys.exit(1)
  1066. def _check_arch_option(arch):
  1067. """Checks that architecture option is valid."""
  1068. if platform_string() == 'windows':
  1069. _windows_arch_option(arch)
  1070. elif platform_string() == 'linux':
  1071. # On linux, we need to be running under docker with the right architecture.
  1072. runtime_arch = platform.architecture()[0]
  1073. if arch == 'default':
  1074. return
  1075. elif runtime_arch == '64bit' and arch == 'x64':
  1076. return
  1077. elif runtime_arch == '32bit' and arch == 'x86':
  1078. return
  1079. else:
  1080. print(
  1081. 'Architecture %s does not match current runtime architecture.' %
  1082. arch)
  1083. sys.exit(1)
  1084. else:
  1085. if args.arch != 'default':
  1086. print('Architecture %s not supported on current platform.' %
  1087. args.arch)
  1088. sys.exit(1)
  1089. def _docker_arch_suffix(arch):
  1090. """Returns suffix to dockerfile dir to use."""
  1091. if arch == 'default' or arch == 'x64':
  1092. return 'x64'
  1093. elif arch == 'x86':
  1094. return 'x86'
  1095. else:
  1096. print('Architecture %s not supported with current settings.' % arch)
  1097. sys.exit(1)
  1098. def runs_per_test_type(arg_str):
  1099. """Auxiliary function to parse the "runs_per_test" flag.
  1100. Returns:
  1101. A positive integer or 0, the latter indicating an infinite number of
  1102. runs.
  1103. Raises:
  1104. argparse.ArgumentTypeError: Upon invalid input.
  1105. """
  1106. if arg_str == 'inf':
  1107. return 0
  1108. try:
  1109. n = int(arg_str)
  1110. if n <= 0:
  1111. raise ValueError
  1112. return n
  1113. except:
  1114. msg = '\'{}\' is not a positive integer or \'inf\''.format(arg_str)
  1115. raise argparse.ArgumentTypeError(msg)
  1116. def percent_type(arg_str):
  1117. pct = float(arg_str)
  1118. if pct > 100 or pct < 0:
  1119. raise argparse.ArgumentTypeError(
  1120. "'%f' is not a valid percentage in the [0, 100] range" % pct)
  1121. return pct
  1122. # This is math.isclose in python >= 3.5
  1123. def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
  1124. return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
  1125. def _shut_down_legacy_server(legacy_server_port):
  1126. """Shut down legacy version of port server."""
  1127. try:
  1128. version = int(
  1129. urllib.request.urlopen('http://localhost:%d/version_number' %
  1130. legacy_server_port,
  1131. timeout=10).read())
  1132. except:
  1133. pass
  1134. else:
  1135. urllib.request.urlopen('http://localhost:%d/quitquitquit' %
  1136. legacy_server_port).read()
  1137. def _calculate_num_runs_failures(list_of_results):
  1138. """Calculate number of runs and failures for a particular test.
  1139. Args:
  1140. list_of_results: (List) of JobResult object.
  1141. Returns:
  1142. A tuple of total number of runs and failures.
  1143. """
  1144. num_runs = len(list_of_results) # By default, there is 1 run per JobResult.
  1145. num_failures = 0
  1146. for jobresult in list_of_results:
  1147. if jobresult.retries > 0:
  1148. num_runs += jobresult.retries
  1149. if jobresult.num_failures > 0:
  1150. num_failures += jobresult.num_failures
  1151. return num_runs, num_failures
  1152. def _has_epollexclusive():
  1153. binary = 'cmake/build/check_epollexclusive'
  1154. if not os.path.exists(binary):
  1155. return False
  1156. try:
  1157. subprocess.check_call(binary)
  1158. return True
  1159. except subprocess.CalledProcessError as e:
  1160. return False
  1161. except OSError as e:
  1162. # For languages other than C and Windows the binary won't exist
  1163. return False
  1164. class BuildAndRunError(object):
  1165. """Represents error type in _build_and_run."""
  1166. BUILD = object()
  1167. TEST = object()
  1168. POST_TEST = object()
  1169. # returns a list of things that failed (or an empty list on success)
  1170. def _build_and_run(check_cancelled,
  1171. newline_on_success,
  1172. xml_report=None,
  1173. build_only=False):
  1174. """Do one pass of building & running tests."""
  1175. # build latest sequentially
  1176. num_failures, resultset = jobset.run(build_steps,
  1177. maxjobs=1,
  1178. stop_on_failure=True,
  1179. newline_on_success=newline_on_success,
  1180. travis=args.travis)
  1181. if num_failures:
  1182. return [BuildAndRunError.BUILD]
  1183. if build_only:
  1184. if xml_report:
  1185. report_utils.render_junit_xml_report(
  1186. resultset, xml_report, suite_name=args.report_suite_name)
  1187. return []
  1188. if not args.travis and not _has_epollexclusive() and platform_string(
  1189. ) in _POLLING_STRATEGIES and 'epollex' in _POLLING_STRATEGIES[
  1190. platform_string()]:
  1191. print('\n\nOmitting EPOLLEXCLUSIVE tests\n\n')
  1192. _POLLING_STRATEGIES[platform_string()].remove('epollex')
  1193. # start antagonists
  1194. antagonists = [
  1195. subprocess.Popen(['tools/run_tests/python_utils/antagonist.py'])
  1196. for _ in range(0, args.antagonists)
  1197. ]
  1198. start_port_server.start_port_server()
  1199. resultset = None
  1200. num_test_failures = 0
  1201. try:
  1202. infinite_runs = runs_per_test == 0
  1203. one_run = set(spec for language in languages
  1204. for spec in language.test_specs()
  1205. if (re.search(args.regex, spec.shortname) and
  1206. (args.regex_exclude == '' or
  1207. not re.search(args.regex_exclude, spec.shortname))))
  1208. # When running on travis, we want out test runs to be as similar as possible
  1209. # for reproducibility purposes.
  1210. if args.travis and args.max_time <= 0:
  1211. massaged_one_run = sorted(one_run, key=lambda x: x.cpu_cost)
  1212. else:
  1213. # whereas otherwise, we want to shuffle things up to give all tests a
  1214. # chance to run.
  1215. massaged_one_run = list(
  1216. one_run) # random.sample needs an indexable seq.
  1217. num_jobs = len(massaged_one_run)
  1218. # for a random sample, get as many as indicated by the 'sample_percent'
  1219. # argument. By default this arg is 100, resulting in a shuffle of all
  1220. # jobs.
  1221. sample_size = int(num_jobs * args.sample_percent / 100.0)
  1222. massaged_one_run = random.sample(massaged_one_run, sample_size)
  1223. if not isclose(args.sample_percent, 100.0):
  1224. assert args.runs_per_test == 1, "Can't do sampling (-p) over multiple runs (-n)."
  1225. print("Running %d tests out of %d (~%d%%)" %
  1226. (sample_size, num_jobs, args.sample_percent))
  1227. if infinite_runs:
  1228. assert len(massaged_one_run
  1229. ) > 0, 'Must have at least one test for a -n inf run'
  1230. runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs
  1231. else itertools.repeat(massaged_one_run, runs_per_test))
  1232. all_runs = itertools.chain.from_iterable(runs_sequence)
  1233. if args.quiet_success:
  1234. jobset.message(
  1235. 'START',
  1236. 'Running tests quietly, only failing tests will be reported',
  1237. do_newline=True)
  1238. num_test_failures, resultset = jobset.run(
  1239. all_runs,
  1240. check_cancelled,
  1241. newline_on_success=newline_on_success,
  1242. travis=args.travis,
  1243. maxjobs=args.jobs,
  1244. maxjobs_cpu_agnostic=max_parallel_tests_for_current_platform(),
  1245. stop_on_failure=args.stop_on_failure,
  1246. quiet_success=args.quiet_success,
  1247. max_time=args.max_time)
  1248. if resultset:
  1249. for k, v in sorted(resultset.items()):
  1250. num_runs, num_failures = _calculate_num_runs_failures(v)
  1251. if num_failures > 0:
  1252. if num_failures == num_runs: # what about infinite_runs???
  1253. jobset.message('FAILED', k, do_newline=True)
  1254. else:
  1255. jobset.message('FLAKE',
  1256. '%s [%d/%d runs flaked]' %
  1257. (k, num_failures, num_runs),
  1258. do_newline=True)
  1259. finally:
  1260. for antagonist in antagonists:
  1261. antagonist.kill()
  1262. if args.bq_result_table and resultset:
  1263. upload_extra_fields = {
  1264. 'compiler': args.compiler,
  1265. 'config': args.config,
  1266. 'iomgr_platform': args.iomgr_platform,
  1267. 'language': args.language[
  1268. 0
  1269. ], # args.language is a list but will always have one element when uploading to BQ is enabled.
  1270. 'platform': platform_string()
  1271. }
  1272. try:
  1273. upload_results_to_bq(resultset, args.bq_result_table,
  1274. upload_extra_fields)
  1275. except NameError as e:
  1276. logging.warning(
  1277. e) # It's fine to ignore since this is not critical
  1278. if xml_report and resultset:
  1279. report_utils.render_junit_xml_report(
  1280. resultset,
  1281. xml_report,
  1282. suite_name=args.report_suite_name,
  1283. multi_target=args.report_multi_target)
  1284. number_failures, _ = jobset.run(post_tests_steps,
  1285. maxjobs=1,
  1286. stop_on_failure=False,
  1287. newline_on_success=newline_on_success,
  1288. travis=args.travis)
  1289. out = []
  1290. if number_failures:
  1291. out.append(BuildAndRunError.POST_TEST)
  1292. if num_test_failures:
  1293. out.append(BuildAndRunError.TEST)
  1294. return out
  1295. # parse command line
  1296. argp = argparse.ArgumentParser(description='Run grpc tests.')
  1297. argp.add_argument('-c',
  1298. '--config',
  1299. choices=sorted(_CONFIGS.keys()),
  1300. default='opt')
  1301. argp.add_argument(
  1302. '-n',
  1303. '--runs_per_test',
  1304. default=1,
  1305. type=runs_per_test_type,
  1306. help='A positive integer or "inf". If "inf", all tests will run in an '
  1307. 'infinite loop. Especially useful in combination with "-f"')
  1308. argp.add_argument('-r', '--regex', default='.*', type=str)
  1309. argp.add_argument('--regex_exclude', default='', type=str)
  1310. argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
  1311. argp.add_argument('-s', '--slowdown', default=1.0, type=float)
  1312. argp.add_argument('-p',
  1313. '--sample_percent',
  1314. default=100.0,
  1315. type=percent_type,
  1316. help='Run a random sample with that percentage of tests')
  1317. argp.add_argument(
  1318. '-t',
  1319. '--travis',
  1320. default=False,
  1321. action='store_const',
  1322. const=True,
  1323. help='When set, indicates that the script is running on CI (= not locally).'
  1324. )
  1325. argp.add_argument('--newline_on_success',
  1326. default=False,
  1327. action='store_const',
  1328. const=True)
  1329. argp.add_argument('-l',
  1330. '--language',
  1331. choices=sorted(_LANGUAGES.keys()),
  1332. nargs='+',
  1333. required=True)
  1334. argp.add_argument('-S',
  1335. '--stop_on_failure',
  1336. default=False,
  1337. action='store_const',
  1338. const=True)
  1339. argp.add_argument('--use_docker',
  1340. default=False,
  1341. action='store_const',
  1342. const=True,
  1343. help='Run all the tests under docker. That provides ' +
  1344. 'additional isolation and prevents the need to install ' +
  1345. 'language specific prerequisites. Only available on Linux.')
  1346. argp.add_argument(
  1347. '--allow_flakes',
  1348. default=False,
  1349. action='store_const',
  1350. const=True,
  1351. help=
  1352. 'Allow flaky tests to show as passing (re-runs failed tests up to five times)'
  1353. )
  1354. argp.add_argument(
  1355. '--arch',
  1356. choices=['default', 'x86', 'x64'],
  1357. default='default',
  1358. help=
  1359. 'Selects architecture to target. For some platforms "default" is the only supported choice.'
  1360. )
  1361. argp.add_argument(
  1362. '--compiler',
  1363. choices=[
  1364. 'default',
  1365. 'gcc5',
  1366. 'gcc10.2',
  1367. 'gcc10.2_openssl102',
  1368. 'gcc11',
  1369. 'gcc_musl',
  1370. 'clang4',
  1371. 'clang13',
  1372. 'python2.7',
  1373. 'python3.5',
  1374. 'python3.6',
  1375. 'python3.7',
  1376. 'python3.8',
  1377. 'python3.9',
  1378. 'pypy',
  1379. 'pypy3',
  1380. 'python_alpine',
  1381. 'all_the_cpythons',
  1382. 'electron1.3',
  1383. 'electron1.6',
  1384. 'coreclr',
  1385. 'cmake',
  1386. 'cmake_ninja_vs2015',
  1387. 'cmake_ninja_vs2017',
  1388. 'cmake_vs2015',
  1389. 'cmake_vs2017',
  1390. 'cmake_vs2019',
  1391. 'mono',
  1392. ],
  1393. default='default',
  1394. help=
  1395. 'Selects compiler to use. Allowed values depend on the platform and language.'
  1396. )
  1397. argp.add_argument('--iomgr_platform',
  1398. choices=['native', 'gevent', 'asyncio'],
  1399. default='native',
  1400. help='Selects iomgr platform to build on')
  1401. argp.add_argument('--build_only',
  1402. default=False,
  1403. action='store_const',
  1404. const=True,
  1405. help='Perform all the build steps but don\'t run any tests.')
  1406. argp.add_argument('--measure_cpu_costs',
  1407. default=False,
  1408. action='store_const',
  1409. const=True,
  1410. help='Measure the cpu costs of tests')
  1411. argp.add_argument('-a', '--antagonists', default=0, type=int)
  1412. argp.add_argument('-x',
  1413. '--xml_report',
  1414. default=None,
  1415. type=str,
  1416. help='Generates a JUnit-compatible XML report')
  1417. argp.add_argument('--report_suite_name',
  1418. default='tests',
  1419. type=str,
  1420. help='Test suite name to use in generated JUnit XML report')
  1421. argp.add_argument(
  1422. '--report_multi_target',
  1423. default=False,
  1424. const=True,
  1425. action='store_const',
  1426. help='Generate separate XML report for each test job (Looks better in UIs).'
  1427. )
  1428. argp.add_argument(
  1429. '--quiet_success',
  1430. default=False,
  1431. action='store_const',
  1432. const=True,
  1433. help=
  1434. 'Don\'t print anything when a test passes. Passing tests also will not be reported in XML report. '
  1435. + 'Useful when running many iterations of each test (argument -n).')
  1436. argp.add_argument(
  1437. '--force_default_poller',
  1438. default=False,
  1439. action='store_const',
  1440. const=True,
  1441. help='Don\'t try to iterate over many polling strategies when they exist')
  1442. argp.add_argument(
  1443. '--force_use_pollers',
  1444. default=None,
  1445. type=str,
  1446. help='Only use the specified comma-delimited list of polling engines. '
  1447. 'Example: --force_use_pollers epoll1,poll '
  1448. ' (This flag has no effect if --force_default_poller flag is also used)')
  1449. argp.add_argument('--max_time',
  1450. default=-1,
  1451. type=int,
  1452. help='Maximum test runtime in seconds')
  1453. argp.add_argument('--bq_result_table',
  1454. default='',
  1455. type=str,
  1456. nargs='?',
  1457. help='Upload test results to a specified BQ table.')
  1458. args = argp.parse_args()
  1459. flaky_tests = set()
  1460. shortname_to_cpu = {}
  1461. if args.force_default_poller:
  1462. _POLLING_STRATEGIES = {}
  1463. elif args.force_use_pollers:
  1464. _POLLING_STRATEGIES[platform_string()] = args.force_use_pollers.split(',')
  1465. jobset.measure_cpu_costs = args.measure_cpu_costs
  1466. # grab config
  1467. run_config = _CONFIGS[args.config]
  1468. build_config = run_config.build_config
  1469. # TODO(jtattermusch): is this setting applied/being used?
  1470. if args.travis:
  1471. _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'api'}
  1472. languages = set(_LANGUAGES[l] for l in args.language)
  1473. for l in languages:
  1474. l.configure(run_config, args)
  1475. if len(languages) != 1:
  1476. print('Building multiple languages simultaneously is not supported!')
  1477. sys.exit(1)
  1478. # If --use_docker was used, respawn the run_tests.py script under a docker container
  1479. # instead of continuing.
  1480. if args.use_docker:
  1481. if not args.travis:
  1482. print('Seen --use_docker flag, will run tests under docker.')
  1483. print('')
  1484. print(
  1485. 'IMPORTANT: The changes you are testing need to be locally committed'
  1486. )
  1487. print(
  1488. 'because only the committed changes in the current branch will be')
  1489. print('copied to the docker environment.')
  1490. time.sleep(5)
  1491. dockerfile_dirs = set([l.dockerfile_dir() for l in languages])
  1492. if len(dockerfile_dirs) > 1:
  1493. print('Languages to be tested require running under different docker '
  1494. 'images.')
  1495. sys.exit(1)
  1496. else:
  1497. dockerfile_dir = next(iter(dockerfile_dirs))
  1498. child_argv = [arg for arg in sys.argv if not arg == '--use_docker']
  1499. run_tests_cmd = 'python3 tools/run_tests/run_tests.py %s' % ' '.join(
  1500. child_argv[1:])
  1501. env = os.environ.copy()
  1502. env['DOCKERFILE_DIR'] = dockerfile_dir
  1503. env['DOCKER_RUN_SCRIPT'] = 'tools/run_tests/dockerize/docker_run.sh'
  1504. env['DOCKER_RUN_SCRIPT_COMMAND'] = run_tests_cmd
  1505. retcode = subprocess.call(
  1506. 'tools/run_tests/dockerize/build_and_run_docker.sh',
  1507. shell=True,
  1508. env=env)
  1509. _print_debug_info_epilogue(dockerfile_dir=dockerfile_dir)
  1510. sys.exit(retcode)
  1511. _check_arch_option(args.arch)
  1512. # collect pre-build steps (which get retried if they fail, e.g. to avoid
  1513. # flakes on downloading dependencies etc.)
  1514. build_steps = list(
  1515. set(
  1516. jobset.JobSpec(cmdline,
  1517. environ=_build_step_environ(
  1518. build_config, extra_env=l.build_steps_environ()),
  1519. timeout_seconds=_PRE_BUILD_STEP_TIMEOUT_SECONDS,
  1520. flake_retries=2)
  1521. for l in languages
  1522. for cmdline in l.pre_build_steps()))
  1523. # collect build steps
  1524. build_steps.extend(
  1525. set(
  1526. jobset.JobSpec(cmdline,
  1527. environ=_build_step_environ(
  1528. build_config, extra_env=l.build_steps_environ()),
  1529. timeout_seconds=None)
  1530. for l in languages
  1531. for cmdline in l.build_steps()))
  1532. # collect post test steps
  1533. post_tests_steps = list(
  1534. set(
  1535. jobset.JobSpec(cmdline,
  1536. environ=_build_step_environ(
  1537. build_config, extra_env=l.build_steps_environ()))
  1538. for l in languages
  1539. for cmdline in l.post_tests_steps()))
  1540. runs_per_test = args.runs_per_test
  1541. errors = _build_and_run(check_cancelled=lambda: False,
  1542. newline_on_success=args.newline_on_success,
  1543. xml_report=args.xml_report,
  1544. build_only=args.build_only)
  1545. if not errors:
  1546. jobset.message('SUCCESS', 'All tests passed', do_newline=True)
  1547. else:
  1548. jobset.message('FAILED', 'Some tests failed', do_newline=True)
  1549. if not _is_use_docker_child():
  1550. # if --use_docker was used, the outer invocation of run_tests.py will
  1551. # print the debug info instead.
  1552. _print_debug_info_epilogue()
  1553. exit_code = 0
  1554. if BuildAndRunError.BUILD in errors:
  1555. exit_code |= 1
  1556. if BuildAndRunError.TEST in errors:
  1557. exit_code |= 2
  1558. if BuildAndRunError.POST_TEST in errors:
  1559. exit_code |= 4
  1560. sys.exit(exit_code)