Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit 543348a

Browse files
committed
Support running UI tests.
1 parent 9219e0d commit 543348a

File tree

8 files changed

+399
-70
lines changed

8 files changed

+399
-70
lines changed

otest-shim/otest-shim/otest-shim.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,13 @@ static void XCTestSuite_performTest(id self, SEL sel, id arg1)
456456
XCPerformTestWithSuppressedExpectedAssertionFailures(self, originalSelector, arg1);
457457
}
458458

459+
#pragma mark - UI Tests
460+
static BOOL XCUIApplication_prefersPlatformLauncher(id self, SEL sel)
461+
{
462+
// Override to force XCUIApplication to not rely on Xcode when running UI tests
463+
return YES;
464+
}
465+
459466
#pragma mark - _enableSymbolication
460467
static BOOL XCTestCase__enableSymbolication(id self, SEL sel)
461468
{
@@ -568,6 +575,9 @@ static void SwizzleXCTestMethodsIfAvailable()
568575
XTSwizzleSelectorForFunction(NSClassFromString(@"XCTestSuite"),
569576
@selector(performTest:),
570577
(IMP)XCTestSuite_performTest);
578+
XTSwizzleSelectorForFunction(NSClassFromString(@"XCUIApplication"),
579+
@selector(prefersPlatformLauncher),
580+
(IMP)XCUIApplication_prefersPlatformLauncher);
571581
if ([NSClassFromString(@"XCTestCase") respondsToSelector:@selector(_enableSymbolication)]) {
572582
// Disable symbolication thing on xctest 7 because it sometimes takes forever.
573583
XTSwizzleClassSelectorForFunction(NSClassFromString(@"XCTestCase"),

xctool/xctool-tests/RunTestsActionTests.m

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,28 @@ - (void)testActionOptionMultipleAppTests
11531153
}];
11541154
}
11551155

1156+
- (void)testActionOptionUITest
1157+
{
1158+
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
1159+
Options *options = [[Options optionsFrom:
1160+
@[
1161+
@"-sdk", @"macosx10.7",
1162+
@"run-tests",
1163+
@"-uiTest",
1164+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest:"
1165+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner:"
1166+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests"
1167+
]] assertOptionsValidate];
1168+
RunTestsAction *action = options.actions[0];
1169+
RunTestsActionUITest *config =
1170+
[[RunTestsActionUITest alloc] initWithHostApp:TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests"
1171+
runnerApp:TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner"];
1172+
assertThat(action.uiTests, equalTo(
1173+
@{TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest": config,
1174+
}));
1175+
}];
1176+
}
1177+
11561178
- (void)testActionOptionMixedLogicAndAppTests
11571179
{
11581180
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
@@ -1210,7 +1232,7 @@ - (void)testWillComplainWhenPassingAppTestThatDoesntExist
12101232
@"-appTest", TEST_DATA @"path/to/this-does-not-exist.xctest:path/to/HostApp.app/HostApp",
12111233
]]
12121234
assertOptionsFailToValidateWithError:
1213-
@"run-tests: Application test at path '" TEST_DATA @"path/to/this-does-not-exist.xctest' does not exist or is not a directory"];
1235+
@"run-tests: option -appTest has invalid argument: path '" TEST_DATA @"path/to/this-does-not-exist.xctest' doesn't exist"];
12141236

