RLMMultiProcessTestCase.m 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. #import "RLMMultiProcessTestCase.h"
  19. @interface RLMMultiProcessTestCase ()
  20. @property (nonatomic) bool isParent;
  21. @property (nonatomic, strong) NSString *testName;
  22. @property (nonatomic, strong) NSString *xctestPath;
  23. @property (nonatomic, strong) NSString *testsPath;
  24. @end
  25. @implementation RLMMultiProcessTestCase
  26. // Override all of the methods for creating a XCTestCase object to capture the current test name
  27. + (id)testCaseWithInvocation:(NSInvocation *)invocation {
  28. RLMMultiProcessTestCase *testCase = [super testCaseWithInvocation:invocation];
  29. testCase.testName = NSStringFromSelector(invocation.selector);
  30. return testCase;
  31. }
  32. - (id)initWithInvocation:(NSInvocation *)invocation {
  33. self = [super initWithInvocation:invocation];
  34. if (self) {
  35. self.testName = NSStringFromSelector(invocation.selector);
  36. }
  37. return self;
  38. }
  39. + (id)testCaseWithSelector:(SEL)selector {
  40. RLMMultiProcessTestCase *testCase = [super testCaseWithSelector:selector];
  41. testCase.testName = NSStringFromSelector(selector);
  42. return testCase;
  43. }
  44. - (id)initWithSelector:(SEL)selector {
  45. self = [super initWithSelector:selector];
  46. if (self) {
  47. self.testName = NSStringFromSelector(selector);
  48. }
  49. return self;
  50. }
  51. - (void)setUp {
  52. self.isParent = !getenv("RLMProcessIsChild");
  53. self.xctestPath = [self locateXCTest];
  54. self.testsPath = [NSBundle bundleForClass:[self class]].bundlePath;
  55. if (!self.isParent) {
  56. // For multi-process tests, the child's concept of a default path needs to match the parent.
  57. // RLMRealmConfiguration isn't aware of this, but our test's RLMDefaultRealmURL helper does.
  58. // Use it to reset the default configuration's path so it matches the parent.
  59. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  60. configuration.fileURL = RLMDefaultRealmURL();
  61. [RLMRealmConfiguration setDefaultConfiguration:configuration];
  62. }
  63. [super setUp];
  64. }
  65. - (void)invokeTest {
  66. CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
  67. [super invokeTest];
  68. CFRunLoopStop(CFRunLoopGetCurrent());
  69. });
  70. CFRunLoopRun();
  71. }
  72. - (void)deleteFiles {
  73. // Only the parent should delete files in setUp/tearDown
  74. if (self.isParent) {
  75. [super deleteFiles];
  76. }
  77. }
  78. + (void)preintializeSchema {
  79. // Do nothing so that we can test global schema init in child processes
  80. }
  81. - (NSString *)locateXCTest {
  82. NSString *pathString = [NSProcessInfo processInfo].environment[@"PATH"];
  83. NSFileManager *fileManager = [NSFileManager defaultManager];
  84. for (NSString *directory in [pathString componentsSeparatedByString:@":"]) {
  85. NSString *candidatePath = [directory stringByAppendingPathComponent:@"xctest"];
  86. if ([fileManager isExecutableFileAtPath:candidatePath])
  87. return candidatePath;
  88. }
  89. return nil;
  90. }
  91. #if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
  92. - (NSTask *)childTask {
  93. NSString *testName = [NSString stringWithFormat:@"%@/%@", self.className, self.testName];
  94. NSMutableDictionary *env = [NSProcessInfo.processInfo.environment mutableCopy];
  95. env[@"RLMProcessIsChild"] = @"true";
  96. env[@"RLMParentProcessBundleID"] = [NSBundle mainBundle].bundleIdentifier;
  97. // Don't inherit the config file in the subprocess, as multiple XCTest
  98. // processes talking to a single Xcode instance doesn't work at all
  99. [env removeObjectForKey:@"XCTestConfigurationFilePath"];
  100. NSTask *task = [[NSTask alloc] init];
  101. task.launchPath = self.xctestPath;
  102. task.arguments = @[@"-XCTest", testName, self.testsPath];
  103. task.environment = env;
  104. task.standardError = nil;
  105. return task;
  106. }
  107. - (int)runChildAndWait {
  108. NSPipe *pipe = [NSPipe pipe];
  109. NSMutableData *buffer = [[NSMutableData alloc] init];
  110. // Filter the output from the child process to reduce xctest noise
  111. pipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *file) {
  112. [buffer appendData:[file availableData]];
  113. const char *newline;
  114. const char *start = buffer.bytes;
  115. const char *end = start + buffer.length;
  116. while ((newline = memchr(start, '\n', end - start))) {
  117. if (newline < start + 17 ||
  118. (memcmp(start, "Test Suite", 10) && memcmp(start, "Test Case", 9) && memcmp(start, " Executed 1 test", 17))) {
  119. fwrite(start, newline - start + 1, 1, stderr);
  120. }
  121. start = newline + 1;
  122. }
  123. // Remove everything up to the last newline, leaving any data not newline-terminated in the buffer
  124. [buffer replaceBytesInRange:NSMakeRange(0, start - (char *)buffer.bytes) withBytes:0 length:0];
  125. };
  126. NSTask *task = [self childTask];
  127. task.standardError = pipe;
  128. [task launch];
  129. [task waitUntilExit];
  130. return task.terminationStatus;
  131. }
  132. #else
  133. - (NSTask *)childTask {
  134. return nil;
  135. }
  136. - (int)runChildAndWait {
  137. return 1;
  138. }
  139. #endif
  140. @end