FBDeviceControl/Commands/FBDeviceXCTestCommands.m (77 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <FBControlCore/FBControlCore.h>
#import <FBControlCore/FBCollectionInformation.h>
#import <XCTestBootstrap/XCTestBootstrap.h>
#import "FBDevice+Private.h"
#import "FBDevice.h"
#import "FBDeviceControlError.h"
#import "FBDeviceXCTestCommands.h"
#import "FBAMDServiceConnection.h"
@interface FBDeviceXCTestCommands ()
@property (nonatomic, weak, readonly) FBDevice *device;
@property (nonatomic, copy, readonly) NSString *workingDirectory;
@property (nonatomic, strong, readonly) FBProcessFetcher *processFetcher;
@property (nonatomic, assign, readwrite) BOOL runningXcodeBuildOperation;
@end
@implementation FBDeviceXCTestCommands
#pragma mark Initializers
+ (instancetype)commandsWithTarget:(FBDevice *)target
{
return [[self alloc] initWithDevice:target workingDirectory:NSTemporaryDirectory()];
}
- (instancetype)initWithDevice:(FBDevice *)device workingDirectory:(NSString *)workingDirectory
{
self = [super init];
if (!self) {
return nil;
}
_device = device;
_workingDirectory = workingDirectory;
_processFetcher = [FBProcessFetcher new];
return self;
}
#pragma mark FBXCTestCommands Implementation
- (FBFuture<NSNull *> *)runTestWithLaunchConfiguration:(FBTestLaunchConfiguration *)testLaunchConfiguration reporter:(id<FBXCTestReporter>)reporter logger:(id<FBControlCoreLogger>)logger
{
// Return early and fail if there is already a test run for the device.
// There should only ever be one test run per-device.
if (self.runningXcodeBuildOperation) {
return [[FBDeviceControlError
describeFormat:@"Cannot Start Test Manager with Configuration %@ as it is already running", testLaunchConfiguration]
failFuture];
}
// Terminate the reparented xcodebuild invocations.
return [[[[FBXcodeBuildOperation
terminateAbandonedXcodebuildProcessesForUDID:self.device.udid processFetcher:self.processFetcher queue:self.device.workQueue logger:logger]
onQueue:self.device.workQueue fmap:^(id _) {
self.runningXcodeBuildOperation = YES;
// Then start the task. This future will yield when the task has *started*.
return [self _startTestWithLaunchConfiguration:testLaunchConfiguration logger:logger];
}]
onQueue:self.device.workQueue fmap:^(FBProcess *task) {
// Then wrap the started task, so that we can augment it with logging and adapt it to the FBiOSTargetOperation interface.
return [FBXcodeBuildOperation confirmExitOfXcodebuildOperation:task configuration:testLaunchConfiguration reporter:reporter target:self.device logger:logger];
}]
onQueue:self.device.workQueue chain:^(FBFuture *future) {
self.runningXcodeBuildOperation = NO;
return future;
}];
}
#pragma mark Private
- (FBFuture<FBProcess *> *)_startTestWithLaunchConfiguration:(FBTestLaunchConfiguration *)configuration logger:(id<FBControlCoreLogger>)logger
{
NSError *error = nil;
// Create the .xctestrun file
NSString *filePath = [FBXcodeBuildOperation createXCTestRunFileAt:self.workingDirectory fromConfiguration:configuration error:&error];
if (!filePath) {
return [FBDeviceControlError failFutureWithError:error];
}
// Find the path to xcodebuild
NSString *xcodeBuildPath = [FBXcodeBuildOperation xcodeBuildPathWithError:&error];
if (!xcodeBuildPath) {
return [FBDeviceControlError failFutureWithError:error];
}
// This is to walk around a bug in xcodebuild. The UDID inside xcodebuild does not match
// UDID reported by device properties (the difference is missing hyphen in xcodebuild).
// This results in xcodebuild returning an error, since it cannot find a device with requested
// id (e.g. we query for 00008101-001D296A2EE8001E, while xcodebuild have
// 00008101001D296A2EE8001E).
NSString *udid = (__bridge NSString *)self.device.calls.CopyDeviceIdentifier(self.device.amDeviceRef);
// Create the Task, wrap it and store it.
return [FBXcodeBuildOperation
operationWithUDID:udid
configuration:configuration
xcodeBuildPath:xcodeBuildPath
testRunFilePath:filePath
simDeviceSet:nil
macOSTestShimPath:nil
queue:self.device.workQueue
logger:[logger withName:@"xcodebuild"]];
}
@end