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

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

Issue 113403002: Move iossim to DVTiPhoneSimulatorRemoteClient. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Check snprintf return and more class-dump param comments Created 7 years 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 | Annotate | Revision Log
« no previous file with comments | « testing/iossim/iossim.gyp ('k') | testing/iossim/redirect-stdout.sh » ('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 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 10 matching lines...) Expand all
21 // However, there are some forward declarations required to get things to 21 // However, there are some forward declarations required to get things to
22 // compile. Also, the DTiPhoneSimulatorSessionDelegate protocol is referenced 22 // compile. Also, the DTiPhoneSimulatorSessionDelegate protocol is referenced
23 // by the iPhoneSimulatorRemoteClient framework, but not defined in the object 23 // by the iPhoneSimulatorRemoteClient framework, but not defined in the object
24 // file, so it must be defined here before importing the generated 24 // file, so it must be defined here before importing the generated
25 // iPhoneSimulatorRemoteClient.h file. 25 // iPhoneSimulatorRemoteClient.h file.
26 26
27 @class DTiPhoneSimulatorApplicationSpecifier; 27 @class DTiPhoneSimulatorApplicationSpecifier;
28 @class DTiPhoneSimulatorSession; 28 @class DTiPhoneSimulatorSession;
29 @class DTiPhoneSimulatorSessionConfig; 29 @class DTiPhoneSimulatorSessionConfig;
30 @class DTiPhoneSimulatorSystemRoot; 30 @class DTiPhoneSimulatorSystemRoot;
31 @class DVTiPhoneSimulatorMessenger;
32
33 @interface DVTPlatform : NSObject
34 + (BOOL)loadAllPlatformsReturningError:(id *)arg1;
lliabraa 2013/12/17 12:36:52 no space before *
justincohen 2013/12/17 14:49:29 Done.
35 @end
36
37 @protocol OS_dispatch_source
38 @end
39 @protocol OS_dispatch_queue
40 @end
41 @class DVTDispatchLock;
42 @class DVTConfinementServiceConnection;
43 @class DVTTask;
31 44
32 @protocol DTiPhoneSimulatorSessionDelegate 45 @protocol DTiPhoneSimulatorSessionDelegate
33 - (void)session:(DTiPhoneSimulatorSession*)session 46 - (void)session:(DTiPhoneSimulatorSession*)session
34 didEndWithError:(NSError*)error; 47 didEndWithError:(NSError*)error;
35 - (void)session:(DTiPhoneSimulatorSession*)session 48 - (void)session:(DTiPhoneSimulatorSession*)session
36 didStart:(BOOL)started 49 didStart:(BOOL)started
37 withError:(NSError*)error; 50 withError:(NSError*)error;
38 @end 51 @end
39 52
40 #import "iPhoneSimulatorRemoteClient.h" 53 #import "DVTiPhoneSimulatorRemoteClient.h"
41 54
42 // An undocumented system log key included in messages from launchd. The value 55 // An undocumented system log key included in messages from launchd. The value
43 // is the PID of the process the message is about (as opposed to launchd's PID). 56 // is the PID of the process the message is about (as opposed to launchd's PID).
44 #define ASL_KEY_REF_PID "RefPID" 57 #define ASL_KEY_REF_PID "RefPID"
45 58
46 namespace { 59 namespace {
47 60
48 // Name of environment variables that control the user's home directory in the 61 // Name of environment variables that control the user's home directory in the
49 // simulator. 62 // simulator.
50 const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME"; 63 const char* const kUserHomeEnvVariable = "CFFIXED_USER_HOME";
(...skipping 15 matching lines...) Expand all
66 const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30; 79 const NSTimeInterval kDefaultSessionStartTimeoutSeconds = 30;
67 80
68 // While the simulated app is running, its stdout is redirected to a file which 81 // While the simulated app is running, its stdout is redirected to a file which
69 // is polled by iossim and written to iossim's stdout using the following 82 // is polled by iossim and written to iossim's stdout using the following
70 // polling interval. 83 // polling interval.
71 const NSTimeInterval kOutputPollIntervalSeconds = 0.1; 84 const NSTimeInterval kOutputPollIntervalSeconds = 0.1;
72 85
73 // The path within the developer dir of the private Simulator frameworks. 86 // The path within the developer dir of the private Simulator frameworks.
74 NSString* const kSimulatorFrameworkRelativePath = 87 NSString* const kSimulatorFrameworkRelativePath =
75 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/" 88 @"Platforms/iPhoneSimulator.platform/Developer/Library/PrivateFrameworks/"
76 @"iPhoneSimulatorRemoteClient.framework"; 89 @"DVTiPhoneSimulatorRemoteClient.framework";
90 NSString* const kDVTFoundationRelativePath =
91 @"../SharedFrameworks/DVTFoundation.framework";
77 NSString* const kDevToolsFoundationRelativePath = 92 NSString* const kDevToolsFoundationRelativePath =
78 @"../OtherFrameworks/DevToolsFoundation.framework"; 93 @"../OtherFrameworks/DevToolsFoundation.framework";
79 NSString* const kSimulatorRelativePath = 94 NSString* const kSimulatorRelativePath =
80 @"Platforms/iPhoneSimulator.platform/Developer/Applications/" 95 @"Platforms/iPhoneSimulator.platform/Developer/Applications/"
81 @"iPhone Simulator.app"; 96 @"iPhone Simulator.app";
82 97
83 // Simulator Error String Key. This can be found by looking in the Simulator's 98 // Simulator Error String Key. This can be found by looking in the Simulator's
84 // Localizable.strings files. 99 // Localizable.strings files.
85 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit."; 100 NSString* const kSimulatorAppQuitErrorKey = @"The simulated application quit.";
86 101
(...skipping 206 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 objectAtIndex:0] intValue]; 308 objectAtIndex:0] intValue];
294 if (majorVersion <= 6) { 309 if (majorVersion <= 6) {
295 // In iOS 6 and before, logging from the simulated apps went to the main 310 // In iOS 6 and before, logging from the simulated apps went to the main
296 // system logs, so use ASL to check if the simulated app exited abnormally 311 // system logs, so use ASL to check if the simulated app exited abnormally
297 // by looking for system log messages from launchd that refer to the 312 // by looking for system log messages from launchd that refer to the
298 // simulated app's PID. Limit query to messages in the last minute since 313 // simulated app's PID. Limit query to messages in the last minute since
299 // PIDs are cyclical. 314 // PIDs are cyclical.
300 aslmsg query = asl_new(ASL_TYPE_QUERY); 315 aslmsg query = asl_new(ASL_TYPE_QUERY);
301 asl_set_query(query, ASL_KEY_SENDER, "launchd", 316 asl_set_query(query, ASL_KEY_SENDER, "launchd",
302 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING); 317 ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_SUBSTRING);
303 asl_set_query(query, ASL_KEY_REF_PID, 318 char session_id[20];
304 [[[session simulatedApplicationPID] stringValue] UTF8String], 319 if (snprintf(session_id, 20, "%d", [session simulatedApplicationPID]) < 0) {
305 ASL_QUERY_OP_EQUAL); 320 LogError(@"Failed to get [session simulatedApplicationPID]");
321 exit(kExitFailure);
322 }
323 asl_set_query(query, ASL_KEY_REF_PID, session_id, ASL_QUERY_OP_EQUAL);
306 asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL); 324 asl_set_query(query, ASL_KEY_TIME, "-1m", ASL_QUERY_OP_GREATER_EQUAL);
307 325
308 // Log any messages found, and take note of any messages that may indicate 326 // Log any messages found, and take note of any messages that may indicate
309 // the app crashed or did not exit cleanly. 327 // the app crashed or did not exit cleanly.
310 aslresponse response = asl_search(NULL, query); 328 aslresponse response = asl_search(NULL, query);
311 aslmsg entry; 329 aslmsg entry;
312 while ((entry = aslresponse_next(response)) != NULL) { 330 while ((entry = aslresponse_next(response)) != NULL) {
313 const char* message = asl_get(entry, ASL_KEY_MSG); 331 const char* message = asl_get(entry, ASL_KEY_MSG);
314 LogWarning(@"Console message: %s", message); 332 LogWarning(@"Console message: %s", message);
315 // Some messages are harmless, so don't trigger a failure for them. 333 // Some messages are harmless, so don't trigger a failure for them.
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 NSString* output = 404 NSString* output =
387 [[[NSString alloc] initWithData:outputData 405 [[[NSString alloc] initWithData:outputData
388 encoding:NSUTF8StringEncoding] autorelease]; 406 encoding:NSUTF8StringEncoding] autorelease];
389 output = [output stringByTrimmingCharactersInSet: 407 output = [output stringByTrimmingCharactersInSet:
390 [NSCharacterSet whitespaceAndNewlineCharacterSet]]; 408 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
391 if ([output length] == 0) 409 if ([output length] == 0)
392 output = nil; 410 output = nil;
393 return output; 411 return output;
394 } 412 }
395 413
414 // Helper to find a class by name and die if it isn't found.
415 Class FindClassByName(NSString* nameOfClass) {
416 Class theClass = NSClassFromString(nameOfClass);
417 if (!theClass) {
418 LogError(@"Failed to find class %@ at runtime.", nameOfClass);
419 exit(kExitInitializationFailure);
420 }
421 return theClass;
422 }
423
396 // Loads the Simulator framework from the given developer dir. 424 // Loads the Simulator framework from the given developer dir.
397 NSBundle* LoadSimulatorFramework(NSString* developerDir) { 425 NSBundle* LoadSimulatorFramework(NSString* developerDir) {
398 // The Simulator framework depends on some of the other Xcode private 426 // The Simulator framework depends on some of the other Xcode private
399 // frameworks; manually load them first so everything can be linked up. 427 // frameworks; manually load them first so everything can be linked up.
428 NSString* dvtFoundationPath = [developerDir
429 stringByAppendingPathComponent:kDVTFoundationRelativePath];
430 NSBundle* dvtFoundationBundle =
431 [NSBundle bundleWithPath:dvtFoundationPath];
432 if (![dvtFoundationBundle load])
433 return nil;
434
400 NSString* devToolsFoundationPath = [developerDir 435 NSString* devToolsFoundationPath = [developerDir
401 stringByAppendingPathComponent:kDevToolsFoundationRelativePath]; 436 stringByAppendingPathComponent:kDevToolsFoundationRelativePath];
402 NSBundle* devToolsFoundationBundle = 437 NSBundle* devToolsFoundationBundle =
403 [NSBundle bundleWithPath:devToolsFoundationPath]; 438 [NSBundle bundleWithPath:devToolsFoundationPath];
404 if (![devToolsFoundationBundle load]) 439 if (![devToolsFoundationBundle load])
405 return nil; 440 return nil;
441
442 // Prime DVTPlatform.
443 NSError* error;
444 Class DVTPlatformClass = FindClassByName(@"DVTPlatform");
445 if (![DVTPlatformClass loadAllPlatformsReturningError:&error]) {
446 LogError(@"Unable to loadAllPlatformsReturningError. Error: %@",
447 [error localizedDescription]);
448 return nil;
449 }
450
406 NSString* simBundlePath = [developerDir 451 NSString* simBundlePath = [developerDir
407 stringByAppendingPathComponent:kSimulatorFrameworkRelativePath]; 452 stringByAppendingPathComponent:kSimulatorFrameworkRelativePath];
408 NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath]; 453 NSBundle* simBundle = [NSBundle bundleWithPath:simBundlePath];
409 if (![simBundle load]) 454 if (![simBundle load])
410 return nil; 455 return nil;
411 return simBundle; 456 return simBundle;
412 } 457 }
413 458
414 // Helper to find a class by name and die if it isn't found.
415 Class FindClassByName(NSString* nameOfClass) {
416 Class theClass = NSClassFromString(nameOfClass);
417 if (!theClass) {
418 LogError(@"Failed to find class %@ at runtime.", nameOfClass);
419 exit(kExitInitializationFailure);
420 }
421 return theClass;
422 }
423
424 // Converts the given app path to an application spec, which requires an 459 // Converts the given app path to an application spec, which requires an
425 // absolute path. 460 // absolute path.
426 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) { 461 DTiPhoneSimulatorApplicationSpecifier* BuildAppSpec(NSString* appPath) {
427 Class applicationSpecifierClass = 462 Class applicationSpecifierClass =
428 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier"); 463 FindClassByName(@"DTiPhoneSimulatorApplicationSpecifier");
429 if (![appPath isAbsolutePath]) { 464 if (![appPath isAbsolutePath]) {
430 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath]; 465 NSString* cwd = [[NSFileManager defaultManager] currentDirectoryPath];
431 appPath = [cwd stringByAppendingPathComponent:appPath]; 466 appPath = [cwd stringByAppendingPathComponent:appPath];
432 } 467 }
433 appPath = [appPath stringByStandardizingPath]; 468 appPath = [appPath stringByStandardizingPath];
(...skipping 13 matching lines...) Expand all
447 } 482 }
448 483
449 // Builds a config object for starting the specified app. 484 // Builds a config object for starting the specified app.
450 DTiPhoneSimulatorSessionConfig* BuildSessionConfig( 485 DTiPhoneSimulatorSessionConfig* BuildSessionConfig(
451 DTiPhoneSimulatorApplicationSpecifier* appSpec, 486 DTiPhoneSimulatorApplicationSpecifier* appSpec,
452 DTiPhoneSimulatorSystemRoot* systemRoot, 487 DTiPhoneSimulatorSystemRoot* systemRoot,
453 NSString* stdoutPath, 488 NSString* stdoutPath,
454 NSString* stderrPath, 489 NSString* stderrPath,
455 NSArray* appArgs, 490 NSArray* appArgs,
456 NSDictionary* appEnv, 491 NSDictionary* appEnv,
457 NSNumber* deviceFamily) { 492 NSNumber* deviceFamily,
493 NSString* deviceName) {
458 Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig"); 494 Class sessionConfigClass = FindClassByName(@"DTiPhoneSimulatorSessionConfig");
459 DTiPhoneSimulatorSessionConfig* sessionConfig = 495 DTiPhoneSimulatorSessionConfig* sessionConfig =
460 [[[sessionConfigClass alloc] init] autorelease]; 496 [[[sessionConfigClass alloc] init] autorelease];
461 sessionConfig.applicationToSimulateOnStart = appSpec; 497 sessionConfig.applicationToSimulateOnStart = appSpec;
462 sessionConfig.simulatedSystemRoot = systemRoot; 498 sessionConfig.simulatedSystemRoot = systemRoot;
463 sessionConfig.localizedClientName = @"chromium"; 499 sessionConfig.localizedClientName = @"chromium";
464 sessionConfig.simulatedApplicationStdErrPath = stderrPath; 500 sessionConfig.simulatedApplicationStdErrPath = stderrPath;
465 sessionConfig.simulatedApplicationStdOutPath = stdoutPath; 501 sessionConfig.simulatedApplicationStdOutPath = stdoutPath;
466 sessionConfig.simulatedApplicationLaunchArgs = appArgs; 502 sessionConfig.simulatedApplicationLaunchArgs = appArgs;
467 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv; 503 sessionConfig.simulatedApplicationLaunchEnvironment = appEnv;
504 sessionConfig.simulatedDeviceInfoName = deviceName;
468 sessionConfig.simulatedDeviceFamily = deviceFamily; 505 sessionConfig.simulatedDeviceFamily = deviceFamily;
469 return sessionConfig; 506 return sessionConfig;
470 } 507 }
471 508
472 // Builds a simulator session that will use the given delegate. 509 // Builds a simulator session that will use the given delegate.
473 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) { 510 DTiPhoneSimulatorSession* BuildSession(SimulatorDelegate* delegate) {
474 Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession"); 511 Class sessionClass = FindClassByName(@"DTiPhoneSimulatorSession");
475 DTiPhoneSimulatorSession* session = 512 DTiPhoneSimulatorSession* session =
476 [[[sessionClass alloc] init] autorelease]; 513 [[[sessionClass alloc] init] autorelease];
477 session.delegate = delegate; 514 session.delegate = delegate;
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
522 } 559 }
523 } 560 }
524 561
525 return YES; 562 return YES;
526 } 563 }
527 564
528 // Creates the necessary directory structure under the given user home directory 565 // Creates the necessary directory structure under the given user home directory
529 // path, then sets the path in the appropriate environment variable. 566 // path, then sets the path in the appropriate environment variable.
530 // Returns YES if successful, NO if unable to create or initialize the given 567 // Returns YES if successful, NO if unable to create or initialize the given
531 // directory. 568 // directory.
532 BOOL InitializeSimulatorUserHome(NSString* userHomePath, NSString* deviceName) { 569 BOOL InitializeSimulatorUserHome(NSString* userHomePath) {
533 if (!CreateHomeDirSubDirs(userHomePath)) 570 if (!CreateHomeDirSubDirs(userHomePath))
534 return NO; 571 return NO;
535 572
536 // Set the device to simulate. Note that the iOS Simulator must not be running
537 // for this setting to take effect.
538 CFStringRef iPhoneSimulatorAppID = CFSTR("com.apple.iphonesimulator");
539 CFPreferencesSetAppValue(CFSTR("SimulateDevice"),
540 deviceName,
541 iPhoneSimulatorAppID);
542 CFPreferencesAppSynchronize(iPhoneSimulatorAppID);
543
544 // Update the environment to use the specified directory as the user home 573 // Update the environment to use the specified directory as the user home
545 // directory. 574 // directory.
546 // Note: the third param of setenv specifies whether or not to overwrite the 575 // Note: the third param of setenv specifies whether or not to overwrite the
547 // variable's value if it has already been set. 576 // variable's value if it has already been set.
548 if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) || 577 if ((setenv(kUserHomeEnvVariable, [userHomePath UTF8String], YES) == -1) ||
549 (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) { 578 (setenv(kHomeEnvVariable, [userHomePath UTF8String], YES) == -1)) {
550 LogError(@"Unable to set environment variables for home directory."); 579 LogError(@"Unable to set environment variables for home directory.");
551 return NO; 580 return NO;
552 } 581 }
553 582
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
721 if (!simHomePath) { 750 if (!simHomePath) {
722 NSString* dirNameTemplate = 751 NSString* dirNameTemplate =
723 [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName]; 752 [NSString stringWithFormat:@"iossim-%@-%@-XXXXXX", appName, deviceName];
724 simHomePath = CreateTempDirectory(dirNameTemplate); 753 simHomePath = CreateTempDirectory(dirNameTemplate);
725 if (!simHomePath) { 754 if (!simHomePath) {
726 LogError(@"Unable to create unique directory for template %@", 755 LogError(@"Unable to create unique directory for template %@",
727 dirNameTemplate); 756 dirNameTemplate);
728 exit(kExitInitializationFailure); 757 exit(kExitInitializationFailure);
729 } 758 }
730 } 759 }
731 if (!InitializeSimulatorUserHome(simHomePath, deviceName)) { 760 if (!InitializeSimulatorUserHome(simHomePath)) {
732 LogError(@"Unable to initialize home directory for simulator: %@", 761 LogError(@"Unable to initialize home directory for simulator: %@",
733 simHomePath); 762 simHomePath);
734 exit(kExitInitializationFailure); 763 exit(kExitInitializationFailure);
735 } 764 }
736 765
737 // Create the config and simulator session. 766 // Create the config and simulator session.
738 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec, 767 DTiPhoneSimulatorSessionConfig* config = BuildSessionConfig(appSpec,
739 systemRoot, 768 systemRoot,
740 stdioPath, 769 stdioPath,
741 stdioPath, 770 stdioPath,
742 appArgs, 771 appArgs,
743 appEnv, 772 appEnv,
744 deviceFamily); 773 deviceFamily,
774 deviceName);
745 SimulatorDelegate* delegate = 775 SimulatorDelegate* delegate =
746 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath 776 [[[SimulatorDelegate alloc] initWithStdioPath:stdioPath
747 developerDir:developerDir 777 developerDir:developerDir
748 simulatorHome:simHomePath] autorelease]; 778 simulatorHome:simHomePath] autorelease];
749 DTiPhoneSimulatorSession* session = BuildSession(delegate); 779 DTiPhoneSimulatorSession* session = BuildSession(delegate);
750 780
751 // Start the simulator session. 781 // Start the simulator session.
752 NSError* error; 782 NSError* error;
753 BOOL started = [session requestStartWithConfig:config 783 BOOL started = [session requestStartWithConfig:config
754 timeout:sessionStartTimeout 784 timeout:sessionStartTimeout
755 error:&error]; 785 error:&error];
756 786
757 // Spin the runtime indefinitely. When the delegate gets the message that the 787 // Spin the runtime indefinitely. When the delegate gets the message that the
758 // app has quit it will exit this program. 788 // app has quit it will exit this program.
759 if (started) { 789 if (started) {
760 [[NSRunLoop mainRunLoop] run]; 790 [[NSRunLoop mainRunLoop] run];
761 } else { 791 } else {
762 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)", 792 LogError(@"Simulator failed request to start: \"%@\" (%@:%ld)",
763 [error localizedDescription], 793 [error localizedDescription],
764 [error domain], static_cast<long int>([error code])); 794 [error domain], static_cast<long int>([error code]));
765 } 795 }
766 796
767 // Note that this code is only executed if the simulator fails to start 797 // Note that this code is only executed if the simulator fails to start
768 // because once the main run loop is started, only the delegate calling 798 // because once the main run loop is started, only the delegate calling
769 // exit() will end the program. 799 // exit() will end the program.
770 [pool drain]; 800 [pool drain];
771 return kExitFailure; 801 return kExitFailure;
772 } 802 }
OLDNEW
« no previous file with comments | « testing/iossim/iossim.gyp ('k') | testing/iossim/redirect-stdout.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698