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 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
69 // is polled by iossim and written to iossim's stdout using the following | 69 // is polled by iossim and written to iossim's stdout using the following |
70 // polling interval. | 70 // polling interval. |
71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1; | 71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1; |
72 | 72 |
73 // The path within the developer dir of the private Simulator frameworks. | 73 // The path within the developer dir of the private Simulator frameworks. |
74 NSString* const kSimulatorFrameworkRelativePath = | 74 NSString* const kSimulatorFrameworkRelativePath = |
75 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/" | 75 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/" |
76 @"iPhoneSimulatorRemoteClient.framework"; | 76 @"iPhoneSimulatorRemoteClient.framework"; |
77 NSString* const kDevToolsFoundationRelativePath = | 77 NSString* const kDevToolsFoundationRelativePath = |
78 @"../OtherFrameworks/DevToolsFoundation.framework"; | 78 @"../OtherFrameworks/DevToolsFoundation.framework"; |
| 79 NSString* const kSimulatorRelativePath = |
| 80 @"Platforms/iPhoneSimulator.platform/Developer/Applications/" |
| 81 @"iPhone Simulator.app"; |
| 82 |
| 83 // Simulator Error String Key. This can be found by looking in the Simulator's |
| 84 // Localizable.strings files. |
| 85 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; |
79 | 86 |
80 const char* gToolName = "iossim"; | 87 const char* gToolName = "iossim"; |
81 | 88 |
82 void LogError(NSString* format, ...) { | 89 void LogError(NSString* format, ...) { |
83 va_list list; | 90 va_list list; |
84 va_start(list, format); | 91 va_start(list, format); |
85 | 92 |
86 NSString* message = | 93 NSString* message = |
87 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; | 94 [[[NSString alloc] initWithFormat:format arguments:list] autorelease]; |
88 | 95 |
(...skipping 15 matching lines...) Expand all Loading... |
104 | 111 |
105 va_end(list); | 112 va_end(list); |
106 } | 113 } |
107 | 114 |
108 } // namespace | 115 } // namespace |
109 | 116 |
110 // A delegate that is called when the simulated app is started or ended in the | 117 // A delegate that is called when the simulated app is started or ended in the |
111 // simulator. | 118 // simulator. |
112 @interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> { | 119 @interface SimulatorDelegate : NSObject <DTiPhoneSimulatorSessionDelegate> { |
113 @private | 120 @private |
114 NSString* stdioPath_; // weak | 121 NSString* stdioPath_; |
| 122 NSString* developerDir_; |
115 NSThread* outputThread_; | 123 NSThread* outputThread_; |
| 124 NSBundle* simulatorBundle_; |
116 BOOL appRunning_; | 125 BOOL appRunning_; |
117 } | 126 } |
118 @end | 127 @end |
119 | 128 |
120 // An implementation that copies the simulated app's stdio to stdout of this | 129 // An implementation that copies the simulated app's stdio to stdout of this |
121 // executable. While it would be nice to get stdout and stderr independently | 130 // executable. While it would be nice to get stdout and stderr independently |
122 // from iOS Simulator, issues like I/O buffering and interleaved output | 131 // from iOS Simulator, issues like I/O buffering and interleaved output |
123 // between iOS Simulator and the app would cause iossim to display things out | 132 // between iOS Simulator and the app would cause iossim to display things out |
124 // of order here. Printing all output to a single file keeps the order correct. | 133 // of order here. Printing all output to a single file keeps the order correct. |
125 // Instances of this classe should be initialized with the location of the | 134 // Instances of this classe should be initialized with the location of the |
126 // simulated app's output file. When the simulated app starts, a thread is | 135 // simulated app's output file. When the simulated app starts, a thread is |
127 // started which handles copying data from the simulated app's output file to | 136 // started which handles copying data from the simulated app's output file to |
128 // the stdout of this executable. | 137 // the stdout of this executable. |
129 @implementation SimulatorDelegate | 138 @implementation SimulatorDelegate |
130 | 139 |
131 // Specifies the file locations of the simulated app's stdout and stderr. | 140 // Specifies the file locations of the simulated app's stdout and stderr. |
132 - (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath { | 141 - (SimulatorDelegate*)initWithStdioPath:(NSString*)stdioPath |
| 142 developerDir:(NSString*)developerDir { |
133 self = [super init]; | 143 self = [super init]; |
134 if (self) | 144 if (self) { |
135 stdioPath_ = [stdioPath copy]; | 145 stdioPath_ = [stdioPath copy]; |
| 146 developerDir_ = [developerDir copy]; |
| 147 } |
136 | 148 |
137 return self; | 149 return self; |
138 } | 150 } |
139 | 151 |
140 - (void)dealloc { | 152 - (void)dealloc { |
141 [stdioPath_ release]; | 153 [stdioPath_ release]; |
| 154 [developerDir_ release]; |
| 155 [simulatorBundle_ release]; |
142 [super dealloc]; | 156 [super dealloc]; |
143 } | 157 } |
144 | 158 |
145 // Reads data from the simulated app's output and writes it to stdout. This | 159 // Reads data from the simulated app's output and writes it to stdout. This |
146 // method blocks, so it should be called in a separate thread. The iOS | 160 // method blocks, so it should be called in a separate thread. The iOS |
147 // Simulator takes a file path for the simulated app's stdout and stderr, but | 161 // Simulator takes a file path for the simulated app's stdout and stderr, but |
148 // this path isn't always available (e.g. when the stdout is Xcode's build | 162 // this path isn't always available (e.g. when the stdout is Xcode's build |
149 // window). As a workaround, iossim creates a temp file to hold output, which | 163 // window). As a workaround, iossim creates a temp file to hold output, which |
150 // this method reads and copies to stdout. | 164 // this method reads and copies to stdout. |
151 - (void)tailOutput { | 165 - (void)tailOutput { |
152 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | 166 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; |
153 | 167 |
154 // Copy data to stdout/stderr while the app is running. | 168 // Copy data to stdout/stderr while the app is running. |
155 NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_]; | 169 NSFileHandle* simio = [NSFileHandle fileHandleForReadingAtPath:stdioPath_]; |
156 NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput]; | 170 NSFileHandle* standardOutput = [NSFileHandle fileHandleWithStandardOutput]; |
157 while (appRunning_) { | 171 while (appRunning_) { |
158 NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init]; | 172 NSAutoreleasePool* innerPool = [[NSAutoreleasePool alloc] init]; |
159 [standardOutput writeData:[simio readDataToEndOfFile]]; | 173 [standardOutput writeData:[simio readDataToEndOfFile]]; |
160 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; | 174 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; |
161 [innerPool drain]; | 175 [innerPool drain]; |
162 } | 176 } |
163 | 177 |
164 // Once the app is no longer running, copy any data that was written during | 178 // Once the app is no longer running, copy any data that was written during |
165 // the last sleep cycle. | 179 // the last sleep cycle. |
166 [standardOutput writeData:[simio readDataToEndOfFile]]; | 180 [standardOutput writeData:[simio readDataToEndOfFile]]; |
167 | 181 |
168 [pool drain]; | 182 [pool drain]; |
169 } | 183 } |
170 | 184 |
| 185 // Fetches a localized error string from the Simulator. |
| 186 - (NSString *)localizedSimulatorErrorString:(NSString*)stringKey { |
| 187 // Lazy load of the simulator bundle. |
| 188 if (simulatorBundle_ == nil) { |
| 189 NSString* simulatorPath = [developerDir_ |
| 190 stringByAppendingPathComponent:kSimulatorRelativePath]; |
| 191 simulatorBundle_ = [NSBundle bundleWithPath:simulatorPath]; |
| 192 } |
| 193 NSString *localizedStr = |
| 194 [simulatorBundle_ localizedStringForKey:stringKey |
| 195 value:nil |
| 196 table:nil]; |
| 197 if ([localizedStr length]) |
| 198 return localizedStr; |
| 199 // Failed to get a value, follow Cocoa conventions and use the key as the |
| 200 // string. |
| 201 return stringKey; |
| 202 } |
| 203 |
171 - (void)session:(DTiPhoneSimulatorSession*)session | 204 - (void)session:(DTiPhoneSimulatorSession*)session |
172 didStart:(BOOL)started | 205 didStart:(BOOL)started |
173 withError:(NSError*)error { | 206 withError:(NSError*)error { |
174 if (!started) { | 207 if (!started) { |
175 // If the test executes very quickly (<30ms), the SimulatorDelegate may not | 208 // If the test executes very quickly (<30ms), the SimulatorDelegate may not |
176 // get the initial session:started:withError: message indicating successful | 209 // get the initial session:started:withError: message indicating successful |
177 // startup of the simulated app. Instead the delegate will get a | 210 // startup of the simulated app. Instead the delegate will get a |
178 // session:started:withError: message after the timeout has elapsed. To | 211 // session:started:withError: message after the timeout has elapsed. To |
179 // account for this case, check if the simulated app's stdio file was | 212 // account for this case, check if the simulated app's stdio file was |
180 // ever created and if it exists dump it to stdout and return success. | 213 // ever created and if it exists dump it to stdout and return success. |
181 NSFileManager* fileManager = [NSFileManager defaultManager]; | 214 NSFileManager* fileManager = [NSFileManager defaultManager]; |
182 if ([fileManager fileExistsAtPath:stdioPath_ isDirectory:NO]) { | 215 if ([fileManager fileExistsAtPath:stdioPath_ isDirectory:NO]) { |
183 appRunning_ = NO; | 216 appRunning_ = NO; |
184 [self tailOutput]; | 217 [self tailOutput]; |
185 // Note that exiting in this state leaves a process running | 218 // Note that exiting in this state leaves a process running |
186 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will | 219 // (e.g. /.../iPhoneSimulator4.3.sdk/usr/libexec/installd -t 30) that will |
187 // prevent future simulator sessions from being started for 30 seconds | 220 // prevent future simulator sessions from being started for 30 seconds |
188 // unless the iOS Simulator application is killed altogether. | 221 // unless the iOS Simulator application is killed altogether. |
189 [self session:session didEndWithError:nil]; | 222 [self session:session didEndWithError:nil]; |
190 | 223 |
191 // session:didEndWithError should not return (because it exits) so | 224 // session:didEndWithError should not return (because it exits) so |
192 // the execution path should never get here. | 225 // the execution path should never get here. |
193 exit(EXIT_FAILURE); | 226 exit(EXIT_FAILURE); |
194 } | 227 } |
195 | 228 |
196 LogError(@"Simulator failed to start: %@", [error localizedDescription]); | 229 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", |
| 230 [error localizedDescription], |
| 231 [error domain], static_cast<long int>([error code])); |
197 exit(EXIT_FAILURE); | 232 exit(EXIT_FAILURE); |
198 } | 233 } |
199 | 234 |
200 // Start a thread to write contents of outputPath to stdout. | 235 // Start a thread to write contents of outputPath to stdout. |
201 appRunning_ = YES; | 236 appRunning_ = YES; |
202 outputThread_ = [[NSThread alloc] initWithTarget:self | 237 outputThread_ = [[NSThread alloc] initWithTarget:self |
203 selector:@selector(tailOutput) | 238 selector:@selector(tailOutput) |
204 object:nil]; | 239 object:nil]; |
205 [outputThread_ start]; | 240 [outputThread_ start]; |
206 } | 241 } |
207 | 242 |
208 - (void)session:(DTiPhoneSimulatorSession*)session | 243 - (void)session:(DTiPhoneSimulatorSession*)session |
209 didEndWithError:(NSError*)error { | 244 didEndWithError:(NSError*)error { |
210 appRunning_ = NO; | 245 appRunning_ = NO; |
211 // Wait for the output thread to finish copying data to stdout. | 246 // Wait for the output thread to finish copying data to stdout. |
212 if (outputThread_) { | 247 if (outputThread_) { |
213 while (![outputThread_ isFinished]) { | 248 while (![outputThread_ isFinished]) { |
214 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; | 249 [NSThread sleepForTimeInterval:kOutputPollIntervalSeconds]; |
215 } | 250 } |
216 [outputThread_ release]; | 251 [outputThread_ release]; |
217 outputThread_ = nil; | 252 outputThread_ = nil; |
218 } | 253 } |
219 | 254 |
220 if (error) { | 255 if (error) { |
221 LogError(@"Simulator ended with error: %@", [error localizedDescription]); | 256 // There appears to be a race condition where sometimes the simulator |
222 exit(EXIT_FAILURE); | 257 // framework will end with an error, but the error is that the simulated |
| 258 // app cleanly shut down; try to trap this error and don't fail the |
| 259 // simulator run. |
| 260 NSString* localizedDescription = [error localizedDescription]; |
| 261 NSString* ignorableErrorStr = |
| 262 [self localizedSimulatorErrorString:kSimulatorAppQuitErrorKey]; |
| 263 if ([ignorableErrorStr isEqual:localizedDescription]) { |
| 264 LogWarning(@"Ignoring that Simulator ended with: \"%@\" (%@:%ld)", |
| 265 localizedDescription, [error domain], |
| 266 static_cast<long int>([error code])); |
| 267 } else { |
| 268 LogError(@"Simulator ended with error: \"%@\" (%@:%ld)", |
| 269 localizedDescription, [error domain], |
| 270 static_cast<long int>([error code])); |
| 271 exit(EXIT_FAILURE); |
| 272 } |
223 } | 273 } |
224 | 274 |
225 // Check if the simulated app exited abnormally by looking for system log | 275 // Check if the simulated app exited abnormally by looking for system log |
226 // messages from launchd that refer to the simulated app's PID. Limit query | 276 // messages from launchd that refer to the simulated app's PID. Limit query |
227 // to messages in the last minute since PIDs are cyclical. | 277 // to messages in the last minute since PIDs are cyclical. |
228 aslmsg query = asl_new(ASL_TYPE_QUERY); | 278 aslmsg query = asl_new(ASL_TYPE_QUERY); |
229 asl_set_query(query, ASL_KEY_SENDER, "launchd", | 279 asl_set_query(query, ASL_KEY_SENDER, "launchd", |
230 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); | 280 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); |
231 asl_set_query(query, ASL_KEY_REF_PID, | 281 asl_set_query(query, ASL_KEY_REF_PID, |
232 [[[session simulatedApplicationPID] stringValue] UTF8String], | 282 [[[session simulatedApplicationPID] stringValue] UTF8String], |
(...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
596 | 646 |
597 // Create the config and simulator session. | 647 // Create the config and simulator session. |
598 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, | 648 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, |
599 systemRoot, | 649 systemRoot, |
600 stdioPath, | 650 stdioPath, |
601 stdioPath, | 651 stdioPath, |
602 appArgs, | 652 appArgs, |
603 appEnv, | 653 appEnv, |
604 deviceFamily); | 654 deviceFamily); |
605 SimulatorDelegate* delegate = | 655 SimulatorDelegate* delegate = |
606 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath] autorelease]; | 656 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath |
| 657 developerDir:developerDir] autorelease]; |
607 DTiPhoneSimulatorSession* session = BuildSession(delegate); | 658 DTiPhoneSimulatorSession* session = BuildSession(delegate); |
608 | 659 |
609 // Start the simulator session. | 660 // Start the simulator session. |
610 NSError* error; | 661 NSError* error; |
611 BOOL started = [session requestStartWithConfig:config | 662 BOOL started = [session requestStartWithConfig:config |
612 timeout:kSessionStartTimeoutSeconds | 663 timeout:kSessionStartTimeoutSeconds |
613 error:&error]; | 664 error:&error]; |
614 | 665 |
615 // Spin the runtime indefinitely. When the delegate gets the message that the | 666 // Spin the runtime indefinitely. When the delegate gets the message that the |
616 // app has quit it will exit this program. | 667 // app has quit it will exit this program. |
617 if (started) | 668 if (started) { |
618 [[NSRunLoop mainRunLoop] run]; | 669 [[NSRunLoop mainRunLoop] run]; |
619 else | 670 } else { |
620 LogError(@"Simulator failed to start: %@", [error localizedDescription]); | 671 LogError(@"Simulator failed to start: \"%@\" (%@:%ld)", |
| 672 [error localizedDescription], |
| 673 [error domain], static_cast<long int>([error code])); |
| 674 } |
621 | 675 |
622 // Note that this code is only executed if the simulator fails to start | 676 // Note that this code is only executed if the simulator fails to start |
623 // because once the main run loop is started, only the delegate calling | 677 // because once the main run loop is started, only the delegate calling |
624 // exit() will end the program. | 678 // exit() will end the program. |
625 [pool drain]; | 679 [pool drain]; |
626 return EXIT_FAILURE; | 680 return EXIT_FAILURE; |
627 } | 681 } |
OLD | NEW |