Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / testing / iossim / iossim.mm
index 1b00e25..48482e8 100644 (file)
@@ -102,9 +102,13 @@ const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30;
 const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
 
 // The path within the developer dir of the private Simulator frameworks.
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
 #if defined(IOSSIM_USE_XCODE_6)
 NSString* const kSimulatorFrameworkRelativePath =
     @"../SharedFrameworks/DVTiPhoneSimulatorRemoteClient.framework";
+NSString* const kCoreSimulatorRelativePath =
+    @"Library/PrivateFrameworks/CoreSimulator.framework";
 #else
 NSString* const kSimulatorFrameworkRelativePath =
     @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/"
@@ -158,6 +162,47 @@ void LogWarning(NSString* format, ...) {
   va_end(list);
 }
 
+// Helper to find a class by name and die if it isn't found.
+Class FindClassByName(NSString* nameOfClass) {
+  Class theClass = NSClassFromString(nameOfClass);
+  if (!theClass) {
+    LogError(@"Failed to find class %@ at runtime.", nameOfClass);
+    exit(kExitInitializationFailure);
+  }
+  return theClass;
+}
+
+// Prints supported devices and SDKs.
+void PrintSupportedDevices() {
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  printf("Supported device/SDK combinations:\n");
+  Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
+  id deviceSet =
+      [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
+  for (id simDevice in [deviceSet availableDevices]) {
+    NSString* deviceInfo =
+        [NSString stringWithFormat:@"  -d '%@' -s '%@'\n",
+            [simDevice name], [[simDevice runtime] versionString]];
+    printf("%s", [deviceInfo UTF8String]);
+  }
+#else
+  printf("Supported SDK versions:\n");
+  Class rootClass = FindClassByName(@"DTiPhoneSimulatorSystemRoot");
+  for (id root in [rootClass knownRoots]) {
+    printf("  '%s'\n", [[root sdkVersion] UTF8String]);
+  }
+  printf("Supported devices:\n");
+  printf("  'iPhone'\n");
+  printf("  'iPhone Retina (3.5-inch)'\n");
+  printf("  'iPhone Retina (4-inch)'\n");
+  printf("  'iPhone Retina (4-inch 64-bit)'\n");
+  printf("  'iPad'\n");
+  printf("  'iPad Retina'\n");
+  printf("  'iPad Retina (64-bit)'\n");
+#endif  // defined(IOSSIM_USE_XCODE_6)
+}
 }  // namespace
 
 // A delegate that is called when the simulated app is started or ended in the
