123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- // 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 <getopt.h>
- #include <string>
- namespace {
- void PrintUsage() {
- fprintf(
- stderr,
- "Usage: iossim [-d device] [-s sdk_version] <app_path> <xctest_path>\n"
- " where <app_path> is the path to the .app directory and <xctest_path> "
- "is the path to an optional xctest bundle.\n"
- "Options:\n"
- " -u Specifies the device udid to use. Will use -d, -s values to get "
- "devices if not specified.\n"
- " -d Specifies the device (must be one of the values from the iOS "
- "Simulator's Hardware -> Device menu. Defaults to 'iPhone 6s'.\n"
- " -w Wipe the device's contents and settings before running the "
- "test.\n"
- " -e Specifies an environment key=value pair that will be"
- " set in the simulated application's environment.\n"
- " -t Specifies a test or test suite that should be included in the "
- "test run. All other tests will be excluded from this run.\n"
- " -c Specifies command line flags to pass to application.\n"
- " -p Print the device's home directory, does not run a test.\n"
- " -s Specifies the SDK version to use (e.g '9.3'). Will use system "
- "default if not specified.\n");
- }
- // Exit status codes.
- const int kExitSuccess = EXIT_SUCCESS;
- const int kExitInvalidArguments = 2;
- void LogError(NSString* format, ...) {
- va_list list;
- va_start(list, format);
- NSString* message =
- [[[NSString alloc] initWithFormat:format arguments:list] autorelease];
- fprintf(stderr, "iossim: ERROR: %s\n", [message UTF8String]);
- fflush(stderr);
- va_end(list);
- }
- }
- // Wrap boiler plate calls to xcrun NSTasks.
- @interface XCRunTask : NSObject {
- NSTask* _task;
- }
- - (instancetype)initWithArguments:(NSArray*)arguments;
- - (void)run;
- - (void)setStandardOutput:(id)output;
- - (void)setStandardError:(id)error;
- - (int)getTerminationStatus;
- @end
- @implementation XCRunTask
- - (instancetype)initWithArguments:(NSArray*)arguments {
- self = [super init];
- if (self) {
- _task = [[NSTask alloc] init];
- SEL selector = @selector(setStartsNewProcessGroup:);
- if ([_task respondsToSelector:selector])
- [_task performSelector:selector withObject:nil];
- [_task setLaunchPath:@"/usr/bin/xcrun"];
- [_task setArguments:arguments];
- }
- return self;
- }
- - (void)dealloc {
- [_task release];
- [super dealloc];
- }
- - (void)setStandardOutput:(id)output {
- [_task setStandardOutput:output];
- }
- - (void)setStandardError:(id)error {
- [_task setStandardError:error];
- }
- - (int)getTerminationStatus {
- return [_task terminationStatus];
- }
- - (void)run {
- [_task launch];
- [_task waitUntilExit];
- }
- - (void)launch {
- [_task launch];
- }
- - (void)waitUntilExit {
- [_task waitUntilExit];
- }
- @end
- // Return array of available iOS runtime dictionaries. Unavailable (old Xcode
- // versions) or other runtimes (tvOS, watchOS) are removed.
- NSArray* Runtimes(NSDictionary* simctl_list) {
- NSMutableArray* runtimes =
- [[simctl_list[@"runtimes"] mutableCopy] autorelease];
- for (NSDictionary* runtime in simctl_list[@"runtimes"]) {
- BOOL available =
- [runtime[@"availability"] isEqualToString:@"(available)"] ||
- runtime[@"isAvailable"];
- if (![runtime[@"identifier"]
- hasPrefix:@"com.apple.CoreSimulator.SimRuntime.iOS"] ||
- !available) {
- [runtimes removeObject:runtime];
- }
- }
- return runtimes;
- }
- // Return array of device dictionaries.
- NSArray* Devices(NSDictionary* simctl_list) {
- NSMutableArray* devicetypes =
- [[simctl_list[@"devicetypes"] mutableCopy] autorelease];
- for (NSDictionary* devicetype in simctl_list[@"devicetypes"]) {
- if (![devicetype[@"identifier"]
- hasPrefix:@"com.apple.CoreSimulator.SimDeviceType.iPad"] &&
- ![devicetype[@"identifier"]
- hasPrefix:@"com.apple.CoreSimulator.SimDeviceType.iPhone"]) {
- [devicetypes removeObject:devicetype];
- }
- }
- return devicetypes;
- }
- // Get list of devices, runtimes, etc from sim_ctl.
- NSDictionary* GetSimulatorList() {
- XCRunTask* task = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"list", @"-j" ]] autorelease];
- NSPipe* out = [NSPipe pipe];
- [task setStandardOutput:out];
- // In the rest of the this file we read from the pipe after -waitUntilExit
- // (We normally wrap -launch and -waitUntilExit in one -run method). However,
- // on some swarming slaves this led to a hang on simctl's pipe. Since the
- // output of simctl is so instant, reading it before exit seems to work, and
- // seems to avoid the hang.
- [task launch];
- NSData* data = [[out fileHandleForReading] readDataToEndOfFile];
- [task waitUntilExit];
- NSError* error = nil;
- return [NSJSONSerialization JSONObjectWithData:data
- options:kNilOptions
- error:&error];
- }
- // List supported runtimes and devices.
- void PrintSupportedDevices(NSDictionary* simctl_list) {
- printf("\niOS devices:\n");
- for (NSDictionary* type in Devices(simctl_list)) {
- printf("%s\n", [type[@"name"] UTF8String]);
- }
- printf("\nruntimes:\n");
- for (NSDictionary* runtime in Runtimes(simctl_list)) {
- printf("%s\n", [runtime[@"version"] UTF8String]);
- }
- }
- // Expand path to absolute path.
- NSString* ResolvePath(NSString* path) {
- path = [path stringByExpandingTildeInPath];
- path = [path stringByStandardizingPath];
- const char* cpath = [path cStringUsingEncoding:NSUTF8StringEncoding];
- char* resolved_name = NULL;
- char* abs_path = realpath(cpath, resolved_name);
- if (abs_path == NULL) {
- return nil;
- }
- return [NSString stringWithCString:abs_path encoding:NSUTF8StringEncoding];
- }
- // Search |simctl_list| for a udid matching |device_name| and |sdk_version|.
- NSString* GetDeviceBySDKAndName(NSDictionary* simctl_list,
- NSString* device_name,
- NSString* sdk_version) {
- NSString* sdk = nil;
- NSString* name = nil;
- // Get runtime identifier based on version property to handle
- // cases when version and identifier are not the same,
- // e.g. below identifer is *13-2 but version is 13.2.2
- // {
- // "version" : "13.2.2",
- // "bundlePath" : "path"
- // "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-13-2",
- // "buildversion" : "17K90"
- // }
- for (NSDictionary* runtime in Runtimes(simctl_list)) {
- if ([runtime[@"version"] isEqualToString:sdk_version]) {
- sdk = runtime[@"identifier"];
- name = runtime[@"name"];
- break;
- }
- }
- if (sdk == nil) {
- printf("\nDid not find Runtime with specified version.\n");
- PrintSupportedDevices(simctl_list);
- exit(kExitInvalidArguments);
- }
- NSArray* devices = [simctl_list[@"devices"] objectForKey:sdk];
- if (devices == nil || [devices count] == 0) {
- // Specific for XCode 10.1 and lower,
- // where name from 'runtimes' uses as a key in 'devices'.
- devices = [simctl_list[@"devices"] objectForKey:name];
- }
- for (NSDictionary* device in devices) {
- if ([device[@"name"] isEqualToString:device_name]) {
- return device[@"udid"];
- }
- }
- return nil;
- }
- // Create and return a device udid of |device| and |sdk_version|.
- NSString* CreateDeviceBySDKAndName(NSString* device, NSString* sdk_version) {
- NSString* sdk = [@"iOS" stringByAppendingString:sdk_version];
- XCRunTask* create = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"create", device, device, sdk ]]
- autorelease];
- [create run];
- NSDictionary* simctl_list = GetSimulatorList();
- return GetDeviceBySDKAndName(simctl_list, device, sdk_version);
- }
- bool FindDeviceByUDID(NSDictionary* simctl_list, NSString* udid) {
- NSDictionary* devices_table = simctl_list[@"devices"];
- for (id runtimes in devices_table) {
- NSArray* devices = devices_table[runtimes];
- for (NSDictionary* device in devices) {
- if ([device[@"udid"] isEqualToString:udid]) {
- return true;
- }
- }
- }
- return false;
- }
- // Prints the HOME environment variable for a device. Used by the bots to
- // package up all the test data.
- void PrintDeviceHome(NSString* udid) {
- XCRunTask* task = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"getenv", udid, @"HOME" ]] autorelease];
- [task run];
- }
- // Erase a device, used by the bots before a clean test run.
- void WipeDevice(NSString* udid) {
- XCRunTask* shutdown = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"shutdown", udid ]] autorelease];
- [shutdown setStandardOutput:nil];
- [shutdown setStandardError:nil];
- [shutdown run];
- XCRunTask* erase = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"erase", udid ]] autorelease];
- [erase run];
- }
- void KillSimulator() {
- XCRunTask* task = [[[XCRunTask alloc]
- initWithArguments:@[ @"killall", @"Simulator" ]] autorelease];
- [task setStandardOutput:nil];
- [task setStandardError:nil];
- [task run];
- }
- int RunApplication(NSString* app_path,
- NSString* xctest_path,
- NSString* udid,
- NSMutableDictionary* app_env,
- NSMutableArray* cmd_args,
- NSMutableArray* tests_filter) {
- NSString* tempFilePath = [NSTemporaryDirectory()
- stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
- [[NSFileManager defaultManager] createFileAtPath:tempFilePath
- contents:nil
- attributes:nil];
- NSMutableDictionary* xctestrun = [NSMutableDictionary dictionary];
- NSMutableDictionary* testTargetName = [NSMutableDictionary dictionary];
- NSMutableDictionary* testingEnvironmentVariables =
- [NSMutableDictionary dictionary];
- [testingEnvironmentVariables setValue:[app_path lastPathComponent]
- forKey:@"IDEiPhoneInternalTestBundleName"];
- [testingEnvironmentVariables
- setValue:
- @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
- @"iPhoneSimulator.platform/Developer/Library/Frameworks"
- forKey:@"DYLD_FRAMEWORK_PATH"];
- [testingEnvironmentVariables
- setValue:
- @"__TESTROOT__/Debug-iphonesimulator:__PLATFORMS__/"
- @"iPhoneSimulator.platform/Developer/Library"
- forKey:@"DYLD_LIBRARY_PATH"];
- if (xctest_path) {
- [testTargetName setValue:xctest_path forKey:@"TestBundlePath"];
- NSString* inject = @"__PLATFORMS__/iPhoneSimulator.platform/Developer/"
- @"usr/lib/libXCTestBundleInject.dylib";
- [testingEnvironmentVariables setValue:inject
- forKey:@"DYLD_INSERT_LIBRARIES"];
- [testingEnvironmentVariables
- setValue:[NSString stringWithFormat:@"__TESTHOST__/%@",
- [[app_path lastPathComponent]
- stringByDeletingPathExtension]]
- forKey:@"XCInjectBundleInto"];
- } else {
- [testTargetName setValue:app_path forKey:@"TestBundlePath"];
- }
- [testTargetName setValue:app_path forKey:@"TestHostPath"];
- if ([app_env count]) {
- [testTargetName setObject:app_env forKey:@"EnvironmentVariables"];
- }
- if ([cmd_args count] > 0) {
- [testTargetName setObject:cmd_args forKey:@"CommandLineArguments"];
- }
- if ([tests_filter count] > 0) {
- [testTargetName setObject:tests_filter forKey:@"OnlyTestIdentifiers"];
- }
- [testTargetName setObject:testingEnvironmentVariables
- forKey:@"TestingEnvironmentVariables"];
- [xctestrun setObject:testTargetName forKey:@"TestTargetName"];
- NSData* data = [NSPropertyListSerialization
- dataWithPropertyList:xctestrun
- format:NSPropertyListXMLFormat_v1_0
- options:0
- error:nil];
- [data writeToFile:tempFilePath atomically:YES];
- XCRunTask* task = [[[XCRunTask alloc] initWithArguments:@[
- @"xcodebuild", @"-xctestrun", tempFilePath, @"-destination",
- [@"platform=iOS Simulator,id=" stringByAppendingString:udid],
- @"test-without-building"
- ]] autorelease];
- if (!xctest_path) {
- // The following stderr messages are meaningless on iossim when not running
- // xctests and can be safely stripped.
- NSArray* ignore_strings = @[
- @"IDETestOperationsObserverErrorDomain", @"** TEST EXECUTE FAILED **"
- ];
- NSPipe* stderr_pipe = [NSPipe pipe];
- stderr_pipe.fileHandleForReading.readabilityHandler =
- ^(NSFileHandle* handle) {
- NSString* log = [[[NSString alloc] initWithData:handle.availableData
- encoding:NSUTF8StringEncoding]
- autorelease];
- for (NSString* ignore_string in ignore_strings) {
- if ([log rangeOfString:ignore_string].location != NSNotFound) {
- return;
- }
- }
- printf("%s", [log UTF8String]);
- };
- [task setStandardError:stderr_pipe];
- }
- [task run];
- return [task getTerminationStatus];
- }
- int main(int argc, char* const argv[]) {
- // When the last running simulator is from Xcode 7, an Xcode 8 run will yeild
- // a failure to "unload a stale CoreSimulatorService job" message. Sending a
- // hidden simctl to do something simple (list devices) helpfully works around
- // this issue.
- XCRunTask* workaround_task = [[[XCRunTask alloc]
- initWithArguments:@[ @"simctl", @"list", @"-j" ]] autorelease];
- [workaround_task setStandardOutput:nil];
- [workaround_task setStandardError:nil];
- [workaround_task run];
- NSString* app_path = nil;
- NSString* xctest_path = nil;
- NSString* udid = nil;
- NSString* device_name = @"iPhone 6s";
- bool wants_wipe = false;
- bool wants_print_home = false;
- NSDictionary* simctl_list = GetSimulatorList();
- float sdk = 0;
- for (NSDictionary* runtime in Runtimes(simctl_list)) {
- sdk = fmax(sdk, [runtime[@"version"] floatValue]);
- }
- NSString* sdk_version = [NSString stringWithFormat:@"%0.1f", sdk];
- NSMutableDictionary* app_env = [NSMutableDictionary dictionary];
- NSMutableArray* cmd_args = [NSMutableArray array];
- NSMutableArray* tests_filter = [NSMutableArray array];
- int c;
- while ((c = getopt(argc, argv, "hs:d:u:t:e:c:pwl")) != -1) {
- switch (c) {
- case 's':
- sdk_version = [NSString stringWithUTF8String:optarg];
- break;
- case 'd':
- device_name = [NSString stringWithUTF8String:optarg];
- break;
- case 'u':
- udid = [NSString stringWithUTF8String:optarg];
- break;
- case 'w':
- wants_wipe = true;
- break;
- case 'c': {
- NSString* cmd_arg = [NSString stringWithUTF8String:optarg];
- [cmd_args addObject:cmd_arg];
- } break;
- case 't': {
- NSString* test = [NSString stringWithUTF8String:optarg];
- [tests_filter addObject:test];
- } break;
- case 'e': {
- NSString* envLine = [NSString stringWithUTF8String:optarg];
- NSRange range = [envLine rangeOfString:@"="];
- if (range.location == NSNotFound) {
- LogError(@"Invalid key=value argument for -e.");
- PrintUsage();
- exit(kExitInvalidArguments);
- }
- NSString* key = [envLine substringToIndex:range.location];
- NSString* value = [envLine substringFromIndex:(range.location + 1)];
- [app_env setObject:value forKey:key];
- } break;
- case 'p':
- wants_print_home = true;
- break;
- case 'l':
- PrintSupportedDevices(simctl_list);
- exit(kExitSuccess);
- case 'h':
- PrintUsage();
- exit(kExitSuccess);
- default:
- PrintUsage();
- exit(kExitInvalidArguments);
- }
- }
- if (udid == nil) {
- udid = GetDeviceBySDKAndName(simctl_list, device_name, sdk_version);
- if (udid == nil) {
- udid = CreateDeviceBySDKAndName(device_name, sdk_version);
- if (udid == nil) {
- LogError(@"Unable to find a device %@ with SDK %@.", device_name,
- sdk_version);
- PrintSupportedDevices(simctl_list);
- exit(kExitInvalidArguments);
- }
- }
- } else {
- if (!FindDeviceByUDID(simctl_list, udid)) {
- LogError(
- @"Unable to find a device with udid %@. Use 'xcrun simctl list' to "
- @"see valid device udids.",
- udid);
- exit(kExitInvalidArguments);
- }
- }
- if (wants_print_home) {
- PrintDeviceHome(udid);
- exit(kExitSuccess);
- }
- KillSimulator();
- if (wants_wipe) {
- WipeDevice(udid);
- printf("Device wiped.\n");
- exit(kExitSuccess);
- }
- // 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) {
- NSString* unresolved_app_path = [[NSFileManager defaultManager]
- stringWithFileSystemRepresentation:argv[optind]
- length:strlen(argv[optind])];
- app_path = ResolvePath(unresolved_app_path);
- if (!app_path) {
- LogError(@"Unable to resolve app_path %@", unresolved_app_path);
- exit(kExitInvalidArguments);
- }
- if (++optind < argc) {
- NSString* unresolved_xctest_path = [[NSFileManager defaultManager]
- stringWithFileSystemRepresentation:argv[optind]
- length:strlen(argv[optind])];
- xctest_path = ResolvePath(unresolved_xctest_path);
- if (!xctest_path) {
- LogError(@"Unable to resolve xctest_path %@", unresolved_xctest_path);
- exit(kExitInvalidArguments);
- }
- }
- } else {
- LogError(@"Unable to parse command line arguments.");
- PrintUsage();
- exit(kExitInvalidArguments);
- }
- int return_code = RunApplication(app_path, xctest_path, udid, app_env,
- cmd_args, tests_filter);
- KillSimulator();
- return return_code;
- }
|