Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(51)

Side by Side Diff: testing/iossim/iossim.mm

Issue 2351613006: Remove old iossim code (pre-Xcode 8) and class-dump. (Closed)
Patch Set: Fix comment Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « testing/iossim/BUILD.gn ('k') | third_party/class-dump/BUILD.gn » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 #import <Foundation/Foundation.h> 4 #import <Foundation/Foundation.h>
5 #if defined(__MAC_10_12) && __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_12
6 #include <getopt.h> 5 #include <getopt.h>
7 #include <string> 6 #include <string>
8 7
9 namespace { 8 namespace {
10 9
11 void PrintUsage() { 10 void PrintUsage() {
12 fprintf( 11 fprintf(
13 stderr, 12 stderr,
14 "Usage: iossim [-d device] [-s sdk_version] <app_path> <xctest_path>\n" 13 "Usage: iossim [-d device] [-s sdk_version] <app_path> <xctest_path>\n"
15 " where <app_path> is the path to the .app directory and <xctest_path> " 14 " where <app_path> is the path to the .app directory and <xctest_path> "
(...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after
415 } else { 414 } else {
416 LogError(@"Unable to parse command line arguments."); 415 LogError(@"Unable to parse command line arguments.");
417 PrintUsage(); 416 PrintUsage();
418 exit(kExitInvalidArguments); 417 exit(kExitInvalidArguments);
419 } 418 }
420 419
421 RunApplication(app_path, xctest_path, udid, app_env, cmd_args); 420 RunApplication(app_path, xctest_path, udid, app_env, cmd_args);
422 KillSimulator(); 421 KillSimulator();
423 return kExitSuccess; 422 return kExitSuccess;
424 } 423 }
425 #else
426 #import <Appkit/Appkit.h>
427 #include <asl.h>
428 #import <Foundation/Foundation.h>
429 #include <libgen.h>
430 #include <stdarg.h>
431 #include <stdio.h>
432
433 // An executable (iossim) that runs an app in the iOS Simulator.
434 // Run 'iossim -h' for usage information.
435 //
436 // For best results, the iOS Simulator application should not be running when
437 // iossim is invoked.
438 //
439 // Headers for iPhoneSimulatorRemoteClient and other frameworks used in this
440 // tool are generated by class-dump, via GYP.
441 // (class-dump is available at http://www.codethecode.com/projects/class-dump/)
442 //
443 // However, there are some forward declarations required to get things to
444 // compile.
445
446 @class DVTStackBacktrace;
447 #import "DVTFoundation.h"
448
449 @protocol SimBridge;
450 @class DVTSimulatorApplication;
451 @class SimDeviceSet;
452 @class SimDeviceType;
453 @class SimProfilesPathMonitor;
454 @class SimRuntime;
455 @class SimServiceConnectionManager;
456 #import "CoreSimulator.h"
457
458 @interface DVTPlatform : NSObject
459 + (BOOL)loadAllPlatformsReturningError:(id*)arg1;
460 @end
461 @class DTiPhoneSimulatorApplicationSpecifier;
462 @class DTiPhoneSimulatorSession;
463 @class DTiPhoneSimulatorSessionConfig;
464 @class DTiPhoneSimulatorSystemRoot;
465 @class DVTConfinementServiceConnection;
466 @class DVTDispatchLock;
467 @class DVTiPhoneSimulatorMessenger;
468 @class DVTNotificationToken;
469 @class DVTTask;
470 // The DTiPhoneSimulatorSessionDelegate protocol is referenced
471 // by the iPhoneSimulatorRemoteClient framework, but not defined in the object
472 // file, so it must be defined here before importing the generated
473 // iPhoneSimulatorRemoteClient.h file.
474 @protocol DTiPhoneSimulatorSessionDelegate
475 - (void)session:(DTiPhoneSimulatorSession*)session
476 didEndWithError:(NSError*)error;
477 - (void)session:(DTiPhoneSimulatorSession*)session
478 didStart:(BOOL)started
479 withError:(NSError*)error;
480 @end
481
482 #import "DVTiPhoneSimulatorRemoteClient.h"
483
484 // An undocumented system log key included in messages from launchd. The value
485 // is the PID of the process the message is about (as opposed to launchd's PID).
486 #define ASL_KEY_REF_PID "RefPID"
487
488 namespace {
489
490 // Name of environment variables that control the user's home directory in the
491 // simulator.
492 const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME";
493 const char* const kHomeEnvVariable = "HOME";
494
495 // Max number of seconds to wait for the simulator session to start.
496 // This timeout must allow time to start up iOS Simulator, install the app
497 // and perform any other black magic that is encoded in the
498 // iPhoneSimulatorRemoteClient framework to kick things off. Normal start up
499 // time is only a couple seconds but machine load, disk caches, etc., can all
500 // affect startup time in the wild so the timeout needs to be fairly generous.
501 // If this timeout occurs iossim will likely exit with non-zero status; the
502 // exception being if the app is invoked and completes execution before the
503 // session is started (this case is handled in session:didStart:withError).
504 const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30;
505
506 // While the simulated app is running, its stdout is redirected to a file which
507 // is polled by iossim and written to iossim's stdout using the following
508 // polling interval.
509 const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
510
511 NSString* const kDVTFoundationRelativePath =
512 @"../SharedFrameworks/DVTFoundation.framework";
513 NSString* const kDevToolsFoundationRelativePath =
514 @"../OtherFrameworks/DevToolsFoundation.framework";
515 NSString* const kSimulatorRelativePath =
516 @"Platforms/iPhoneSimulator.platform/Developer/Applications/"
517 @"iPhone Simulator.app";
518
519 // Simulator Error String Key. This can be found by looking in the Simulator's
520 // Localizable.strings files.
521 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit.";
522
523 const char* gToolName = "iossim";
524
525 // Exit status codes.
526 const int kExitSuccess = EXIT_SUCCESS;
527 const int kExitFailure = EXIT_FAILURE;
528 const int kExitInvalidArguments = 2;
529 const int kExitInitializationFailure = 3;
530 const int kExitAppFailedToStart = 4;
531 const int kExitAppCrashed = 5;
532
533 void LogError(NSString* format, ...) {
534 va_list list;
535 va_start(list, format);
536
537 NSString* message =
538 [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
539
540 fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]);
541 fflush(stderr);
542
543 va_end(list);
544 }
545
546 void LogWarning(NSString* format, ...) {
547 va_list list;
548 va_start(list, format);
549
550 NSString* message =
551 [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
552
553 fprintf(stderr, "%s: WARNING: %s\n", gToolName, [message UTF8String]);
554 fflush(stderr);
555
556 va_end(list);
557 }
558
559 // Helper to find a class by name and die if it isn't found.
560 Class FindClassByName(NSString* nameOfClass) {
561 Class theClass = NSClassFromString(nameOfClass);
562 if (!theClass) {
563 LogError(@"Failed to find class %@ at runtime.", nameOfClass);
564 exit(kExitInitializationFailure);
565 }
566 return theClass;
567 }
568
569 // Returns the a NSString containing the stdout from running an NSTask that
570 // launches |toolPath| with th given command line |args|.
571 NSString* GetOutputFromTask(NSString* toolPath, NSArray* args) {
572 NSTask* task = [[[NSTask alloc] init] autorelease];
573 [task setLaunchPath:toolPath];
574 [task setArguments:args];
575 NSPipe* outputPipe = [NSPipe pipe];
576 [task setStandardOutput:outputPipe];
577 NSFileHandle* outputFile = [outputPipe fileHandleForReading];
578
579 [task launch];
580 NSData* outputData = [outputFile readDataToEndOfFile];
581 [task waitUntilExit];
582 if ([task isRunning]) {
583 LogError(@"Task '%@ %@' is still running.",
584 toolPath,
585 [args componentsJoinedByString:@" "]);
586 return nil;
587 } else if ([task terminationStatus]) {
588 LogError(@"Task '%@ %@' exited with return code %d.",
589 toolPath,
590 [args componentsJoinedByString:@" "],
591 [task terminationStatus]);
592 return nil;
593 }
594 return [[[NSString alloc] initWithData:outputData
595 encoding:NSUTF8StringEncoding] autorelease];
596 }
597
598 // Prints supported devices and SDKs.
599 void PrintSupportedDevices() {
600 printf("Supported device/SDK combinations:\n");
601 Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
602 id deviceSet =
603 [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
604 for (id simDevice in [deviceSet availableDevices]) {
605 NSString* deviceInfo =
606 [NSString stringWithFormat:@" -d '%@' -s '%@'\n",
607 [simDevice name], [[simDevice runtime] versionString]];
608 printf("%s", [deviceInfo UTF8String]);
609 }
610 }
611 } // namespace
612
613 // A delegate that is called when the simulated app is started or ended in the
614 // simulator.
615 @interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> {
616 @private
617 NSString* stdioPath_;
618 NSString* developerDir_;
619 NSString* simulatorHome_;
620 NSThread* outputThread_;
621 NSBundle* simulatorBundle_;
622 BOOL appRunning_;
623 }
624 @end
625
626 // An implementation that copies the simulated app's stdio to stdout of this
627 // executable. While it would be nice to get stdout and stderr independently
628 // from iOS Simulator, issues like I/O buffering and interleaved output
629 // between iOS Simulator and the app would cause iossim to display things out
630 // of order here. Printing all output to a single file keeps the order correct.
631 // Instances of this classe should be initialized with the location of the
632 // simulated app's output file. When the simulated app starts, a thread is
633 // started which handles copying data from the simulated app's output file to
634 // the stdout of this executable.
635 @implementation SimulatorDelegate
636
637 // Specifies the file locations of the simulated app's stdout and stderr.
638 - (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath
639 developerDir:(NSString*)developerDir
640 simulatorHome:(NSString*)simulatorHome {
641 self = [super init];
642 if (self) {
643 stdioPath_ = [stdioPath copy];
644 developerDir_ = [developerDir copy];
645 simulatorHome_ = [simulatorHome copy];
646 }
647
648 return self;
649 }
650
651 - (void)dealloc {
652 [stdioPath_ release];
653 [developerDir_ release];
654 [simulatorBundle_ release];
655 [super dealloc];
656 }
657
658 // Reads data from the simulated app's output and writes it to stdout. This
659 // method blocks, so it should be called in a separate thread. The iOS
660 // Simulator takes a file path for the simulated app's stdout and stderr, but
661 // this path isn't always available (e.g. when the stdout is Xcode's build
662 // window). As a workaround, iossim creates a temp file to hold output, which
663 // this method reads and copies to stdout.
664 - (void)tailOutputForSession:(DTiPhoneSimulatorSession*)session {
665 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
666
667 NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
668 NSString* versionString =
669 [[[session sessionConfig] simulatedSystemRoot] sdkVersion];
670 NSInteger majorVersion = [[[versionString componentsSeparatedByString:@"."]
671 objectAtIndex:0] intValue];
672 if (majorVersion >= 8) {
673 NSString* dataPath = session.sessionConfig.device.dataPath;
674 NSString* appOutput =
675 [dataPath stringByAppendingPathComponent:stdioPath_];
676 simio = [NSFileHandle fileHandleForReadingAtPath:appOutput];
677 }
678 NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput];
679 // Copy data to stdout/stderr while the app is running.
680 while (appRunning_) {
681 NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init];
682 [standardOutput writeData:[simio readDataToEndOfFile]];
683 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
684 [innerPool drain];
685 }
686
687 // Once the app is no longer running, copy any data that was written during
688 // the last sleep cycle.
689 [standardOutput writeData:[simio readDataToEndOfFile]];
690
691 [pool drain];
692 }
693
694 // Fetches a localized error string from the Simulator.
695 - (NSString *)localizedSimulatorErrorString:(NSString*)stringKey {
696 // Lazy load of the simulator bundle.
697 if (simulatorBundle_ == nil) {
698 NSString* simulatorPath = [developerDir_
699 stringByAppendingPathComponent:kSimulatorRelativePath];
700 simulatorBundle_ = [NSBundle bundleWithPath:simulatorPath];
701 }
702 NSString *localizedStr =
703 [simulatorBundle_ localizedStringForKey:stringKey
704 value:nil
705 table:nil];
706 if ([localizedStr length])
707 return localizedStr;
708 // Failed to get a value, follow Cocoa conventions and use the key as the
709 // string.
710 return stringKey;
711 }
712
713 - (void)session:(DTiPhoneSimulatorSession*)session
714 didStart:(BOOL)started
715 withError:(NSError*)error {
716 if (!started) {
717 // If the test executes very quickly (<30ms), the SimulatorDelegate may not
718 // get the initial session:started:withError: message indicating successful
719 // startup of the simulated app. Instead the delegate will get a
720 // session:started:withError: message after the timeout has elapsed. To
721 // account for this case, check if the simulated app's stdio file was
722 // ever created and if it exists dump it to stdout and return success.
723 NSFileManager* fileManager = [NSFileManager defaultManager];
724 if ([fileManager fileExistsAtPath:stdioPath_]) {
725 appRunning_ = NO;
726 [self tailOutputForSession:session];
727 // Note that exiting in this state leaves a process running
728 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will
729 // prevent future simulator sessions from being started for 30 seconds
730 // unless the iOS Simulator application is killed altogether.
731 [self session:session didEndWithError:nil];
732
733 // session:didEndWithError should not return (because it exits) so
734 // the execution path should never get here.
735 exit(kExitFailure);
736 }
737
738 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)",
739 [error localizedDescription],
740 [error domain], static_cast<long int>([error code]));
741 PrintSupportedDevices();
742 exit(kExitAppFailedToStart);
743 }
744
745 // Start a thread to write contents of outputPath to stdout.
746 appRunning_ = YES;
747 outputThread_ =
748 [[NSThread alloc] initWithTarget:self
749 selector:@selector(tailOutputForSession:)
750 object:session];
751 [outputThread_ start];
752 }
753
754 - (void)session:(DTiPhoneSimulatorSession*)session
755 didEndWithError:(NSError*)error {
756 appRunning_ = NO;
757 // Wait for the output thread to finish copying data to stdout.
758 if (outputThread_) {
759 while (![outputThread_ isFinished]) {
760 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
761 }
762 [outputThread_ release];
763 outputThread_ = nil;
764 }
765
766 if (error) {
767 // There appears to be a race condition where sometimes the simulator
768 // framework will end with an error, but the error is that the simulated
769 // app cleanly shut down; try to trap this error and don't fail the
770 // simulator run.
771 NSString* localizedDescription = [error localizedDescription];
772 NSString* ignorableErrorStr =
773 [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey];
774 if ([ignorableErrorStr isEqual:localizedDescription]) {
775 LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)",
776 localizedDescription, [error domain],
777 static_cast<long int>([error code]));
778 } else {
779 LogError(@"Simulator ended with error: \"%@\" (%@:%ld)",
780 localizedDescription, [error domain],
781 static_cast<long int>([error code]));
782 exit(kExitFailure);
783 }
784 }
785
786 // Try to determine if the simulated app crashed or quit with a non-zero
787 // status code. iOS Simluator handles things a bit differently depending on
788 // the version, so first determine the iOS version being used.
789 BOOL badEntryFound = NO;
790
791 // The iOS Simulator's system logging is sandboxed, so parse the sandboxed
792 // system.log file for known errors.
793 NSString* path;
794 NSString* dataPath = session.sessionConfig.device.dataPath;
795 path =
796 [dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"];
797 NSFileManager* fileManager = [NSFileManager defaultManager];
798 if ([fileManager fileExistsAtPath:path]) {
799 NSString* content =
800 [NSString stringWithContentsOfFile:path
801 encoding:NSUTF8StringEncoding
802 error:NULL];
803 NSArray* lines = [content componentsSeparatedByCharactersInSet:
804 [NSCharacterSet newlineCharacterSet]];
805 NSString* simulatedAppPID =
806 [NSString stringWithFormat:@"%d", session.simulatedApplicationPID];
807 NSArray* kErrorStrings = @[
808 @"Service exited with abnormal code:",
809 @"Service exited due to signal:",
810 ];
811 for (NSString* line in lines) {
812 if ([line rangeOfString:simulatedAppPID].location != NSNotFound) {
813 for (NSString* errorString in kErrorStrings) {
814 if ([line rangeOfString:errorString].location != NSNotFound) {
815 LogWarning(@"Console message: %@", line);
816 badEntryFound = YES;
817 break;
818 }
819 }
820 if (badEntryFound) {
821 break;
822 }
823 }
824 }
825 // Remove the log file so subsequent invocations of iossim won't be
826 // looking at stale logs.
827 remove([path fileSystemRepresentation]);
828 } else {
829 LogWarning(@"Unable to find system log at '%@'.", path);
830 }
831
832 // If the query returned any nasty-looking results, iossim should exit with
833 // non-zero status.
834 if (badEntryFound) {
835 LogError(@"Simulated app crashed or exited with non-zero status");
836 exit(kExitAppCrashed);
837 }
838 exit(kExitSuccess);
839 }
840 @end
841
842 namespace {
843
844 // Finds the developer dir via xcode-select or the DEVELOPER_DIR environment
845 // variable.
846 NSString* FindDeveloperDir() {
847 // Check the env first.
848 NSDictionary* env = [[NSProcessInfo processInfo] environment];
849 NSString* developerDir = [env objectForKey:@"DEVELOPER_DIR"];
850 if ([developerDir length] > 0)
851 return developerDir;
852
853 // Go look for it via xcode-select.
854 NSString* output = GetOutputFromTask(@"/usr/bin/xcode-select",
855 @[ @"-print-path" ]);
856 output = [output stringByTrimmingCharactersInSet:
857 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
858 if ([output length] == 0)
859 output = nil;
860 return output;
861 }
862
863 // Loads the Simulator framework from the given developer dir.
864 NSBundle* LoadSimulatorFramework(NSString* developerDir) {
865 // The Simulator framework depends on some of the other Xcode private
866 // frameworks; manually load them first so everything can be linked up.
867 NSString* dvtFoundationPath = [developerDir
868 stringByAppendingPathComponent:kDVTFoundationRelativePath];
869 NSBundle* dvtFoundationBundle =
870 [NSBundle bundleWithPath:dvtFoundationPath];
871 if (![dvtFoundationBundle load])
872 return nil;
873
874 NSString* devToolsFoundationPath = [developerDir
875 stringByAppendingPathComponent:kDevToolsFoundationRelativePath];
876 NSBundle* devToolsFoundationBundle =
877 [NSBundle bundleWithPath:devToolsFoundationPath];
878 if (![devToolsFoundationBundle load])
879 return nil;
880
881 // Prime DVTPlatform.
882 NSError* error;
883 Class DVTPlatformClass = FindClassByName(@"DVTPlatform");
884 if (![DVTPlatformClass loadAllPlatformsReturningError:&error]) {
885 LogError(@"Unable to loadAllPlatformsReturningError. Error: %@",
886 [error localizedDescription]);
887 return nil;
888 }
889
890 // The path within the developer dir of the private Simulator frameworks.
891 NSString* simulatorFrameworkRelativePath;
892 simulatorFrameworkRelativePath =
893 @"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework";
894 NSString* const kCoreSimulatorRelativePath =
895 @"Library/PrivateFrameworks/CoreSimulator.framework";
896 NSString* coreSimulatorPath = [developerDir
897 stringByAppendingPathComponent:kCoreSimulatorRelativePath];
898 NSBundle* coreSimulatorBundle =
899 [NSBundle bundleWithPath:coreSimulatorPath];
900 if (![coreSimulatorBundle load])
901 return nil;
902 NSString* simBundlePath = [developerDir
903 stringByAppendingPathComponent:simulatorFrameworkRelativePath];
904 NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
905 if (![simBundle load])
906 return nil;
907 return simBundle;
908 }
909
910 // Converts the given app path to an application spec, which requires an
911 // absolute path.
912 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) {
913 Class applicationSpecifierClass =
914 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier");
915 if (![appPath isAbsolutePath]) {
916 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath];
917 appPath = [cwd stringByAppendingPathComponent:appPath];
918 }
919 appPath = [appPath stringByStandardizingPath];
920 NSFileManager* fileManager = [NSFileManager defaultManager];
921 if (![fileManager fileExistsAtPath:appPath]) {
922 LogError(@"File not found: %@", appPath);
923 exit(kExitInvalidArguments);
924 }
925 return [applicationSpecifierClass specifierWithApplicationPath:appPath];
926 }
927
928 // Returns the system root for the given SDK version. If sdkVersion is nil, the
929 // default system root is returned. Will return nil if the sdkVersion is not
930 // valid.
931 DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) {
932 Class systemRootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot");
933 Class simRuntimeClass = FindClassByName(@"SimRuntime");
934 NSArray* sorted =
935 [[simRuntimeClass supportedRuntimes] sortedArrayUsingDescriptors:@[
936 [NSSortDescriptor sortDescriptorWithKey:@"version" ascending:YES]
937 ]];
938 NSString* versionString = [[sorted lastObject] versionString];
939 DTiPhoneSimulatorSystemRoot* systemRoot =
940 [systemRootClass rootWithSDKVersion:versionString];
941 if (sdkVersion)
942 systemRoot = [systemRootClass rootWithSDKVersion:sdkVersion];
943
944 return systemRoot;
945 }
946
947 // Builds a config object for starting the specified app.
948 DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
949 DTiPhoneSimulatorApplicationSpecifier* appSpec,
950 DTiPhoneSimulatorSystemRoot* systemRoot,
951 NSString* stdoutPath,
952 NSString* stderrPath,
953 NSArray* appArgs,
954 NSDictionary* appEnv,
955 NSNumber* deviceFamily,
956 NSString* deviceName) {
957 Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig");
958 DTiPhoneSimulatorSessionConfig* sessionConfig =
959 [[[sessionConfigClass alloc] init] autorelease];
960 sessionConfig.applicationToSimulateOnStart = appSpec;
961 sessionConfig.simulatedSystemRoot = systemRoot;
962 sessionConfig.localizedClientName = @"chromium";
963 sessionConfig.simulatedApplicationStdErrPath = stderrPath;
964 sessionConfig.simulatedApplicationStdOutPath = stdoutPath;
965 sessionConfig.simulatedApplicationLaunchArgs = appArgs;
966 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv;
967 sessionConfig.simulatedDeviceInfoName = deviceName;
968 sessionConfig.simulatedDeviceFamily = deviceFamily;
969
970 Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
971 id simDeviceType =
972 [simDeviceTypeClass supportedDeviceTypesByAlias][deviceName];
973 Class simRuntimeClass = FindClassByName(@"SimRuntime");
974 NSString* identifier = systemRoot.runtime.identifier;
975 id simRuntime = [simRuntimeClass supportedRuntimesByIdentifier][identifier];
976
977 // Attempt to use an existing device, but create one if a suitable match
978 // can't be found. For example, if the simulator is running with a
979 // non-default home directory (e.g. via iossim's -u command line arg) then
980 // there won't be any devices so one will have to be created.
981 Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
982 id deviceSet =
983 [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
984 id simDevice = nil;
985 for (id device in [deviceSet availableDevices]) {
986 if ([device runtime] == simRuntime &&
987 [device deviceType] == simDeviceType) {
988 simDevice = device;
989 break;
990 }
991 }
992 if (!simDevice) {
993 NSError* error = nil;
994 // n.b. only the device name is necessary because the iOS Simulator menu
995 // already splits devices by runtime version.
996 NSString* name = [NSString stringWithFormat:@"iossim - %@ ", deviceName];
997 simDevice = [deviceSet createDeviceWithType:simDeviceType
998 runtime:simRuntime
999 name:name
1000 error:&error];
1001 if (error) {
1002 LogError(@"Failed to create device: %@", error);
1003 exit(kExitInitializationFailure);
1004 }
1005 }
1006 sessionConfig.device = simDevice;
1007 return sessionConfig;
1008 }
1009
1010 // Builds a simulator session that will use the given delegate.
1011 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) {
1012 Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession");
1013 DTiPhoneSimulatorSession* session =
1014 [[[sessionClass alloc] init] autorelease];
1015 session.delegate = delegate;
1016 return session;
1017 }
1018
1019 // Creates a temporary directory with a unique name based on the provided
1020 // template. The template should not contain any path separators and be suffixed
1021 // with X's, which will be substituted with a unique alphanumeric string (see
1022 // 'man mkdtemp' for details). The directory will be created as a subdirectory
1023 // of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX',
1024 // this method would return something like '/path/to/tempdir/test-3n2'.
1025 //
1026 // Returns the absolute path of the newly-created directory, or nill if unable
1027 // to create a unique directory.
1028 NSString* CreateTempDirectory(NSString* dirNameTemplate) {
1029 NSString* fullPathTemplate =
1030 [NSTemporaryDirectory() stringByAppendingPathComponent:dirNameTemplate];
1031 char* fullPath = mkdtemp(const_cast<char*>([fullPathTemplate UTF8String]));
1032 if (fullPath == NULL)
1033 return nil;
1034
1035 return [NSString stringWithUTF8String:fullPath];
1036 }
1037
1038 // Creates the necessary directory structure under the given user home directory
1039 // path.
1040 // Returns YES if successful, NO if unable to create the directories.
1041 BOOL CreateHomeDirSubDirs(NSString* userHomePath) {
1042 NSFileManager* fileManager = [NSFileManager defaultManager];
1043
1044 // Create user home and subdirectories.
1045 NSArray* subDirsToCreate = [NSArray arrayWithObjects:
1046 @"Documents",
1047 @"Library/Caches",
1048 @"Library/Preferences",
1049 nil];
1050 for (NSString* subDir in subDirsToCreate) {
1051 NSString* path = [userHomePath stringByAppendingPathComponent:subDir];
1052 NSError* error;
1053 if (![fileManager createDirectoryAtPath:path
1054 withIntermediateDirectories:YES
1055 attributes:nil
1056 error:&error]) {
1057 LogError(@"Unable to create directory: %@. Error: %@",
1058 path, [error localizedDescription]);
1059 return NO;
1060 }
1061 }
1062
1063 return YES;
1064 }
1065
1066 // Creates the necessary directory structure under the given user home directory
1067 // path, then sets the path in the appropriate environment variable.
1068 // Returns YES if successful, NO if unable to create or initialize the given
1069 // directory.
1070 BOOL InitializeSimulatorUserHome(NSString* userHomePath) {
1071 if (!CreateHomeDirSubDirs(userHomePath))
1072 return NO;
1073
1074 // Update the environment to use the specified directory as the user home
1075 // directory.
1076 // Note: the third param of setenv specifies whether or not to overwrite the
1077 // variable's value if it has already been set.
1078 if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) ||
1079 (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) {
1080 LogError(@"Unable to set environment variables for home directory.");
1081 return NO;
1082 }
1083
1084 return YES;
1085 }
1086
1087 // Prints the usage information to stderr.
1088 void PrintUsage() {
1089 fprintf(stderr, "Usage: iossim [-d device] [-s sdkVersion] [-u homeDir] "
1090 "[-e envKey=value]* [-t startupTimeout] <appPath> [<appArgs>]\n"
1091 " where <appPath> is the path to the .app directory and appArgs are any"
1092 " arguments to send the simulated app.\n"
1093 "\n"
1094 "Options:\n"
1095 " -d Specifies the device (must be one of the values from the iOS"
1096 " Simulator's Hardware -> Device menu. Defaults to 'iPhone'.\n"
1097 " -s Specifies the SDK version to use (e.g '4.3')."
1098 " Will use system default if not specified.\n"
1099 " -u Specifies a user home directory for the simulator."
1100 " Will create a new directory if not specified.\n"
1101 " -e Specifies an environment key=value pair that will be"
1102 " set in the simulated application's environment.\n"
1103 " -t Specifies the session startup timeout (in seconds)."
1104 " Defaults to %d.\n"
1105 " -l List supported devices and iOS versions.\n",
1106 static_cast<int>(kDefaultSessionStartTimeoutSeconds));
1107 }
1108 } // namespace
1109
1110 int main(int argc, char* const argv[]) {
1111 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
1112
1113 // basename() may modify the passed in string and it returns a pointer to an
1114 // internal buffer. Give it a copy to modify, and copy what it returns.
1115 char* worker = strdup(argv[0]);
1116 char* toolName = basename(worker);
1117 if (toolName != NULL) {
1118 toolName = strdup(toolName);
1119 if (toolName != NULL)
1120 gToolName = toolName;
1121 }
1122 if (worker != NULL)
1123 free(worker);
1124
1125 NSString* appPath = nil;
1126 NSString* appName = nil;
1127 NSString* sdkVersion = nil;
1128 NSString* deviceName = @"iPhone 5s";
1129 NSString* simHomePath = nil;
1130 NSMutableArray* appArgs = [NSMutableArray array];
1131 NSMutableDictionary* appEnv = [NSMutableDictionary dictionary];
1132 NSTimeInterval sessionStartTimeout = kDefaultSessionStartTimeoutSeconds;
1133
1134 NSString* developerDir = FindDeveloperDir();
1135 if (!developerDir) {
1136 LogError(@"Unable to find developer directory.");
1137 exit(kExitInitializationFailure);
1138 }
1139
1140 NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir);
1141 if (!simulatorFramework) {
1142 LogError(@"Failed to load the Simulator Framework.");
1143 exit(kExitInitializationFailure);
1144 }
1145
1146 // Parse the optional arguments
1147 int c;
1148 while ((c = getopt(argc, argv, "hs:d:u:e:t:l")) != -1) {
1149 switch (c) {
1150 case 's':
1151 sdkVersion = [NSString stringWithUTF8String:optarg];
1152 break;
1153 case 'd':
1154 deviceName = [NSString stringWithUTF8String:optarg];
1155 break;
1156 case 'u':
1157 simHomePath = [[NSFileManager defaultManager]
1158 stringWithFileSystemRepresentation:optarg length:strlen(optarg)];
1159 break;
1160 case 'e': {
1161 NSString* envLine = [NSString stringWithUTF8String:optarg];
1162 NSRange range = [envLine rangeOfString:@"="];
1163 if (range.location == NSNotFound) {
1164 LogError(@"Invalid key=value argument for -e.");
1165 PrintUsage();
1166 exit(kExitInvalidArguments);
1167 }
1168 NSString* key = [envLine substringToIndex:range.location];
1169 NSString* value = [envLine substringFromIndex:(range.location + 1)];
1170 [appEnv setObject:value forKey:key];
1171 }
1172 break;
1173 case 't': {
1174 int timeout = atoi(optarg);
1175 if (timeout > 0) {
1176 sessionStartTimeout = static_cast<NSTimeInterval>(timeout);
1177 } else {
1178 LogError(@"Invalid startup timeout (%s).", optarg);
1179 PrintUsage();
1180 exit(kExitInvalidArguments);
1181 }
1182 }
1183 break;
1184 case 'l':
1185 PrintSupportedDevices();
1186 exit(kExitSuccess);
1187 break;
1188 case 'h':
1189 PrintUsage();
1190 exit(kExitSuccess);
1191 default:
1192 PrintUsage();
1193 exit(kExitInvalidArguments);
1194 }
1195 }
1196
1197 // There should be at least one arg left, specifying the app path. Any
1198 // additional args are passed as arguments to the app.
1199 if (optind < argc) {
1200 appPath = [[NSFileManager defaultManager]
1201 stringWithFileSystemRepresentation:argv[optind]
1202 length:strlen(argv[optind])];
1203 appName = [appPath lastPathComponent];
1204 while (++optind < argc) {
1205 [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]];
1206 }
1207 } else {
1208 LogError(@"Unable to parse command line arguments.");
1209 PrintUsage();
1210 exit(kExitInvalidArguments);
1211 }
1212
1213 // Make sure the app path provided is legit.
1214 DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath);
1215 if (!appSpec) {
1216 LogError(@"Invalid app path: %@", appPath);
1217 exit(kExitInitializationFailure);
1218 }
1219
1220 // Make sure the SDK path provided is legit (or nil).
1221 DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion);
1222 if (!systemRoot) {
1223 LogError(@"Invalid SDK version: %@", sdkVersion);
1224 PrintSupportedDevices();
1225 exit(kExitInitializationFailure);
1226 }
1227
1228 // Get the paths for stdout and stderr so the simulated app's output will show
1229 // up in the caller's stdout/stderr.
1230 NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX");
1231 NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"];
1232
1233 // Determine the deviceFamily based on the deviceName
1234 NSNumber* deviceFamily = nil;
1235 Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
1236 if ([simDeviceTypeClass supportedDeviceTypesByAlias][deviceName] == nil) {
1237 LogError(@"Invalid device name: %@.", deviceName);
1238 PrintSupportedDevices();
1239 exit(kExitInvalidArguments);
1240 }
1241
1242 // Set up the user home directory for the simulator only if a non-default
1243 // value was specified.
1244 if (simHomePath) {
1245 if (!InitializeSimulatorUserHome(simHomePath)) {
1246 LogError(@"Unable to initialize home directory for simulator: %@",
1247 simHomePath);
1248 exit(kExitInitializationFailure);
1249 }
1250 } else {
1251 simHomePath = NSHomeDirectory();
1252 }
1253
1254 // Create the config and simulator session.
1255 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec,
1256 systemRoot,
1257 stdioPath,
1258 stdioPath,
1259 appArgs,
1260 appEnv,
1261 deviceFamily,
1262 deviceName);
1263 SimulatorDelegate* delegate =
1264 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath
1265 developerDir:developerDir
1266 simulatorHome:simHomePath] autorelease];
1267 DTiPhoneSimulatorSession* session = BuildSession(delegate);
1268
1269 // Start the simulator session.
1270 NSError* error;
1271 BOOL started = [session requestStartWithConfig:config
1272 timeout:sessionStartTimeout
1273 error:&error];
1274
1275 // Spin the runtime indefinitely. When the delegate gets the message that the
1276 // app has quit it will exit this program.
1277 if (started) {
1278 [[NSRunLoop mainRunLoop] run];
1279 } else {
1280 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)",
1281 [error localizedDescription],
1282 [error domain], static_cast<long int>([error code]));
1283 }
1284
1285 // Note that this code is only executed if the simulator fails to start
1286 // because once the main run loop is started, only the delegate calling
1287 // exit() will end the program.
1288 [pool drain];
1289 return kExitFailure;
1290 }
1291
1292 #endif
OLDNEW
« no previous file with comments | « testing/iossim/BUILD.gn ('k') | third_party/class-dump/BUILD.gn » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698