12151237
}];
12161238
}
@@ -1225,8 +1247,51 @@ - (void)testWillComplainWhenPassingHostAppBinaryThatDoesntExist
12251247
TEST_DATA @"path/to/NonExistentHostApp.app/HostApp",
12261248
]]
12271249
assertOptionsFailToValidateWithError:
1228-
@"run-tests: Application test host binary at path '" TEST_DATA "path/to/NonExistentHostApp.app/HostApp' does not exist or is not a file"];
1250+
@"run-tests: option -appTest has invalid argument: path '" TEST_DATA "path/to/NonExistentHostApp.app/HostApp' doesn't exist"];
1251+
1252+
}];
1253+
}
1254+
1255+
- (void)testWillComplainWhenPassingUITestBundleThatDoesntExist
1256+
{
1257+
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
1258+
[[Options optionsFrom:
1259+
@[@"-sdk", @"iphonesimulator",
1260+
@"run-tests",
1261+
@"-uiTest",
1262+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITestsFAKE.xctest:"
1263+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner:"
1264+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests"]]
1265+
assertOptionsFailToValidateWithError:
1266+
@"run-tests: option -uiTest has invalid argument: path '"TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITestsFAKE.xctest' doesn't exist"];
1267+
}];
1268+
}
1269+
1270+
- (void)testWillComplainWhenPassingUITestRunnerAppThatDoesntExist {
1271+
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
1272+
[[Options optionsFrom:
1273+
@[@"-sdk", @"iphonesimulator",
1274+
@"run-tests",
1275+
@"-uiTest",
1276+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest:"
1277+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-RunnerFAKE:"
1278+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests"]]
1279+
assertOptionsFailToValidateWithError:
1280+
@"run-tests: option -uiTest has invalid argument: path '"TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-RunnerFAKE' doesn't exist"];
1281+
}];
1282+
}
12291283

1284+
- (void)testWillComplainWhenPassingUITestHostAppThatDoesntExist {
1285+
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
1286+
[[Options optionsFrom:
1287+
@[@"-sdk", @"iphonesimulator",
1288+
@"run-tests",
1289+
@"-uiTest",
1290+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest:"
1291+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner:"
1292+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITestsFAKE"]]
1293+
assertOptionsFailToValidateWithError:
1294+
@"run-tests: option -uiTest has invalid argument: path '"TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITestsFAKE' doesn't exist"];
12301295
}];
12311296
}
12321297

@@ -1247,6 +1312,26 @@ - (void)testWillComplainWhenPassingSameLogicTestForMultipleTestHostApps
12471312
}];
12481313
}
12491314

1315+
- (void)testWillComplainWhenPassingSameLogicTestForMultipleUITests
1316+
{
1317+
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{
1318+
[[Options optionsFrom:
1319+
@[@"-sdk", @"iphonesimulator",
1320+
@"run-tests",
1321+
@"-uiTest",
1322+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest:"
1323+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner:"
1324+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests",
1325+
@"-uiTest",
1326+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest:"
1327+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/TestProject-UITestsUITests-Runner:"
1328+
TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITests.app/TestProject-UITests",
1329+
]]
1330+
assertOptionsFailToValidateWithError:
1331+
@"run-tests: The same test bundle '"TEST_DATA @"TestProject-UITests/Build/Products/Debug-iphonesimulator/TestProject-UITestsUITests-Runner.app/PlugIns/TestProject-UITestsUITests.xctest' cannot have more than one test configuration of host app and runner"];
1332+
}];
1333+
}
1334+
12501335
- (void)testPassingLogicTestViaCommandLine
12511336
{
12521337
[[FakeTaskManager sharedManager] runBlockWithFakeTasks:^{

xctool/xctool/OCUnitIOSAppTestRunner.m

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ - (void)runTestsAndFeedOutputTo:(FdOutputLineFeedBlock)outputLineBlock
4444
return;
4545
}
4646

47+
NSString *testRunnerBundlePath;
48+
NSString *testRunnerBundleID;
49+
if ([_buildSettings[Xcode_USES_XCTRUNNER] boolValue]) {
50+
// manually specified
51+
if (_buildSettings[Xcode_UI_RUNNER_APP] != nil) {
52+
testRunnerBundleID =
53+
[self bundleIDForAppAtPath:_buildSettings[Xcode_UI_RUNNER_APP]
54+
bundlePath:&testRunnerBundlePath
55+
error:startupError];
56+
} else {
57+
// extract from build settings
58+
testRunnerBundlePath = [_buildSettings[Xcode_TARGET_BUILD_DIR] stringByDeletingLastPathComponent];
59+
testRunnerBundleID = AppBundleIDForAppAtPath(testRunnerBundlePath);
60+
}
61+
}
62+
4763
BOOL (^prepareSimulator)(BOOL freshSimulator, BOOL resetSimulator, NSString **error) =
4864
^(BOOL freshSimulator, BOOL resetSimulator, NSString **error) {
4965
if (freshSimulator || resetSimulator) {
@@ -120,6 +136,14 @@ - (void)runTestsAndFeedOutputTo:(FdOutputLineFeedBlock)outputLineBlock
120136
error:error]) {
121137
return NO;
122138
}
139+
140+
if (testRunnerBundleID != nil &&
141+
![SimulatorWrapper uninstallTestHostBundleID:testRunnerBundleID
142+
device:[_simulatorInfo simulatedDevice]
143+
reporters:_reporters
144+
error:error]) {
145+
return NO;
146+
}
123147
}
124148

125149
// Always install the app before running it. We've observed that
@@ -139,6 +163,14 @@ - (void)runTestsAndFeedOutputTo:(FdOutputLineFeedBlock)outputLineBlock
139163
error:error]) {
140164
return NO;
141165
}
166+
if (testRunnerBundleID != nil && testRunnerBundlePath != nil &&
167+
![SimulatorWrapper installTestHostBundleID:testRunnerBundleID
168+
fromBundlePath:testRunnerBundlePath
169+
device:[_simulatorInfo simulatedDevice]
170+
reporters:_reporters
171+
error:error]) {
172+
return NO;
173+
}
142174
return YES;
143175
};
144176