@@ -211,12 +256,23 @@ void LogWarning(NSString* format, ...) {
 // this path isn't always available (e.g. when the stdout is Xcode's build
 // window). As a workaround, iossim creates a temp file to hold output, which
 // this method reads and copies to stdout.
-- (void)tailOutput {
+- (void)tailOutputForSession:(DTiPhoneSimulatorSession*)session {
   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
 
-  // Copy data to stdout/stderr while the app is running.
   NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  // With iOS 8 simulators on Xcode 6, the app output is relative to the
+  // simulator's data directory.
+  if ([session.sessionConfig.simulatedSystemRoot.sdkVersion isEqual:@"8.0"]) {
+    NSString* dataPath = session.sessionConfig.device.dataPath;
+    NSString* appOutput = [dataPath stringByAppendingPathComponent:stdioPath_];
+    simio = [NSFileHandle fileHandleForReadingAtPath:appOutput];
+  }
+#endif
   NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput];
+  // Copy data to stdout/stderr while the app is running.
   while (appRunning_) {
     NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init];
     [standardOutput writeData:[simio readDataToEndOfFile]];
@@ -263,7 +319,7 @@ void LogWarning(NSString* format, ...) {
     NSFileManager* fileManager = [NSFileManager defaultManager];
     if ([fileManager fileExistsAtPath:stdioPath_]) {
       appRunning_ = NO;
-      [self tailOutput];
+      [self tailOutputForSession:session];
       // Note that exiting in this state leaves a process running
       // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will
       // prevent future simulator sessions from being started for 30 seconds
@@ -278,14 +334,16 @@ void LogWarning(NSString* format, ...) {
     LogError(@"Simulator failed to start: \"%@\" (%@:%ld)",
              [error localizedDescription],
              [error domain], static_cast<long int>([error code]));
+    PrintSupportedDevices();
     exit(kExitAppFailedToStart);
   }
 
   // Start a thread to write contents of outputPath to stdout.
   appRunning_ = YES;
-  outputThread_ = [[NSThread alloc] initWithTarget:self
-                                          selector:@selector(tailOutput)
-                                            object:nil];
+  outputThread_ =
+      [[NSThread alloc] initWithTarget:self
+                              selector:@selector(tailOutputForSession:)
+                                object:session];
   [outputThread_ start];
 }
 
@@ -361,11 +419,19 @@ void LogWarning(NSString* format, ...) {
   } else {
     // Otherwise, the iOS Simulator's system logging is sandboxed, so parse the
     // sandboxed system.log file for known errors.
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  NSString* dataPath = session.sessionConfig.device.dataPath;
+  NSString* path =
+      [dataPath stringByAppendingPathComponent:@"Library/Logs/system.log"];
+#else
     NSString* relativePathToSystemLog =
         [NSString stringWithFormat:
             @"Library/Logs/iOS Simulator/%@/system.log", versionString];
     NSString* path =
         [simulatorHome_ stringByAppendingPathComponent:relativePathToSystemLog];
+#endif  // defined(IOSSIM_USE_XCODE_6)
     NSFileManager* fileManager = [NSFileManager defaultManager];
     if ([fileManager fileExistsAtPath:path]) {
       NSString* content =
@@ -374,9 +440,12 @@ void LogWarning(NSString* format, ...) {
                                        error:NULL];
       NSArray* lines = [content componentsSeparatedByCharactersInSet:
           [NSCharacterSet newlineCharacterSet]];
+      NSString* simulatedAppPID =
+          [NSString stringWithFormat:@"%d", session.simulatedApplicationPID];
       for (NSString* line in lines) {
         NSString* const kErrorString = @"Service exited with abnormal code:";
-        if ([line rangeOfString:kErrorString].location != NSNotFound) {
+        if ([line rangeOfString:kErrorString].location != NSNotFound &&
+            [line rangeOfString:simulatedAppPID].location != NSNotFound) {
           LogWarning(@"Console message: %@", line);
           badEntryFound = YES;
           break;
@@ -386,7 +455,7 @@ void LogWarning(NSString* format, ...) {
       // looking at stale logs.
       remove([path fileSystemRepresentation]);
     } else {
-        LogWarning(@"Unable to find sandboxed system log.");
+        LogWarning(@"Unable to find system log at '%@'.", path);
     }
   }
 
@@ -434,16 +503,6 @@ NSString* FindDeveloperDir() {
   return output;
 }
 
-// Helper to find a class by name and die if it isn't found.
-Class FindClassByName(NSString* nameOfClass) {
-  Class theClass = NSClassFromString(nameOfClass);
-  if (!theClass) {
-    LogError(@"Failed to find class %@ at runtime.", nameOfClass);
-    exit(kExitInitializationFailure);
-  }
-  return theClass;
-}
-
 // Loads the Simulator framework from the given developer dir.
 NSBundle* LoadSimulatorFramework(NSString* developerDir) {
   // The Simulator framework depends on some of the other Xcode private
@@ -471,6 +530,17 @@ NSBundle* LoadSimulatorFramework(NSString* developerDir) {
     return nil;
   }
 
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  NSString* coreSimulatorPath = [developerDir
+      stringByAppendingPathComponent:kCoreSimulatorRelativePath];
+  NSBundle* coreSimulatorBundle =
+      [NSBundle bundleWithPath:coreSimulatorPath];
+  if (![coreSimulatorBundle load])
+    return nil;
+#endif  // defined(IOSSIM_USE_XCODE_6)
+
   NSString* simBundlePath = [developerDir
       stringByAppendingPathComponent:kSimulatorFrameworkRelativePath];
   NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
@@ -489,6 +559,11 @@ DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) {
     appPath = [cwd stringByAppendingPathComponent:appPath];
   }
   appPath = [appPath stringByStandardizingPath];
+  NSFileManager* fileManager = [NSFileManager defaultManager];
+  if (![fileManager fileExistsAtPath:appPath]) {
+    LogError(@"File not found: %@", appPath);
+    exit(kExitInvalidArguments);
+  }
   return [applicationSpecifierClass specifierWithApplicationPath:appPath];
 }
 
@@ -526,6 +601,48 @@ DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
   sessionConfig.simulatedApplicationLaunchEnvironment = appEnv;
   sessionConfig.simulatedDeviceInfoName = deviceName;
   sessionConfig.simulatedDeviceFamily = deviceFamily;
+
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
+  id simDeviceType =
+      [simDeviceTypeClass supportedDeviceTypesByName][deviceName];
+  Class simRuntimeClass = FindClassByName(@"SimRuntime");
+  NSString* identifier = systemRoot.runtime.identifier;
+  id simRuntime = [simRuntimeClass supportedRuntimesByIdentifier][identifier];
+
+  // Attempt to use an existing device, but create one if a suitable match can't
+  // be found. For example, if the simulator is running with a non-default home
+  // directory (e.g. via iossim's -u command line arg) then there won't be any
+  // devices so one will have to be created.
+  Class simDeviceSetClass = FindClassByName(@"SimDeviceSet");
+  id deviceSet =
+      [simDeviceSetClass setForSetPath:[simDeviceSetClass defaultSetPath]];
+  id simDevice = nil;
+  for (id device in [deviceSet availableDevices]) {
+    if ([device runtime] == simRuntime &&
+        [device deviceType] == simDeviceType) {
+      simDevice = device;
+      break;
+    }
+  }
+  if (!simDevice) {
+    NSError* error = nil;
+    // n.b. only the device name is necessary because the iOS Simulator menu
+    // already splits devices by runtime version.
+    NSString* name = [NSString stringWithFormat:@"iossim - %@ ", deviceName];
+    simDevice = [deviceSet createDeviceWithType:simDeviceType
+                                        runtime:simRuntime
+                                           name:name
+                                          error:&error];
+    if (error) {
+      LogError(@"Failed to create device: %@", error);
+      exit(kExitInitializationFailure);
+    }
+  }
+  sessionConfig.device = simDevice;
+#endif
   return sessionConfig;
 }
 
@@ -633,10 +750,10 @@ void PrintUsage() {
       "  -e  Specifies an environment key=value pair that will be"
       " set in the simulated application's environment.\n"
       "  -t  Specifies the session startup timeout (in seconds)."
-      " Defaults to %d.\n",
+      " Defaults to %d.\n"
+      "  -l  List supported devices and iOS versions.\n",
       static_cast<int>(kDefaultSessionStartTimeoutSeconds));
 }
-
 }  // namespace
 
 int main(int argc, char* const argv[]) {
@@ -657,15 +774,33 @@ int main(int argc, char* const argv[]) {
   NSString* appPath = nil;
   NSString* appName = nil;
   NSString* sdkVersion = nil;
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  NSString* deviceName = @"iPhone 5";
+#else
   NSString* deviceName = @"iPhone";
+#endif
   NSString* simHomePath = nil;
   NSMutableArray* appArgs = [NSMutableArray array];
   NSMutableDictionary* appEnv = [NSMutableDictionary dictionary];
   NSTimeInterval sessionStartTimeout = kDefaultSessionStartTimeoutSeconds;
 
+  NSString* developerDir = FindDeveloperDir();
+  if (!developerDir) {
+    LogError(@"Unable to find developer directory.");
+    exit(kExitInitializationFailure);
+  }
+
+  NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir);
+  if (!simulatorFramework) {
+    LogError(@"Failed to load the Simulator Framework.");
+    exit(kExitInitializationFailure);
+  }
+
   // Parse the optional arguments
   int c;
-  while ((c = getopt(argc, argv, "hs:d:u:e:t:")) != -1) {
+  while ((c = getopt(argc, argv, "hs:d:u:e:t:l")) != -1) {
     switch (c) {
       case 's':
         sdkVersion = [NSString stringWithUTF8String:optarg];
@@ -701,6 +836,10 @@ int main(int argc, char* const argv[]) {
         }
       }
         break;
+      case 'l':
+        PrintSupportedDevices();
+        exit(kExitSuccess);
+        break;
       case 'h':
         PrintUsage();
         exit(kExitSuccess);
@@ -726,18 +865,6 @@ int main(int argc, char* const argv[]) {
     exit(kExitInvalidArguments);
   }
 
-  NSString* developerDir = FindDeveloperDir();
-  if (!developerDir) {
-    LogError(@"Unable to find developer directory.");
-    exit(kExitInitializationFailure);
-  }
-
-  NSBundle* simulatorFramework = LoadSimulatorFramework(developerDir);
-  if (!simulatorFramework) {
-    LogError(@"Failed to load the Simulator Framework.");
-    exit(kExitInitializationFailure);
-  }
-
   // Make sure the app path provided is legit.
   DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath);
   if (!appSpec) {
@@ -749,6 +876,7 @@ int main(int argc, char* const argv[]) {
   DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion);
   if (!systemRoot) {
     LogError(@"Invalid SDK version: %@", sdkVersion);
+    PrintSupportedDevices();
     exit(kExitInitializationFailure);
   }
 
@@ -759,32 +887,38 @@ int main(int argc, char* const argv[]) {
 
   // Determine the deviceFamily based on the deviceName
   NSNumber* deviceFamily = nil;
+// TODO(lliabraa): Once all builders are on Xcode 6 this ifdef can be removed
+// (crbug.com/385030).
+#if defined(IOSSIM_USE_XCODE_6)
+  Class simDeviceTypeClass = FindClassByName(@"SimDeviceType");
+  if ([simDeviceTypeClass supportedDeviceTypesByName][deviceName] == nil) {
+    LogError(@"Invalid device name: %@.", deviceName);
+    PrintSupportedDevices();
+    exit(kExitInvalidArguments);
+  }
+#else
   if (!deviceName || CaseInsensitivePrefixSearch(deviceName, @"iPhone")) {
     deviceFamily = [NSNumber numberWithInt:kIPhoneFamily];
   } else if (CaseInsensitivePrefixSearch(deviceName, @"iPad")) {
     deviceFamily = [NSNumber numberWithInt:kIPadFamily];
-  } else {
+  }
+  else {
     LogError(@"Invalid device name: %@. Must begin with 'iPhone' or 'iPad'",
              deviceName);
     exit(kExitInvalidArguments);
   }
-
-  // Set up the user home directory for the simulator
-  if (!simHomePath) {
-    NSString* dirNameTemplate =
-        [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName];
-    simHomePath = CreateTempDirectory(dirNameTemplate);
-    if (!simHomePath) {
-      LogError(@"Unable to create unique directory for template %@",
-               dirNameTemplate);
+#endif  // !defined(IOSSIM_USE_XCODE_6)
+
+  // Set up the user home directory for the simulator only if a non-default
+  // value was specified.
+  if (simHomePath) {
+    if (!InitializeSimulatorUserHome(simHomePath)) {
+      LogError(@"Unable to initialize home directory for simulator: %@",
+               simHomePath);
       exit(kExitInitializationFailure);
     }
-  }
-
-  if (!InitializeSimulatorUserHome(simHomePath)) {
-    LogError(@"Unable to initialize home directory for simulator: %@",
-             simHomePath);
-    exit(kExitInitializationFailure);
+  } else {
+    simHomePath = NSHomeDirectory();
   }
 
   // Create the config and simulator session.