Skip to content
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

Merged
merged 43 commits into from
May 15, 2017
Merged

Reuse Simulator (from sim-reuse branch) #54

merged 43 commits into from
May 15, 2017

Conversation

njuliyu
Copy link
Contributor

@njuliyu njuliyu commented Feb 8, 2017

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.

njuliyu and others added 17 commits January 25, 2017 12:24
* 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.
…eID" to "useSimUDID" and "deleteSimUDID" respectively.
@ob ob mentioned this pull request Feb 10, 2017
- 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;

Copy link
Contributor Author

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.

Copy link
Member

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];
Copy link
Member

@oliverhu oliverhu Feb 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove, same below

Copy link
Contributor Author

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.

Copy link
Member

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{})

Copy link
Contributor Author

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;
Copy link
Member

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]];
Copy link
Member

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;

Copy link
Member

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]);

Copy link
Member

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()];
Copy link
Member

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are too many ifs

Copy link
Contributor Author

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?

Copy link
Member

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
Copy link
Member

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 {
Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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];
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got you

njuliyu and others added 9 commits February 22, 2017 18:03
…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;
Copy link
Member

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)

Copy link
Contributor Author

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

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowReuseSim looks better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reuseSimAllowed ?

Copy link
Contributor Author

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


Copy link
Member

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 ||
Copy link
Member

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
Copy link
Member

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

Copy link
Contributor Author

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 {
Copy link
Member

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
Copy link
Member

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 {
Copy link
Member

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]

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change to simulatorWithDeviceUDID?

Copy link
Contributor Author

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 {
Copy link
Member

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

Copy link
Contributor Author

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);
Copy link
Member

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?

Copy link
Member

@oliverhu oliverhu left a 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
Copy link
Member

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
Copy link
Member

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 {
Copy link
Member

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++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces between operators!!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i = 0, i < xx

@njuliyu njuliyu merged commit 3bcef0c into master May 15, 2017
@oliverhu oliverhu deleted the sim-reuse branch July 11, 2017 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants