• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import <Foundation/Foundation.h>
6#include <getopt.h>
7
8#include <string>
9
10namespace {
11
12void PrintUsage() {
13  fprintf(
14      stderr,
15      "Usage: iossim [-d device] [-s sdk_version] <app_path> <xctest_path>\n"
16      "  where <app_path> is the path to the .app directory and <xctest_path> "
17      "is the path to an optional xctest bundle.\n"
18      "Options:\n"
19      "  -u  Specifies the device udid to use. Will use -d, -s values to get "
20      "devices if not specified.\n"
21      "  -d  Specifies the device (must be one of the values from the iOS "
22      "Simulator's Hardware -> Device menu. Defaults to 'iPhone 6s'.\n"
23      "  -w  Wipe the device's contents and settings before running the "
24      "test.\n"
25      "  -e  Specifies an environment key=value pair that will be"
26      " set in the simulated application's environment.\n"
27      "  -t  Specifies a test or test suite that should be included in the "
28      "test run. All other tests will be excluded from this run. This is "
29      "incompatible with -i.\n"
30      "  -c  Specifies command line flags to pass to application.\n"
31      "  -p  Print the device's home directory, does not run a test.\n"
32      "  -s  Specifies the SDK version to use (e.g '9.3'). Will use system "
33      "default if not specified.\n"
34      "  -v  Be more verbose, showing all the xcrun commands we call\n"
35      "  -k  When to kill the iOS Simulator : before, after, both, never "
36      "(default: both)\n"
37      "  -i  Use iossim instead of xcodebuild (disables all xctest "
38      "features). This is incompatible with -t.\n");
39}
40
41// Exit status codes.
42const int kExitSuccess = EXIT_SUCCESS;
43const int kExitInvalidArguments = 2;
44
45void LogError(NSString* format, ...) {
46  va_list list;
47  va_start(list, format);
48
49  NSString* message = [[NSString alloc] initWithFormat:format arguments:list];
50
51  NSLog(@"ERROR: %@", message);
52
53  va_end(list);
54}
55
56}
57
58typedef enum {
59  KILL_NEVER = 0,
60  KILL_BEFORE = 1 << 0,
61  KILL_AFTER = 1 << 1,
62  KILL_BOTH = KILL_BEFORE | KILL_AFTER,
63} SimulatorKill;
64
65// See https://stackoverflow.com/a/51895129 and
66// https://github.com/facebook/xctool/pull/159/files.
67@interface NSTask (PrivateAPI)
68- (void)setStartsNewProcessGroup:(BOOL)startsNewProcessGroup;
69@end
70
71// Wrap boiler plate calls to xcrun NSTasks.
72@interface XCRunTask : NSObject
73- (instancetype)initWithArguments:(NSArray*)arguments;
74- (void)run:(bool)verbose;
75- (void)launch:(bool)verbose;
76- (void)setStandardOutput:(id)output;
77- (void)setStandardError:(id)error;
78- (int)terminationStatus;
79@end
80
81@implementation XCRunTask {
82  NSTask* __strong _task;
83}
84
85- (instancetype)initWithArguments:(NSArray*)arguments {
86  self = [super init];
87  if (self) {
88    _task = [[NSTask alloc] init];
89    [_task setStartsNewProcessGroup:NO];
90    _task.launchPath = @"/usr/bin/xcrun";
91    _task.arguments = arguments;
92  }
93  return self;
94}
95
96- (void)setStandardOutput:(id)output {
97  _task.standardOutput = output;
98}
99
100- (void)setStandardError:(id)error {
101  _task.standardError = error;
102}
103
104- (int)terminationStatus {
105  return _task.terminationStatus;
106}
107
108- (void)run:(bool)verbose {
109  if (verbose) {
110    NSLog(@"Running xcrun %@", [_task.arguments componentsJoinedByString:@" "]);
111  }
112  [_task launch];
113  [_task waitUntilExit];
114}
115
116- (void)launch:(bool)verbose {
117  if (verbose) {
118    NSLog(@"Running xcrun %@", [_task.arguments componentsJoinedByString:@" "]);
119  }
120  [_task launch];
121}
122
123- (void)waitUntilExit {
124  [_task waitUntilExit];
125}
126
127@end
128
129// Return array of available iOS runtime dictionaries.  Unavailable (old Xcode
130// versions) or other runtimes (tvOS, watchOS) are removed.
131NSArray* Runtimes(NSDictionary* simctl_list) {
132  NSMutableArray* runtimes = [simctl_list[@"runtimes"] mutableCopy];
133  for (NSDictionary* runtime in simctl_list[@"runtimes"]) {
134    BOOL available =
135        [runtime[@"availability"] isEqualToString:@"(available)"] ||
136        runtime[@"isAvailable"];
137
138    if (![runtime[@"identifier"]
139            hasPrefix:@"com.apple.CoreSimulator.SimRuntime.iOS"] ||
140        !available) {
141      [runtimes removeObject:runtime];
142    }
143  }
144  return runtimes;
145}
146
147// Return array of device dictionaries.
148NSArray* Devices(NSDictionary* simctl_list) {
149  NSMutableArray* devicetypes = [simctl_list[@"devicetypes"] mutableCopy];
150  for (NSDictionary* devicetype in simctl_list[@"devicetypes"]) {
151    if (![devicetype[@"identifier"]
152            hasPrefix:@"com.apple.CoreSimulator.SimDeviceType.iPad"] &&
153        ![devicetype[@"identifier"]
154            hasPrefix:@"com.apple.CoreSimulator.SimDeviceType.iPhone"]) {
155      [devicetypes removeObject:devicetype];
156    }
157  }
158  return devicetypes;
159}
160
161// Get list of devices, runtimes, etc from sim_ctl.
162NSDictionary* GetSimulatorList(bool verbose) {
163  XCRunTask* task =
164      [[XCRunTask alloc] initWithArguments:@[ @"simctl", @"list", @"-j" ]];
165  NSPipe* out = [NSPipe pipe];
166  task.standardOutput = out;
167
168  // In the rest of the this file we read from the pipe after -waitUntilExit
169  // (We normally wrap -launch and -waitUntilExit in one -run method).  However,
170  // on some swarming slaves this led to a hang on simctl's pipe.  Since the
171  // output of simctl is so instant, reading it before exit seems to work, and
172  // seems to avoid the hang.
173  [task launch:verbose];
174  NSData* data = [out.fileHandleForReading readDataToEndOfFile];
175  [task waitUntilExit];
176
177  NSError* error = nil;
178  return [NSJSONSerialization JSONObjectWithData:data
179                                         options:kNilOptions
180                                           error:&error];
181}
182
183// List supported runtimes and devices.
184void PrintSupportedDevices(NSDictionary* simctl_list) {
185  printf("\niOS devices:\n");
186  for (NSDictionary* type in Devices(simctl_list)) {
187    printf("%s\n", [type[@"name"] UTF8String]);
188  }
189  printf("\nruntimes:\n");
190  for (NSDictionary* runtime in Runtimes(simctl_list)) {
191    printf("%s\n", [runtime[@"version"] UTF8String]);
192  }
193}
194
195// Expand path to absolute path.
196NSString* ResolvePath(NSString* path) {
197  path = path.stringByExpandingTildeInPath;
198  path = path.stringByStandardizingPath;
199  const char* cpath = path.UTF8String;
200  char* resolved_name = nullptr;
201  char* abs_path = realpath(cpath, resolved_name);
202  if (abs_path == nullptr) {
203    return nil;
204  }
205  return @(abs_path);
206}
207
208// Search |simctl_list| for a udid matching |device_name| and |sdk_version|.
209NSString* GetDeviceBySDKAndName(NSDictionary* simctl_list,
210                                NSString* device_name,
211                                NSString* sdk_version) {
212  NSString* sdk = nil;
213  NSString* name = nil;
214  // Get runtime identifier based on version property to handle
215  // cases when version and identifier are not the same,
216  // e.g. below identifer is *13-2 but version is 13.2.2
217  // {
218  //   "version" : "13.2.2",
219  //   "bundlePath" : "path"
220  //   "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-13-2",
221  //   "buildversion" : "17K90"
222  // }
223  for (NSDictionary* runtime in Runtimes(simctl_list)) {
224    if ([runtime[@"version"] isEqualToString:sdk_version]) {
225      sdk = runtime[@"identifier"];
226      name = runtime[@"name"];
227      break;
228    }
229  }
230  if (sdk == nil) {
231    printf("\nDid not find Runtime with specified version.\n");
232    PrintSupportedDevices(simctl_list);
233    exit(kExitInvalidArguments);
234  }
235  NSArray* devices = [simctl_list[@"devices"] objectForKey:sdk];
236  if (devices == nil || devices.count == 0) {
237    // Specific for XCode 10.1 and lower,
238    // where name from 'runtimes' uses as a key in 'devices'.
239    devices = [simctl_list[@"devices"] objectForKey:name];
240  }
241  for (NSDictionary* device in devices) {
242    if ([device[@"name"] isEqualToString:device_name]) {
243      return device[@"udid"];
244    }
245  }
246  return nil;
247}
248
249// Create and return a device udid of |device| and |sdk_version|.
250NSString* CreateDeviceBySDKAndName(NSString* device,
251                                   NSString* sdk_version,
252                                   bool verbose) {
253  NSString* sdk = [@"iOS" stringByAppendingString:sdk_version];
254  XCRunTask* create = [[XCRunTask alloc]
255      initWithArguments:@[ @"simctl", @"create", device, device, sdk ]];
256  [create run:verbose];
257
258  NSDictionary* simctl_list = GetSimulatorList(verbose);
259  return GetDeviceBySDKAndName(simctl_list, device, sdk_version);
260}
261
262bool FindDeviceByUDID(NSDictionary* simctl_list, NSString* udid) {
263  NSDictionary* devices_table = simctl_list[@"devices"];
264  for (id runtimes in devices_table) {
265    NSArray* devices = devices_table[runtimes];
266    for (NSDictionary* device in devices) {
267      if ([device[@"udid"] isEqualToString:udid]) {
268        return true;
269      }
270    }
271  }
272  return false;
273}
274
275// Prints the HOME environment variable for a device.  Used by the bots to
276// package up all the test data.
277void PrintDeviceHome(NSString* udid, bool verbose) {
278  XCRunTask* task = [[XCRunTask alloc]
279      initWithArguments:@[ @"simctl", @"getenv", udid, @"HOME" ]];
280  [task run:verbose];
281}
282
283// Erase a device, used by the bots before a clean test run.
284void WipeDevice(NSString* udid, bool verbose) {
285  XCRunTask* shutdown =
286      [[XCRunTask alloc] initWithArguments:@[ @"simctl", @"shutdown", udid ]];
287  shutdown.standardOutput = nil;
288  shutdown.standardError = nil;
289  [shutdown run:verbose];
290
291  XCRunTask* erase =
292      [[XCRunTask alloc] initWithArguments:@[ @"simctl", @"erase", udid ]];
293  [erase run:verbose];
294}
295
296void KillSimulator(bool verbose) {
297  XCRunTask* task =
298      [[XCRunTask alloc] initWithArguments:@[ @"killall", @"Simulator" ]];
299  task.standardOutput = nil;
300  task.standardError = nil;
301  [task run:verbose];
302}
303
304NSString* GetBundleIdentifierFromPath(NSString* app_path) {
305  NSFileManager* file_manager = [NSFileManager defaultManager];
306  NSString* info_plist_path =
307      [app_path stringByAppendingPathComponent:@"Info.plist"];
308  if (![file_manager fileExistsAtPath:info_plist_path]) {
309    return nil;
310  }
311
312  NSDictionary* info_dictionary =
313      [NSDictionary dictionaryWithContentsOfFile:info_plist_path];
314  NSString* bundle_identifier = info_dictionary[@"CFBundleIdentifier"];
315  return bundle_identifier;
316}
317
318int RunSimCtl(NSArray* arguments, bool verbose) {
319  XCRunTask* task = [[XCRunTask alloc]
320      initWithArguments:[@[ @"simctl" ]
321                            arrayByAddingObjectsFromArray:arguments]];
322  [task run:verbose];
323  int ret = [task terminationStatus];
324  if (ret) {
325    NSLog(@"Warning: the following command failed: xcrun simctl %@",
326          [arguments componentsJoinedByString:@" "]);
327  }
328  return ret;
329}
330
331void PrepareWebTests(NSString* udid, NSString* app_path, bool verbose) {
332  NSString* bundle_identifier = GetBundleIdentifierFromPath(app_path);
333
334  RunSimCtl(@[ @"uninstall", udid, bundle_identifier ], verbose);
335  RunSimCtl(@[ @"install", udid, app_path ], verbose);
336}
337
338int RunWebTest(NSString* app_path,
339               NSString* udid,
340               NSMutableArray* cmd_args,
341               bool verbose) {
342  NSMutableArray* arguments = [NSMutableArray array];
343  [arguments addObject:@"simctl"];
344  [arguments addObject:@"launch"];
345  [arguments addObject:@"--console"];
346  [arguments addObject:@"--terminate-running-process"];
347  [arguments addObject:udid];
348  [arguments addObject:GetBundleIdentifierFromPath(app_path)];
349  if (cmd_args.count == 1) {
350    for (NSString* arg in [cmd_args[0] componentsSeparatedByString:@" "]) {
351      [arguments addObject:arg];
352    }
353  }
354  [arguments addObject:@"-"];
355  XCRunTask* task = [[XCRunTask alloc] initWithArguments:arguments];
356
357  // The following stderr message causes a lot of test faiures on the web
358  // tests. Strip the message here.
359  NSArray* ignore_strings = @[ @"Class SwapLayerEAGL" ];
360  NSPipe* stderr_pipe = [NSPipe pipe];
361  stderr_pipe.fileHandleForReading.readabilityHandler =
362      ^(NSFileHandle* handle) {
363        NSString* log = [[NSString alloc] initWithData:handle.availableData
364                                              encoding:NSUTF8StringEncoding];
365        for (NSString* ignore_string in ignore_strings) {
366          if ([log rangeOfString:ignore_string].location != NSNotFound) {
367            return;
368          }
369        }
370        fprintf(stderr, "%s", log.UTF8String);
371      };
372  task.standardError = stderr_pipe;
373
374  [task run:verbose];
375  return [task terminationStatus];
376}
377
378bool isSimDeviceBooted(NSDictionary* simctl_list, NSString* udid) {
379  for (NSString* sdk in simctl_list[@"devices"]) {
380    for (NSDictionary* device in simctl_list[@"devices"][sdk]) {
381      if ([device[@"udid"] isEqualToString:udid]) {
382        if ([device[@"state"] isEqualToString:@"Booted"]) {
383          return true;
384        }
385      }
386    }
387  }
388  return false;
389}
390
391int SimpleRunApplication(NSString* app_path,
392                         NSString* udid,
393                         NSMutableArray* cmd_args,
394                         bool verbose) {
395  NSString* bundle_id = GetBundleIdentifierFromPath(app_path);
396
397  RunSimCtl(@[ @"uninstall", udid, bundle_id ], verbose);
398  RunSimCtl(@[ @"install", udid, app_path ], verbose);
399
400  NSArray* command = [@[
401    @"launch", @"--console", @"--terminate-running-process", udid, bundle_id
402  ] arrayByAddingObjectsFromArray:cmd_args];
403  return RunSimCtl(command, verbose);
404}
405
406int RunApplication(NSString* app_path,
407                   NSString* xctest_path,
408                   NSString* udid,
409                   NSMutableDictionary* app_env,
410                   NSMutableArray* cmd_args,
411                   NSMutableArray* tests_filter,
412                   bool verbose) {
413  NSString* tempFilePath = [NSTemporaryDirectory()
414      stringByAppendingPathComponent:NSUUID.UUID.UUIDString];
415  [NSFileManager.defaultManager createFileAtPath:tempFilePath
416                                        contents:nil
417                                      attributes:nil];
418
419  NSMutableDictionary* xctestrun = [NSMutableDictionary dictionary];
420  NSMutableDictionary* testTargetName = [NSMutableDictionary dictionary];
421
422  NSMutableDictionary* testingEnvironmentVariables =
423      [NSMutableDictionary dictionary];
424  testingEnvironmentVariables[@"IDEiPhoneInternalTestBundleName"] =
425      app_path.lastPathComponent;
426
427  testingEnvironmentVariables[@"DYLD_FRAMEWORK_PATH"] =
428      @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
429      @"iPhoneSimulator.platform/Developer/Library/Frameworks";
430  testingEnvironmentVariables[@"DYLD_LIBRARY_PATH"] =
431      @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
432      @"iPhoneSimulator.platform/Developer/Library";
433
434  if (xctest_path) {
435    testTargetName[@"TestBundlePath"] = xctest_path;
436    testingEnvironmentVariables[@"DYLD_INSERT_LIBRARIES"] =
437        @"__PLATFORMS__/iPhoneSimulator.platform/Developer/"
438        @"usr/lib/libXCTestBundleInject.dylib";
439    testingEnvironmentVariables[@"XCInjectBundleInto"] =
440        [NSString stringWithFormat:@"__TESTHOST__/%@",
441                                   app_path.lastPathComponent
442                                       .stringByDeletingPathExtension];
443  } else {
444    testTargetName[@"TestBundlePath"] = app_path;
445  }
446  testTargetName[@"TestHostPath"] = app_path;
447
448  if (app_env.count) {
449    testTargetName[@"EnvironmentVariables"] = app_env;
450  }
451
452  if (cmd_args.count > 0) {
453    testTargetName[@"CommandLineArguments"] = cmd_args;
454  }
455
456  if (tests_filter.count > 0) {
457    testTargetName[@"OnlyTestIdentifiers"] = tests_filter;
458  }
459
460  testTargetName[@"TestingEnvironmentVariables"] = testingEnvironmentVariables;
461  xctestrun[@"TestTargetName"] = testTargetName;
462
463  NSData* data = [NSPropertyListSerialization
464      dataWithPropertyList:xctestrun
465                    format:NSPropertyListXMLFormat_v1_0
466                   options:0
467                     error:nil];
468  [data writeToFile:tempFilePath atomically:YES];
469
470  XCRunTask* task = [[XCRunTask alloc] initWithArguments:@[
471    @"xcodebuild", @"-xctestrun", tempFilePath, @"-destination",
472    [@"platform=iOS Simulator,id=" stringByAppendingString:udid],
473    @"test-without-building"
474  ]];
475
476  if (!xctest_path) {
477    // The following stderr messages are meaningless on iossim when not running
478    // xctests and can be safely stripped.
479    NSArray* ignore_strings = @[
480      @"IDETestOperationsObserverErrorDomain", @"** TEST EXECUTE FAILED **"
481    ];
482    NSPipe* stderr_pipe = [NSPipe pipe];
483    stderr_pipe.fileHandleForReading.readabilityHandler =
484        ^(NSFileHandle* handle) {
485          NSString* log = [[NSString alloc] initWithData:handle.availableData
486                                                encoding:NSUTF8StringEncoding];
487          for (NSString* ignore_string in ignore_strings) {
488            if ([log rangeOfString:ignore_string].location != NSNotFound) {
489              return;
490            }
491          }
492          printf("%s", log.UTF8String);
493        };
494    task.standardError = stderr_pipe;
495  }
496  [task run:verbose];
497  return [task terminationStatus];
498}
499
500int main(int argc, char* const argv[]) {
501  NSString* app_path = nil;
502  NSString* xctest_path = nil;
503  NSString* udid = nil;
504  NSString* device_name = @"iPhone 6s";
505  bool wants_wipe = false;
506  bool wants_print_home = false;
507  bool wants_print_supported_devices = false;
508  bool run_web_test = false;
509  bool prepare_web_test = false;
510  NSString* sdk_version = nil;
511  NSMutableDictionary* app_env = [NSMutableDictionary dictionary];
512  NSMutableArray* cmd_args = [NSMutableArray array];
513  NSMutableArray* tests_filter = [NSMutableArray array];
514  bool verbose_commands = false;
515  SimulatorKill kill_simulator = KILL_BOTH;
516  bool wants_simple_iossim = false;
517
518  int c;
519  while ((c = getopt(argc, argv, "hs:d:u:t:e:c:pwlvk:i")) != -1) {
520    switch (c) {
521      case 's':
522        sdk_version = @(optarg);
523        break;
524      case 'd':
525        device_name = @(optarg);
526        break;
527      case 'u':
528        udid = @(optarg);
529        break;
530      case 'w':
531        wants_wipe = true;
532        break;
533      case 'c': {
534        NSString* cmd_arg = @(optarg);
535        [cmd_args addObject:cmd_arg];
536      } break;
537      case 't': {
538        NSString* test = @(optarg);
539        [tests_filter addObject:test];
540      } break;
541      case 'e': {
542        NSString* envLine = @(optarg);
543        NSRange range = [envLine rangeOfString:@"="];
544        if (range.location == NSNotFound) {
545          LogError(@"Invalid key=value argument for -e.");
546          PrintUsage();
547          exit(kExitInvalidArguments);
548        }
549        NSString* key = [envLine substringToIndex:range.location];
550        NSString* value = [envLine substringFromIndex:(range.location + 1)];
551        [app_env setObject:value forKey:key];
552      } break;
553      case 'p':
554        wants_print_home = true;
555        break;
556      case 'l':
557        wants_print_supported_devices = true;
558        break;
559      case 'v':
560        verbose_commands = true;
561        break;
562      case 'k': {
563        NSString* cmd_arg = @(optarg);
564        if ([cmd_arg isEqualToString:@"before"]) {
565          kill_simulator = KILL_BEFORE;
566        } else if ([cmd_arg isEqualToString:@"after"]) {
567          kill_simulator = KILL_AFTER;
568        } else if ([cmd_arg isEqualToString:@"both"]) {
569          kill_simulator = KILL_BOTH;
570        } else if ([cmd_arg isEqualToString:@"never"]) {
571          kill_simulator = KILL_NEVER;
572        } else {
573          PrintUsage();
574          exit(kExitInvalidArguments);
575        }
576      } break;
577      case 'i':
578        wants_simple_iossim = true;
579        break;
580      case 'h':
581        PrintUsage();
582        exit(kExitSuccess);
583      default:
584        PrintUsage();
585        exit(kExitInvalidArguments);
586    }
587  }
588
589  if (wants_simple_iossim && [tests_filter count]) {
590    LogError(@"Cannot specify tests with -t when using -i.");
591    exit(kExitInvalidArguments);
592  }
593
594  NSDictionary* simctl_list = GetSimulatorList(verbose_commands);
595
596  if (wants_print_supported_devices) {
597    PrintSupportedDevices(simctl_list);
598    exit(kExitSuccess);
599  }
600
601  if (!sdk_version) {
602    float sdk = 0;
603    for (NSDictionary* runtime in Runtimes(simctl_list)) {
604      sdk = fmax(sdk, [runtime[@"version"] floatValue]);
605    }
606    sdk_version = [NSString stringWithFormat:@"%0.1f", sdk];
607  }
608
609  NSRange range;
610  for (NSString* cmd_arg in cmd_args) {
611    range = [cmd_arg rangeOfString:@"--run-web-tests"];
612    if (range.location != NSNotFound) {
613      run_web_test = true;
614      break;
615    }
616  }
617
618  for (NSString* cmd_arg in cmd_args) {
619    range = [cmd_arg rangeOfString:@"--prepare-web-tests"];
620    if (range.location != NSNotFound) {
621      prepare_web_test = true;
622      break;
623    }
624  }
625
626  if (udid == nil) {
627    udid = GetDeviceBySDKAndName(simctl_list, device_name, sdk_version);
628    if (udid == nil) {
629      udid =
630          CreateDeviceBySDKAndName(device_name, sdk_version, verbose_commands);
631      if (udid == nil) {
632        LogError(@"Unable to find a device %@ with SDK %@.", device_name,
633                 sdk_version);
634        PrintSupportedDevices(simctl_list);
635        exit(kExitInvalidArguments);
636      }
637    }
638  } else {
639    if (!FindDeviceByUDID(simctl_list, udid)) {
640      LogError(
641          @"Unable to find a device with udid %@. Use 'xcrun simctl list' to "
642          @"see valid device udids.",
643          udid);
644      exit(kExitInvalidArguments);
645    }
646  }
647
648  if (wants_print_home) {
649    PrintDeviceHome(udid, verbose_commands);
650    exit(kExitSuccess);
651  }
652
653  if (kill_simulator & KILL_BEFORE) {
654    KillSimulator(verbose_commands);
655  }
656
657  if (wants_wipe) {
658    WipeDevice(udid, verbose_commands);
659    printf("Device wiped.\n");
660    exit(kExitSuccess);
661  }
662
663  // There should be at least one arg left, specifying the app path. Any
664  // additional args are passed as arguments to the app.
665  if (optind < argc) {
666    NSString* unresolved_app_path = [NSFileManager.defaultManager
667        stringWithFileSystemRepresentation:argv[optind]
668                                    length:strlen(argv[optind])];
669    app_path = ResolvePath(unresolved_app_path);
670    if (!app_path) {
671      LogError(@"Unable to resolve app_path %@", unresolved_app_path);
672      exit(kExitInvalidArguments);
673    }
674
675    if (++optind < argc) {
676      if (wants_simple_iossim) {
677        fprintf(stderr, "Warning: xctest_path ignored when using -i");
678      } else {
679        NSString* unresolved_xctest_path = [NSFileManager.defaultManager
680            stringWithFileSystemRepresentation:argv[optind]
681                                        length:strlen(argv[optind])];
682        xctest_path = ResolvePath(unresolved_xctest_path);
683        if (!xctest_path) {
684          LogError(@"Unable to resolve xctest_path %@", unresolved_xctest_path);
685          exit(kExitInvalidArguments);
686        }
687      }
688    }
689  } else {
690    LogError(@"Unable to parse command line arguments.");
691    PrintUsage();
692    exit(kExitInvalidArguments);
693  }
694
695  if ((prepare_web_test || run_web_test || wants_simple_iossim) &&
696      !isSimDeviceBooted(simctl_list, udid)) {
697    RunSimCtl(@[ @"boot", udid ], verbose_commands);
698  }
699
700  int return_code = -1;
701  if (prepare_web_test) {
702    PrepareWebTests(udid, app_path, verbose_commands);
703    return_code = kExitSuccess;
704  } else if (run_web_test) {
705    return_code = RunWebTest(app_path, udid, cmd_args, verbose_commands);
706  } else if (wants_simple_iossim) {
707    return_code =
708        SimpleRunApplication(app_path, udid, cmd_args, verbose_commands);
709  } else {
710    return_code = RunApplication(app_path, xctest_path, udid, app_env, cmd_args,
711                                 tests_filter, verbose_commands);
712  }
713
714  if (kill_simulator & KILL_AFTER) {
715    KillSimulator(verbose_commands);
716  }
717
718  return return_code;
719}
720