in Shims/Shimulator/TestReporterShim/XCTestReporterShim.m [623:714]
static void listBundle(NSString *testBundlePath, NSString *outputFile)
{
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:outputFile];
NSBundle *bundle = [NSBundle bundleWithPath:testBundlePath];
if (!bundle) {
fprintf(
stderr,
"Bundle '%s' does not identify an accessible bundle directory.\n",
testBundlePath.UTF8String
);
exit(TestShimExitCodeBundleOpenError);
}
if (![bundle executablePath]) {
fprintf(stderr, "The bundle at %s does not contain an executable.\n", [testBundlePath UTF8String]);
exit(TestShimExitCodeMissingExecutable);
}
// Make sure the 'XCTest' preference is cleared before we load the
// test bundle - otherwise we may accidentally start running tests.
//
// Instead of seeing the JSON list of test methods, you'll see output like ...
//
// Test Suite 'All tests' started at 2013-11-07 23:47:46 +0000
// Test Suite 'All tests' finished at 2013-11-07 23:47:46 +0000.
// Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
//
// Here's what happens -- As soon as we dlopen() the test bundle, it will also
// trigger the linker to load XCTest.framework since those are linked by the test bundle.
// And, as soon as the testing framework loads, the class initializer
// '+[XCTestSuite initialize]' is triggered.
//
// By clearing the preference, we can prevent tests from running.
[NSUserDefaults.standardUserDefaults removeObjectForKey:XCTestFrameworkName];
[NSUserDefaults.standardUserDefaults synchronize];
// We use dlopen() instead of -[NSBundle loadAndReturnError] because, if
// something goes wrong, dlerror() gives us a much more helpful error message.
if (dlopen([[bundle executablePath] UTF8String], RTLD_LAZY) == NULL) {
fprintf(stderr, "%s\n", dlerror());
exit(TestShimExitCodeDLOpenError);
}
// Load the Test Bundle's 'Principal Class' and initialize it.
// This is necessary for some Testing Frameworks that dynamically add XCTests
// inside the basic `-init` method.
Class principalClass = [bundle principalClass];
if (principalClass && [principalClass instancesRespondToSelector:@selector(init)]) {
NSLog(@"Calling Principal Class initializer -[%@ init]", NSStringFromClass(principalClass));
id principalObject = [[principalClass alloc] init];
NSLog(@"Principal Class %@ initialized", principalObject);
}
// Ensure that the principal class exists.
Class testSuiteClass = NSClassFromString(XCTestSuiteClassName);
NSCAssert(testSuiteClass, @"Should have %@ class", XCTestFrameworkName);
// By setting `-XCTest None`, we'll make `-[XCTestSuite allTests]`
// return all tests.
[NSUserDefaults.standardUserDefaults setObject:@"None" forKey:XCTestFilterArg];
XCTestSuite *allTestsSuite = [testSuiteClass performSelector:@selector(allTests)];
NSCAssert(allTestsSuite, @"Should have gotten a test suite from allTests");
// Enumerate the test cases, constructing the reported name for them.
NSArray<XCTestCase *> *allTestCases = TestsFromSuite(allTestsSuite);
NSMutableArray<NSDictionary<NSString *, NSString *> *> *testsToReport = NSMutableArray.array;
for (XCTestCase *testCase in allTestCases) {
NSString *className = nil;
NSString *methodName = nil;
NSString *testKey = nil;
parseXCTestCase(testCase, &className, &methodName, &testKey);
NSString *legacyTestName = [NSString stringWithFormat:@"%@/%@", className, methodName];
[testsToReport addObject:@{
kReporter_ListTest_LegacyTestNameKey: legacyTestName,
kReporter_ListTest_ClassNameKey: className,
kReporter_ListTest_MethodNameKey: methodName,
kReporter_ListTest_TestKey: testKey,
}];
}
// Now write them out after sorting
[testsToReport sortUsingComparator:^ NSComparisonResult (NSDictionary<NSString *, NSString *> *left, NSDictionary<NSString *, NSString *> *right) {
return [left[kReporter_ListTest_LegacyTestNameKey] compare:right[kReporter_ListTest_LegacyTestNameKey]];
}];
NSError *error = nil;
NSData *output = [NSJSONSerialization dataWithJSONObject:testsToReport options:0 error:&error];
NSCAssert(output, @"Failed to generate list test JSON", error);
[fileHandle writeData:output];
// Close the file so the other end knows this is the end of the input.
[fileHandle closeFile];
exit(TestShimExitCodeSuccess);
}