test_apps.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. # Copyright 2020 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Test apps for running tests using xcodebuild."""
  5. import os
  6. import plistlib
  7. import struct
  8. import subprocess
  9. import time
  10. import shard_util
  11. import test_runner
  12. import test_runner_errors
  13. import xcode_util
  14. OUTPUT_DISABLED_TESTS_TEST_ARG = '--write-compiled-tests-json-to-writable-path'
  15. def get_gtest_filter(included, excluded):
  16. """Returns the GTest filter to filter the given test cases.
  17. If only included or excluded is provided, uses GTest filter inclusion or
  18. exclusion syntax for the given list. If both are provided, uses included list
  19. minus any tests in excluded list as tests to be included.
  20. Args:
  21. included: List of test cases to be included.
  22. excluded: List of test cases to be excluded.
  23. Returns:
  24. A string which can be supplied to --gtest_filter.
  25. """
  26. assert included or excluded, 'One of included or excluded list should exist.'
  27. if included and excluded:
  28. included = list(set(included) - set(excluded))
  29. excluded = []
  30. # A colon-separated list of tests cases.
  31. # e.g. a:b:c matches a, b, c.
  32. # e.g. -a:b:c matches everything except a, b, c.
  33. test_filter = ':'.join(test for test in sorted(included + excluded))
  34. # This means all tests in |included| are in |excluded|.
  35. if not test_filter:
  36. return '-*'
  37. return '-%s' % test_filter if excluded else test_filter
  38. def get_bundle_id(app_path):
  39. """Get bundle identifier for app.
  40. Args:
  41. app_path: (str) A path to app.
  42. """
  43. return subprocess.check_output([
  44. '/usr/libexec/PlistBuddy',
  45. '-c',
  46. 'Print:CFBundleIdentifier',
  47. os.path.join(app_path, 'Info.plist'),
  48. ]).decode("utf-8").rstrip()
  49. def is_running_rosetta():
  50. """Returns whether Python is being translated by Rosetta.
  51. Returns:
  52. True if the Python interpreter is being run as an x86_64 binary on an arm64
  53. macOS machine. False if it is running as an arm64 binary, or if it is
  54. running on an Intel machine.
  55. """
  56. translated = subprocess.check_output(
  57. ['sysctl', '-i', '-b', 'sysctl.proc_translated'])
  58. # "sysctl -b" is expected to return a 4-byte integer response. 1 means the
  59. # current process is running under Rosetta, 0 means it is not. On x86_64
  60. # machines, this variable does not exist at all, so "-i" is used to return a
  61. # 0-byte response instead of throwing an error.
  62. if len(translated) != 4:
  63. return False
  64. return struct.unpack('i', translated)[0] > 0
  65. class GTestsApp(object):
  66. """Gtests app to run.
  67. Stores data about egtests:
  68. test_app: full path to an app.
  69. """
  70. def __init__(self, test_app, **kwargs):
  71. """Initialize Egtests.
  72. Args:
  73. test_app: (str) full path to egtests app.
  74. (Following are potential args in **kwargs)
  75. included_tests: (list) Specific tests to run
  76. E.g.
  77. [ 'TestCaseClass1/testMethod1', 'TestCaseClass2/testMethod2']
  78. excluded_tests: (list) Specific tests not to run
  79. E.g.
  80. [ 'TestCaseClass1', 'TestCaseClass2/testMethod2']
  81. test_args: List of strings to pass as arguments to the test when
  82. launching.
  83. env_vars: List of environment variables to pass to the test itself.
  84. release: (bool) Whether the app is release build.
  85. repeat_count: (int) Number of times to run each test case.
  86. inserted_libs: List of libraries to insert when running the test.
  87. Raises:
  88. AppNotFoundError: If the given app does not exist
  89. """
  90. if not os.path.exists(test_app):
  91. raise test_runner.AppNotFoundError(test_app)
  92. self.test_app_path = test_app
  93. self.project_path = os.path.dirname(self.test_app_path)
  94. self.test_args = kwargs.get('test_args') or []
  95. self.env_vars = {}
  96. for env_var in kwargs.get('env_vars') or []:
  97. env_var = env_var.split('=', 1)
  98. self.env_vars[env_var[0]] = None if len(env_var) == 1 else env_var[1]
  99. # Keep the initial included tests since creating target. Do not modify.
  100. self.initial_included_tests = kwargs.get('included_tests') or []
  101. # This may be modified between test launches.
  102. self.included_tests = kwargs.get('included_tests') or []
  103. # This may be modified between test launches.
  104. self.excluded_tests = kwargs.get('excluded_tests') or []
  105. self.disabled_tests = []
  106. self.module_name = os.path.splitext(os.path.basename(test_app))[0]
  107. self.release = kwargs.get('release')
  108. self.repeat_count = kwargs.get('repeat_count') or 1
  109. self.host_app_path = kwargs.get('host_app_path')
  110. self.inserted_libs = kwargs.get('inserted_libs') or []
  111. def remove_gtest_sharding_env_vars(self):
  112. """Removes sharding related env vars from self.env_vars."""
  113. for env_var_key in ['GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS']:
  114. self.env_vars.pop(env_var_key, None)
  115. def fill_xctest_run(self, out_dir):
  116. """Fills xctestrun file by egtests.
  117. Args:
  118. out_dir: (str) A path where xctestrun will store.
  119. Returns:
  120. A path to xctestrun file.
  121. """
  122. folder = os.path.abspath(os.path.join(out_dir, os.pardir))
  123. if not os.path.exists(folder):
  124. os.makedirs(folder)
  125. xctestrun = os.path.join(folder, 'run_%d.xctestrun' % int(time.time()))
  126. if not os.path.exists(xctestrun):
  127. with open(xctestrun, 'w'):
  128. pass
  129. # Creates a dict with data about egtests to run - fill all required fields:
  130. # egtests_module, egtest_app_path, egtests_xctest_path and
  131. # filtered tests if filter is specified.
  132. # Write data in temp xctest run file.
  133. plistlib.writePlist(self.fill_xctestrun_node(), xctestrun)
  134. return xctestrun
  135. @staticmethod
  136. def _replace_multiple_slashes(name):
  137. """Replace slashes with dots (.) except at the end."""
  138. count = name.count('/')
  139. if count == 0:
  140. return name
  141. return name.replace('/', '.', count - 1)
  142. def fill_xctestrun_node(self):
  143. """Fills only required nodes for egtests in xctestrun file.
  144. Returns:
  145. A node with filled required fields about egtests.
  146. """
  147. module = self.module_name + '_module'
  148. # If --run-with-custom-webkit is passed as a test arg, set up
  149. # DYLD_FRAMEWORK_PATH and DYLD_LIBRARY_PATH to load the custom webkit
  150. # modules.
  151. dyld_path = self.project_path
  152. if '--run-with-custom-webkit' in self.test_args:
  153. if self.host_app_path:
  154. webkit_path = os.path.join(self.host_app_path, 'WebKitFrameworks')
  155. else:
  156. webkit_path = os.path.join(self.test_app_path, 'WebKitFrameworks')
  157. dyld_path = dyld_path + ':' + webkit_path
  158. module_data = {
  159. 'TestBundlePath': self.test_app_path,
  160. 'TestHostPath': self.test_app_path,
  161. 'TestHostBundleIdentifier': get_bundle_id(self.test_app_path),
  162. 'TestingEnvironmentVariables': {
  163. 'DYLD_LIBRARY_PATH':
  164. '%s:__PLATFORMS__/iPhoneSimulator.platform/Developer/Library' %
  165. dyld_path,
  166. 'DYLD_FRAMEWORK_PATH':
  167. '%s:__PLATFORMS__/iPhoneSimulator.platform/'
  168. 'Developer/Library/Frameworks' % dyld_path,
  169. }
  170. }
  171. if self.inserted_libs:
  172. module_data['TestingEnvironmentVariables'][
  173. 'DYLD_INSERT_LIBRARIES'] = ':'.join(self.inserted_libs)
  174. xctestrun_data = {module: module_data}
  175. gtest_filter = []
  176. if self.included_tests or self.excluded_tests:
  177. gtest_filter = get_gtest_filter(self.included_tests, self.excluded_tests)
  178. # Removed previous gtest-filter if exists.
  179. self.test_args = [el for el in self.test_args
  180. if not el.startswith('--gtest_filter=')]
  181. self.test_args.append('--gtest_filter=%s' % gtest_filter)
  182. if self.repeat_count > 1:
  183. self.test_args.append('--gtest_repeat=%s' % self.repeat_count)
  184. if self.env_vars:
  185. xctestrun_data[module].update({'EnvironmentVariables': self.env_vars})
  186. if self.test_args:
  187. xctestrun_data[module].update({'CommandLineArguments': self.test_args})
  188. if self.excluded_tests:
  189. xctestrun_data[module].update({
  190. 'SkipTestIdentifiers': [
  191. self._replace_multiple_slashes(x) for x in self.excluded_tests
  192. ]
  193. })
  194. if self.included_tests:
  195. xctestrun_data[module].update({
  196. 'OnlyTestIdentifiers': [
  197. self._replace_multiple_slashes(x) for x in self.included_tests
  198. ]
  199. })
  200. return xctestrun_data
  201. def command(self, out_dir, destination, shards):
  202. """Returns the command that launches tests using xcodebuild.
  203. Format of command:
  204. xcodebuild test-without-building -xctestrun file.xctestrun \
  205. -parallel-testing-enabled YES -parallel-testing-worker-count %d% \
  206. [-destination "destination"] -resultBundlePath %output_path%
  207. Args:
  208. out_dir: (str) An output directory.
  209. destination: (str) A destination of running simulator.
  210. shards: (int) A number of shards.
  211. Returns:
  212. A list of strings forming the command to launch the test.
  213. """
  214. cmd = []
  215. if is_running_rosetta():
  216. cmd.extend(['arch', '-arch', 'arm64'])
  217. cmd.extend([
  218. 'xcodebuild', 'test-without-building', '-xctestrun',
  219. self.fill_xctest_run(out_dir), '-destination', destination,
  220. '-resultBundlePath', out_dir
  221. ])
  222. if shards > 1:
  223. cmd.extend([
  224. '-parallel-testing-enabled', 'YES', '-parallel-testing-worker-count',
  225. str(shards)
  226. ])
  227. return cmd
  228. def get_all_tests(self):
  229. """Gets all tests to run in this object."""
  230. # Method names that starts with test* and also are in *TestCase classes
  231. # but they are not test-methods.
  232. # TODO(crbug.com/982435): Rename not test methods with test-suffix.
  233. non_test_prefixes = [
  234. 'ChromeTestCase/testServer', 'FindInPageTestCase/testURL',
  235. 'setUpForTestCase'
  236. ]
  237. # TODO(crbug.com/1123681): Move all_tests to class var. Set all_tests,
  238. # disabled_tests values in initialization to avoid multiple calls to otool.
  239. all_tests = []
  240. # Only store the tests when there is the test arg.
  241. store_disabled_tests = OUTPUT_DISABLED_TESTS_TEST_ARG in self.test_args
  242. self.disabled_tests = []
  243. for test_class, test_method in shard_util.fetch_test_names(
  244. self.test_app_path,
  245. self.host_app_path,
  246. self.release,
  247. enabled_tests_only=False):
  248. test_name = '%s/%s' % (test_class, test_method)
  249. if any(test_name.startswith(prefix) for prefix in non_test_prefixes):
  250. continue
  251. # |self.initial_included_tests| contains the tests to execute, which
  252. # may be a subset of all tests b/c of the iOS test sharding logic in
  253. # run.py. Filter by |self.initial_included_tests| if specified.
  254. # |self.initial_included_tests| might store test class or full name.
  255. included = self.initial_included_tests
  256. if not included or test_name in included or test_class in included:
  257. if test_method.startswith('test'):
  258. all_tests.append(test_name)
  259. elif store_disabled_tests:
  260. self.disabled_tests.append(test_name)
  261. return all_tests
  262. class EgtestsApp(GTestsApp):
  263. """Egtests to run.
  264. Stores data about egtests:
  265. egtests_app: full path to egtests app.
  266. project_path: root project folder.
  267. module_name: egtests module name.
  268. included_tests: List of tests to run.
  269. excluded_tests: List of tests not to run.
  270. """
  271. def __init__(self, egtests_app, **kwargs):
  272. """Initialize Egtests.
  273. Args:
  274. egtests_app: (str) full path to egtests app.
  275. (Following are potential args in **kwargs)
  276. included_tests: (list) Specific tests to run
  277. E.g.
  278. [ 'TestCaseClass1/testMethod1', 'TestCaseClass2/testMethod2']
  279. excluded_tests: (list) Specific tests not to run
  280. E.g.
  281. [ 'TestCaseClass1', 'TestCaseClass2/testMethod2']
  282. test_args: List of strings to pass as arguments to the test when
  283. launching.
  284. env_vars: List of environment variables to pass to the test itself.
  285. host_app_path: (str) full path to host app.
  286. inserted_libs: List of libraries to insert when running the test.
  287. repeat_count: (int) Number of times to run each test case.
  288. Raises:
  289. AppNotFoundError: If the given app does not exist
  290. """
  291. inserted_libs = list(kwargs.get('inserted_libs') or [])
  292. inserted_libs.append('__PLATFORMS__/iPhoneSimulator.platform/Developer/'
  293. 'usr/lib/libXCTestBundleInject.dylib')
  294. kwargs['inserted_libs'] = inserted_libs
  295. super(EgtestsApp, self).__init__(egtests_app, **kwargs)
  296. def _xctest_path(self):
  297. """Gets xctest-file from egtests/PlugIns folder.
  298. Returns:
  299. A path for xctest in the format of /PlugIns/file.xctest
  300. Raises:
  301. PlugInsNotFoundError: If no PlugIns folder found in egtests.app.
  302. XCTestPlugInNotFoundError: If no xctest-file found in PlugIns.
  303. """
  304. plugins_dir = os.path.join(self.test_app_path, 'PlugIns')
  305. if not os.path.exists(plugins_dir):
  306. raise test_runner.PlugInsNotFoundError(plugins_dir)
  307. plugin_xctest = None
  308. if os.path.exists(plugins_dir):
  309. for plugin in os.listdir(plugins_dir):
  310. if plugin.endswith('.xctest'):
  311. plugin_xctest = os.path.join(plugins_dir, plugin)
  312. if not plugin_xctest:
  313. raise test_runner.XCTestPlugInNotFoundError(plugin_xctest)
  314. return plugin_xctest.replace(self.test_app_path, '')
  315. def command(self, out_dir, destination, shards):
  316. """Returns the command that launches tests for EG Tests.
  317. See details in parent class method docstring. This method appends the
  318. command line switch if test repeat is required.
  319. """
  320. cmd = super(EgtestsApp, self).command(out_dir, destination, shards)
  321. if self.repeat_count > 1:
  322. if xcode_util.using_xcode_13_or_higher():
  323. cmd += ['-test-iterations', str(self.repeat_count)]
  324. else:
  325. raise test_runner_errors.XcodeUnsupportedFeatureError(
  326. 'Test repeat is only supported in Xcode 13 or higher!')
  327. return cmd
  328. def fill_xctestrun_node(self):
  329. """Fills only required nodes for egtests in xctestrun file.
  330. Returns:
  331. A node with filled required fields about egtests.
  332. """
  333. xctestrun_data = super(EgtestsApp, self).fill_xctestrun_node()
  334. module_data = xctestrun_data[self.module_name + '_module']
  335. module_data['TestBundlePath'] = '__TESTHOST__%s' % self._xctest_path()
  336. module_data['TestingEnvironmentVariables'][
  337. 'XCInjectBundleInto'] = '__TESTHOST__/%s' % self.module_name
  338. if self.host_app_path:
  339. # Module data specific to EG2 tests
  340. module_data['IsUITestBundle'] = True
  341. module_data['IsXCTRunnerHostedTestBundle'] = True
  342. module_data['UITargetAppPath'] = '%s' % self.host_app_path
  343. module_data['UITargetAppBundleIdentifier'] = get_bundle_id(
  344. self.host_app_path)
  345. # Special handling for Xcode10.2
  346. dependent_products = [
  347. module_data['UITargetAppPath'],
  348. module_data['TestBundlePath'],
  349. module_data['TestHostPath']
  350. ]
  351. module_data['DependentProductPaths'] = dependent_products
  352. # Module data specific to EG1 tests
  353. else:
  354. module_data['IsAppHostedTestBundle'] = True
  355. return xctestrun_data
  356. class DeviceXCTestUnitTestsApp(GTestsApp):
  357. """XCTest hosted unit tests to run on devices.
  358. This is for the XCTest framework hosted unit tests running on devices.
  359. Stores data about tests:
  360. tests_app: full path to tests app.
  361. project_path: root project folder.
  362. module_name: egtests module name.
  363. included_tests: List of tests to run.
  364. excluded_tests: List of tests not to run.
  365. """
  366. def __init__(self, tests_app, **kwargs):
  367. """Initialize the class.
  368. Args:
  369. tests_app: (str) full path to tests app.
  370. (Following are potential args in **kwargs)
  371. included_tests: (list) Specific tests to run
  372. E.g.
  373. [ 'TestCaseClass1/testMethod1', 'TestCaseClass2/testMethod2']
  374. excluded_tests: (list) Specific tests not to run
  375. E.g.
  376. [ 'TestCaseClass1', 'TestCaseClass2/testMethod2']
  377. test_args: List of strings to pass as arguments to the test when
  378. launching. Test arg to run as XCTest based unit test will be appended.
  379. env_vars: List of environment variables to pass to the test itself.
  380. repeat_count: (int) Number of times to run each test case.
  381. Raises:
  382. AppNotFoundError: If the given app does not exist
  383. """
  384. test_args = list(kwargs.get('test_args') or [])
  385. test_args.append('--enable-run-ios-unittests-with-xctest')
  386. kwargs['test_args'] = test_args
  387. super(DeviceXCTestUnitTestsApp, self).__init__(tests_app, **kwargs)
  388. # TODO(crbug.com/1077277): Refactor class structure and remove duplicate code.
  389. def _xctest_path(self):
  390. """Gets xctest-file from egtests/PlugIns folder.
  391. Returns:
  392. A path for xctest in the format of /PlugIns/file.xctest
  393. Raises:
  394. PlugInsNotFoundError: If no PlugIns folder found in egtests.app.
  395. XCTestPlugInNotFoundError: If no xctest-file found in PlugIns.
  396. """
  397. plugins_dir = os.path.join(self.test_app_path, 'PlugIns')
  398. if not os.path.exists(plugins_dir):
  399. raise test_runner.PlugInsNotFoundError(plugins_dir)
  400. plugin_xctest = None
  401. if os.path.exists(plugins_dir):
  402. for plugin in os.listdir(plugins_dir):
  403. if plugin.endswith('.xctest'):
  404. plugin_xctest = os.path.join(plugins_dir, plugin)
  405. if not plugin_xctest:
  406. raise test_runner.XCTestPlugInNotFoundError(plugin_xctest)
  407. return plugin_xctest.replace(self.test_app_path, '')
  408. def fill_xctestrun_node(self):
  409. """Fills only required nodes for XCTest hosted unit tests in xctestrun file.
  410. Returns:
  411. A node with filled required fields about tests.
  412. """
  413. xctestrun_data = {
  414. 'TestTargetName': {
  415. 'IsAppHostedTestBundle': True,
  416. 'TestBundlePath': '__TESTHOST__%s' % self._xctest_path(),
  417. 'TestHostBundleIdentifier': get_bundle_id(self.test_app_path),
  418. 'TestHostPath': '%s' % self.test_app_path,
  419. 'TestingEnvironmentVariables': {
  420. 'DYLD_INSERT_LIBRARIES':
  421. '__TESTHOST__/Frameworks/libXCTestBundleInject.dylib',
  422. 'DYLD_LIBRARY_PATH':
  423. '__PLATFORMS__/iPhoneOS.platform/Developer/Library',
  424. 'DYLD_FRAMEWORK_PATH':
  425. '__PLATFORMS__/iPhoneOS.platform/Developer/'
  426. 'Library/Frameworks',
  427. 'XCInjectBundleInto':
  428. '__TESTHOST__/%s' % self.module_name
  429. }
  430. }
  431. }
  432. if self.env_vars:
  433. xctestrun_data['TestTargetName'].update(
  434. {'EnvironmentVariables': self.env_vars})
  435. if self.included_tests or self.excluded_tests:
  436. gtest_filter = get_gtest_filter(self.included_tests, self.excluded_tests)
  437. # Removed previous gtest-filter if exists.
  438. self.test_args = [
  439. el for el in self.test_args if not el.startswith('--gtest_filter=')
  440. ]
  441. self.test_args.append('--gtest_filter=%s' % gtest_filter)
  442. if self.repeat_count > 1:
  443. self.test_args.append('--gtest_repeat=%s' % self.repeat_count)
  444. self.test_args.append('--gmock_verbose=error')
  445. xctestrun_data['TestTargetName'].update(
  446. {'CommandLineArguments': self.test_args})
  447. return xctestrun_data
  448. class SimulatorXCTestUnitTestsApp(GTestsApp):
  449. """XCTest hosted unit tests to run on simulators.
  450. This is for the XCTest framework hosted unit tests running on simulators.
  451. Stores data about tests:
  452. tests_app: full path to tests app.
  453. project_path: root project folder.
  454. module_name: egtests module name.
  455. included_tests: List of tests to run.
  456. excluded_tests: List of tests not to run.
  457. """
  458. def __init__(self, tests_app, **kwargs):
  459. """Initialize the class.
  460. Args:
  461. tests_app: (str) full path to tests app.
  462. (Following are potential args in **kwargs)
  463. included_tests: (list) Specific tests to run
  464. E.g.
  465. [ 'TestCaseClass1/testMethod1', 'TestCaseClass2/testMethod2']
  466. excluded_tests: (list) Specific tests not to run
  467. E.g.
  468. [ 'TestCaseClass1', 'TestCaseClass2/testMethod2']
  469. test_args: List of strings to pass as arguments to the test when
  470. launching. Test arg to run as XCTest based unit test will be appended.
  471. env_vars: List of environment variables to pass to the test itself.
  472. repeat_count: (int) Number of times to run each test case.
  473. Raises:
  474. AppNotFoundError: If the given app does not exist
  475. """
  476. test_args = list(kwargs.get('test_args') or [])
  477. test_args.append('--enable-run-ios-unittests-with-xctest')
  478. kwargs['test_args'] = test_args
  479. super(SimulatorXCTestUnitTestsApp, self).__init__(tests_app, **kwargs)
  480. # TODO(crbug.com/1077277): Refactor class structure and remove duplicate code.
  481. def _xctest_path(self):
  482. """Gets xctest-file from egtests/PlugIns folder.
  483. Returns:
  484. A path for xctest in the format of /PlugIns/file.xctest
  485. Raises:
  486. PlugInsNotFoundError: If no PlugIns folder found in egtests.app.
  487. XCTestPlugInNotFoundError: If no xctest-file found in PlugIns.
  488. """
  489. plugins_dir = os.path.join(self.test_app_path, 'PlugIns')
  490. if not os.path.exists(plugins_dir):
  491. raise test_runner.PlugInsNotFoundError(plugins_dir)
  492. plugin_xctest = None
  493. if os.path.exists(plugins_dir):
  494. for plugin in os.listdir(plugins_dir):
  495. if plugin.endswith('.xctest'):
  496. plugin_xctest = os.path.join(plugins_dir, plugin)
  497. if not plugin_xctest:
  498. raise test_runner.XCTestPlugInNotFoundError(plugin_xctest)
  499. return plugin_xctest.replace(self.test_app_path, '')
  500. def fill_xctestrun_node(self):
  501. """Fills only required nodes for XCTest hosted unit tests in xctestrun file.
  502. Returns:
  503. A node with filled required fields about tests.
  504. """
  505. xctestrun_data = {
  506. 'TestTargetName': {
  507. 'IsAppHostedTestBundle': True,
  508. 'TestBundlePath': '__TESTHOST__%s' % self._xctest_path(),
  509. 'TestHostBundleIdentifier': get_bundle_id(self.test_app_path),
  510. 'TestHostPath': '%s' % self.test_app_path,
  511. 'TestingEnvironmentVariables': {
  512. 'DYLD_INSERT_LIBRARIES':
  513. '__PLATFORMS__/iPhoneSimulator.platform/Developer/usr/lib/'
  514. 'libXCTestBundleInject.dylib',
  515. 'DYLD_LIBRARY_PATH':
  516. '__PLATFORMS__/iPhoneSimulator.platform/Developer/Library',
  517. 'DYLD_FRAMEWORK_PATH':
  518. '__PLATFORMS__/iPhoneSimulator.platform/Developer/'
  519. 'Library/Frameworks',
  520. 'XCInjectBundleInto':
  521. '__TESTHOST__/%s' % self.module_name
  522. }
  523. }
  524. }
  525. if self.env_vars:
  526. xctestrun_data['TestTargetName'].update(
  527. {'EnvironmentVariables': self.env_vars})
  528. if self.included_tests or self.excluded_tests:
  529. gtest_filter = get_gtest_filter(self.included_tests, self.excluded_tests)
  530. # Removed previous gtest-filter if exists.
  531. self.test_args = [
  532. el for el in self.test_args if not el.startswith('--gtest_filter=')
  533. ]
  534. self.test_args.append('--gtest_filter=%s' % gtest_filter)
  535. if self.repeat_count > 1:
  536. self.test_args.append('--gtest_repeat=%s' % self.repeat_count)
  537. self.test_args.append('--gmock_verbose=error')
  538. xctestrun_data['TestTargetName'].update(
  539. {'CommandLineArguments': self.test_args})
  540. return xctestrun_data