summaryrefslogtreecommitdiffstats
path: root/testing/iossim
diff options
context:
space:
mode:
authorlliabraa@chromium.org <lliabraa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-27 15:30:39 +0000
committerlliabraa@chromium.org <lliabraa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-27 15:30:39 +0000
commit6bb12cc75319a92820f4584b89fdd7b123a6aa1b (patch)
tree3271d756b6c159d52918cab22e9665f5f43e7118 /testing/iossim
parent3e13e5e9072aea9e547a3442271055aa179005ef (diff)
downloadchromium_src-6bb12cc75319a92820f4584b89fdd7b123a6aa1b.zip
chromium_src-6bb12cc75319a92820f4584b89fdd7b123a6aa1b.tar.gz
chromium_src-6bb12cc75319a92820f4584b89fdd7b123a6aa1b.tar.bz2
Add iossim testing tool for running iOS unit tests.
iossim is a command line tool used to run an iOS app in the iOS Simulator. BUG=None TEST=None Review URL: https://chromiumcodereview.appspot.com/10805004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148753 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'testing/iossim')
-rw-r--r--testing/iossim/OWNERS2
-rw-r--r--testing/iossim/iossim.gyp60
-rw-r--r--testing/iossim/iossim.mm522
-rwxr-xr-xtesting/iossim/redirect-stdout.sh20
4 files changed, 604 insertions, 0 deletions
diff --git a/testing/iossim/OWNERS b/testing/iossim/OWNERS
new file mode 100644
index 0000000..1b3348e
--- /dev/null
+++ b/testing/iossim/OWNERS
@@ -0,0 +1,2 @@
+rohitrao@chromium.org
+stuartmorgan@chromium.org
diff --git a/testing/iossim/iossim.gyp b/testing/iossim/iossim.gyp
new file mode 100644
index 0000000..ffb4f7d
--- /dev/null
+++ b/testing/iossim/iossim.gyp
@@ -0,0 +1,60 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'variables': {
+ 'iphone_sim_path': '$(DEVELOPER_DIR)/Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks',
+ 'other_frameworks_path': '$(DEVELOPER_DIR)/../OtherFrameworks'
+ },
+ 'targets': [
+ {
+ 'target_name': 'iossim',
+ 'type': 'executable',
+ 'dependencies': [
+ '<(DEPTH)/testing/iossim/third_party/class-dump/class-dump.gyp:class-dump',
+ ],
+ 'include_dirs': [
+ '<(INTERMEDIATE_DIR)/iossim',
+ ],
+ 'sources': [
+ 'iossim.mm',
+ '<(INTERMEDIATE_DIR)/iossim/iPhoneSimulatorRemoteClient.h',
+ ],
+ 'libraries': [
+ '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
+ '<(iphone_sim_path)/iPhoneSimulatorRemoteClient.framework',
+ ],
+ 'mac_framework_dirs': [
+ '<(iphone_sim_path)',
+ ],
+ 'xcode_settings': {
+ 'LD_RUNPATH_SEARCH_PATHS': [
+ '<(iphone_sim_path)',
+ '<(other_frameworks_path)',
+ ]
+ },
+ 'actions': [
+ {
+ 'action_name': 'generate_iphone_sim_header',
+ 'inputs': [
+ '<(iphone_sim_path)/iPhoneSimulatorRemoteClient.framework/Versions/Current/iPhoneSimulatorRemoteClient',
+ '$(BUILD_DIR)/$(CONFIGURATION)/class-dump',
+ ],
+ 'outputs': [
+ '<(INTERMEDIATE_DIR)/iossim/iPhoneSimulatorRemoteClient.h'
+ ],
+ 'action': [
+ # Actions don't provide a way to redirect stdout, so a custom
+ # script is invoked that will execute the first argument and write
+ # the output to the file specified as the second argument.
+ '<(DEPTH)/testing/iossim/RedirectStdout.sh',
+ '$(BUILD_DIR)/$(CONFIGURATION)/class-dump -CiPhoneSimulator <(iphone_sim_path)/iPhoneSimulatorRemoteClient.framework',
+ '<(INTERMEDIATE_DIR)/iossim/iPhoneSimulatorRemoteClient.h',
+ ],
+ 'message': 'Generating header',
+ },
+ ],
+ },
+ ],
+}
diff --git a/testing/iossim/iossim.mm b/testing/iossim/iossim.mm
new file mode 100644
index 0000000..50b50a3
--- /dev/null
+++ b/testing/iossim/iossim.mm
@@ -0,0 +1,522 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Foundation/Foundation.h>
+#include <asl.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+// An executable (iossim) that runs an app in the iOS Simulator.
+// Run 'iossim -h' for usage information.
+//
+// For best results, the iOS Simulator application should not be running when
+// iossim is invoked.
+//
+// Headers for the iPhoneSimulatorRemoteClient framework used in this tool are
+// generated by class-dump, via GYP.
+// (class-dump is available at http://www.codethecode.com/projects/class-dump/)
+//
+// However, there are some forward declarations required to get things to
+// compile. Also, the DTiPhoneSimulatorSessionDelegate protocol is referenced
+// by the iPhoneSimulatorRemoteClient framework, but not defined in the object
+// file, so it must be defined here before importing the generated
+// iPhoneSimulatorRemoteClient.h file.
+
+@class DTiPhoneSimulatorApplicationSpecifier;
+@class DTiPhoneSimulatorSession;
+@class DTiPhoneSimulatorSessionConfig;
+@class DTiPhoneSimulatorSystemRoot;
+
+@protocol DTiPhoneSimulatorSessionDelegate
+- (void)session:(DTiPhoneSimulatorSession*)session
+ didEndWithError:(NSError*)error;
+- (void)session:(DTiPhoneSimulatorSession*)session
+ didStart:(BOOL)started
+ withError:(NSError*)error;
+@end
+
+#import "iPhoneSimulatorRemoteClient.h"
+
+// An undocumented system log key included in messages from launchd. The value
+// is the PID of the process the message is about (as opposed to launchd's PID).
+#define ASL_KEY_REF_PID "RefPID"
+
+namespace {
+
+// Name of environment variables that control the user's home directory in the
+// simulator.
+const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME";
+const char* const kHomeEnvVariable = "HOME";
+
+// Device family codes for iPhone and iPad.
+const int kIPhoneFamily = 1;
+const int kIPadFamily = 2;
+
+// Max number of seconds to wait for the simulator session to start.
+// This timeout must allow time to start up iOS Simulator, install the app
+// and perform any other black magic that is encoded in the
+// iPhoneSimulatorRemoteClient framework to kick things off. Normal start up
+// time is only a couple seconds but machine load, disk caches, etc., can all
+// affect startup time in the wild so the timeout needs to be fairly generous.
+// If this timeout occurs iossim will likely exit with non-zero status; the
+// exception being if the app is invoked and completes execution before the
+// session is started (this case is handled in session:didStart:withError).
+const NSTimeInterval kSessionStartTimeoutSeconds = 30;
+
+// While the simulated app is running, its stdout is redirected to a file which
+// is polled by iossim and written to iossim's stdout using the following
+// polling interval.
+const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
+
+const char* gToolName = "iossim";
+
+void LogError(NSString* format, ...) {
+ va_list list;
+ va_start(list, format);
+
+ NSString* message =
+ [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
+
+ fprintf(stderr, "%s: ERROR: %s\n", gToolName, [message UTF8String]);
+ fflush(stderr);
+
+ va_end(list);
+}
+
+void LogWarning(NSString* format, ...) {
+ va_list list;
+ va_start(list, format);
+
+ NSString* message =
+ [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
+
+ fprintf(stderr, "%s: WARNING: %s\n", gToolName, [message UTF8String]);
+ fflush(stderr);
+
+ va_end(list);
+}
+
+} // namespace
+
+// A delegate that is called when the simulated app is started or ended in the
+// simulator.
+@interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> {
+ @private
+ NSString* stdioPath_; // weak
+ NSThread* outputThread_;
+ BOOL appRunning_;
+}
+@end
+
+// An implementation that copies the simulated app's stdio to stdout of this
+// executable. While it would be nice to get stdout and stderr independently
+// from iOS Simulator, issues like I/O buffering and interleaved output
+// between iOS Simulator and the app would cause iossim to display things out
+// of order here. Printing all output to a single file keeps the order correct.
+// Instances of this classe should be initialized with the location of the
+// simulated app's output file. When the simulated app starts, a thread is
+// started which handles copying data from the simulated app's output file to
+// the stdout of this executable.
+@implementation SimulatorDelegate
+
+// Specifies the file locations of the simulated app's stdout and stderr.
+- (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath {
+ self = [super init];
+ if (self)
+ stdioPath_ = [stdioPath copy];
+
+ return self;
+}
+
+- (void)dealloc {
+ [stdioPath_ release];
+ [super dealloc];
+}
+
+// Reads data from the simulated app's output and writes it to stdout. This
+// method blocks, so it should be called in a separate thread. The iOS
+// Simulator takes a file path for the simulated app's stdout and stderr, but
+// 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 {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ // Copy data to stdout/stderr while the app is running.
+ NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_];
+ NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput];
+ while (appRunning_) {
+ NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init];
+ [standardOutput writeData:[simio readDataToEndOfFile]];
+ [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
+ [innerPool drain];
+ }
+
+ // Once the app is no longer running, copy any data that was written during
+ // the last sleep cycle.
+ [standardOutput writeData:[simio readDataToEndOfFile]];
+
+ [pool drain];
+}
+
+- (void)session:(DTiPhoneSimulatorSession*)session
+ didStart:(BOOL)started
+ withError:(NSError*)error {
+ if (!started) {
+ // If the test executes very quickly (<30ms), the SimulatorDelegate may not
+ // get the initial session:started:withError: message indicating successful
+ // startup of the simulated app. Instead the delegate will get a
+ // session:started:withError: message after the timeout has elapsed. To
+ // account for this case, check if the simulated app's stdio file was
+ // ever created and if it exists dump it to stdout and return success.
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if ([fileManager fileExistsAtPath:stdioPath_ isDirectory:NO]) {
+ appRunning_ = NO;
+ [self tailOutput];
+ // 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
+ // unless the iOS Simulator application is killed altogether.
+ [self session:session didEndWithError:nil];
+
+ // session:didEndWithError should not return (because it exits) so
+ // the execution path should never get here.
+ exit(EXIT_FAILURE);
+ }
+
+ LogError(@"Simulator failed to start: %@", [error localizedDescription]);
+ exit(EXIT_FAILURE);
+ }
+
+ // Start a thread to write contents of outputPath to stdout.
+ appRunning_ = YES;
+ outputThread_ = [[NSThread alloc] initWithTarget:self
+ selector:@selector(tailOutput)
+ object:nil];
+ [outputThread_ start];
+}
+
+- (void)session:(DTiPhoneSimulatorSession*)session
+ didEndWithError:(NSError*)error {
+ appRunning_ = NO;
+ // Wait for the output thread to finish copying data to stdout.
+ if (outputThread_) {
+ while (![outputThread_ isFinished]) {
+ [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds];
+ }
+ [outputThread_ release];
+ outputThread_ = nil;
+ }
+
+ if (error) {
+ LogError(@"Simulator ended with error: %@", [error localizedDescription]);
+ exit(EXIT_FAILURE);
+ }
+
+ // Check if the simulated app exited abnormally by looking for system log
+ // messages from launchd that refer to the simulated app's PID. Limit query
+ // to messages in the last minute since PIDs are cyclical.
+ aslmsg query = asl_new(ASL_TYPE_QUERY);
+ asl_set_query(query, ASL_KEY_SENDER, "launchd",
+ ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING);
+ asl_set_query(query, ASL_KEY_REF_PID,
+ [[[session simulatedApplicationPID] stringValue] UTF8String],
+ ASL_QUERY_OP_EQUAL);
+ asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL);
+
+ // Log any messages found.
+ aslresponse response = asl_search(NULL, query);
+ BOOL entryFound = NO;
+ aslmsg entry;
+ while ((entry = aslresponse_next(response)) != NULL) {
+ entryFound = YES;
+ LogWarning(@"Console message: %s", asl_get(entry, ASL_KEY_MSG));
+ }
+
+ // launchd only sends messages if the process crashed or exits with a
+ // non-zero status, so if the query returned any results iossim should exit
+ // with non-zero status.
+ if (entryFound) {
+ LogError(@"Simulated app crashed or exited with non-zero status");
+ exit(EXIT_FAILURE);
+ }
+ exit(EXIT_SUCCESS);
+}
+@end
+
+namespace {
+
+// Converts the given app path to an application spec, which requires an
+// absolute path.
+DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) {
+ if (![appPath isAbsolutePath]) {
+ NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath];
+ appPath = [cwd stringByAppendingPathComponent:appPath];
+ }
+ appPath = [appPath stringByStandardizingPath];
+ return [DTiPhoneSimulatorApplicationSpecifier
+ specifierWithApplicationPath:appPath];
+}
+
+// Returns the system root for the given SDK version. If sdkVersion is nil, the
+// default system root is returned. Will return nil if the sdkVersion is not
+// valid.
+DTiPhoneSimulatorSystemRoot* BuildSystemRoot(NSString* sdkVersion) {
+ DTiPhoneSimulatorSystemRoot* systemRoot =
+ [DTiPhoneSimulatorSystemRoot defaultRoot];
+ if (sdkVersion)
+ systemRoot = [DTiPhoneSimulatorSystemRoot rootWithSDKVersion:sdkVersion];
+
+ return systemRoot;
+}
+
+// Builds a config object for starting the specified app.
+DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
+ DTiPhoneSimulatorApplicationSpecifier* appSpec,
+ DTiPhoneSimulatorSystemRoot* systemRoot,
+ NSString* stdoutPath,
+ NSString* stderrPath,
+ NSArray* appArgs,
+ NSNumber* deviceFamily) {
+ DTiPhoneSimulatorSessionConfig* sessionConfig =
+ [[[DTiPhoneSimulatorSessionConfig alloc] init] autorelease];
+ sessionConfig.applicationToSimulateOnStart = appSpec;
+ sessionConfig.simulatedSystemRoot = systemRoot;
+ sessionConfig.localizedClientName = @"chromium";
+ sessionConfig.simulatedApplicationStdErrPath = stderrPath;
+ sessionConfig.simulatedApplicationStdOutPath = stdoutPath;
+ sessionConfig.simulatedApplicationLaunchArgs = appArgs;
+ // TODO(lliabraa): Add support for providing environment variables/values
+ // for the simulated app's environment. The environment can be set using:
+ // sessionConfig.simulatedApplicationLaunchEnvironment =
+ // [NSDictionary dictionary];
+ sessionConfig.simulatedDeviceFamily = deviceFamily;
+ return sessionConfig;
+}
+
+// Builds a simulator session that will use the given delegate.
+DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) {
+ DTiPhoneSimulatorSession* session =
+ [[[DTiPhoneSimulatorSession alloc] init] autorelease];
+ session.delegate = delegate;
+ return session;
+}
+
+// Creates a temporary directory with a unique name based on the provided
+// template. The template should not contain any path separators and be suffixed
+// with X's, which will be substituted with a unique alphanumeric string (see
+// 'man mkdtemp' for details). The directory will be created as a subdirectory
+// of NSTemporaryDirectory(). For example, if dirNameTemplate is 'test-XXX',
+// this method would return something like '/path/to/tempdir/test-3n2'.
+//
+// Returns the absolute path of the newly-created directory, or nill if unable
+// to create a unique directory.
+NSString* CreateTempDirectory(NSString* dirNameTemplate) {
+ NSString* fullPathTemplate =
+ [NSTemporaryDirectory() stringByAppendingPathComponent:dirNameTemplate];
+ char* fullPath = mkdtemp(const_cast<char*>([fullPathTemplate UTF8String]));
+ if (fullPath == NULL)
+ return nil;
+
+ return [NSString stringWithUTF8String:fullPath];
+}
+
+// Creates the necessary directory structure under the given user home directory
+// path.
+// Returns YES if successful, NO if unable to create the directories.
+BOOL CreateHomeDirSubDirs(NSString* userHomePath) {
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+
+ // Create user home and subdirectories.
+ NSArray* subDirsToCreate = [NSArray arrayWithObjects:
+ @"Documents",
+ @"Library/Caches",
+ nil];
+ for (NSString* subDir in subDirsToCreate) {
+ NSString* path = [userHomePath stringByAppendingPathComponent:subDir];
+ NSError* error;
+ if (![fileManager createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:&error]) {
+ LogError(@"Unable to create directory: %@. Error: %@",
+ path, [error localizedDescription]);
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+// Creates the necessary directory structure under the given user home directory
+// path, then sets the path in the appropriate environment variable.
+// Returns YES if successful, NO if unable to create or initialize the given
+// directory.
+BOOL InitializeSimulatorUserHome(NSString* userHomePath) {
+ if (!CreateHomeDirSubDirs(userHomePath))
+ return NO;
+
+ // Update the environment to use the specified directory as the user home
+ // directory.
+ // Note: the third param of setenv specifies whether or not to overwrite the
+ // variable's value if it has already been set.
+ if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) ||
+ (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) {
+ LogError(@"Unable to set environment variables for home directory.");
+ return NO;
+ }
+
+ return YES;
+}
+
+// Prints the usage information to stderr.
+void PrintUsage() {
+ fprintf(stderr, "Usage: iossim [-d device] [-s sdkVersion] [-u homeDir] "
+ "<appPath> [<appArgs>]\n"
+ " where <appPath> is the path to the .app directory and appArgs are any"
+ " arguments to send the simulated app.\n"
+ "\n"
+ "Options:\n"
+ " -d Specifies the device (either 'iPhone' or 'iPad')."
+ " Defaults to 'iPhone'.\n"
+ " -s Specifies the SDK version to use (e.g '4.3')."
+ " Will use system default if not specified.\n"
+ " -u Specifies a user home directory for the simulator."
+ " Will create a new directory if not specified.\n");
+}
+
+} // namespace
+
+int main(int argc, char* const argv[]) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ char* toolName = basename(argv[0]);
+ if (toolName != NULL)
+ gToolName = toolName;
+
+ NSString* appPath = nil;
+ NSString* appName = nil;
+ NSString* sdkVersion = nil;
+ NSString* deviceName = @"iPhone";
+ NSString* simHomePath = nil;
+ NSMutableArray* appArgs = [NSMutableArray array];
+
+ // Parse the optional arguments
+ int c;
+ while ((c = getopt(argc, argv, "hs:d:u:")) != -1) {
+ switch (c) {
+ case 's':
+ sdkVersion = [NSString stringWithUTF8String:optarg];
+ break;
+ case 'd':
+ deviceName = [NSString stringWithUTF8String:optarg];
+ break;
+ case 'u':
+ simHomePath = [[NSFileManager defaultManager]
+ stringWithFileSystemRepresentation:optarg length:strlen(optarg)];
+ break;
+ case 'h':
+ PrintUsage();
+ exit(EXIT_SUCCESS);
+ default:
+ PrintUsage();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ // There should be at least one arg left, specifying the app path. Any
+ // additional args are passed as arguments to the app.
+ if (optind < argc) {
+ appPath = [[NSFileManager defaultManager]
+ stringWithFileSystemRepresentation:argv[optind]
+ length:strlen(argv[optind])];
+ appName = [appPath lastPathComponent];
+ while (++optind < argc) {
+ [appArgs addObject:[NSString stringWithUTF8String:argv[optind]]];
+ }
+ } else {
+ LogError(@"Unable to parse command line arguments.");
+ PrintUsage();
+ exit(EXIT_FAILURE);
+ }
+
+ // Make sure the app path provided is legit.
+ DTiPhoneSimulatorApplicationSpecifier* appSpec = BuildAppSpec(appPath);
+ if (!appSpec) {
+ LogError(@"Invalid app path: %@", appPath);
+ exit(EXIT_FAILURE);
+ }
+
+ // Make sure the SDK path provided is legit (or nil).
+ DTiPhoneSimulatorSystemRoot* systemRoot = BuildSystemRoot(sdkVersion);
+ if (!systemRoot) {
+ LogError(@"Invalid SDK version: %@", sdkVersion);
+ exit(EXIT_FAILURE);
+ }
+
+ // Get the paths for stdout and stderr so the simulated app's output will show
+ // up in the caller's stdout/stderr.
+ NSString* outputDir = CreateTempDirectory(@"iossim-XXXXXX");
+ NSString* stdioPath = [outputDir stringByAppendingPathComponent:@"stdio.txt"];
+
+ // Make sure the device name is legit.
+ NSNumber* deviceFamily = nil;
+ if (!deviceName ||
+ [@"iPhone" caseInsensitiveCompare:deviceName] == NSOrderedSame) {
+ deviceFamily = [NSNumber numberWithInt:kIPhoneFamily];
+ } else if ([@"iPad" caseInsensitiveCompare:deviceName] == NSOrderedSame) {
+ deviceFamily = [NSNumber numberWithInt:kIPadFamily];
+ } else {
+ LogError(@"Invalid device name: %@", deviceName);
+ exit(EXIT_FAILURE);
+ }
+
+ // 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);
+ exit(EXIT_FAILURE);
+ }
+ }
+ if (!InitializeSimulatorUserHome(simHomePath)) {
+ LogError(@"Unable to initialize home directory for simulator: %@",
+ simHomePath);
+ exit(EXIT_FAILURE);
+ }
+
+ // Create the config and simulator session.
+ DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec,
+ systemRoot,
+ stdioPath,
+ stdioPath,
+ appArgs,
+ deviceFamily);
+ SimulatorDelegate* delegate =
+ [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath] autorelease];
+ DTiPhoneSimulatorSession* session = BuildSession(delegate);
+
+ // Start the simulator session.
+ NSError* error;
+ BOOL started = [session requestStartWithConfig:config
+ timeout:kSessionStartTimeoutSeconds
+ error:&error];
+
+ // Spin the runtime indefinitely. When the delegate gets the message that the
+ // app has quit it will exit this program.
+ if (started)
+ [[NSRunLoop mainRunLoop] run];
+ else
+ LogError(@"Simulator failed to start: %@", [error localizedDescription]);
+
+ // Note that this code is only executed if the simulator fails to start
+ // because once the main run loop is started, only the delegate calling
+ // exit() will end the program.
+ [pool drain];
+ return EXIT_FAILURE;
+}
diff --git a/testing/iossim/redirect-stdout.sh b/testing/iossim/redirect-stdout.sh
new file mode 100755
index 0000000..feff2c9
--- /dev/null
+++ b/testing/iossim/redirect-stdout.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This script executes the command given as the first argument and redirects
+# the command's stdout to the file given as the second argument.
+#
+# Example: Write the text 'foo' to a file called out.txt:
+# RedirectStdout.sh "echo foo" out.txt
+#
+# This script is invoked from iossim.gyp in order to redirect the output of
+# class-dump to a file (because gyp actions don't support redirecting output).
+
+if [ ${#} -ne 2 ] ; then
+ echo "usage: ${0} <command> <output file>"
+ exit 2
+fi
+
+exec $1 > $2 \ No newline at end of file