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