Skip to content

Commit d35f56c

Browse files
authored
Fix hangs (#49)
* Add a few test cases for when the app hangs/crashes on launch. (@ob) * Fix for hanging and crashing before test run. (@vargon) * Add a variable for tests that do not launch an application (@vargon) * Clean up the message and use the new guard for detecting parsing tests (@ob) * Add dump of current commits vs. master to start of script (@vargon) * Lower timeout test timeout from 5 seconds to 2. (@vargon)
1 parent 1069426 commit d35f56c

File tree

11 files changed

+117
-39
lines changed

11 files changed

+117
-39
lines changed

BPSampleApp/BPSampleApp/AppDelegate.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
2020
// Override point for customization after application launch.
2121

2222
// This is a sample app for Bluepill testing.
23+
if (getenv("_BP_TEST_CRASH_ON_LAUNCH")) {
24+
char *p = NULL;
25+
NSLog(@"CRASHING AT USER'S REQUEST");
26+
strcpy(p, "I know this will crash my app");
27+
}
28+
if (getenv("_BP_TEST_HANG_ON_LAUNCH")) {
29+
NSLog(@"HANGING AT USER'S REQUEST");
30+
while(1) {
31+
sleep(10);
32+
}
33+
}
2334
return YES;
2435
}
2536

Bluepill-cli/BPInstanceTests/BPTreeParserTests.m

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
@interface BPTreeParserTests : XCTestCase
2121

22+
@property (nonatomic, strong) BPConfiguration* config;
23+
2224
@end
2325

2426
@implementation BPTreeParserTests
@@ -29,6 +31,8 @@ - (void)setUp {
2931
[BPUtils quietMode:YES];
3032
[BPUtils enableDebugOutput:NO];
3133
}
34+
self.config = [[BPConfiguration alloc] init];
35+
self.config.testing_NoAppWillRun = YES;
3236
}
3337

3438
- (void)tearDown {
@@ -53,8 +57,7 @@ - (void)testParsingCrash {
5357

5458
BPWriter *writer = getWriter();
5559
BPTreeParser *parser = [[BPTreeParser alloc] initWithWriter:writer];
56-
BPConfiguration *config = [[BPConfiguration alloc] init];
57-
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:config];
60+
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config];
5861

5962
parser.delegate = monitor;
6063

@@ -74,8 +77,7 @@ - (void)testCrashIntermixedWithPass {
7477

7578
BPWriter *writer = getWriter();
7679
BPTreeParser *parser = [[BPTreeParser alloc] initWithWriter:writer];
77-
BPConfiguration *config = [[BPConfiguration alloc] init];
78-
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:config];
80+
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config];
7981

8082
parser.delegate = monitor;
8183

@@ -95,8 +97,7 @@ - (void)testBadFilenameParsing {
9597

9698
BPWriter *writer = getWriter();
9799
BPTreeParser *parser = [[BPTreeParser alloc] initWithWriter:writer];
98-
BPConfiguration *config = [[BPConfiguration alloc] init];
99-
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:config];
100+
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config];
100101

101102
parser.delegate = monitor;
102103

@@ -116,8 +117,7 @@ - (void)testMissedCrash {
116117

117118
BPWriter *writer = getWriter();
118119
BPTreeParser *parser = [[BPTreeParser alloc] initWithWriter:writer];
119-
BPConfiguration *config = [[BPConfiguration alloc] init];
120-
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:config];
120+
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config];
121121
monitor.maxTimeWithNoOutput = 2.0; // change the max output time to 2 seconds
122122

123123
parser.delegate = monitor;
@@ -145,8 +145,7 @@ - (void)testErrorOnlyCrash {
145145

146146
BPWriter *writer = getWriter();
147147
BPTreeParser *parser = [[BPTreeParser alloc] initWithWriter:writer];
148-
BPConfiguration *config = [[BPConfiguration alloc] init];
149-
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:config];
148+
SimulatorMonitor *monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config];
150149

151150
parser.delegate = monitor;
152151

