RLMSyncTestCase.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2016 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 "RLMSyncTestCase.h"
  19. #import <XCTest/XCTest.h>
  20. #import <Realm/Realm.h>
  21. #import "RLMRealm_Dynamic.h"
  22. #import "RLMRealm_Private.hpp"
  23. #import "RLMRealmConfiguration_Private.h"
  24. #import "RLMSyncManager_Private.h"
  25. #import "RLMSyncSessionRefreshHandle+ObjectServerTests.h"
  26. #import "RLMSyncConfiguration_Private.h"
  27. #import "RLMUtil.hpp"
  28. #import "sync/sync_manager.hpp"
  29. #import "sync/sync_session.hpp"
  30. #import "sync/sync_user.hpp"
  31. // Set this to 1 if you want the test ROS instance to log its debug messages to console.
  32. #define LOG_ROS_OUTPUT 0
  33. #if !TARGET_OS_MAC
  34. #error These tests can only be run on a macOS host.
  35. #endif
  36. static NSString *nodePath() {
  37. static NSString *path = [] {
  38. NSDictionary *environment = NSProcessInfo.processInfo.environment;
  39. if (NSString *path = environment[@"REALM_NODE_PATH"]) {
  40. return path;
  41. }
  42. return @"/usr/local/bin/node";
  43. }();
  44. return path;
  45. }
  46. @interface RLMSyncManager ()
  47. + (void)_setCustomBundleID:(NSString *)customBundleID;
  48. - (void)configureWithRootDirectory:(NSURL *)rootDirectory;
  49. - (NSArray<RLMSyncUser *> *)_allUsers;
  50. @end
  51. @interface RLMSyncTestCase ()
  52. @property (nonatomic) NSTask *task;
  53. @end
  54. @interface RLMSyncCredentials ()
  55. + (instancetype)credentialsWithDebugUserID:(NSString *)userID isAdmin:(BOOL)isAdmin;
  56. @end
  57. @interface RLMSyncSession ()
  58. - (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback;
  59. - (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback;
  60. @end
  61. @interface RLMSyncUser()
  62. - (std::shared_ptr<realm::SyncUser>)_syncUser;
  63. @end
  64. @implementation SyncObject
  65. @end
  66. @implementation HugeSyncObject
  67. + (instancetype)object {
  68. const NSInteger fakeDataSize = 1000000;
  69. HugeSyncObject *object = [[self alloc] init];
  70. char fakeData[fakeDataSize];
  71. memset(fakeData, 16, sizeof(fakeData));
  72. object.dataProp = [NSData dataWithBytes:fakeData length:sizeof(fakeData)];
  73. return object;
  74. }
  75. @end
  76. static NSTask *s_task;
  77. static NSURL *syncDirectoryForChildProcess() {
  78. NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)[0];
  79. NSBundle *bundle = [NSBundle mainBundle];
  80. NSString *bundleIdentifier = bundle.bundleIdentifier ?: bundle.executablePath.lastPathComponent;
  81. path = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-child", bundleIdentifier]];
  82. return [NSURL fileURLWithPath:path isDirectory:YES];
  83. }
  84. @interface RealmObjectServer : NSObject
  85. @property (nonatomic, readonly) NSURL *serverDataRoot;
  86. + (instancetype)sharedServer;
  87. - (void)launch;
  88. @end
  89. @implementation RealmObjectServer {
  90. NSTask *_task;
  91. NSURL *_serverDataRoot;
  92. }
  93. + (instancetype)sharedServer {
  94. static RealmObjectServer *instance = [RealmObjectServer new];
  95. return instance;
  96. }
  97. - (instancetype)init {
  98. if (self = [super init]) {
  99. _serverDataRoot = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"test-ros-data"]];
  100. }
  101. return self;
  102. }
  103. - (void)launch {
  104. if (_task) {
  105. return;
  106. }
  107. // Clean up any old state from the server
  108. [[NSTask launchedTaskWithLaunchPath:@"/usr/bin/pkill"
  109. arguments:@[@"-f", @"node.*test-ros-server.js"]] waitUntilExit];
  110. NSError *error;
  111. [NSFileManager.defaultManager removeItemAtURL:self.serverDataRoot error:&error];
  112. if (error && error.code != NSFileNoSuchFileError) {
  113. NSLog(@"Failed to delete old test state: %@", error);
  114. abort();
  115. }
  116. error = nil;
  117. [NSFileManager.defaultManager createDirectoryAtURL:self.serverDataRoot
  118. withIntermediateDirectories:YES attributes:nil error:&error];
  119. if (error) {
  120. NSLog(@"Failed to create scratch directory: %@", error);
  121. abort();
  122. }
  123. // Install ROS if it isn't already present
  124. [self downloadObjectServer];
  125. // Set up the actual ROS task
  126. NSPipe *pipe = [NSPipe pipe];
  127. _task = [[NSTask alloc] init];
  128. _task.currentDirectoryPath = self.serverDataRoot.path;
  129. _task.launchPath = nodePath();
  130. NSString *directory = [@(__FILE__) stringByDeletingLastPathComponent];
  131. _task.arguments = @[[directory stringByAppendingPathComponent:@"test-ros-server.js"],
  132. self.serverDataRoot.path];
  133. _task.standardOutput = pipe;
  134. [_task launch];
  135. NSData *childStdout = pipe.fileHandleForReading.readDataToEndOfFile;
  136. if (![childStdout isEqual:[@"started\n" dataUsingEncoding:NSUTF8StringEncoding]]) {
  137. abort();
  138. }
  139. atexit([] {
  140. auto self = RealmObjectServer.sharedServer;
  141. [self->_task terminate];
  142. [self->_task waitUntilExit];
  143. [NSFileManager.defaultManager removeItemAtURL:self->_serverDataRoot error:nil];
  144. });
  145. }
  146. - (NSString *)desiredObjectServerVersion {
  147. auto path = [[[[@(__FILE__) stringByDeletingLastPathComponent] // RLMSyncTestCase.mm
  148. stringByDeletingLastPathComponent] // ObjectServerTests
  149. stringByDeletingLastPathComponent] // Realm
  150. stringByAppendingPathComponent:@"dependencies.list"];
  151. auto file = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
  152. if (!file) {
  153. NSLog(@"Failed to read dependencies.list");
  154. abort();
  155. }
  156. auto regex = [NSRegularExpression regularExpressionWithPattern:@"^REALM_OBJECT_SERVER_VERSION=(.*)$"
  157. options:NSRegularExpressionAnchorsMatchLines error:nil];
  158. auto match = [regex firstMatchInString:file options:0 range:{0, file.length}];
  159. if (!match) {
  160. NSLog(@"Failed to read REALM_OBJECT_SERVER_VERSION from dependencies.list");
  161. abort();
  162. }
  163. return [file substringWithRange:[match rangeAtIndex:1]];
  164. }
  165. - (NSString *)currentObjectServerVersion {
  166. auto path = [[[[@(__FILE__) stringByDeletingLastPathComponent] // RLMSyncTestCase.mm
  167. stringByAppendingPathComponent:@"node_modules"]
  168. stringByAppendingPathComponent:@"realm-object-server"]
  169. stringByAppendingPathComponent:@"package.json"];
  170. auto file = [NSData dataWithContentsOfFile:path];
  171. if (!file) {
  172. return nil;
  173. }
  174. NSError *error;
  175. NSDictionary *json = [NSJSONSerialization JSONObjectWithData:file options:0 error:&error];
  176. if (!json) {
  177. NSLog(@"Error reading version from installed ROS: %@", error);
  178. abort();
  179. }
  180. return json[@"version"];
  181. }
  182. - (void)downloadObjectServer {
  183. NSString *desiredVersion = [self desiredObjectServerVersion];
  184. NSString *currentVersion = [self currentObjectServerVersion];
  185. if ([currentVersion isEqualToString:desiredVersion]) {
  186. return;
  187. }
  188. NSLog(@"Installing Realm Object Server %@", desiredVersion);
  189. NSTask *task = [[NSTask alloc] init];
  190. task.currentDirectoryPath = [@(__FILE__) stringByDeletingLastPathComponent];
  191. task.launchPath = nodePath();
  192. task.arguments = @[[[nodePath() stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"npm"],
  193. @"--scripts-prepend-node-path=auto",
  194. @"--no-color",
  195. @"--no-progress",
  196. @"--no-save",
  197. @"--no-package-lock",
  198. @"install",
  199. [@"realm-object-server@" stringByAppendingString:desiredVersion]
  200. ];
  201. [task launch];
  202. [task waitUntilExit];
  203. }
  204. @end
  205. @implementation RLMSyncTestCase
  206. #pragma mark - Helper methods
  207. - (BOOL)isPartial {
  208. return NO;
  209. }
  210. + (NSURL *)authServerURL {
  211. return [NSURL URLWithString:@"http://127.0.0.1:9080"];
  212. }
  213. + (NSURL *)secureAuthServerURL {
  214. return [NSURL URLWithString:@"https://localhost:9443"];
  215. }
  216. + (RLMSyncCredentials *)basicCredentialsWithName:(NSString *)name register:(BOOL)shouldRegister {
  217. return [RLMSyncCredentials credentialsWithUsername:name
  218. password:@"a"
  219. register:shouldRegister];
  220. }
  221. + (NSURL *)onDiskPathForSyncedRealm:(RLMRealm *)realm {
  222. return [NSURL fileURLWithPath:@(realm->_realm->config().path.data())];
  223. }
  224. - (void)addSyncObjectsToRealm:(RLMRealm *)realm descriptions:(NSArray<NSString *> *)descriptions {
  225. [realm beginWriteTransaction];
  226. for (NSString *desc in descriptions) {
  227. [SyncObject createInRealm:realm withValue:@[desc]];
  228. }
  229. [realm commitWriteTransaction];
  230. }
  231. - (void)waitForDownloadsForUser:(RLMSyncUser *)user
  232. realms:(NSArray<RLMRealm *> *)realms
  233. realmURLs:(NSArray<NSURL *> *)realmURLs
  234. expectedCounts:(NSArray<NSNumber *> *)counts {
  235. NSAssert(realms.count == counts.count && realms.count == realmURLs.count,
  236. @"Test logic error: all array arguments must be the same size.");
  237. for (NSUInteger i = 0; i < realms.count; i++) {
  238. [self waitForDownloadsForUser:user url:realmURLs[i] expectation:nil error:nil];
  239. [realms[i] refresh];
  240. CHECK_COUNT([counts[i] integerValue], SyncObject, realms[i]);
  241. }
  242. }
  243. - (RLMRealm *)openRealmForURL:(NSURL *)url user:(RLMSyncUser *)user {
  244. return [self openRealmForURL:url user:user immediatelyBlock:nil];
  245. }
  246. - (RLMRealm *)openRealmForURL:(NSURL *)url user:(RLMSyncUser *)user immediatelyBlock:(void(^)(void))block {
  247. return [self openRealmForURL:url
  248. user:user
  249. encryptionKey:nil
  250. stopPolicy:RLMSyncStopPolicyAfterChangesUploaded
  251. immediatelyBlock:block];
  252. }
  253. - (RLMRealm *)openRealmForURL:(NSURL *)url
  254. user:(RLMSyncUser *)user
  255. encryptionKey:(nullable NSData *)encryptionKey
  256. stopPolicy:(RLMSyncStopPolicy)stopPolicy
  257. immediatelyBlock:(nullable void(^)(void))block {
  258. const NSTimeInterval timeout = 4;
  259. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  260. RLMSyncManager.sharedManager.sessionCompletionNotifier = ^(NSError *error) {
  261. if (error) {
  262. XCTFail(@"Received an asynchronous error when trying to open Realm at '%@' for user '%@': %@ (process: %@)",
  263. url, user.identity, error, self.isParent ? @"parent" : @"child");
  264. }
  265. dispatch_semaphore_signal(sema);
  266. };
  267. RLMRealm *realm = [self immediatelyOpenRealmForURL:url user:user encryptionKey:encryptionKey stopPolicy:stopPolicy];
  268. if (block) {
  269. block();
  270. }
  271. // Wait for login to succeed or fail.
  272. XCTAssert(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))) == 0,
  273. @"Timed out while trying to asynchronously open Realm for URL: %@", url);
  274. RLMSyncManager.sharedManager.sessionCompletionNotifier = nil;
  275. return realm;
  276. }
  277. - (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration {
  278. return [self openRealmWithConfiguration:configuration immediatelyBlock:nullptr];
  279. }
  280. - (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration
  281. immediatelyBlock:(nullable void(^)(void))block {
  282. const NSTimeInterval timeout = 4;
  283. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  284. RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
  285. RLMSyncManager.sharedManager.sessionCompletionNotifier = ^(NSError *error) {
  286. if (error) {
  287. XCTFail(@"Received an asynchronous error when trying to open Realm at '%@' for user '%@': %@ (process: %@)",
  288. syncConfig.realmURL, syncConfig.user.identity, error, self.isParent ? @"parent" : @"child");
  289. }
  290. dispatch_semaphore_signal(sema);
  291. };
  292. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nullptr];
  293. if (block) {
  294. block();
  295. }
  296. // Wait for login to succeed or fail.
  297. XCTAssert(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))) == 0,
  298. @"Timed out while trying to asynchronously open Realm for URL: %@", syncConfig.realmURL);
  299. return realm;
  300. }
  301. - (RLMRealm *)asyncOpenRealmWithConfiguration:(RLMRealmConfiguration *)config {
  302. __block RLMRealm *realm = nil;
  303. XCTestExpectation *ex = [self expectationWithDescription:@"Should asynchronously open a Realm"];
  304. [RLMRealm asyncOpenWithConfiguration:config
  305. callbackQueue:dispatch_get_main_queue()
  306. callback:^(RLMRealm *r, NSError *err){
  307. XCTAssertNil(err);
  308. XCTAssertNotNil(r);
  309. realm = r;
  310. [ex fulfill];
  311. }];
  312. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  313. return realm;
  314. }
  315. - (NSError *)asyncOpenErrorWithConfiguration:(RLMRealmConfiguration *)config {
  316. __block NSError *error = nil;
  317. XCTestExpectation *ex = [self expectationWithDescription:@"Should fail to asynchronously open a Realm"];
  318. [RLMRealm asyncOpenWithConfiguration:config
  319. callbackQueue:dispatch_get_main_queue()
  320. callback:^(RLMRealm *r, NSError *err){
  321. XCTAssertNotNil(err);
  322. XCTAssertNil(r);
  323. error = err;
  324. [ex fulfill];
  325. }];
  326. [self waitForExpectationsWithTimeout:10.0 handler:nil];
  327. return error;
  328. }
  329. - (RLMRealm *)immediatelyOpenRealmForURL:(NSURL *)url user:(RLMSyncUser *)user {
  330. return [self immediatelyOpenRealmForURL:url
  331. user:user
  332. encryptionKey:nil
  333. stopPolicy:RLMSyncStopPolicyAfterChangesUploaded];
  334. }
  335. - (RLMRealm *)immediatelyOpenRealmForURL:(NSURL *)url
  336. user:(RLMSyncUser *)user
  337. encryptionKey:(NSData *)encryptionKey
  338. stopPolicy:(RLMSyncStopPolicy)stopPolicy {
  339. auto c = [user configurationWithURL:url fullSynchronization:!self.isPartial];
  340. c.encryptionKey = encryptionKey;
  341. RLMSyncConfiguration *syncConfig = c.syncConfiguration;
  342. syncConfig.stopPolicy = stopPolicy;
  343. c.syncConfiguration = syncConfig;
  344. return [RLMRealm realmWithConfiguration:c error:nil];
  345. }
  346. - (RLMSyncUser *)logInUserForCredentials:(RLMSyncCredentials *)credentials
  347. server:(NSURL *)url {
  348. NSString *process = self.isParent ? @"parent" : @"child";
  349. __block RLMSyncUser *theUser = nil;
  350. XCTestExpectation *expectation = [self expectationWithDescription:@"Should log in the user properly"];
  351. [RLMSyncUser logInWithCredentials:credentials
  352. authServerURL:url
  353. onCompletion:^(RLMSyncUser *user, NSError *error) {
  354. XCTAssertTrue(NSThread.isMainThread);
  355. XCTAssertNil(error,
  356. @"Error when trying to log in a user: %@ (process: %@)",
  357. error, process);
  358. XCTAssertNotNil(user);
  359. theUser = user;
  360. [expectation fulfill];
  361. }];
  362. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  363. XCTAssertTrue(theUser.state == RLMSyncUserStateActive,
  364. @"User should have been valid, but wasn't. (process: %@)", process);
  365. return theUser;
  366. }
  367. - (RLMSyncUser *)createAdminUserForURL:(NSURL *)url username:(NSString *)username {
  368. return [self logInUserForCredentials:[RLMSyncCredentials credentialsWithDebugUserID:username isAdmin:YES]
  369. server:url];
  370. }
  371. - (NSString *)adminToken {
  372. NSURL *target = [RealmObjectServer.sharedServer.serverDataRoot
  373. URLByAppendingPathComponent:@"/keys/admin.json"];
  374. if (![[NSFileManager defaultManager] fileExistsAtPath:[target path]]) {
  375. XCTFail(@"Could not find the JSON file containing the admin token.");
  376. return nil;
  377. }
  378. NSData *raw = [NSData dataWithContentsOfURL:target];
  379. NSDictionary *json = [NSJSONSerialization JSONObjectWithData:raw options:0 error:nil];
  380. NSString *token = json[@"ADMIN_TOKEN"];
  381. if ([token length] == 0) {
  382. XCTFail(@"Could not successfully extract the token.");
  383. }
  384. return token;
  385. }
  386. - (NSString *)emailForAddress:(NSString *)email {
  387. NSURL *target = [[RealmObjectServer.sharedServer.serverDataRoot
  388. URLByAppendingPathComponent:@"/email"]
  389. URLByAppendingPathComponent:email];
  390. NSString *body = [NSString stringWithContentsOfURL:target encoding:NSUTF8StringEncoding error:nil];
  391. if (body) {
  392. [NSFileManager.defaultManager removeItemAtURL:target error:nil];
  393. }
  394. return body;
  395. }
  396. - (void)waitForDownloadsForRealm:(RLMRealm *)realm {
  397. [self waitForDownloadsForRealm:realm error:nil];
  398. }
  399. - (void)waitForUploadsForRealm:(RLMRealm *)realm {
  400. [self waitForUploadsForRealm:realm error:nil];
  401. }
  402. - (void)waitForDownloadsForUser:(RLMSyncUser *)user
  403. url:(NSURL *)url
  404. expectation:(XCTestExpectation *)expectation
  405. error:(NSError **)error {
  406. RLMSyncSession *session = [user sessionForURL:url];
  407. NSAssert(session, @"Cannot call with invalid URL");
  408. XCTestExpectation *ex = expectation ?: [self expectationWithDescription:@"Wait for download completion"];
  409. __block NSError *theError = nil;
  410. BOOL queued = [session waitForDownloadCompletionOnQueue:nil callback:^(NSError *err) {
  411. theError = err;
  412. [ex fulfill];
  413. }];
  414. if (!queued) {
  415. XCTFail(@"Download waiter did not queue; session was invalid or errored out.");
  416. return;
  417. }
  418. [self waitForExpectations:@[ex] timeout:20.0];
  419. if (error) {
  420. *error = theError;
  421. }
  422. }
  423. - (void)waitForUploadsForRealm:(RLMRealm *)realm error:(NSError **)error {
  424. RLMSyncSession *session = realm.syncSession;
  425. NSAssert(session, @"Cannot call with invalid Realm");
  426. XCTestExpectation *ex = [self expectationWithDescription:@"Wait for upload completion"];
  427. __block NSError *completionError;
  428. BOOL queued = [session waitForUploadCompletionOnQueue:nil callback:^(NSError *error) {
  429. completionError = error;
  430. [ex fulfill];
  431. }];
  432. if (!queued) {
  433. XCTFail(@"Upload waiter did not queue; session was invalid or errored out.");
  434. return;
  435. }
  436. [self waitForExpectations:@[ex] timeout:20.0];
  437. if (error)
  438. *error = completionError;
  439. }
  440. - (void)waitForDownloadsForRealm:(RLMRealm *)realm error:(NSError **)error {
  441. RLMSyncSession *session = realm.syncSession;
  442. NSAssert(session, @"Cannot call with invalid Realm");
  443. XCTestExpectation *ex = [self expectationWithDescription:@"Wait for download completion"];
  444. __block NSError *completionError;
  445. BOOL queued = [session waitForDownloadCompletionOnQueue:nil callback:^(NSError *error) {
  446. completionError = error;
  447. [ex fulfill];
  448. }];
  449. if (!queued) {
  450. XCTFail(@"Download waiter did not queue; session was invalid or errored out.");
  451. return;
  452. }
  453. [self waitForExpectations:@[ex] timeout:20.0];
  454. if (error)
  455. *error = completionError;
  456. }
  457. - (void)manuallySetRefreshTokenForUser:(RLMSyncUser *)user value:(NSString *)tokenValue {
  458. [user _syncUser]->update_refresh_token(tokenValue.UTF8String);
  459. }
  460. // FIXME: remove this API once the new token system is implemented.
  461. - (void)primeSyncManagerWithSemaphore:(dispatch_semaphore_t)semaphore {
  462. if (semaphore == nil) {
  463. [[RLMSyncManager sharedManager] setSessionCompletionNotifier:^(__unused NSError *error){ }];
  464. return;
  465. }
  466. [[RLMSyncManager sharedManager] setSessionCompletionNotifier:^(NSError *error) {
  467. XCTAssertNil(error, @"Session completion block returned with an error: %@", error);
  468. dispatch_semaphore_signal(semaphore);
  469. }];
  470. }
  471. #pragma mark - XCUnitTest Lifecycle
  472. - (void)setUp {
  473. [super setUp];
  474. self.continueAfterFailure = NO;
  475. REALM_ASSERT(RLMSyncManager.sharedManager._allUsers.count == 0);
  476. [RLMSyncManager resetForTesting];
  477. [self setupSyncManager];
  478. }
  479. - (void)tearDown {
  480. [self resetSyncManager];
  481. [super tearDown];
  482. }
  483. - (void)setupSyncManager {
  484. NSURL *clientDataRoot;
  485. if (self.isParent) {
  486. [RealmObjectServer.sharedServer launch];
  487. clientDataRoot = [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)];
  488. }
  489. else {
  490. clientDataRoot = syncDirectoryForChildProcess();
  491. }
  492. NSError *error;
  493. [NSFileManager.defaultManager removeItemAtURL:clientDataRoot error:&error];
  494. [NSFileManager.defaultManager createDirectoryAtURL:clientDataRoot
  495. withIntermediateDirectories:YES attributes:nil error:&error];
  496. RLMSyncManager *syncManager = RLMSyncManager.sharedManager;
  497. [syncManager configureWithRootDirectory:clientDataRoot];
  498. syncManager.logLevel = RLMSyncLogLevelOff;
  499. syncManager.userAgent = self.name;
  500. }
  501. - (void)resetSyncManager {
  502. NSMutableArray *expectations = [NSMutableArray new];
  503. for (RLMSyncUser *user in RLMSyncManager.sharedManager._allUsers) {
  504. [user logOut];
  505. // Sessions are removed from the user asynchronously after a logout.
  506. // We need to wait for this to happen before calling resetForTesting as
  507. // that expects all sessions to be cleaned up first. This doesn't apply
  508. // to admin token users, which don't logout at all (and don't have an
  509. // auth server).
  510. if (user.authenticationServer && user.allSessions.count) {
  511. [expectations addObject:[self expectationForPredicate:[NSPredicate predicateWithFormat:@"allSessions.@count == 0"]
  512. evaluatedWithObject:user handler:nil]];
  513. }
  514. }
  515. [self waitForExpectations:expectations timeout:5.0];
  516. [RLMSyncManager resetForTesting];
  517. [RLMSyncSessionRefreshHandle calculateFireDateUsingTestLogic:NO blockOnRefreshCompletion:nil];
  518. }
  519. @end