OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import <Foundation/Foundation.h> | 5 #import <Foundation/Foundation.h> |
6 #include <asl.h> | 6 #include <asl.h> |
7 #include <libgen.h> | 7 #include <libgen.h> |
8 #include <stdarg.h> | 8 #include <stdarg.h> |
9 #include <stdio.h> | 9 #include <stdio.h> |
10 | 10 |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 NSString* const kSimulatorRelativePath = | 79 NSString* const kSimulatorRelativePath = |
80 @"Platforms/iPhoneSimulator.platform/Developer/Applications/" | 80 @"Platforms/iPhoneSimulator.platform/Developer/Applications/" |
81 @"iPhone Simulator.app"; | 81 @"iPhone Simulator.app"; |
82 | 82 |
83 // Simulator Error String Key. This can be found by looking in the Simulator's | 83 // Simulator Error String Key. This can be found by looking in the Simulator's |
84 // Localizable.strings files. | 84 // Localizable.strings files. |
85 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; | 85 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; |
86 | 86 |
87 const char* gToolName = "iossim"; | 87 const char* gToolName = "iossim"; |
88 | 88 |
| 89 // Exit status codes. |
| 90 const int kExitSuccess = EXIT_SUCCESS; |
| 91 const int kExitFailure = EXIT_FAILURE; |
| 92 const int kExitInvalidArguments = 2; |
| 93 const int kExitInitializationFailure = 3; |
| 94 const int kExitAppFailedToStart = 4; |
| 95 const int kExitAppCrashed = 5; |
| 96 |
89 void LogError(NSString* format, ...) { | 97 void LogError(NSString* format, ...) { |
90 va_list list; | 98 va_list list; |
91 va_start(list, format); | 99 va_start(list, format); |
92 | 100 |
93 NSString* message = | 101 NSString* message = |
94 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; | 102 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; |
95 | 103 |
96 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); | 104 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]); |
97 fflush(stderr); | 105 fflush(stderr); |
98 | 106 |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
216 appRunning_ = NO; | 224 appRunning_ = NO; |
217 [self tailOutput]; | 225 [self tailOutput]; |
218 // Note that exiting in this state leaves a process running | 226 // Note that exiting in this state leaves a process running |
219 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will | 227 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will |
220 // prevent future simulator sessions from being started for 30 seconds | 228 // prevent future simulator sessions from being started for 30 seconds |
221 // unless the iOS Simulator application is killed altogether. | 229 // unless the iOS Simulator application is killed altogether. |
222 [self session:session didEndWithError:nil]; | 230 [self session:session didEndWithError:nil]; |
223 | 231 |
224 // session:didEndWithError should not return (because it exits) so | 232 // session:didEndWithError should not return (because it exits) so |
225 // the execution path should never get here. | 233 // the execution path should never get here. |
226 exit(EXIT_FAILURE); | 234 exit(kExitFailure); |
227 } | 235 } |
228 | 236 |
229 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", | 237 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", |
230 [error localizedDescription], | 238 [error localizedDescription], |
231 [error domain], static_cast<long int>([error code])); | 239 [error domain], static_cast<long int>([error code])); |
232 exit(EXIT_FAILURE); | 240 exit(kExitAppFailedToStart); |
233 } | 241 } |
234 | 242 |
235 // Start a thread to write contents of outputPath to stdout. | 243 // Start a thread to write contents of outputPath to stdout. |
236 appRunning_ = YES; | 244 appRunning_ = YES; |
237 outputThread_ = [[NSThread alloc] initWithTarget:self | 245 outputThread_ = [[NSThread alloc] initWithTarget:self |
238 selector:@selector(tailOutput) | 246 selector:@selector(tailOutput) |
239 object:nil]; | 247 object:nil]; |
240 [outputThread_ start]; | 248 [outputThread_ start]; |
241 } | 249 } |
242 | 250 |
(...skipping 18 matching lines...) Expand all Loading... |
261 NSString* ignorableErrorStr = | 269 NSString* ignorableErrorStr = |
262 [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey]; | 270 [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey]; |
263 if ([ignorableErrorStr isEqual:localizedDescription]) { | 271 if ([ignorableErrorStr isEqual:localizedDescription]) { |
264 LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)", | 272 LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)", |
265 localizedDescription, [error domain], | 273 localizedDescription, [error domain], |
266 static_cast<long int>([error code])); | 274 static_cast<long int>([error code])); |
267 } else { | 275 } else { |
268 LogError(@"Simulator ended with error: \"%@\" (%@:%ld)", | 276 LogError(@"Simulator ended with error: \"%@\" (%@:%ld)", |
269 localizedDescription, [error domain], | 277 localizedDescription, [error domain], |
270 static_cast<long int>([error code])); | 278 static_cast<long int>([error code])); |
271 exit(EXIT_FAILURE); | 279 exit(kExitFailure); |
272 } | 280 } |
273 } | 281 } |
274 | 282 |
275 // Check if the simulated app exited abnormally by looking for system log | 283 // Check if the simulated app exited abnormally by looking for system log |
276 // messages from launchd that refer to the simulated app's PID. Limit query | 284 // messages from launchd that refer to the simulated app's PID. Limit query |
277 // to messages in the last minute since PIDs are cyclical. | 285 // to messages in the last minute since PIDs are cyclical. |
278 aslmsg query = asl_new(ASL_TYPE_QUERY); | 286 aslmsg query = asl_new(ASL_TYPE_QUERY); |
279 asl_set_query(query, ASL_KEY_SENDER, "launchd", | 287 asl_set_query(query, ASL_KEY_SENDER, "launchd", |
280 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); | 288 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); |
281 asl_set_query(query, ASL_KEY_REF_PID, | 289 asl_set_query(query, ASL_KEY_REF_PID, |
282 [[[session simulatedApplicationPID] stringValue] UTF8String], | 290 [[[session simulatedApplicationPID] stringValue] UTF8String], |
283 ASL_QUERY_OP_EQUAL); | 291 ASL_QUERY_OP_EQUAL); |
284 asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL); | 292 asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL); |
285 | 293 |
286 // Log any messages found. | 294 // Log any messages found. |
287 aslresponse response = asl_search(NULL, query); | 295 aslresponse response = asl_search(NULL, query); |
288 BOOL entryFound = NO; | 296 BOOL entryFound = NO; |
289 aslmsg entry; | 297 aslmsg entry; |
290 while ((entry = aslresponse_next(response)) != NULL) { | 298 while ((entry = aslresponse_next(response)) != NULL) { |
291 entryFound = YES; | 299 entryFound = YES; |
292 LogWarning(@"Console message: %s", asl_get(entry, ASL_KEY_MSG)); | 300 LogWarning(@"Console message: %s", asl_get(entry, ASL_KEY_MSG)); |
293 } | 301 } |
294 | 302 |
295 // launchd only sends messages if the process crashed or exits with a | 303 // launchd only sends messages if the process crashed or exits with a |
296 // non-zero status, so if the query returned any results iossim should exit | 304 // non-zero status, so if the query returned any results iossim should exit |
297 // with non-zero status. | 305 // with non-zero status. |
298 if (entryFound) { | 306 if (entryFound) { |
299 LogError(@"Simulated app crashed or exited with non-zero status"); | 307 LogError(@"Simulated app crashed or exited with non-zero status"); |
300 exit(EXIT_FAILURE); | 308 exit(kExitAppCrashed); |
301 } | 309 } |
302 exit(EXIT_SUCCESS); | 310 exit(kExitSuccess); |
303 } | 311 } |
304 @end | 312 @end |
305 | 313 |
306 namespace { | 314 namespace { |
307 | 315 |
308 // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment | 316 // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment |
309 // variable. | 317 // variable. |
310 NSString* FindDeveloperDir() { | 318 NSString* FindDeveloperDir() { |
311 // Check the env first. | 319 // Check the env first. |
312 NSDictionary* env = [[NSProcessInfo processInfo] environment]; | 320 NSDictionary* env = [[NSProcessInfo processInfo] environment]; |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 if (![simBundle load]) | 361 if (![simBundle load]) |
354 return nil; | 362 return nil; |
355 return simBundle; | 363 return simBundle; |
356 } | 364 } |
357 | 365 |
358 // Helper to find a class by name and die if it isn't found. | 366 // Helper to find a class by name and die if it isn't found. |
359 Class FindClassByName(NSString* nameOfClass) { | 367 Class FindClassByName(NSString* nameOfClass) { |
360 Class theClass = NSClassFromString(nameOfClass); | 368 Class theClass = NSClassFromString(nameOfClass); |
361 if (!theClass) { | 369 if (!theClass) { |
362 LogError(@"Failed to find class %@ at runtime.", nameOfClass); | 370 LogError(@"Failed to find class %@ at runtime.", nameOfClass); |
363 exit(EXIT_FAILURE); | 371 exit(kExitInitializationFailure); |
364 } | 372 } |
365 return theClass; | 373 return theClass; |
366 } | 374 } |
367 | 375 |
368 // Converts the given app path to an application spec, which requires an | 376 // Converts the given app path to an application spec, which requires an |
369 // absolute path. | 377 // absolute path. |
370 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { | 378 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { |
371 Class applicationSpecifierClass = | 379 Class applicationSpecifierClass = |
372 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); | 380 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); |
373 if (![appPath isAbsolutePath]) { | 381 if (![appPath isAbsolutePath]) { |
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
569 case 'u': | 577 case 'u': |
570 simHomePath = [[NSFileManager defaultManager] | 578 simHomePath = [[NSFileManager defaultManager] |
571 stringWithFileSystemRepresentation:optarg length:strlen(optarg)]; | 579 stringWithFileSystemRepresentation:optarg length:strlen(optarg)]; |
572 break; | 580 break; |
573 case 'e': { | 581 case 'e': { |
574 NSString* envLine = [NSString stringWithUTF8String:optarg]; | 582 NSString* envLine = [NSString stringWithUTF8String:optarg]; |
575 NSRange range = [envLine rangeOfString:@"="]; | 583 NSRange range = [envLine rangeOfString:@"="]; |
576 if (range.location == NSNotFound) { | 584 if (range.location == NSNotFound) { |
577 LogError(@"Invalid key=value argument for -e."); | 585 LogError(@"Invalid key=value argument for -e."); |
578 PrintUsage(); | 586 PrintUsage(); |
579 exit(EXIT_FAILURE); | 587 exit(kExitInvalidArguments); |
580 } | 588 } |
581 NSString* key = [envLine substringToIndex:range.location]; | 589 NSString* key = [envLine substringToIndex:range.location]; |
582 NSString* value = [envLine substringFromIndex:(range.location + 1)]; | 590 NSString* value = [envLine substringFromIndex:(range.location + 1)]; |
583 [appEnv setObject:value forKey:key]; | 591 [appEnv setObject:value forKey:key]; |
584 } | 592 } |
585 break; | 593 break; |
586 case 't': { | 594 case 't': { |
587 int timeout = atoi(optarg); | 595 int timeout = atoi(optarg); |
588 if (timeout > 0) { | 596 if (timeout > 0) { |
589 sessionStartTimeout = static_cast<NSTimeInterval>(timeout); | 597 sessionStartTimeout = static_cast<NSTimeInterval>(timeout); |
590 } else { | 598 } else { |
591 LogError(@"Invalid startup timeout (%s).", optarg); | 599 LogError(@"Invalid startup timeout (%s).", optarg); |
592 PrintUsage(); | 600 PrintUsage(); |
593 exit(EXIT_FAILURE); | 601 exit(kExitInvalidArguments); |
594 } | 602 } |
595 } | 603 } |
596 break; | 604 break; |
597 case 'h': | 605 case 'h': |
598 PrintUsage(); | 606 PrintUsage(); |
599 exit(EXIT_SUCCESS); | 607 exit(kExitSuccess); |
600 default: | 608 default: |
601 PrintUsage(); | 609 PrintUsage(); |
602 exit(EXIT_FAILURE); | 610 exit(kExitInvalidArguments); |
603 } | 611 } |
604 } | 612 } |
605 | 613 |
606 // There should be at least one arg left, specifying the app path. Any | 614 // There should be at least one arg left, specifying the app path. Any |
607 // additional args are passed as arguments to the app. | 615 // additional args are passed as arguments to the app. |
608 if (optind < argc) { | 616 if (optind < argc) { |
609 appPath = [[NSFileManager defaultManager] | 617 appPath = [[NSFileManager defaultManager] |
610 stringWithFileSystemRepresentation:argv[optind] | 618 stringWithFileSystemRepresentation:argv[optind] |
611 length:strlen(argv[optind])]; | 619 length:strlen(argv[optind])]; |
612 appName = [appPath lastPathComponent]; | 620 appName = [appPath lastPathComponent]; |
613 while (++optind < argc) { | 621 while (++optind < argc) { |
614 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; | 622 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]]; |
615 } | 623 } |
616 } else { | 624 } else { |
617 LogError(@"Unable to parse command line arguments."); | 625 LogError(@"Unable to parse command line arguments."); |
618 PrintUsage(); | 626 PrintUsage(); |
619 exit(EXIT_FAILURE); | 627 exit(kExitInvalidArguments); |
620 } | 628 } |
621 | 629 |
622 NSString* developerDir = FindDeveloperDir(); | 630 NSString* developerDir = FindDeveloperDir(); |
623 if (!developerDir) { | 631 if (!developerDir) { |
624 LogError(@"Unable to find developer directory."); | 632 LogError(@"Unable to find developer directory."); |
625 exit(EXIT_FAILURE); | 633 exit(kExitInitializationFailure); |
626 } | 634 } |
627 | 635 |
628 NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir); | 636 NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir); |
629 if (!simulatorFramework) { | 637 if (!simulatorFramework) { |
630 LogError(@"Failed to load the Simulator Framework."); | 638 LogError(@"Failed to load the Simulator Framework."); |
631 exit(EXIT_FAILURE); | 639 exit(kExitInitializationFailure); |
632 } | 640 } |
633 | 641 |
634 // Make sure the app path provided is legit. | 642 // Make sure the app path provided is legit. |
635 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); | 643 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath); |
636 if (!appSpec) { | 644 if (!appSpec) { |
637 LogError(@"Invalid app path: %@", appPath); | 645 LogError(@"Invalid app path: %@", appPath); |
638 exit(EXIT_FAILURE); | 646 exit(kExitInitializationFailure); |
639 } | 647 } |
640 | 648 |
641 // Make sure the SDK path provided is legit (or nil). | 649 // Make sure the SDK path provided is legit (or nil). |
642 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); | 650 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion); |
643 if (!systemRoot) { | 651 if (!systemRoot) { |
644 LogError(@"Invalid SDK version: %@", sdkVersion); | 652 LogError(@"Invalid SDK version: %@", sdkVersion); |
645 exit(EXIT_FAILURE); | 653 exit(kExitInitializationFailure); |
646 } | 654 } |
647 | 655 |
648 // Get the paths for stdout and stderr so the simulated app's output will show | 656 // Get the paths for stdout and stderr so the simulated app's output will show |
649 // up in the caller's stdout/stderr. | 657 // up in the caller's stdout/stderr. |
650 NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX"); | 658 NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX"); |
651 NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"]; | 659 NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"]; |
652 | 660 |
653 // Determine the deviceFamily based on the deviceName | 661 // Determine the deviceFamily based on the deviceName |
654 NSNumber* deviceFamily = nil; | 662 NSNumber* deviceFamily = nil; |
655 if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) { | 663 if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) { |
656 deviceFamily = [NSNumber numberWithInt:kIPhoneFamily]; | 664 deviceFamily = [NSNumber numberWithInt:kIPhoneFamily]; |
657 } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) { | 665 } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) { |
658 deviceFamily = [NSNumber numberWithInt:kIPadFamily]; | 666 deviceFamily = [NSNumber numberWithInt:kIPadFamily]; |
659 } else { | 667 } else { |
660 LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'", | 668 LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'", |
661 deviceName); | 669 deviceName); |
662 exit(EXIT_FAILURE); | 670 exit(kExitInvalidArguments); |
663 } | 671 } |
664 | 672 |
665 // Set up the user home directory for the simulator | 673 // Set up the user home directory for the simulator |
666 if (!simHomePath) { | 674 if (!simHomePath) { |
667 NSString* dirNameTemplate = | 675 NSString* dirNameTemplate = |
668 [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName]; | 676 [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName]; |
669 simHomePath = CreateTempDirectory(dirNameTemplate); | 677 simHomePath = CreateTempDirectory(dirNameTemplate); |
670 if (!simHomePath) { | 678 if (!simHomePath) { |
671 LogError(@"Unable to create unique directory for template %@", | 679 LogError(@"Unable to create unique directory for template %@", |
672 dirNameTemplate); | 680 dirNameTemplate); |
673 exit(EXIT_FAILURE); | 681 exit(kExitInitializationFailure); |
674 } | 682 } |
675 } | 683 } |
676 if (!InitializeSimulatorUserHome(simHomePath, deviceName)) { | 684 if (!InitializeSimulatorUserHome(simHomePath, deviceName)) { |
677 LogError(@"Unable to initialize home directory for simulator: %@", | 685 LogError(@"Unable to initialize home directory for simulator: %@", |
678 simHomePath); | 686 simHomePath); |
679 exit(EXIT_FAILURE); | 687 exit(kExitInitializationFailure); |
680 } | 688 } |
681 | 689 |
682 // Create the config and simulator session. | 690 // Create the config and simulator session. |
683 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, | 691 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, |
684 systemRoot, | 692 systemRoot, |
685 stdioPath, | 693 stdioPath, |
686 stdioPath, | 694 stdioPath, |
687 appArgs, | 695 appArgs, |
688 appEnv, | 696 appEnv, |
689 deviceFamily); | 697 deviceFamily); |
(...skipping 15 matching lines...) Expand all Loading... |
705 } else { | 713 } else { |
706 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)", | 714 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)", |
707 [error localizedDescription], | 715 [error localizedDescription], |
708 [error domain], static_cast<long int>([error code])); | 716 [error domain], static_cast<long int>([error code])); |
709 } | 717 } |
710 | 718 |
711 // Note that this code is only executed if the simulator fails to start | 719 // Note that this code is only executed if the simulator fails to start |
712 // because once the main run loop is started, only the delegate calling | 720 // because once the main run loop is started, only the delegate calling |
713 // exit() will end the program. | 721 // exit() will end the program. |
714 [pool drain]; | 722 [pool drain]; |
715 return EXIT_FAILURE; | 723 return kExitFailure; |
716 } | 724 } |
OLD | NEW |