Bluepill-cli/BPInstanceTests/BluepillTests.m

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ - (void)setUp {
4747
self.config.jsonOutput = NO;
4848
self.config.headlessMode = NO;
4949
self.config.junitOutput = NO;
50+
self.config.testing_NoAppWillRun = YES;
5051
NSString *path = @"testScheme.xcscheme";
5152
self.config.schemePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:path];
5253
[BPUtils quietMode:YES];
@@ -78,17 +79,30 @@ - (void)tearDown {
7879
[super tearDown];
7980
}
8081

81-
// This is a template to run Voyager's tests
82-
- (void)testVoyager {
83-
// self.config.outputDirectory = @"/Users/khu/tmp/simulator";
84-
// self.config.schemePath = @"/Users/khu/ios/mntf-ios-sample-app_trunk/./mntf-ios-sample-app.xcodeproj/xcshareddata/xcschemes/mntf-ios-sample-app-ui-tests.xcscheme";
85-
// self.config.testCasesToRun = @[@"MNTFSampleAppUITests/testRotate", @"MNTFSampleAppUITests/testScrollToAnIndex"];
86-
// self.config.appBundlePath =
87-
// @"/Users/khu/ios/mntf-ios-sample-app_trunk/build/mntf-ios-sample-app/Build/Products/Debug-iphonesimulator/mntf-ios-sample-app.app";
88-
// self.config.testBundlePath = @"/Users/khu/ios/mntf-ios-sample-app_trunk/build/mntf-ios-sample-app/Build/Products/Debug-iphonesimulator/mntf-ios-sample-app.app/PlugIns/mntf-ios-sample-app-UITests.xctest";
89-
// BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run];
82+
- (void)testAppThatCrashesOnLaunch {
83+
NSString *testBundlePath = [BPTestHelper sampleAppBalancingTestsBunldePath];
84+
self.config.testBundlePath = testBundlePath;
85+
self.config.testing_CrashAppOnLaunch = YES;
86+
self.config.testing_NoAppWillRun = NO;
87+
BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run];
88+
XCTAssert(exitCode == BPExitStatusAppCrashed);
89+
90+
self.config.testing_NoAppWillRun = YES;
91+
}
92+
93+
- (void)testAppThatHangsOnLaunch {
94+
NSString *testBundlePath = [BPTestHelper sampleAppBalancingTestsBunldePath];
95+
self.config.testBundlePath = testBundlePath;
96+
self.config.testing_HangAppOnLaunch = YES;
97+
self.config.testing_NoAppWillRun = NO;
98+
self.config.stuckTimeout = @3;
99+
BPExitStatus exitCode = [[[Bluepill alloc] initWithConfiguration:self.config] run];
100+
XCTAssert(exitCode == BPExitStatusTestTimeout);
101+
102+
self.config.testing_NoAppWillRun = YES;
90103
}
91104

