test_runner.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright 2020 The Chromium Authors. All rights reserved.
  4. # Use of this source code is governed by a BSD-style license that can be
  5. # found in the LICENSE file.
  6. """This script facilitates running tests for lacros on Linux.
  7. In order to run lacros tests on Linux, please first follow bit.ly/3juQVNJ
  8. to setup build directory with the lacros-chrome-on-linux build configuration,
  9. and corresponding test targets are built successfully.
  10. * Example usages:
  11. ./build/lacros/test_runner.py test out/lacros/url_unittests
  12. ./build/lacros/test_runner.py test out/lacros/browser_tests
  13. The commands above run url_unittests and browser_tests respecitively, and more
  14. specifically, url_unitests is executed directly while browser_tests is
  15. executed with the latest version of prebuilt ash-chrome, and the behavior is
  16. controlled by |_TARGETS_REQUIRE_ASH_CHROME|, and it's worth noting that the
  17. list is maintained manually, so if you see something is wrong, please upload a
  18. CL to fix it.
  19. ./build/lacros/test_runner.py test out/lacros/browser_tests \\
  20. --gtest_filter=BrowserTest.Title
  21. The above command only runs 'BrowserTest.Title', and any argument accepted by
  22. the underlying test binary can be specified in the command.
  23. ./build/lacros/test_runner.py test out/lacros/browser_tests \\
  24. --ash-chrome-version=793554
  25. The above command runs tests with a given version of ash-chrome, which is
  26. useful to reproduce test failures, the version corresponds to the commit
  27. position of commits on the master branch, and a list of prebuilt versions can
  28. be found at: gs://ash-chromium-on-linux-prebuilts/x86_64.
  29. ./testing/xvfb.py ./build/lacros/test_runner.py test out/lacros/browser_tests
  30. The above command starts ash-chrome with xvfb instead of an X11 window, and
  31. it's useful when running tests without a display attached, such as sshing.
  32. For version skew testing when passing --ash-chrome-path-override, the runner
  33. will try to find the ash major version and Lacros major version. If ash is
  34. newer(major version larger), the runner will not run any tests and just
  35. returns success.
  36. """
  37. import argparse
  38. import json
  39. import os
  40. import logging
  41. import re
  42. import shutil
  43. import signal
  44. import subprocess
  45. import sys
  46. import tempfile
  47. import time
  48. import zipfile
  49. _SRC_ROOT = os.path.abspath(
  50. os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir))
  51. sys.path.append(os.path.join(_SRC_ROOT, 'third_party', 'depot_tools'))
  52. # Base GS URL to store prebuilt ash-chrome.
  53. _GS_URL_BASE = 'gs://ash-chromium-on-linux-prebuilts/x86_64'
  54. # Latest file version.
  55. _GS_URL_LATEST_FILE = _GS_URL_BASE + '/latest/ash-chromium.txt'
  56. # GS path to the zipped ash-chrome build with any given version.
  57. _GS_ASH_CHROME_PATH = 'ash-chromium.zip'
  58. # Directory to cache downloaded ash-chrome versions to avoid re-downloading.
  59. _PREBUILT_ASH_CHROME_DIR = os.path.join(os.path.dirname(__file__),
  60. 'prebuilt_ash_chrome')
  61. # Number of seconds to wait for ash-chrome to start.
  62. ASH_CHROME_TIMEOUT_SECONDS = (
  63. 300 if os.environ.get('ASH_WRAPPER', None) else 10)
  64. # List of targets that require ash-chrome as a Wayland server in order to run.
  65. _TARGETS_REQUIRE_ASH_CHROME = [
  66. 'app_shell_unittests',
  67. 'aura_unittests',
  68. 'browser_tests',
  69. 'components_unittests',
  70. 'compositor_unittests',
  71. 'content_unittests',
  72. 'dbus_unittests',
  73. 'extensions_unittests',
  74. 'media_unittests',
  75. 'message_center_unittests',
  76. 'snapshot_unittests',
  77. 'sync_integration_tests',
  78. 'unit_tests',
  79. 'views_unittests',
  80. 'wm_unittests',
  81. # regex patterns.
  82. '.*_browsertests',
  83. '.*interactive_ui_tests'
  84. ]
  85. # List of targets that require ash-chrome to support crosapi mojo APIs.
  86. _TARGETS_REQUIRE_MOJO_CROSAPI = [
  87. # TODO(jamescook): Add 'browser_tests' after multiple crosapi connections
  88. # are allowed. For now we only enable crosapi in targets that run tests
  89. # serially.
  90. 'interactive_ui_tests',
  91. 'lacros_chrome_browsertests',
  92. 'lacros_chrome_browsertests_run_in_series'
  93. ]
  94. # Default test filter file for each target. These filter files will be
  95. # used by default if no other filter file get specified.
  96. _DEFAULT_FILTER_FILES_MAPPING = {
  97. 'browser_tests': 'linux-lacros.browser_tests.filter',
  98. 'components_unittests': 'linux-lacros.components_unittests.filter',
  99. 'content_browsertests': 'linux-lacros.content_browsertests.filter',
  100. 'interactive_ui_tests': 'linux-lacros.interactive_ui_tests.filter',
  101. 'lacros_chrome_browsertests':
  102. 'linux-lacros.lacros_chrome_browsertests.filter',
  103. 'sync_integration_tests': 'linux-lacros.sync_integration_tests.filter',
  104. 'unit_tests': 'linux-lacros.unit_tests.filter',
  105. }
  106. def _GetAshChromeDirPath(version):
  107. """Returns a path to the dir storing the downloaded version of ash-chrome."""
  108. return os.path.join(_PREBUILT_ASH_CHROME_DIR, version)
  109. def _remove_unused_ash_chrome_versions(version_to_skip):
  110. """Removes unused ash-chrome versions to save disk space.
  111. Currently, when an ash-chrome zip is downloaded and unpacked, the atime/mtime
  112. of the dir and the files are NOW instead of the time when they were built, but
  113. there is no garanteen it will always be the behavior in the future, so avoid
  114. removing the current version just in case.
  115. Args:
  116. version_to_skip (str): the version to skip removing regardless of its age.
  117. """
  118. days = 7
  119. expiration_duration = 60 * 60 * 24 * days
  120. for f in os.listdir(_PREBUILT_ASH_CHROME_DIR):
  121. if f == version_to_skip:
  122. continue
  123. p = os.path.join(_PREBUILT_ASH_CHROME_DIR, f)
  124. if os.path.isfile(p):
  125. # The prebuilt ash-chrome dir is NOT supposed to contain any files, remove
  126. # them to keep the directory clean.
  127. os.remove(p)
  128. continue
  129. chrome_path = os.path.join(p, 'test_ash_chrome')
  130. if not os.path.exists(chrome_path):
  131. chrome_path = p
  132. age = time.time() - os.path.getatime(chrome_path)
  133. if age > expiration_duration:
  134. logging.info(
  135. 'Removing ash-chrome: "%s" as it hasn\'t been used in the '
  136. 'past %d days', p, days)
  137. shutil.rmtree(p)
  138. def _GsutilCopyWithRetry(gs_path, local_name, retry_times=3):
  139. """Gsutil copy with retry.
  140. Args:
  141. gs_path: The gs path for remote location.
  142. local_name: The local file name.
  143. retry_times: The total try times if the gsutil call fails.
  144. Raises:
  145. RuntimeError: If failed to download the specified version, for example,
  146. if the version is not present on gcs.
  147. """
  148. import download_from_google_storage
  149. gsutil = download_from_google_storage.Gsutil(
  150. download_from_google_storage.GSUTIL_DEFAULT_PATH)
  151. exit_code = 1
  152. retry = 0
  153. while exit_code and retry < retry_times:
  154. retry += 1
  155. exit_code = gsutil.call('cp', gs_path, local_name)
  156. if exit_code:
  157. raise RuntimeError('Failed to download: "%s"' % gs_path)
  158. def _DownloadAshChromeIfNecessary(version):
  159. """Download a given version of ash-chrome if not already exists.
  160. Args:
  161. version: A string representing the version, such as "793554".
  162. Raises:
  163. RuntimeError: If failed to download the specified version, for example,
  164. if the version is not present on gcs.
  165. """
  166. def IsAshChromeDirValid(ash_chrome_dir):
  167. # This function assumes that once 'chrome' is present, other dependencies
  168. # will be present as well, it's not always true, for example, if the test
  169. # runner process gets killed in the middle of unzipping (~2 seconds), but
  170. # it's unlikely for the assumption to break in practice.
  171. return os.path.isdir(ash_chrome_dir) and os.path.isfile(
  172. os.path.join(ash_chrome_dir, 'test_ash_chrome'))
  173. ash_chrome_dir = _GetAshChromeDirPath(version)
  174. if IsAshChromeDirValid(ash_chrome_dir):
  175. return
  176. shutil.rmtree(ash_chrome_dir, ignore_errors=True)
  177. os.makedirs(ash_chrome_dir)
  178. with tempfile.NamedTemporaryFile() as tmp:
  179. logging.info('Ash-chrome version: %s', version)
  180. gs_path = _GS_URL_BASE + '/' + version + '/' + _GS_ASH_CHROME_PATH
  181. _GsutilCopyWithRetry(gs_path, tmp.name)
  182. # https://bugs.python.org/issue15795. ZipFile doesn't preserve permissions.
  183. # And in order to workaround the issue, this function is created and used
  184. # instead of ZipFile.extractall().
  185. # The solution is copied from:
  186. # https://stackoverflow.com/questions/42326428/zipfile-in-python-file-permission
  187. def ExtractFile(zf, info, extract_dir):
  188. zf.extract(info.filename, path=extract_dir)
  189. perm = info.external_attr >> 16
  190. os.chmod(os.path.join(extract_dir, info.filename), perm)
  191. with zipfile.ZipFile(tmp.name, 'r') as zf:
  192. # Extra all files instead of just 'chrome' binary because 'chrome' needs
  193. # other resources and libraries to run.
  194. for info in zf.infolist():
  195. ExtractFile(zf, info, ash_chrome_dir)
  196. _remove_unused_ash_chrome_versions(version)
  197. def _GetLatestVersionOfAshChrome():
  198. """Returns the latest version of uploaded ash-chrome."""
  199. with tempfile.NamedTemporaryFile() as tmp:
  200. _GsutilCopyWithRetry(_GS_URL_LATEST_FILE, tmp.name)
  201. with open(tmp.name, 'r') as f:
  202. return f.read().strip()
  203. def _WaitForAshChromeToStart(tmp_xdg_dir, lacros_mojo_socket_file,
  204. enable_mojo_crosapi, ash_ready_file):
  205. """Waits for Ash-Chrome to be up and running and returns a boolean indicator.
  206. Determine whether ash-chrome is up and running by checking whether two files
  207. (lock file + socket) have been created in the |XDG_RUNTIME_DIR| and the lacros
  208. mojo socket file has been created if enabling the mojo "crosapi" interface.
  209. TODO(crbug.com/1107966): Figure out a more reliable hook to determine the
  210. status of ash-chrome, likely through mojo connection.
  211. Args:
  212. tmp_xdg_dir (str): Path to the XDG_RUNTIME_DIR.
  213. lacros_mojo_socket_file (str): Path to the lacros mojo socket file.
  214. enable_mojo_crosapi (bool): Whether to bootstrap the crosapi mojo interface
  215. between ash and the lacros test binary.
  216. ash_ready_file (str): Path to a non-existing file. After ash is ready for
  217. testing, the file will be created.
  218. Returns:
  219. A boolean indicating whether Ash-chrome is up and running.
  220. """
  221. def IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file,
  222. enable_mojo_crosapi, ash_ready_file):
  223. # There should be 2 wayland files.
  224. if len(os.listdir(tmp_xdg_dir)) < 2:
  225. return False
  226. if enable_mojo_crosapi and not os.path.exists(lacros_mojo_socket_file):
  227. return False
  228. return os.path.exists(ash_ready_file)
  229. time_counter = 0
  230. while not IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file,
  231. enable_mojo_crosapi, ash_ready_file):
  232. time.sleep(0.5)
  233. time_counter += 0.5
  234. if time_counter > ASH_CHROME_TIMEOUT_SECONDS:
  235. break
  236. return IsAshChromeReady(tmp_xdg_dir, lacros_mojo_socket_file,
  237. enable_mojo_crosapi, ash_ready_file)
  238. def _ExtractAshMajorVersion(file_path):
  239. """Extract major version from file_path.
  240. File path like this:
  241. ../../lacros_version_skew_tests_v94.0.4588.0/test_ash_chrome
  242. Returns:
  243. int representing the major version. Or 0 if it can't extract
  244. major version.
  245. """
  246. m = re.search(
  247. 'lacros_version_skew_tests_v(?P<version>[0-9]+).[0-9]+.[0-9]+.[0-9]+/',
  248. file_path)
  249. if (m and 'version' in m.groupdict().keys()):
  250. return int(m.group('version'))
  251. logging.warning('Can not find the ash version in %s.' % file_path)
  252. # Returns ash major version as 0, so we can still run tests.
  253. # This is likely happen because user is running in local environments.
  254. return 0
  255. def _FindLacrosMajorVersionFromMetadata():
  256. # This handles the logic on bots. When running on bots,
  257. # we don't copy source files to test machines. So we build a
  258. # metadata.json file which contains version information.
  259. if not os.path.exists('metadata.json'):
  260. logging.error('Can not determine current version.')
  261. # Returns 0 so it can't run any tests.
  262. return 0
  263. version = ''
  264. with open('metadata.json', 'r') as file:
  265. content = json.load(file)
  266. version = content['content']['version']
  267. return int(version[:version.find('.')])
  268. def _FindLacrosMajorVersion():
  269. """Returns the major version in the current checkout.
  270. It would try to read src/chrome/VERSION. If it's not available,
  271. then try to read metadata.json.
  272. Returns:
  273. int representing the major version. Or 0 if it fails to
  274. determine the version.
  275. """
  276. version_file = os.path.abspath(
  277. os.path.join(os.path.abspath(os.path.dirname(__file__)),
  278. '../../chrome/VERSION'))
  279. # This is mostly happens for local development where
  280. # src/chrome/VERSION exists.
  281. if os.path.exists(version_file):
  282. lines = open(version_file, 'r').readlines()
  283. return int(lines[0][lines[0].find('=') + 1:-1])
  284. return _FindLacrosMajorVersionFromMetadata()
  285. def _ParseSummaryOutput(forward_args):
  286. """Find the summary output file path.
  287. Args:
  288. forward_args (list): Args to be forwarded to the test command.
  289. Returns:
  290. None if not found, or str representing the output file path.
  291. """
  292. logging.warning(forward_args)
  293. for arg in forward_args:
  294. if arg.startswith('--test-launcher-summary-output='):
  295. return arg[len('--test-launcher-summary-output='):]
  296. return None
  297. def _IsRunningOnBots(forward_args):
  298. """Detects if the script is running on bots or not.
  299. Args:
  300. forward_args (list): Args to be forwarded to the test command.
  301. Returns:
  302. True if the script is running on bots. Otherwise returns False.
  303. """
  304. return '--test-launcher-bot-mode' in forward_args
  305. def _RunTestWithAshChrome(args, forward_args):
  306. """Runs tests with ash-chrome.
  307. Args:
  308. args (dict): Args for this script.
  309. forward_args (list): Args to be forwarded to the test command.
  310. """
  311. if args.ash_chrome_path_override:
  312. ash_chrome_file = args.ash_chrome_path_override
  313. ash_major_version = _ExtractAshMajorVersion(ash_chrome_file)
  314. lacros_major_version = _FindLacrosMajorVersion()
  315. if ash_major_version > lacros_major_version:
  316. logging.warning('''Not running any tests, because we do not \
  317. support version skew testing for Lacros M%s against ash M%s''' %
  318. (lacros_major_version, ash_major_version))
  319. # Create an empty output.json file so result adapter can read
  320. # the file. Or else result adapter will report no file found
  321. # and result infra failure.
  322. output_json = _ParseSummaryOutput(forward_args)
  323. if output_json:
  324. with open(output_json, 'w') as f:
  325. f.write("""{"all_tests":[],"disabled_tests":[],"global_tags":[],
  326. "per_iteration_data":[],"test_locations":{}}""")
  327. # Although we don't run any tests, this is considered as success.
  328. return 0
  329. if not os.path.exists(ash_chrome_file):
  330. logging.error("""Can not find ash chrome at %s. Did you download \
  331. the ash from CIPD? If you don't plan to build your own ash, you need \
  332. to download first. Example commandlines:
  333. $ cipd auth-login
  334. $ echo "chromium/testing/linux-ash-chromium/x86_64/ash.zip \
  335. version:92.0.4515.130" > /tmp/ensure-file.txt
  336. $ cipd ensure -ensure-file /tmp/ensure-file.txt \
  337. -root lacros_version_skew_tests_v92.0.4515.130
  338. Then you can use --ash-chrome-path-override=\
  339. lacros_version_skew_tests_v92.0.4515.130/test_ash_chrome
  340. """ % ash_chrome_file)
  341. return 1
  342. elif args.ash_chrome_path:
  343. ash_chrome_file = args.ash_chrome_path
  344. else:
  345. ash_chrome_version = (args.ash_chrome_version
  346. or _GetLatestVersionOfAshChrome())
  347. _DownloadAshChromeIfNecessary(ash_chrome_version)
  348. logging.info('Ash-chrome version: %s', ash_chrome_version)
  349. ash_chrome_file = os.path.join(_GetAshChromeDirPath(ash_chrome_version),
  350. 'test_ash_chrome')
  351. try:
  352. # Starts Ash-Chrome.
  353. tmp_xdg_dir_name = tempfile.mkdtemp()
  354. tmp_ash_data_dir_name = tempfile.mkdtemp()
  355. # Please refer to below file for how mojo connection is set up in testing.
  356. # //chrome/browser/ash/crosapi/test_mojo_connection_manager.h
  357. lacros_mojo_socket_file = '%s/lacros.sock' % tmp_ash_data_dir_name
  358. lacros_mojo_socket_arg = ('--lacros-mojo-socket-for-testing=%s' %
  359. lacros_mojo_socket_file)
  360. ash_ready_file = '%s/ash_ready.txt' % tmp_ash_data_dir_name
  361. enable_mojo_crosapi = any(t == os.path.basename(args.command)
  362. for t in _TARGETS_REQUIRE_MOJO_CROSAPI)
  363. ash_wayland_socket_name = 'wayland-exo'
  364. ash_process = None
  365. ash_env = os.environ.copy()
  366. ash_env['XDG_RUNTIME_DIR'] = tmp_xdg_dir_name
  367. ash_cmd = [
  368. ash_chrome_file,
  369. '--user-data-dir=%s' % tmp_ash_data_dir_name,
  370. '--enable-wayland-server',
  371. '--no-startup-window',
  372. '--enable-features=LacrosSupport,LacrosPrimary,LacrosOnly',
  373. '--ash-ready-file-path=%s' % ash_ready_file,
  374. '--wayland-server-socket=%s' % ash_wayland_socket_name,
  375. ]
  376. if enable_mojo_crosapi:
  377. ash_cmd.append(lacros_mojo_socket_arg)
  378. # Users can specify a wrapper for the ash binary to do things like
  379. # attaching debuggers. For example, this will open a new terminal window
  380. # and run GDB.
  381. # $ export ASH_WRAPPER="gnome-terminal -- gdb --ex=r --args"
  382. ash_wrapper = os.environ.get('ASH_WRAPPER', None)
  383. if ash_wrapper:
  384. logging.info('Running ash with "ASH_WRAPPER": %s', ash_wrapper)
  385. ash_cmd = list(ash_wrapper.split()) + ash_cmd
  386. ash_process_has_started = False
  387. total_tries = 3
  388. num_tries = 0
  389. # Create a log file if the user wanted to have one.
  390. log = None
  391. if args.ash_logging_path:
  392. log = open(args.ash_logging_path, 'a')
  393. # Ash logs can be useful. Enable ash log by default on bots.
  394. elif _IsRunningOnBots(forward_args):
  395. summary_file = _ParseSummaryOutput(forward_args)
  396. if summary_file:
  397. ash_log_path = os.path.join(os.path.dirname(summary_file),
  398. 'ash_chrome.log')
  399. log = open(ash_log_path, 'a')
  400. while not ash_process_has_started and num_tries < total_tries:
  401. num_tries += 1
  402. if log is None:
  403. ash_process = subprocess.Popen(ash_cmd, env=ash_env)
  404. else:
  405. ash_process = subprocess.Popen(ash_cmd,
  406. env=ash_env,
  407. stdout=log,
  408. stderr=log)
  409. ash_process_has_started = _WaitForAshChromeToStart(
  410. tmp_xdg_dir_name, lacros_mojo_socket_file, enable_mojo_crosapi,
  411. ash_ready_file)
  412. if ash_process_has_started:
  413. break
  414. logging.warning('Starting ash-chrome timed out after %ds',
  415. ASH_CHROME_TIMEOUT_SECONDS)
  416. logging.warning('Are you using test_ash_chrome?')
  417. logging.warning('Printing the output of "ps aux" for debugging:')
  418. subprocess.call(['ps', 'aux'])
  419. if ash_process and ash_process.poll() is None:
  420. ash_process.kill()
  421. if not ash_process_has_started:
  422. raise RuntimeError('Timed out waiting for ash-chrome to start')
  423. # Starts tests.
  424. if enable_mojo_crosapi:
  425. forward_args.append(lacros_mojo_socket_arg)
  426. test_env = os.environ.copy()
  427. test_env['WAYLAND_DISPLAY'] = ash_wayland_socket_name
  428. test_env['EGL_PLATFORM'] = 'surfaceless'
  429. test_env['XDG_RUNTIME_DIR'] = tmp_xdg_dir_name
  430. test_process = subprocess.Popen([args.command] + forward_args, env=test_env)
  431. return test_process.wait()
  432. finally:
  433. if ash_process and ash_process.poll() is None:
  434. ash_process.terminate()
  435. # Allow process to do cleanup and exit gracefully before killing.
  436. time.sleep(0.5)
  437. ash_process.kill()
  438. shutil.rmtree(tmp_xdg_dir_name, ignore_errors=True)
  439. shutil.rmtree(tmp_ash_data_dir_name, ignore_errors=True)
  440. def _RunTestDirectly(args, forward_args):
  441. """Runs tests by invoking the test command directly.
  442. args (dict): Args for this script.
  443. forward_args (list): Args to be forwarded to the test command.
  444. """
  445. try:
  446. p = None
  447. p = subprocess.Popen([args.command] + forward_args)
  448. return p.wait()
  449. finally:
  450. if p and p.poll() is None:
  451. p.terminate()
  452. time.sleep(0.5)
  453. p.kill()
  454. def _HandleSignal(sig, _):
  455. """Handles received signals to make sure spawned test process are killed.
  456. sig (int): An integer representing the received signal, for example SIGTERM.
  457. """
  458. logging.warning('Received signal: %d, killing spawned processes', sig)
  459. # Don't do any cleanup here, instead, leave it to the finally blocks.
  460. # Assumption is based on https://docs.python.org/3/library/sys.html#sys.exit:
  461. # cleanup actions specified by finally clauses of try statements are honored.
  462. # https://tldp.org/LDP/abs/html/exitcodes.html:
  463. # Exit code 128+n -> Fatal error signal "n".
  464. sys.exit(128 + sig)
  465. def _ExpandFilterFileIfNeeded(test_target, forward_args):
  466. if (test_target in _DEFAULT_FILTER_FILES_MAPPING.keys() and not any(
  467. [arg.startswith('--test-launcher-filter-file') for arg in forward_args])):
  468. file_path = os.path.abspath(
  469. os.path.join(os.path.dirname(__file__), '..', '..', 'testing',
  470. 'buildbot', 'filters',
  471. _DEFAULT_FILTER_FILES_MAPPING[test_target]))
  472. forward_args.append(f'--test-launcher-filter-file={file_path}')
  473. def _RunTest(args, forward_args):
  474. """Runs tests with given args.
  475. args (dict): Args for this script.
  476. forward_args (list): Args to be forwarded to the test command.
  477. Raises:
  478. RuntimeError: If the given test binary doesn't exist or the test runner
  479. doesn't know how to run it.
  480. """
  481. if not os.path.isfile(args.command):
  482. raise RuntimeError('Specified test command: "%s" doesn\'t exist' %
  483. args.command)
  484. test_target = os.path.basename(args.command)
  485. _ExpandFilterFileIfNeeded(test_target, forward_args)
  486. # |_TARGETS_REQUIRE_ASH_CHROME| may not always be accurate as it is updated
  487. # with a best effort only, therefore, allow the invoker to override the
  488. # behavior with a specified ash-chrome version, which makes sure that
  489. # automated CI/CQ builders would always work correctly.
  490. requires_ash_chrome = any(
  491. re.match(t, test_target) for t in _TARGETS_REQUIRE_ASH_CHROME)
  492. if not requires_ash_chrome and not args.ash_chrome_version:
  493. return _RunTestDirectly(args, forward_args)
  494. return _RunTestWithAshChrome(args, forward_args)
  495. def Main():
  496. for sig in (signal.SIGTERM, signal.SIGINT):
  497. signal.signal(sig, _HandleSignal)
  498. logging.basicConfig(level=logging.INFO)
  499. arg_parser = argparse.ArgumentParser()
  500. arg_parser.usage = __doc__
  501. subparsers = arg_parser.add_subparsers()
  502. test_parser = subparsers.add_parser('test', help='Run tests')
  503. test_parser.set_defaults(func=_RunTest)
  504. test_parser.add_argument(
  505. 'command',
  506. help='A single command to invoke the tests, for example: '
  507. '"./url_unittests". Any argument unknown to this test runner script will '
  508. 'be forwarded to the command, for example: "--gtest_filter=Suite.Test"')
  509. version_group = test_parser.add_mutually_exclusive_group()
  510. version_group.add_argument(
  511. '--ash-chrome-version',
  512. type=str,
  513. help='Version of an prebuilt ash-chrome to use for testing, for example: '
  514. '"793554", and the version corresponds to the commit position of commits '
  515. 'on the main branch. If not specified, will use the latest version '
  516. 'available')
  517. version_group.add_argument(
  518. '--ash-chrome-path',
  519. type=str,
  520. help='Path to an locally built ash-chrome to use for testing. '
  521. 'In general you should build //chrome/test:test_ash_chrome.')
  522. # This is for version skew testing. The current CI/CQ builder builds
  523. # an ash chrome and pass it using --ash-chrome-path. In order to use the same
  524. # builder for version skew testing, we use a new argument to override
  525. # the ash chrome.
  526. test_parser.add_argument(
  527. '--ash-chrome-path-override',
  528. type=str,
  529. help='The same as --ash-chrome-path. But this will override '
  530. '--ash-chrome-path or --ash-chrome-version if any of these '
  531. 'arguments exist.')
  532. test_parser.add_argument(
  533. '--ash-logging-path',
  534. type=str,
  535. help='File & path to ash-chrome logging output while running Lacros '
  536. 'browser tests. If not provided, no output will be generated.')
  537. args = arg_parser.parse_known_args()
  538. return args[0].func(args[0], args[1])
  539. if __name__ == '__main__':
  540. sys.exit(Main())