-
Notifications
You must be signed in to change notification settings - Fork 231
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reuse Simulator (from sim-reuse branch) #54
Conversation
* find the running simulator app process by device id so we can shutdown the simulator cleanly * bluebill will delete all simulators after all bp instances has finished when keep-simulator is enabled * added test suite BPIntegrationTests
…d when Retry when it has been deleted due to crash or hang in the first run. * added test case testReuseSimulatorRetryAppCrashingTestsSet();
* support recreate simulator when the specified device id is not valid and retry is allowed. * added test case testReuseSimulatorNotExistWithRetry
…lativePathInConfigurationFile to sync with the Linkedin repository.
* fixed some minor style issues.
* minor style fixes.
…eID" to "useSimUDID" and "deleteSimUDID" respectively.
- fixed some minor style issue - changed some function names from "deviceID" to "UDID"
context.simulatorCreated = NO; //also use this flag to tell writeSimUDIDFile() the simulator not avaiable | ||
context.config.useSimUDID = nil; //prevent reuse this device when RETRY | ||
self.config.useSimUDID = nil; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous comments from @vargon:
Does self.config need to be altered here? It should be treated as immutable. That's why we store executionConfigCopy which we do treat as mutable.
My comments:
Sorry I don't know self.config should be treated as immutable. But If we don't reset it here, the original value will be copied from self.config to context.config again when retry, and the invalid UDID may be reused, so only changing context.config is not enough.
If we don't want to modify self.config in any way, one workaround is to add one more property to Bluepill class, e.g. noUseSimUDID, and use that to control if we should use useSimUDID, but that looks redundant and awkward too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make BPConfiguration fields readonly
@@ -344,6 +344,137 @@ - (void)testRunWithPassingTestsSet { | |||
XCTAssert(exitCode == BPExitStatusTestsAllPassed); | |||
} | |||
|
|||
- (void)testReuseSimulator { | |||
//[BPUtils quietMode:NO]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove, same below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you mean remove "//[BPUtils quietMode:NO];", but I find it very useful to uncomment it during test & debug. If we don't like commented code, I would like to uncomment and disable quite mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about making [BPUtils quietModel:YES]
as default in tests? (move to +(void)setUp{}
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move this to setup as default, then there will be too much output from every test case.
ideally, I think should have different control when we run the tests in different environments, like CI vs local. So the hardcoded way is not the good way. Better way should be controlled by an environment variable or CLI option passed to test (not sure how to do) .
As @ashit said, our quite and debug mode in the current code is a mess. So I think I will just leave these few lines of commented code in the test cases for now, and do a clean up when we have sorted out a better way to control quite and debug mode.
@@ -28,4 +28,6 @@ | |||
|
|||
- (instancetype)init NS_UNAVAILABLE; | |||
|
|||
- (NSString *)getSimulatorUDID; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
documentation pls.
Make this a (readonly) propoerty
BOOL success = [context.runner uninstallApplicationAndReturnError:&error]; | ||
|
||
[[BPStats sharedStats] endTimer:stepName]; | ||
[BPUtils printInfo:(success ? INFO : ERROR) withString:[@"Completed: " stringByAppendingString:stepName]]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@"Completed: %@", stepName
context.simulatorCreated = NO; //also use this flag to tell writeSimUDIDFile() the simulator not avaiable | ||
context.config.useSimUDID = nil; //prevent reuse this device when RETRY | ||
self.config.useSimUDID = nil; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make BPConfiguration fields readonly
context.simulatorCreated = YES; //if we don't set this flag, deleteSimulatorWithContext() won't proceed | ||
|
||
NEXT([self deleteSimulatorWithContext:context andStatus:BPExitStatusSimulatorDeleted]); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra line
NSString *idStr = [self getSimulatorUDID]; | ||
if (!idStr || !self.context.simulatorCreated) return; | ||
|
||
NSString *tempFileName = [NSString stringWithFormat:@"bluepill-deviceid.%d",getpid()]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
space before getpid()
@@ -61,6 +61,27 @@ - (void)createSimulatorWithDeviceName:(NSString *)deviceName completion:(void (^ | |||
}]; | |||
} | |||
|
|||
- (BOOL)useSimulatorWithDeviceUDID:(NSUUID *)deviceUDID { | |||
self.device = [SimulatorRunner findDeviceWithConfig:self.config andDeviceID:deviceUDID]; | |||
if (self.device) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are too many if
s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are. But the code block are small, so the readability are pretty good. Any better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!self.device) {return no;}
if (![self.device.stateString isEqualToString:@"Booted"]) {printError, return NO}
Other logic?
if (!installed) return FALSE; | ||
return TRUE; | ||
+ (BOOL)uninstallAppWithBundleID:(NSString *)hostBundleID | ||
bundlePath:(NSString *)hostBundlePath |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indentation
@@ -190,6 +217,43 @@ + (void)createDeviceWithConfig:(BPConfiguration *)config andName:(NSString *)dev | |||
}]; | |||
} | |||
|
|||
+ (SimDevice *)findDeviceWithConfig:(BPConfiguration *)config andDeviceID:(NSUUID *)deviceID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass in an NSError **
to store the error instead of printing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't see the benefit of passing NSError ** here. look at the code of createDeviceWithConfig, which has completion:(void (^)(NSError *, SimDevice *))completion. But findDeviceWithConfig doesn't have an async call with completion block. any benefit of create our own NSError object and return to the caller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, by passing a NSError back to the caller, it is up to the caller to decide how to deal with the error (suppress, error handling etc), instead of printing things out just here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To reduce your concern, I moved the printing of error messages to the caller when device lookup fail.
I think in this simply case of dictionary lookup, return of nil is good enough to indicate an error to the caller, no extra NSError object is needed. The other methods in SimulatorRunner.m which need a NSError pointer actually pass that to the completion block, but it doesn't apply in this sync method, so we should be OK.
} | ||
|
||
+ (NSRunningApplication *)findSimAppWithDeviceUDID:(NSString *)deviceUDID { | ||
NSString * cmd = [NSString stringWithFormat:@"ps -A | grep 'Simulator\\.app.*-CurrentDeviceUDID %@'", deviceUDID]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd prefer xcrun simctl list | grep booted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we are looking for the process id of the running simulator app with GUI. simctl command won't help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got you
…invalid simulator in retry - add a new property mayUseSim to Bluepill class to indicate if we may reuse the specified simulator - added test case testDeleteSimulatorNotExistWithRetry
- change the Bluepill method getSimulatorUDID to a readonly property simulatorUDID - reorganize the code of useSimulatorWithDeviceUDID() to reduce the level of nested if - fixed the missing step name when printing the information of uninstalling app - fixed other minor style issues
- process the options differently based on master or slave program before calling getopt_long() - allow "--test" option both at master and slave program - allow "--reuse-simulator" option at master program only, and the other 3 reusing sim related options at slave program only - if "--delete-simulator" option is used, no other options are required.
@@ -14,6 +14,8 @@ | |||
|
|||
@interface Bluepill : NSObject | |||
|
|||
@property (nonatomic, readonly) NSString *simulatorUDID; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move this to BPConfiguration?
two benefits:
- avoid polluting the bluepill class
- users can also use this field for single bluepill-cli instance (point to specific simulator by UUID)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BPConfiguration already has "@Property (nonatomic, strong) NSString *useSimUDID;"
simulatorUDID is defined as
- (NSString *)simulatorUDID {
return self.context.runner.UDID;
}
Which is the runtime UDID and could be different than the one specified from the CLI.
Currently simulatorUDID is mostly used in the test cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to test_simulatorUDID as we discussed.
@@ -40,6 +40,7 @@ @interface Bluepill()<BPTestBundleConnectionDelegate> | |||
@property (nonatomic, assign) BOOL exitLoop; | |||
@property (nonatomic, assign) NSInteger failureTolerance; | |||
@property (nonatomic, assign) NSInteger retries; | |||
@property (nonatomic, assign) BOOL mayReuseSim; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember if I left a comment for this before.. but I am not a fan of may or possible for variable name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allowReuseSim looks better?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reuseSimAllowed ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} else { | ||
self.mayReuseSim = NO; //prevent reuse this device when RETRY | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra line
@@ -458,8 +518,12 @@ - (void)runnerCompletedWithContext:(BPExecutionContext *)context { | |||
if (context.simulatorCrashed) { | |||
// If we crashed, we need to retry | |||
[self deleteSimulatorWithContext:context andStatus:BPExitStatusSimulatorCrashed]; | |||
} else if (self.config.keepSimulator && (context.runner.exitStatus == BPExitStatusTestsAllPassed || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (a
&& e
&& (b || c))
@@ -145,6 +169,107 @@ - (NSError *)waitForDeviceReady { | |||
return nil; | |||
} | |||
|
|||
+ (BOOL)installAppWithBundleID:(NSString *)hostBundleID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is merge conflict , use - (BOOL)installApp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new method added, not merge conflict. but it is OK to change it to a instance method.
error:error]; | ||
} | ||
|
||
+ (void)createDeviceWithConfig:(BPConfiguration *)config andName:(NSString *)deviceName completion:(void (^)(NSError *, SimDevice *))completion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, merge conflict
error:error]; | ||
} | ||
|
||
+ (BOOL)uninstallAppWithBundleID:(NSString *)hostBundleID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to instance method
return device; //could be nil when not found | ||
} | ||
|
||
+ (NSRunningApplication *)findSimAppWithDeviceUDID:(NSString *)deviceUDID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confusing name. runningAppInDeviceUDID
Why do you need this method btw? can you just do a [simDevice getRunningApp]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you asked the same question before :-)
This is to find the simulator GUI app, not the app running in the simulator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change to simulatorWithDeviceUDID
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the name of 'simulator" is even more confusing, which sounds like a sim device. I changed the method name to "findSimGUIAppWith DeviceUDID"
[BPUtils printInfo:INFO withString:@" ¯\\_(ツ)_/¯"]; | ||
} | ||
|
||
+ (SimDevice *)findDeviceWithConfig:(BPConfiguration *)config andDeviceID:(NSUUID *)deviceID { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we move deviceID into config, we can have deviceWithConfig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the deviceID could be one of config.useSimUDID or config.deleteSimUDID, so we have to keep it.
@@ -66,14 +66,14 @@ - (void)testOneBPInstance { | |||
BPRunner *runner = [BPRunner BPRunnerForApp:app withConfig:self.config withBpPath:bpPath]; | |||
int rc = [runner run]; | |||
XCTAssert(rc == 0); | |||
//XCTAssert([runner.nsTaskList count] == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason to keep this commented out line?
* renamed a few variables and methods * changed a few class methods to instance methods. * minor fixes to code style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
almost there:D
@@ -145,6 +169,55 @@ - (NSError *)waitForDeviceReady { | |||
return nil; | |||
} | |||
|
|||
- (BOOL)installAppWithBundleID:(NSString *)hostBundleID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this method. Device
should be self's device
error:error]; | ||
} | ||
|
||
- (BOOL)uninstallAppWithBundleID:(NSString *)hostBundleID |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@@ -171,8 +244,16 @@ - (BOOL)installApplicationAndReturnError:(NSError *__autoreleasing *)error { | |||
return YES; | |||
} | |||
|
|||
- (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCompletion:(void (^)(NSError *, pid_t))completion isHostApp:(BOOL)isHostApp { | |||
- (BOOL)uninstallApplicationAndReturnError:(NSError *__autoreleasing *)error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
possibly also here?
@@ -210,6 +245,12 @@ - (int)run { | |||
seconds += 1; | |||
} | |||
|
|||
for (int i=0; i<[deviceList count]; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spaces between operators!!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i = 0, i < xx
Option added:
"reuse-simulator" added at the bluepill command level.
"keep-simulator", "use-simulator", "delete-simulator" added at the bp command level for internal use.
Below is how this feature works:
Start bluepill command with "--reuse-simulator" or add "reuse-simulator : true" to config.json
The driver process will start a bunch of bp instance with "keep-simulator: true"
When "keep-simulator" is enabled, the bp instance won't shutdown and delete the simulator when the test bundle is finished normally (pass or normal fail).
If the test cases crash or hang, or simulator crashes, the bp instance will force delete the simulator and won't allow to reuse it.
The bp instance pass the device id of the simulator back to the driver process by writing the id to a temporary file before it terminates.
The bluepill driver process read the temporary file to get the simulator device id and stored it in a pool ready to be reused.
If there are available simulator device id to reuse, the driver process will pass the option "use-simualtor: [device id string]" when starting a new bp instance.
When a bp instance start with a specified device id, it won't create new device first, it will try to look for a running device with specified id first, and also connect to the existing running simulator GUI app if it is in the non-headless mode.
if a device is successfully reused, bp will continue to install the app and launch the test cases to run.
if a device is invalid and cannot be reused, but retry is allowed by the configuration, a new device will be created.
When all test bundles have been finished, and the device id pool to reuse is not empty, the bluepill driver will launch new bp instance with "delete-simulator:[device id string]" for each device to shutdown and delete all the running simulators. But it is fire & forget so the driver won't wait for the bp to return.
Some test cases have been added to BluepillTest.m and a new BPIntegrationTests.m to test this feature.