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