xref: /aosp_15_r20/external/cronet/testing/iossim/iossim.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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* filename =
414      [NSUUID.UUID.UUIDString stringByAppendingString:@".xctestrun"];
415  NSString* tempFilePath =
416      [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
417  [NSFileManager.defaultManager createFileAtPath:tempFilePath
418                                        contents:nil
419                                      attributes:nil];
420
421  NSMutableDictionary* xctestrun = [NSMutableDictionary dictionary];
422  NSMutableDictionary* testTargetName = [NSMutableDictionary dictionary];
423
424  NSMutableDictionary* testingEnvironmentVariables =
425      [NSMutableDictionary dictionary];
426  testingEnvironmentVariables[@"IDEiPhoneInternalTestBundleName"] =
427      app_path.lastPathComponent;
428
429  testingEnvironmentVariables[@"DYLD_FRAMEWORK_PATH"] =
430      @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
431      @"iPhoneSimulator.platform/Developer/Library/Frameworks";
432  testingEnvironmentVariables[@"DYLD_LIBRARY_PATH"] =
433      @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
434      @"iPhoneSimulator.platform/Developer/Library";
435
436  if (xctest_path) {
437    testTargetName[@"TestBundlePath"] = xctest_path;
438    testingEnvironmentVariables[@"DYLD_INSERT_LIBRARIES"] =
439        @"__PLATFORMS__/iPhoneSimulator.platform/Developer/"
440        @"usr/lib/libXCTestBundleInject.dylib";
441    testingEnvironmentVariables[@"XCInjectBundleInto"] =
442        [NSString stringWithFormat:@"__TESTHOST__/%@",
443                                   app_path.lastPathComponent
444                                       .stringByDeletingPathExtension];
445  } else {
446    testTargetName[@"TestBundlePath"] = app_path;
447  }
448  testTargetName[@"TestHostPath"] = app_path;
449
450  if (app_env.count) {
451    testTargetName[@"EnvironmentVariables"] = app_env;
452  }
453
454  if (cmd_args.count > 0) {
455    testTargetName[@"CommandLineArguments"] = cmd_args;
456  }
457
458  if (tests_filter.count > 0) {
459    testTargetName[@"OnlyTestIdentifiers"] = tests_filter;
460  }
461
462  testTargetName[@"TestingEnvironmentVariables"] = testingEnvironmentVariables;
463  xctestrun[@"TestTargetName"] = testTargetName;
464
465  NSData* data = [NSPropertyListSerialization
466      dataWithPropertyList:xctestrun
467                    format:NSPropertyListXMLFormat_v1_0
468                   options:0
469                     error:nil];
470  [data writeToFile:tempFilePath atomically:YES];
471
472  XCRunTask* task = [[XCRunTask alloc] initWithArguments:@[
473    @"xcodebuild", @"-xctestrun", tempFilePath, @"-destination",
474    [@"platform=iOS Simulator,id=" stringByAppendingString:udid],
475    @"test-without-building"
476  ]];
477
478  if (!xctest_path) {
479    // The following stderr messages are meaningless on iossim when not running
480    // xctests and can be safely stripped.
481    NSArray* ignore_strings = @[
482      @"IDETestOperationsObserverErrorDomain", @"** TEST EXECUTE FAILED **"
483    ];
484    NSPipe* stderr_pipe = [NSPipe pipe];
485    stderr_pipe.fileHandleForReading.readabilityHandler =
486        ^(NSFileHandle* handle) {
487          NSString* log = [[NSString alloc] initWithData:handle.availableData
488                                                encoding:NSUTF8StringEncoding];
489          for (NSString* ignore_string in ignore_strings) {
490            if ([log rangeOfString:ignore_string].location != NSNotFound) {
491              return;
492            }
493          }
494          printf("%s", log.UTF8String);
495        };
496    task.standardError = stderr_pipe;
497  }
498  [task run:verbose];
499  return [task terminationStatus];
500}
501
502int main(int argc, char* const argv[]) {
503  NSString* app_path = nil;
504  NSString* xctest_path = nil;
505  NSString* udid = nil;
506  NSString* device_name = @"iPhone 6s";
507  bool wants_wipe = false;
508  bool wants_print_home = false;
509  bool wants_print_supported_devices = false;
510  bool run_web_test = false;
511  bool prepare_web_test = false;
512  NSString* sdk_version = nil;
513  NSMutableDictionary* app_env = [NSMutableDictionary dictionary];
514  NSMutableArray* cmd_args = [NSMutableArray array];
515  NSMutableArray* tests_filter = [NSMutableArray array];
516  bool verbose_commands = false;
517  SimulatorKill kill_simulator = KILL_BOTH;
518  bool wants_simple_iossim = false;
519
520  int c;
521  while ((c = getopt(argc, argv, "hs:d:u:t:e:c:pwlvk:i")) != -1) {
522    switch (c) {
523      case 's':
524        sdk_version = @(optarg);
525        break;
526      case 'd':
527        device_name = @(optarg);
528        break;
529      case 'u':
530        udid = @(optarg);
531        break;
532      case 'w':
533        wants_wipe = true;
534        break;
535      case 'c': {
536        NSString* cmd_arg = @(optarg);
537        [cmd_args addObject:cmd_arg];
538      } break;
539      case 't': {
540        NSString* test = @(optarg);
541        [tests_filter addObject:test];
542      } break;
543      case 'e': {
544        NSString* envLine = @(optarg);
545        NSRange range = [envLine rangeOfString:@"="];
546        if (range.location == NSNotFound) {
547          LogError(@"Invalid key=value argument for -e.");
548          PrintUsage();
549          exit(kExitInvalidArguments);
550        }
551        NSString* key = [envLine substringToIndex:range.location];
552        NSString* value = [envLine substringFromIndex:(range.location + 1)];
553        [app_env setObject:value forKey:key];
554      } break;
555      case 'p':
556        wants_print_home = true;
557        break;
558      case 'l':
559        wants_print_supported_devices = true;
560        break;
561      case 'v':
562        verbose_commands = true;
563        break;
564      case 'k': {
565        NSString* cmd_arg = @(optarg);
566        if ([cmd_arg isEqualToString:@"before"]) {
567          kill_simulator = KILL_BEFORE;
568        } else if ([cmd_arg isEqualToString:@"after"]) {
569          kill_simulator = KILL_AFTER;
570        } else if ([cmd_arg isEqualToString:@"both"]) {
571          kill_simulator = KILL_BOTH;
572        } else if ([cmd_arg isEqualToString:@"never"]) {
573          kill_simulator = KILL_NEVER;
574        } else {
575          PrintUsage();
576          exit(kExitInvalidArguments);
577        }
578      } break;
579      case 'i':
580        wants_simple_iossim = true;
581        break;
582      case 'h':
583        PrintUsage();
584        exit(kExitSuccess);
585      default:
586        PrintUsage();
587        exit(kExitInvalidArguments);
588    }
589  }
590
591  if (wants_simple_iossim && [tests_filter count]) {
592    LogError(@"Cannot specify tests with -t when using -i.");
593    exit(kExitInvalidArguments);
594  }
595
596  NSDictionary* simctl_list = GetSimulatorList(verbose_commands);
597
598  if (wants_print_supported_devices) {
599    PrintSupportedDevices(simctl_list);
600    exit(kExitSuccess);
601  }
602
603  if (!sdk_version) {
604    float sdk = 0;
605    for (NSDictionary* runtime in Runtimes(simctl_list)) {
606      sdk = fmax(sdk, [runtime[@"version"] floatValue]);
607    }
608    sdk_version = [NSString stringWithFormat:@"%0.1f", sdk];
609  }
610
611  NSRange range;
612  for (NSString* cmd_arg in cmd_args) {
613    range = [cmd_arg rangeOfString:@"--run-web-tests"];
614    if (range.location != NSNotFound) {
615      run_web_test = true;
616      break;
617    }
618  }
619
620  for (NSString* cmd_arg in cmd_args) {
621    range = [cmd_arg rangeOfString:@"--prepare-web-tests"];
622    if (range.location != NSNotFound) {
623      prepare_web_test = true;
624      break;
625    }
626  }
627
628  if (udid == nil) {
629    udid = GetDeviceBySDKAndName(simctl_list, device_name, sdk_version);
630    if (udid == nil) {
631      udid =
632          CreateDeviceBySDKAndName(device_name, sdk_version, verbose_commands);
633      if (udid == nil) {
634        LogError(@"Unable to find a device %@ with SDK %@.", device_name,
635                 sdk_version);
636        PrintSupportedDevices(simctl_list);
637        exit(kExitInvalidArguments);
638      }
639    }
640  } else {
641    if (!FindDeviceByUDID(simctl_list, udid)) {
642      LogError(
643          @"Unable to find a device with udid %@. Use 'xcrun simctl list' to "
644          @"see valid device udids.",
645          udid);
646      exit(kExitInvalidArguments);
647    }
648  }
649
650  if (wants_print_home) {
651    PrintDeviceHome(udid, verbose_commands);
652    exit(kExitSuccess);
653  }
654
655  if (kill_simulator & KILL_BEFORE) {
656    KillSimulator(verbose_commands);
657  }
658
659  if (wants_wipe) {
660    WipeDevice(udid, verbose_commands);
661    printf("Device wiped.\n");
662    exit(kExitSuccess);
663  }
664
665  // There should be at least one arg left, specifying the app path. Any
666  // additional args are passed as arguments to the app.
667  if (optind < argc) {
668    NSString* unresolved_app_path = [NSFileManager.defaultManager
669        stringWithFileSystemRepresentation:argv[optind]
670                                    length:strlen(argv[optind])];
671    app_path = ResolvePath(unresolved_app_path);
672    if (!app_path) {
673      LogError(@"Unable to resolve app_path %@", unresolved_app_path);
674      exit(kExitInvalidArguments);
675    }
676
677    if (++optind < argc) {
678      if (wants_simple_iossim) {
679        fprintf(stderr, "Warning: xctest_path ignored when using -i");
680      } else {
681        NSString* unresolved_xctest_path = [NSFileManager.defaultManager
682            stringWithFileSystemRepresentation:argv[optind]
683                                        length:strlen(argv[optind])];
684        xctest_path = ResolvePath(unresolved_xctest_path);
685        if (!xctest_path) {
686          LogError(@"Unable to resolve xctest_path %@", unresolved_xctest_path);
687          exit(kExitInvalidArguments);
688        }
689      }
690    }
691  } else {
692    LogError(@"Unable to parse command line arguments.");
693    PrintUsage();
694    exit(kExitInvalidArguments);
695  }
696
697  if ((prepare_web_test || run_web_test || wants_simple_iossim) &&
698      !isSimDeviceBooted(simctl_list, udid)) {
699    RunSimCtl(@[ @"boot", udid ], verbose_commands);
700  }
701
702  int return_code = -1;
703  if (prepare_web_test) {
704    PrepareWebTests(udid, app_path, verbose_commands);
705    return_code = kExitSuccess;
706  } else if (run_web_test) {
707    return_code = RunWebTest(app_path, udid, cmd_args, verbose_commands);
708  } else if (wants_simple_iossim) {
709    return_code =
710        SimpleRunApplication(app_path, udid, cmd_args, verbose_commands);
711  } else {
712    return_code = RunApplication(app_path, xctest_path, udid, app_env, cmd_args,
713                                 tests_filter, verbose_commands);
714  }
715
716  if (kill_simulator & KILL_AFTER) {
717    KillSimulator(verbose_commands);
718  }
719
720  return return_code;
721}
722