static void listBundle()

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