@@ -191,7 +223,7 @@ - (void)runTestsAndFeedOutputTo:(FdOutputLineFeedBlock)outputLineBlock
191223
// Let's try several times to run before reporting about failure to callers.
192224
for (NSInteger remainingAttempts = kMaxRunTestsAttempts - 1; remainingAttempts >= 0; --remainingAttempts) {
193225
NSError *error = nil;
194-
BOOL infraSucceeded = [SimulatorWrapper runHostAppTests:testHostBundleID
226+
BOOL infraSucceeded = [SimulatorWrapper runHostAppTests:testRunnerBundleID ?: testHostBundleID
195227
device:[_simulatorInfo simulatedDevice]
196228
arguments:appLaunchArgs
197229
environment:appLaunchEnvironment

xctool/xctool/OCUnitTestRunner.m

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,18 +345,47 @@ - (NSArray *)testArgumentsWithSpecifiedTestsToRun
345345

346346
- (NSDictionary *)testEnvironmentWithSpecifiedTestConfiguration
347347
{
348-
NSArray *testCasesToSkip = [self testCasesToSkip];
349-
350348
Class XCTestConfigurationClass = NSClassFromString(@"XCTestConfiguration");
351349
NSAssert(XCTestConfigurationClass, @"XCTestConfiguration isn't available");
352350

353-
XCTestConfiguration *configuration = [[XCTestConfigurationClass alloc] init];
354-
[configuration setProductModuleName:_buildSettings[Xcode_PRODUCT_MODULE_NAME]];
355-
[configuration setTestBundleURL:[NSURL fileURLWithPath:[_simulatorInfo productBundlePath]]];
356-
[configuration setTestsToSkip:[NSSet setWithArray:testCasesToSkip]];
357-
[configuration setReportResultsToIDE:NO];
351+
NSArray *testCasesToSkip = [self testCasesToSkip];
352+
NSString *testHostAppPath = FixedAppPathFromAppPath(_simulatorInfo.testHostPath);
353+
NSString *testHostBundleID = AppBundleIDForAppAtPath(testHostAppPath);
354+
355+
XCTestConfiguration *configuration = [XCTestConfigurationClass new];
356+
configuration.testBundleURL = [NSURL fileURLWithPath:_simulatorInfo.productBundlePath];
357+
configuration.productModuleName = _buildSettings[Xcode_PRODUCT_MODULE_NAME];
358+
configuration.testsToSkip = [NSSet setWithArray:testCasesToSkip];
359+
configuration.targetApplicationPath = testHostAppPath;
360+
configuration.targetApplicationBundleID = testHostBundleID;
361+
configuration.reportActivities = YES;
362+
363+
// UI tests require special treatment
364+
if ([_buildSettings[Xcode_USES_XCTRUNNER] boolValue]) {
365+
configuration.testsMustRunOnMainThread = YES;
366+
configuration.initializeForUITesting = YES;
367+
}
358368

359-
NSString *XCTestConfigurationFilename = [NSString stringWithFormat:@"%@-%@", _buildSettings[Xcode_PRODUCT_NAME], [configuration.sessionIdentifier UUIDString]];
369+
// Xcode also sets these properties for UI (and may be other) tests:
370+
// - testBundleRelativePath:
371+
// uses __TESTHOST__ placeholder in the path to xctest bundle
372+
// - sessionIdentifier:
373+
// needs to be reset, so xctest doesn't expect any session to be running
374+
// - testApplicationDependencies:
375+
// @{@"runner bundle id" -> "runner app bundle path",
376+
// @"target app bundle id" -> "target app bundle path"}
377+
// - aggregateStatisticsBeforeCrash:
378+
// @{@"XCSuiteRecordsKey": @{}}
379+
// - automationFrameworkPath:
380+
// set to XCTAutomationSupport.framework path inside Xcode app bundle
381+
// - automationFrameworkPath:
382+
// set to 1
383+
// - systemAttachmentLifetime:
384+
// set to 1
385+
386+
NSString *XCTestConfigurationFilename = [NSString stringWithFormat:@"%@-%@",
387+
_buildSettings[Xcode_PRODUCT_MODULE_NAME],
388+
configuration.sessionIdentifier.UUIDString];
360389
NSString *XCTestConfigurationFilePath = [MakeTempFileWithPrefix(XCTestConfigurationFilename) stringByAppendingPathExtension:@"xctestconfiguration"];
361390
if ([[NSFileManager defaultManager] fileExistsAtPath:XCTestConfigurationFilePath]) {
362391
[[NSFileManager defaultManager] removeItemAtPath:XCTestConfigurationFilePath error:nil];

xctool/xctool/RunTestsAction.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ typedef NS_ENUM(NSInteger, BucketBy) {
4848
// be in the same bucket.
4949
BucketByClass,
5050

51-
} ;
51+
};
52+
53+
@class RunTestsActionUITest;
5254

5355
@interface RunTestsAction : Action<TestRunning>
5456

@@ -65,7 +67,8 @@ typedef NS_ENUM(NSInteger, BucketBy) {
6567
@property (nonatomic, strong) NSMutableArray *onlyList;
6668
@property (nonatomic, strong) NSMutableArray *omitList;
6769
@property (nonatomic, strong) NSMutableArray *logicTests;
68-
@property (nonatomic, strong) NSMutableDictionary *appTests;
70+
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSString *> *appTests; // testBundle -> hostApp
71+
@property (nonatomic, strong) NSMutableDictionary<NSString *, RunTestsActionUITest *> *uiTests; // testBundle -> (host app, runner app)
6972
@property (nonatomic, copy) NSString *targetedDeviceFamily;
7073

7174
- (void)setLogicTestBucketSizeValue:(NSString *)str;
@@ -74,3 +77,9 @@ typedef NS_ENUM(NSInteger, BucketBy) {
7477
- (void)setTestTimeoutValue:(NSString *)str;
7578

7679
@end
80+
81+
@interface RunTestsActionUITest: NSObject
82+
@property (nonatomic, copy, readonly) NSString *hostApp;
83+
@property (nonatomic, copy, readonly) NSString *runnerApp;
84+
- (instancetype)initWithHostApp:(NSString *)hostApp runnerApp:(NSString *)runnerApp;
85+
@end

0 commit comments

Comments
 (0)