105+
92106
- (void)testRunningOnlyCertainTestcases {
93107
NSString *testBundlePath = [BPTestHelper sampleAppBalancingTestsBunldePath];
94108
self.config.testBundlePath = testBundlePath;
@@ -193,7 +207,7 @@ - (void)testReportWithFatalErrorTestsSet {
193207
}
194208

195209
- (void)testReportWithAppHangingTestsSet {
196-
self.config.stuckTimeout = @5;
210+
self.config.stuckTimeout = @2;
197211
self.config.plainOutput = YES;
198212
NSString *testBundlePath = [BPTestHelper sampleAppHangingTestsBundlePath];
199213
self.config.testBundlePath = testBundlePath;

Bluepill-cli/Bluepill-cli/Bluepill/BPStats.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
- (void)addTestFailure;
3737
- (void)addTestError;
3838
- (void)addSimulatorCrash;
39+
- (void)addApplicationCrash;
3940
- (void)addRetry;
4041
- (void)addTestRuntimeTimeout;
41-
- (void)addTestBPExitStatusTestTimeout;
42+
- (void)addTestOutputTimeout;
4243
- (void)addSimulatorCreateFailure;
4344
- (void)addSimulatorDeleteFailure;
4445
- (void)addSimulatorInstallFailure;

Bluepill-cli/Bluepill-cli/Bluepill/BPStats.m

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ @interface BPStats()
2424
@property (nonatomic, assign) NSInteger testsTotal;
2525
@property (nonatomic, assign) NSInteger testFailures;
2626
@property (nonatomic, assign) NSInteger testErrors;
27-
@property (nonatomic, assign) NSInteger crashes;
27+
@property (nonatomic, assign) NSInteger simCrashes;
28+
@property (nonatomic, assign) NSInteger appCrashes;
2829
@property (nonatomic, assign) NSInteger retries;
2930
@property (nonatomic, assign) NSInteger runtimeTimeout;
3031
@property (nonatomic, assign) NSInteger outputTimeout;
@@ -133,7 +134,11 @@ - (void)addTestError {
133134
}
134135

135136
- (void)addSimulatorCrash {
136-
self.crashes++;
137+
self.simCrashes++;
138+
}
139+
140+
- (void)addApplicationCrash {
141+
self.appCrashes++;
137142
}
138143

139144
- (void)addRetry {
@@ -144,7 +149,7 @@ - (void)addTestRuntimeTimeout {
144149
self.runtimeTimeout++;
145150
}
146151

147-
- (void)addTestBPExitStatusTestTimeout {
152+
- (void)addTestOutputTimeout {
148153
self.outputTimeout++;
149154
}
150155

@@ -188,7 +193,8 @@ - (void)generateFullReportWithWriter:(BPWriter *)writer exitCode:(int)exitCode {
188193
[writer writeLine:@"Timeout due to test run-time: %d", self.runtimeTimeout];
189194
[writer writeLine:@"Timeout due to no output: %d", self.outputTimeout];
190195
[writer writeLine:@"Retries: %d", self.retries];
191-
[writer writeLine:@"Simulator Crashes: %d", self.crashes];
196+
[writer writeLine:@"Application Crashes: %d", self.appCrashes];
197+
[writer writeLine:@"Simulator Crashes: %d", self.simCrashes];
192198
[writer writeLine:@"Simulator Creation Failures: %d", self.simulatorCreateFailures];
193199
[writer writeLine:@"Simulator Deletion Failures: %d", self.simulatorDeleteFailures];
194200
[writer writeLine:@"App Install Failures: %d", self.simulatorInstallFailures];

Bluepill-cli/Bluepill-cli/Bluepill/Bluepill.m

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,28 @@ - (void)checkProcessWithContext:(BPExecutionContext *)context {
308308
return;
309309
}
310310

311+
// This check should be last after all of the more specific tests
312+
// It checks if the app is even running, which it must be at this point
313+
// If it's not running and we passed the above checks (e.g., the tests are not yet completed)
314+
// then it must mean the app has crashed.
315+
// However, we have a short-circuit for tests because those may not actually run any app
316+
if (!isRunning && context.pid > 0 && !self.config.testing_NoAppWillRun) {
317+
// The tests ended before they even got started or the process is gone for some other reason
318+
[[BPStats sharedStats] endTimer:RUN_TESTS(context.attemptNumber)];
319+
[BPUtils printInfo:ERROR withString:@"Application crashed before tests started!"];
320+
[[BPStats sharedStats] addApplicationCrash];
321+
[self deleteSimulatorWithContext:context andStatus:BPExitStatusAppCrashed];
322+
return;
323+
}
324+
311325
NEXT([self checkProcessWithContext:context]);
312326
}
313327

314328
- (BOOL)isProcessRunningWithContext:(BPExecutionContext *)context {
329+
if (self.config.testing_NoAppWillRun) {
330+
return NO;
331+
}
332+
NSAssert(context.pid > 0, @"Application PID must be > 0");
315333
int rc = kill(context.pid, 0);
316334
return !(rc < 0);
317335
}

Bluepill-cli/Bluepill-cli/Simulator/SimulatorMonitor.m

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
typedef NS_ENUM(NSInteger, SimulatorState) {
1717
Idle,
18+
AppLaunched,
1819
Running,
1920
Completed
2021
};
@@ -172,6 +173,10 @@ - (void)onTestSuiteEnded:(NSString *)testSuiteName
172173
- (void)onOutputReceived:(NSString *)output {
173174
NSDate *currentTime = [NSDate date];
174175

176+
if (self.simulatorState == Idle) {
177+
self.simulatorState = AppLaunched;
178+
}
179+
175180
self.currentOutputId++; // Increment the Output ID for this instance since we've moved on to the next bit of output
176181

177182
__block NSUInteger previousOutputId = self.currentOutputId;
@@ -186,20 +191,24 @@ - (void)onOutputReceived:(NSString *)output {
186191
forTestName:(__self.currentTestName ?: __self.previousTestName)
187192
inClass:(__self.currentClassName ?: __self.previousClassName)];
188193
__self.exitStatus = BPExitStatusAppCrashed;
189-
[[BPStats sharedStats] addSimulatorCrash];
194+
[[BPStats sharedStats] addApplicationCrash];
190195
}
191196
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(__self.maxTimeWithNoOutput * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
192-
if (__self.currentOutputId == previousOutputId && __self.simulatorState == Running) {
197+
if (__self.currentOutputId == previousOutputId && (__self.simulatorState == Running || __self.simulatorState == AppLaunched)) {
193198
NSString *testClass = (__self.currentClassName ?: __self.previousClassName);
194199
NSString *testName = (__self.currentTestName ?: __self.previousTestName);
195-
[BPUtils printInfo:TIMEOUT withString:@" %10.6fs waiting for output from %@/%@",
196-
__self.maxTimeWithNoOutput, testClass, testName];
200+
if (testClass == nil && testName == nil && (__self.simulatorState == AppLaunched || __self.simulatorState == Idle)) {
201+
[BPUtils printInfo:ERROR withString:@"It appears that tests have not yet started. The test app has frozen prior to the first test."];
202+
} else {
203+
[BPUtils printInfo:TIMEOUT withString:@" %10.6fs waiting for output from %@/%@",
204+
__self.maxTimeWithNoOutput, testClass, testName];
205+
[[BPStats sharedStats] endTimer:[NSString stringWithFormat:TEST_CASE_FORMAT, [BPStats sharedStats].attemptNumber, testClass, testName]];
206+
}
197207
[__self stopTestsWithErrorMessage:@"Timed out waiting for the test to produce output. Test was aboorted."
198208
forTestName:testName
199209
inClass:testClass];
200210
__self.exitStatus = BPExitStatusTestTimeout;
201-
[[BPStats sharedStats] endTimer:[NSString stringWithFormat:TEST_CASE_FORMAT, [BPStats sharedStats].attemptNumber, testClass, testName]];
202-
[[BPStats sharedStats] addTestBPExitStatusTestTimeout];
211+
[[BPStats sharedStats] addTestOutputTimeout];
203212
}
204213
});
205214
self.lastOutput = currentTime;
@@ -210,15 +219,12 @@ - (void)stopTestsWithErrorMessage:(NSString *)message forTestName:(NSString *)te
210219
// Timeout or crash on a test means we should skip it when we rerun the tests
211220
[self updateExecutedTestCaseList:testName inClass:testClass];
212221

213-
if (![[self.device stateString] isEqualToString:@"Shutdown"]) {
214-
// self.appPID can be zero when running the parsing tests
215-
// since we're not actually creating a simulator and running an app.
222+
if (![[self.device stateString] isEqualToString:@"Shutdown"] && !self.config.testing_NoAppWillRun) {
216223
[BPUtils printInfo:ERROR withString:@"Will kill the process with appPID: %d", self.appPID];
217-
if (self.appPID && (kill(self.appPID, 0) == 0) && (kill(self.appPID, SIGTERM) < 0)) {
218-
[BPUtils printInfo:ERROR withString:@"Failed to kill the process with appPID: %d", self.appPID];
219-
perror("kill");
220-
} else {
221-
[BPUtils printInfo:ERROR withString:@"Success killing the process with appPID: %d", self.appPID];
224+
NSAssert(self.appPID > 0, @"Failed to find a valid PID");
225+
if ((kill(self.appPID, 0) == 0) && (kill(self.appPID, SIGTERM) < 0)) {
226+
[BPUtils printInfo:ERROR withString:@"Failed to kill the process with appPID: %d: %s",
227+
self.appPID, strerror(errno)];
222228
}
223229
}
224230

Bluepill-cli/Bluepill-cli/Simulator/SimulatorRunner.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ - (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCom
242242
config:self.config]];
243243
[appLaunchEnvironment addEntriesFromDictionary:argsAndEnv[@"env"]];
244244

245+
if (self.config.testing_CrashAppOnLaunch) {
246+
appLaunchEnvironment[@"_BP_TEST_CRASH_ON_LAUNCH"] = @"YES";
247+
}
248+
if (self.config.testing_HangAppOnLaunch) {
249+
appLaunchEnvironment[@"_BP_TEST_HANG_ON_LAUNCH"] = @"YES";
250+
}
251+
245252
// Intercept stdout, stderr and post as simulator-output events
246253
NSString *stdout_stderr = [NSString stringWithFormat:@"%@/tmp/stdout_stderr_%@", self.device.dataPath, [[self.device UDID] UUIDString]];
247254
NSString *simStdoutPath = [BPUtils mkstemp:stdout_stderr withError:nil];

Source/Shared/BPConfiguration.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121
@interface BPConfiguration : NSObject <NSCopying>
2222

23+
/*
24+
* WARNING: Any fields you add here need to be explicitly handled in the copyWithZone
25+
* and mutableCopyWithZone methods. Yeah, it's stupid, we should fix it.
26+
*/
27+
2328
@property (nonatomic, strong) NSString *appBundlePath;
2429
@property (nonatomic, strong) NSString *testBundlePath;
2530
@property (nonatomic, strong) NSArray *additionalTestBundles;
@@ -47,6 +52,11 @@
4752
@property (nonatomic, strong) NSNumber *listTestsOnly;
4853
@property (nonatomic) BOOL quiet;
4954

55+
// These fields are for testing.
56+
@property (nonatomic) BOOL testing_CrashAppOnLaunch;
57+
@property (nonatomic) BOOL testing_HangAppOnLaunch;
58+
@property (nonatomic) BOOL testing_NoAppWillRun;
59+
5060
// Generated fields
5161
@property (nonatomic, strong) NSString *xcodePath;
5262

Source/Shared/BPConfiguration.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,9 @@ - (id)mutableCopyWithZone:(NSZone *)zone {
248248
newConfig.simDeviceType = self.simDeviceType;
249249
#endif
250250
newConfig.xcodePath = self.xcodePath;
251+
newConfig.testing_CrashAppOnLaunch = self.testing_CrashAppOnLaunch;
252+
newConfig.testing_HangAppOnLaunch = self.testing_HangAppOnLaunch;
253+
newConfig.testing_NoAppWillRun = self.testing_NoAppWillRun;
251254

252255
return newConfig;
253256
}

scripts/bluepill.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ bluepill_build()
2525

2626
bluepill_test()
2727
{
28+
# Dump our current diff state with master
29+
git --no-pager log master..HEAD
30+
2831
default_runtime=`grep BP_DEFAULT_RUNTIME ./Source/Shared/BPConstants.h | sed 's/.*BP_DEFAULT_RUNTIME *//;s/"//g;s/ *$//g;'`
2932
xcrun simctl list runtimes | grep -q "$default_runtime" || {
3033
echo "Your system doesn't contain latest runtime: iOS $default_runtime"

0 commit comments

Comments
 (0)