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