FBSimulatorControl/Strategies/FBSimulatorShutdownStrategy.m (116 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 "FBSimulatorShutdownStrategy.h" #import <CoreSimulator/SimDevice.h> #import <FBControlCore/FBControlCore.h> #import "FBSimulator.h" #import "FBSimulatorError.h" #import "FBSimulator.h" #import "FBSimulatorControlConfiguration.h" #import "FBSimulatorError.h" @interface FBSimulatorShutdownStrategy () @property (nonatomic, strong, readonly) FBSimulator *simulator; @end @implementation FBSimulatorShutdownStrategy #pragma mark Initializers + (instancetype)strategyWithSimulator:(FBSimulator *)simulator { return [[self alloc] initWithSimulator:simulator]; } - (instancetype)initWithSimulator:(FBSimulator *)simulator { self = [super init]; if (!self) { return nil; } _simulator = simulator; return self; } #pragma mark Public Methdos + (FBFuture<NSNull *> *)shutdown:(FBSimulator *)simulator { id<FBControlCoreLogger> logger = simulator.logger; [logger.debug logFormat:@"Starting Safe Shutdown of %@", simulator.udid]; // If the device is in a strange state, we should bail now if (simulator.state == FBiOSTargetStateUnknown) { return [[FBSimulatorError describe:@"Failed to prepare simulator for usage as it is in an unknown state"] failFuture]; } // Calling shutdown when already shutdown should be avoided (if detected). if (simulator.state == FBiOSTargetStateShutdown) { [logger.debug logFormat:@"Shutdown of %@ succeeded as it is already shutdown", simulator.udid]; return FBFuture.empty; } // Xcode 7 has a 'Creating' step that we should wait on before confirming the simulator is ready. // On many occasions this is the case as we wait for the Simulator to be usable. if (simulator.state == FBiOSTargetStateCreating) { return [FBSimulatorShutdownStrategy transitionCreatingToShutdown:simulator]; } // The error code for 'Unable to shutdown device in current state: Shutdown' // can be safely ignored since these codes confirm that the simulator is already shutdown. return [FBSimulatorShutdownStrategy shutdownSimulator:simulator]; } + (FBFuture<NSNull *> *)shutdownAll:(NSArray<FBSimulator *> *)simulators { NSMutableArray<FBFuture<NSNull *> *> *futures = NSMutableArray.array; for (FBSimulator *simulator in simulators) { [futures addObject:[self shutdown:simulator]]; } return [[FBFuture futureWithFutures:futures] mapReplace:NSNull.null]; } #pragma mark Private + (NSInteger)errorCodeForShutdownWhenShuttingDown { if (FBXcodeConfiguration.isXcode9OrGreater) { return 164; } return 163; } + (FBFuture<NSNull *> *)shutdownSimulator:(FBSimulator *)simulator { FBMutableFuture<NSNull *> *future = FBMutableFuture.future; id<FBControlCoreLogger> logger = simulator.logger; NSInteger errorCodeForShutdownWhenShuttingDown = FBSimulatorShutdownStrategy.errorCodeForShutdownWhenShuttingDown; [logger.debug logFormat:@"Shutting down Simulator %@", simulator.udid]; [simulator.device shutdownAsyncWithCompletionQueue:simulator.asyncQueue completionHandler:^(NSError *error){ if (error && error.code == errorCodeForShutdownWhenShuttingDown) { [logger logFormat:@"Got Error Code %lu from shutdown, simulator is already shutdown", error.code]; [future resolveWithResult:NSNull.null]; } else if (error) { [future resolveWithError:error]; } else { [future resolveWithResult:NSNull.null]; } }]; return [future onQueue:simulator.workQueue fmap:^(id _){ return [simulator resolveState:FBiOSTargetStateShutdown]; }]; } + (FBFuture<NSNull *> *)transitionCreatingToShutdown:(FBSimulator *)simulator { return [[[simulator resolveState:FBiOSTargetStateShutdown] timeout:FBControlCoreGlobalConfiguration.regularTimeout waitingFor:@"Simulator to resolve state %@", FBiOSTargetStateStringShutdown] onQueue:simulator.workQueue chain:^FBFuture<NSNull *> *(FBFuture *future) { if (future.result) { return FBFuture.empty; } return [FBSimulatorShutdownStrategy eraseSimulator:simulator]; }]; } + (FBFuture<NSNull *> *)eraseSimulator:(FBSimulator *)simulator { FBMutableFuture<NSNull *> *future = FBMutableFuture.future; id<FBControlCoreLogger> logger = simulator.logger; [logger.debug logFormat:@"Erasing Simulator %@", simulator.udid]; [simulator.device eraseContentsAndSettingsAsyncWithCompletionQueue:simulator.asyncQueue completionHandler:^(NSError *error) { if (error) { [future resolveWithError:error]; } else { [future resolveWithResult:NSNull.null]; } }]; return [future onQueue:simulator.workQueue fmap:^(id _) { return [[simulator resolveState:FBiOSTargetStateShutdown] timeout:FBControlCoreGlobalConfiguration.regularTimeout waitingFor:@"Timed out waiting for Simulator to transition from Creating -> Shutdown"]; }]; } @end