RLMSyncTestCase.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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+ObjectServerTests.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. - (instancetype)initWithCustomRootDirectory:(NSURL *)rootDirectory;
  49. @end
  50. @interface RLMSyncTestCase ()
  51. @property (nonatomic) NSTask *task;
  52. @end
  53. @interface RLMSyncCredentials ()
  54. + (instancetype)credentialsWithDebugUserID:(NSString *)userID isAdmin:(BOOL)isAdmin;
  55. @end
  56. @interface RLMSyncSession ()
  57. - (BOOL)waitForUploadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback;
  58. - (BOOL)waitForDownloadCompletionOnQueue:(dispatch_queue_t)queue callback:(void(^)(NSError *))callback;
  59. @end
  60. @interface RLMSyncUser()
  61. - (std::shared_ptr<realm::SyncUser>)_syncUser;
  62. @end
  63. @implementation SyncObject
  64. @end
  65. @implementation HugeSyncObject
  66. + (instancetype)object {
  67. const NSInteger fakeDataSize = 1000000;
  68. HugeSyncObject *object = [[self alloc] init];
  69. char fakeData[fakeDataSize];
  70. memset(fakeData, sizeof(fakeData), 16);
  71. object.dataProp = [NSData dataWithBytes:fakeData length:sizeof(fakeData)];
  72. return object;
  73. }
  74. @end
  75. static NSTask *s_task;
  76. static RLMSyncManager *s_managerForTest;
  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. + (RLMSyncManager *)managerForCurrentTest {
  207. return s_managerForTest;
  208. }
  209. #pragma mark - Helper methods
  210. - (BOOL)isPartial {
  211. return NO;
  212. }
  213. + (NSURL *)authServerURL {
  214. return [NSURL URLWithString:@"http://127.0.0.1:9080"];
  215. }
  216. + (NSURL *)secureAuthServerURL {
  217. return [NSURL URLWithString:@"https://localhost:9443"];
  218. }
  219. + (RLMSyncCredentials *)basicCredentialsWithName:(NSString *)name register:(BOOL)shouldRegister {
  220. return [RLMSyncCredentials credentialsWithUsername:name
  221. password:@"a"
  222. register:shouldRegister];
  223. }
  224. + (NSURL *)onDiskPathForSyncedRealm:(RLMRealm *)realm {
  225. return [NSURL fileURLWithPath:@(realm->_realm->config().path.data())];
  226. }
  227. - (void)addSyncObjectsToRealm:(RLMRealm *)realm descriptions:(NSArray<NSString *> *)descriptions {
  228. [realm beginWriteTransaction];
  229. for (NSString *desc in descriptions) {
  230. [SyncObject createInRealm:realm withValue:@[desc]];
  231. }
  232. [realm commitWriteTransaction];
  233. }
  234. - (void)waitForDownloadsForUser:(RLMSyncUser *)user
  235. realms:(NSArray<RLMRealm *> *)realms
  236. realmURLs:(NSArray<NSURL *> *)realmURLs
  237. expectedCounts:(NSArray<NSNumber *> *)counts {
  238. NSAssert(realms.count == counts.count && realms.count == realmURLs.count,
  239. @"Test logic error: all array arguments must be the same size.");
  240. for (NSUInteger i = 0; i < realms.count; i++) {
  241. [self waitForDownloadsForUser:user url:realmURLs[i] expectation:nil error:nil];
  242. [realms[i] refresh];
  243. CHECK_COUNT([counts[i] integerValue], SyncObject, realms[i]);
  244. }
  245. }
  246. - (RLMRealm *)openRealmForURL:(NSURL *)url user:(RLMSyncUser *)user {
  247. return [self openRealmForURL:url user:user immediatelyBlock:nil];
  248. }
  249. - (RLMRealm *)openRealmForURL:(NSURL *)url user:(RLMSyncUser *)user immediatelyBlock:(void(^)(void))block {
  250. return [self openRealmForURL:url
  251. user:user
  252. encryptionKey:nil
  253. stopPolicy:RLMSyncStopPolicyAfterChangesUploaded
  254. immediatelyBlock:block];
  255. }
  256. - (RLMRealm *)openRealmForURL:(NSURL *)url
  257. user:(RLMSyncUser *)user
  258. encryptionKey:(nullable NSData *)encryptionKey
  259. stopPolicy:(RLMSyncStopPolicy)stopPolicy
  260. immediatelyBlock:(nullable void(^)(void))block {
  261. const NSTimeInterval timeout = 4;
  262. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  263. RLMSyncManager.sharedManager.sessionCompletionNotifier = ^(NSError *error) {
  264. if (error) {
  265. XCTFail(@"Received an asynchronous error when trying to open Realm at '%@' for user '%@': %@ (process: %@)",
  266. url, user.identity, error, self.isParent ? @"parent" : @"child");
  267. }
  268. dispatch_semaphore_signal(sema);
  269. };
  270. RLMRealm *realm = [self immediatelyOpenRealmForURL:url user:user encryptionKey:encryptionKey stopPolicy:stopPolicy];
  271. if (block) {
  272. block();
  273. }
  274. // Wait for login to succeed or fail.
  275. XCTAssert(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))) == 0,
  276. @"Timed out while trying to asynchronously open Realm for URL: %@", url);
  277. return realm;
  278. }
  279. - (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration {
  280. return [self openRealmWithConfiguration:configuration immediatelyBlock:nullptr];
  281. }
  282. - (RLMRealm *)openRealmWithConfiguration:(RLMRealmConfiguration *)configuration
  283. immediatelyBlock:(nullable void(^)(void))block {
  284. const NSTimeInterval timeout = 4;
  285. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  286. RLMSyncConfiguration *syncConfig = configuration.syncConfiguration;
  287. RLMSyncManager.sharedManager.sessionCompletionNotifier = ^(NSError *error) {
  288. if (error) {
  289. XCTFail(@"Received an asynchronous error when trying to open Realm at '%@' for user '%@': %@ (process: %@)",
  290. syncConfig.realmURL, syncConfig.user.identity, error, self.isParent ? @"parent" : @"child");
  291. }
  292. dispatch_semaphore_signal(sema);
  293. };
  294. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nullptr];
  295. if (block) {
  296. block();
  297. }
  298. // Wait for login to succeed or fail.
  299. XCTAssert(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC))) == 0,
  300. @"Timed out while trying to asynchronously open Realm for URL: %@", syncConfig.realmURL);
  301. return realm;
  302. }
  303. - (RLMRealm *)immediatelyOpenRealmForURL:(NSURL *)url user:(RLMSyncUser *)user {
  304. return [self immediatelyOpenRealmForURL:url
  305. user:user
  306. encryptionKey:nil
  307. stopPolicy:RLMSyncStopPolicyAfterChangesUploaded];
  308. }
  309. - (RLMRealm *)immediatelyOpenRealmForURL:(NSURL *)url
  310. user:(RLMSyncUser *)user
  311. encryptionKey:(NSData *)encryptionKey
  312. stopPolicy:(RLMSyncStopPolicy)stopPolicy {
  313. auto c = [user configurationWithURL:url fullSynchronization:!self.isPartial];
  314. c.encryptionKey = encryptionKey;
  315. RLMSyncConfiguration *syncConfig = c.syncConfiguration;
  316. syncConfig.stopPolicy = stopPolicy;
  317. c.syncConfiguration = syncConfig;
  318. return [RLMRealm realmWithConfiguration:c error:nil];
  319. }
  320. - (RLMSyncUser *)logInUserForCredentials:(RLMSyncCredentials *)credentials
  321. server:(NSURL *)url {
  322. NSString *process = self.isParent ? @"parent" : @"child";
  323. __block RLMSyncUser *theUser = nil;
  324. XCTestExpectation *expectation = [self expectationWithDescription:@"Should log in the user properly"];
  325. [RLMSyncUser logInWithCredentials:credentials
  326. authServerURL:url
  327. onCompletion:^(RLMSyncUser *user, NSError *error) {
  328. XCTAssertTrue(NSThread.isMainThread);
  329. XCTAssertNil(error,
  330. @"Error when trying to log in a user: %@ (process: %@)",
  331. error, process);
  332. XCTAssertNotNil(user);
  333. theUser = user;
  334. [expectation fulfill];
  335. }];
  336. [self waitForExpectationsWithTimeout:4.0 handler:nil];
  337. XCTAssertTrue(theUser.state == RLMSyncUserStateActive,
  338. @"User should have been valid, but wasn't. (process: %@)", process);
  339. return theUser;
  340. }
  341. - (RLMSyncUser *)createAdminUserForURL:(NSURL *)url username:(NSString *)username {
  342. return [self logInUserForCredentials:[RLMSyncCredentials credentialsWithDebugUserID:username isAdmin:YES]
  343. server:url];
  344. }
  345. - (NSString *)adminToken {
  346. NSURL *target = [RealmObjectServer.sharedServer.serverDataRoot
  347. URLByAppendingPathComponent:@"/keys/admin.json"];
  348. if (![[NSFileManager defaultManager] fileExistsAtPath:[target path]]) {
  349. XCTFail(@"Could not find the JSON file containing the admin token.");
  350. return nil;
  351. }
  352. NSData *raw = [NSData dataWithContentsOfURL:target];
  353. NSDictionary *json = [NSJSONSerialization JSONObjectWithData:raw options:0 error:nil];
  354. NSString *token = json[@"ADMIN_TOKEN"];
  355. if ([token length] == 0) {
  356. XCTFail(@"Could not successfully extract the token.");
  357. }
  358. return token;
  359. }
  360. - (NSString *)emailForAddress:(NSString *)email {
  361. NSURL *target = [[RealmObjectServer.sharedServer.serverDataRoot
  362. URLByAppendingPathComponent:@"/email"]
  363. URLByAppendingPathComponent:email];
  364. NSString *body = [NSString stringWithContentsOfURL:target encoding:NSUTF8StringEncoding error:nil];
  365. if (body) {
  366. [NSFileManager.defaultManager removeItemAtURL:target error:nil];
  367. }
  368. return body;
  369. }
  370. - (void)waitForDownloadsForRealm:(RLMRealm *)realm {
  371. [self waitForDownloadsForRealm:realm error:nil];
  372. }
  373. - (void)waitForUploadsForRealm:(RLMRealm *)realm {
  374. [self waitForUploadsForRealm:realm error:nil];
  375. }
  376. - (void)waitForDownloadsForUser:(RLMSyncUser *)user
  377. url:(NSURL *)url
  378. expectation:(XCTestExpectation *)expectation
  379. error:(NSError **)error {
  380. RLMSyncSession *session = [user sessionForURL:url];
  381. NSAssert(session, @"Cannot call with invalid URL");
  382. XCTestExpectation *ex = expectation ?: [self expectationWithDescription:@"Wait for download completion"];
  383. __block NSError *theError = nil;
  384. BOOL queued = [session waitForDownloadCompletionOnQueue:nil callback:^(NSError *err) {
  385. theError = err;
  386. [ex fulfill];
  387. }];
  388. if (!queued) {
  389. XCTFail(@"Download waiter did not queue; session was invalid or errored out.");
  390. return;
  391. }
  392. [self waitForExpectations:@[ex] timeout:20.0];
  393. if (error) {
  394. *error = theError;
  395. }
  396. }
  397. - (void)waitForUploadsForRealm:(RLMRealm *)realm error:(NSError **)error {
  398. RLMSyncSession *session = realm.syncSession;
  399. NSAssert(session, @"Cannot call with invalid Realm");
  400. XCTestExpectation *ex = [self expectationWithDescription:@"Wait for upload completion"];
  401. __block NSError *completionError;
  402. BOOL queued = [session waitForUploadCompletionOnQueue:nil callback:^(NSError *error) {
  403. completionError = error;
  404. [ex fulfill];
  405. }];
  406. if (!queued) {
  407. XCTFail(@"Upload waiter did not queue; session was invalid or errored out.");
  408. return;
  409. }
  410. [self waitForExpectations:@[ex] timeout:20.0];
  411. if (error)
  412. *error = completionError;
  413. }
  414. - (void)waitForDownloadsForRealm:(RLMRealm *)realm error:(NSError **)error {
  415. RLMSyncSession *session = realm.syncSession;
  416. NSAssert(session, @"Cannot call with invalid Realm");
  417. XCTestExpectation *ex = [self expectationWithDescription:@"Wait for download completion"];
  418. __block NSError *completionError;
  419. BOOL queued = [session waitForDownloadCompletionOnQueue:nil callback:^(NSError *error) {
  420. completionError = error;
  421. [ex fulfill];
  422. }];
  423. if (!queued) {
  424. XCTFail(@"Download waiter did not queue; session was invalid or errored out.");
  425. return;
  426. }
  427. [self waitForExpectations:@[ex] timeout:20.0];
  428. if (error)
  429. *error = completionError;
  430. }
  431. - (void)manuallySetRefreshTokenForUser:(RLMSyncUser *)user value:(NSString *)tokenValue {
  432. [user _syncUser]->update_refresh_token(tokenValue.UTF8String);
  433. }
  434. // FIXME: remove this API once the new token system is implemented.
  435. - (void)primeSyncManagerWithSemaphore:(dispatch_semaphore_t)semaphore {
  436. if (semaphore == nil) {
  437. [[RLMSyncManager sharedManager] setSessionCompletionNotifier:^(__unused NSError *error){ }];
  438. return;
  439. }
  440. [[RLMSyncManager sharedManager] setSessionCompletionNotifier:^(NSError *error) {
  441. XCTAssertNil(error, @"Session completion block returned with an error: %@", error);
  442. dispatch_semaphore_signal(semaphore);
  443. }];
  444. }
  445. #pragma mark - XCUnitTest Lifecycle
  446. - (void)setUp {
  447. [super setUp];
  448. self.continueAfterFailure = NO;
  449. NSURL *clientDataRoot;
  450. if (self.isParent) {
  451. [RealmObjectServer.sharedServer launch];
  452. clientDataRoot = [NSURL fileURLWithPath:RLMDefaultDirectoryForBundleIdentifier(nil)];
  453. }
  454. else {
  455. clientDataRoot = syncDirectoryForChildProcess();
  456. }
  457. NSError *error;
  458. [NSFileManager.defaultManager removeItemAtURL:clientDataRoot error:&error];
  459. [NSFileManager.defaultManager createDirectoryAtURL:clientDataRoot
  460. withIntermediateDirectories:YES attributes:nil error:&error];
  461. s_managerForTest = [[RLMSyncManager alloc] initWithCustomRootDirectory:clientDataRoot];
  462. [RLMSyncManager sharedManager].logLevel = RLMSyncLogLevelOff;
  463. [RLMSyncManager sharedManager].userAgent = self.name;
  464. }
  465. - (void)tearDown {
  466. [s_managerForTest prepareForDestruction];
  467. s_managerForTest = nil;
  468. [RLMSyncSessionRefreshHandle calculateFireDateUsingTestLogic:NO blockOnRefreshCompletion:nil];
  469. [super tearDown];
  470. }
  471. @end