Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(205)

Side by Side Diff: ios/build/bots/scripts/test_runner.py

Issue 2262733004: Add iOS device test runner (Closed) Base URL: https://chromium.googlesource.com/chromium/src@master
Patch Set: Minor fixes Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ios/build/bots/scripts/run.py ('k') | ios/build/bots/scripts/test_runner_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2016 The Chromium Authors. All rights reserved. 1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Test runners for iOS.""" 5 """Test runners for iOS."""
6 6
7 import argparse 7 import argparse
8 import collections 8 import collections
9 import errno
9 import os 10 import os
10 import shutil 11 import shutil
11 import subprocess 12 import subprocess
12 import sys 13 import sys
13 import tempfile 14 import tempfile
14 import time 15 import time
15 16
16 import find_xcode 17 import find_xcode
17 import gtest_utils 18 import gtest_utils
18 19
(...skipping 10 matching lines...) Expand all
29 30
30 class AppLaunchError(TestRunnerError): 31 class AppLaunchError(TestRunnerError):
31 """The app failed to launch.""" 32 """The app failed to launch."""
32 pass 33 pass
33 34
34 35
35 class AppNotFoundError(TestRunnerError): 36 class AppNotFoundError(TestRunnerError):
36 """The requested app was not found.""" 37 """The requested app was not found."""
37 def __init__(self, app_path): 38 def __init__(self, app_path):
38 super(AppNotFoundError, self).__init__( 39 super(AppNotFoundError, self).__init__(
39 'App does not exist: %s' % app_path) 40 'App does not exist: %s' % app_path)
41
42
43 class DeviceDetectionError(TestRunnerError):
44 """Unexpected number of devices detected."""
45 def __init__(self, udids):
46 super(DeviceDetectionError, self).__init__(
47 'Expected one device, found %s:\n%s' % (len(udids), '\n'.join(udids)))
40 48
41 49
42 class SimulatorNotFoundError(TestRunnerError): 50 class SimulatorNotFoundError(TestRunnerError):
43 """The given simulator binary was not found.""" 51 """The given simulator binary was not found."""
44 def __init__(self, iossim_path): 52 def __init__(self, iossim_path):
45 super(SimulatorNotFoundError, self).__init__( 53 super(SimulatorNotFoundError, self).__init__(
46 'Simulator does not exist: %s' % iossim_path) 54 'Simulator does not exist: %s' % iossim_path)
47 55
48 56
49 class XcodeVersionNotFound(TestRunnerError): 57 class XcodeVersionNotFound(TestRunnerError):
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
89 # e.g. -a:b:c matches everything except a, b, c. 97 # e.g. -a:b:c matches everything except a, b, c.
90 test_filter = ':'.join(test for test in tests) 98 test_filter = ':'.join(test for test in tests)
91 if invert: 99 if invert:
92 return '-%s' % test_filter 100 return '-%s' % test_filter
93 return test_filter 101 return test_filter
94 102
95 103
96 class TestRunner(object): 104 class TestRunner(object):
97 """Base class containing common functionality.""" 105 """Base class containing common functionality."""
98 106
99 def __init__(self, app_path, xcode_version, out_dir, test_args=None): 107 def __init__(
108 self, app_path, xcode_version, out_dir, env_vars=None, test_args=None):
100 """Initializes a new instance of this class. 109 """Initializes a new instance of this class.
101 110
102 Args: 111 Args:
103 app_path: Path to the compiled .app to run. 112 app_path: Path to the compiled .app to run.
104 xcode_version: Version of Xcode to use when running the test. 113 xcode_version: Version of Xcode to use when running the test.
105 out_dir: Directory to emit test data into. 114 out_dir: Directory to emit test data into.
115 env_vars: List of environment variables to pass to the test itself.
106 test_args: List of strings to pass as arguments to the test when 116 test_args: List of strings to pass as arguments to the test when
107 launching. 117 launching.
108 118
109 Raises: 119 Raises:
110 AppNotFoundError: If the given app does not exist. 120 AppNotFoundError: If the given app does not exist.
111 XcodeVersionNotFoundError: If the given Xcode version does not exist. 121 XcodeVersionNotFoundError: If the given Xcode version does not exist.
112 """ 122 """
113 if not os.path.exists(app_path): 123 if not os.path.exists(app_path):
114 raise AppNotFoundError(app_path) 124 raise AppNotFoundError(app_path)
115 125
116 if not find_xcode.find_xcode(xcode_version)['found']: 126 if not find_xcode.find_xcode(xcode_version)['found']:
117 raise XcodeVersionNotFoundError(xcode_version) 127 raise XcodeVersionNotFoundError(xcode_version)
118 128
119 if not os.path.exists(out_dir): 129 if not os.path.exists(out_dir):
120 os.makedirs(out_dir) 130 os.makedirs(out_dir)
121 131
122 self.app_name = os.path.splitext(os.path.split(app_path)[-1])[0] 132 self.app_name = os.path.splitext(os.path.split(app_path)[-1])[0]
123 self.app_path = app_path 133 self.app_path = app_path
124 self.cfbundleid = subprocess.check_output([ 134 self.cfbundleid = subprocess.check_output([
125 '/usr/libexec/PlistBuddy', 135 '/usr/libexec/PlistBuddy',
126 '-c', 'Print:CFBundleIdentifier', 136 '-c', 'Print:CFBundleIdentifier',
127 os.path.join(app_path, 'Info.plist'), 137 os.path.join(app_path, 'Info.plist'),
128 ]).rstrip() 138 ]).rstrip()
139 self.env_vars = env_vars or []
129 self.logs = collections.OrderedDict() 140 self.logs = collections.OrderedDict()
130 self.out_dir = out_dir 141 self.out_dir = out_dir
131 self.test_args = test_args or [] 142 self.test_args = test_args or []
143 self.xcode_version = xcode_version
132 144
133 def get_launch_command(self, test_filter=None, invert=False): 145 def get_launch_command(self, test_filter=None, invert=False):
134 """Returns the command that can be used to launch the test app. 146 """Returns the command that can be used to launch the test app.
135 147
136 Args: 148 Args:
137 test_filter: List of test cases to filter. 149 test_filter: List of test cases to filter.
138 invert: Whether to invert the filter or not. Inverted, the filter will 150 invert: Whether to invert the filter or not. Inverted, the filter will
139 match everything except the given test cases. 151 match everything except the given test cases.
140 152
141 Returns: 153 Returns:
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 """Class for running tests on iossim.""" 272 """Class for running tests on iossim."""
261 273
262 def __init__( 274 def __init__(
263 self, 275 self,
264 app_path, 276 app_path,
265 iossim_path, 277 iossim_path,
266 platform, 278 platform,
267 version, 279 version,
268 xcode_version, 280 xcode_version,
269 out_dir, 281 out_dir,
282 env_vars=None,
270 test_args=None, 283 test_args=None,
271 ): 284 ):
272 """Initializes a new instance of this class. 285 """Initializes a new instance of this class.
273 286
274 Args: 287 Args:
275 app_path: Path to the compiled .app or .ipa to run. 288 app_path: Path to the compiled .app or .ipa to run.
276 iossim_path: Path to the compiled iossim binary to use. 289 iossim_path: Path to the compiled iossim binary to use.
277 platform: Name of the platform to simulate. Supported values can be found 290 platform: Name of the platform to simulate. Supported values can be found
278 by running "iossim -l". e.g. "iPhone 5s", "iPad Retina". 291 by running "iossim -l". e.g. "iPhone 5s", "iPad Retina".
279 version: Version of iOS the platform should be running. Supported values 292 version: Version of iOS the platform should be running. Supported values
280 can be found by running "iossim -l". e.g. "9.3", "8.2", "7.1". 293 can be found by running "iossim -l". e.g. "9.3", "8.2", "7.1".
281 xcode_version: Version of Xcode to use when running the test. 294 xcode_version: Version of Xcode to use when running the test.
282 out_dir: Directory to emit test data into. 295 out_dir: Directory to emit test data into.
296 env_vars: List of environment variables to pass to the test itself.
283 test_args: List of strings to pass as arguments to the test when 297 test_args: List of strings to pass as arguments to the test when
284 launching. 298 launching.
285 299
286 Raises: 300 Raises:
287 AppNotFoundError: If the given app does not exist. 301 AppNotFoundError: If the given app does not exist.
288 XcodeVersionNotFoundError: If the given Xcode version does not exist. 302 XcodeVersionNotFoundError: If the given Xcode version does not exist.
289 """ 303 """
290 super(SimulatorTestRunner, self).__init__( 304 super(SimulatorTestRunner, self).__init__(
291 app_path, xcode_version, out_dir, test_args=test_args) 305 app_path,
306 xcode_version,
307 out_dir,
308 env_vars=env_vars,
309 test_args=test_args,
310 )
292 311
293 if not os.path.exists(iossim_path): 312 if not os.path.exists(iossim_path):
294 raise SimulatorNotFoundError(iossim_path) 313 raise SimulatorNotFoundError(iossim_path)
295 314
296 self.homedir = '' 315 self.homedir = ''
297 self.iossim_path = iossim_path 316 self.iossim_path = iossim_path
298 self.platform = platform 317 self.platform = platform
299 self.start_time = None 318 self.start_time = None
300 self.version = version 319 self.version = version
301 320
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
410 '-s', self.version, 429 '-s', self.version,
411 '-t', '120', 430 '-t', '120',
412 '-u', self.homedir, 431 '-u', self.homedir,
413 ] 432 ]
414 args = [] 433 args = []
415 434
416 if test_filter: 435 if test_filter:
417 kif_filter = get_kif_test_filter(test_filter, invert=invert) 436 kif_filter = get_kif_test_filter(test_filter, invert=invert)
418 gtest_filter = get_gtest_filter(test_filter, invert=invert) 437 gtest_filter = get_gtest_filter(test_filter, invert=invert)
419 cmd.extend(['-e', 'GKIF_SCENARIO_FILTER=%s' % kif_filter]) 438 cmd.extend(['-e', 'GKIF_SCENARIO_FILTER=%s' % kif_filter])
420 args.append('--gtest_filter=%s' % gtest_filter) 439
440 if self.xcode_version == '8.0':
441 args.extend(['-c', '--gtest_filter=%s' % gtest_filter])
442 else:
443 args.append('--gtest_filter=%s' % gtest_filter)
444
445 for env_var in self.env_vars:
446 cmd.extend(['-e', env_var])
421 447
422 cmd.append(self.app_path) 448 cmd.append(self.app_path)
423 cmd.extend(self.test_args) 449 cmd.extend(self.test_args)
424 cmd.extend(args) 450 cmd.extend(args)
425 return cmd 451 return cmd
452
453
454 class DeviceTestRunner(TestRunner):
455 """Class for running tests on devices."""
456
457 def __init__(
458 self, app_path, xcode_version, out_dir, env_vars=None, test_args=None):
459 """Initializes a new instance of this class.
460
461 Args:
462 app_path: Path to the compiled .app to run.
463 xcode_version: Version of Xcode to use when running the test.
464 out_dir: Directory to emit test data into.
465 env_vars: List of environment variables to pass to the test itself.
466 test_args: List of strings to pass as arguments to the test when
467 launching.
468
469 Raises:
470 AppNotFoundError: If the given app does not exist.
471 XcodeVersionNotFoundError: If the given Xcode version does not exist.
472 """
473 super(DeviceTestRunner, self).__init__(
474 app_path, xcode_version, out_dir, env_vars=env_vars, test_args=test_args)
475
476 self.udid = subprocess.check_output(['idevice_id', '--list']).rstrip()
477 if len(self.udid.splitlines()) != 1:
478 raise DeviceDetectionError(self.udid)
479
480 def uninstall_apps(self):
481 """Uninstalls all apps found on the device."""
482 for app in subprocess.check_output(
483 ['idevicefs', '--udid', self.udid, 'ls', '@']).splitlines():
484 subprocess.check_call(
485 ['ideviceinstaller', '--udid', self.udid, '--uninstall', app])
486
487 def install_app(self):
488 """Installs the app."""
489 subprocess.check_call(
490 ['ideviceinstaller', '--udid', self.udid, '--install', self.app_path])
491
492 def set_up(self):
493 """Performs setup actions which must occur prior to every test launch."""
494 self.uninstall_apps()
495 self.install_app()
496
497 def extract_test_data(self):
498 """Extracts data emitted by the test."""
499 subprocess.check_call([
500 'idevicefs',
501 '--udid', self.udid,
502 'pull',
503 '@%s/Documents' % self.cfbundleid,
504 os.path.join(self.out_dir, 'Documents'),
505 ])
506
507 def tear_down(self):
508 """Performs cleanup actions which must occur after every test launch."""
509 self.extract_test_data()
510 self.screenshot_desktop()
511 self.uninstall_apps()
512
513 def get_launch_command(self, test_filter=None, invert=False):
514 """Returns the command that can be used to launch the test app.
515
516 Args:
517 test_filter: List of test cases to filter.
518 invert: Whether to invert the filter or not. Inverted, the filter will
519 match everything except the given test cases.
520
521 Returns:
522 A list of strings forming the command to launch the test.
523 """
524 cmd = [
525 'idevice-app-runner',
526 '--udid', self.udid,
527 '--start', self.cfbundleid,
528 ]
529 args = []
530
531 if test_filter:
532 kif_filter = get_kif_test_filter(test_filter, invert=invert)
533 gtest_filter = get_gtest_filter(test_filter, invert=invert)
534 cmd.extend(['-D', 'GKIF_SCENARIO_FILTER=%s' % kif_filter])
535 args.append('--gtest-filter=%s' % gtest_filter)
536
537 for env_var in self.env_vars:
538 cmd.extend(['-D', env_var])
539
540 if args or self.test_args:
541 cmd.append('--args')
542 cmd.extend(self.test_args)
543 cmd.extend(args)
544
545 return cmd
OLDNEW
« no previous file with comments | « ios/build/bots/scripts/run.py ('k') | ios/build/bots/scripts/test_runner_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698