CompactionTests.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2017 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 "RLMTestCase.h"
  19. @interface RLMRealm ()
  20. - (BOOL)compact;
  21. @end
  22. @interface CompactionTests : RLMTestCase
  23. @end
  24. @implementation CompactionTests {
  25. uint64_t _expectedTotalBytesBefore;
  26. }
  27. static const NSUInteger expectedUsedBytesBeforeMin = 50000;
  28. static const NSUInteger count = 1000;
  29. #pragma mark - Helpers
  30. - (unsigned long long)fileSize:(NSURL *)fileURL {
  31. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:fileURL.path error:nil];
  32. return [attributes[NSFileSize] unsignedLongLongValue];
  33. }
  34. - (void)setUp {
  35. [super setUp];
  36. @autoreleasepool {
  37. // Make compactable Realm
  38. RLMRealm *realm = self.realmWithTestPath;
  39. NSString *uuid = [[NSUUID UUID] UUIDString];
  40. [realm transactionWithBlock:^{
  41. [StringObject createInRealm:realm withValue:@[@"A"]];
  42. for (NSUInteger i = 0; i < count; ++i) {
  43. [StringObject createInRealm:realm withValue:@[uuid]];
  44. }
  45. [StringObject createInRealm:realm withValue:@[@"B"]];
  46. }];
  47. }
  48. _expectedTotalBytesBefore = [self fileSize:RLMTestRealmURL()];
  49. }
  50. #pragma mark - Tests
  51. - (void)testCompact {
  52. RLMRealm *realm = self.realmWithTestPath;
  53. unsigned long long fileSizeBefore = [self fileSize:realm.configuration.fileURL];
  54. StringObject *object = [StringObject allObjectsInRealm:realm].firstObject;
  55. XCTAssertTrue([realm compact]);
  56. XCTAssertTrue(object.isInvalidated);
  57. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  58. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  59. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  60. unsigned long long fileSizeAfter = [self fileSize:realm.configuration.fileURL];
  61. XCTAssertGreaterThan(fileSizeBefore, fileSizeAfter);
  62. }
  63. - (void)testSuccessfulCompactOnLaunch {
  64. // Configure the Realm to compact on launch
  65. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  66. configuration.fileURL = RLMTestRealmURL();
  67. configuration.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
  68. // Confirm expected sizes
  69. XCTAssertEqual(totalBytes, _expectedTotalBytesBefore);
  70. XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin));
  71. // Compact if the file is over 500KB in size and less than 20% 'used'
  72. // In practice, users might want to use values closer to 100MB and 50%
  73. NSUInteger fiveHundredKB = 500 * 1024;
  74. return (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2;
  75. };
  76. // Confirm expected sizes before and after opening the Realm
  77. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  78. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  79. XCTAssertLessThan([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  80. // Validate that the file still contains what it should
  81. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  82. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  83. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  84. }
  85. - (void)testNoBlockCompactOnLaunch {
  86. // Configure the Realm to compact on launch
  87. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  88. configuration.fileURL = RLMTestRealmURL();
  89. // Confirm expected sizes before and after opening the Realm
  90. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  91. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  92. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  93. // Validate that the file still contains what it should
  94. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  95. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  96. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  97. }
  98. - (void)testCachedRealmCompactOnLaunch {
  99. // Test that the compaction block never gets called if there are cached Realms
  100. // Access Realm before opening it with a compaction block
  101. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  102. configuration.fileURL = RLMTestRealmURL();
  103. __unused RLMRealm *firstRealm = [RLMRealm realmWithConfiguration:configuration error:nil];
  104. // Configure the Realm to compact on launch
  105. RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy];
  106. __block BOOL compactBlockInvoked = NO;
  107. configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  108. compactBlockInvoked = YES;
  109. // Always attempt to compact
  110. return YES;
  111. };
  112. // Confirm expected sizes before and after opening the Realm
  113. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  114. RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil];
  115. XCTAssertFalse(compactBlockInvoked);
  116. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  117. // Validate that the file still contains what it should
  118. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  119. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  120. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  121. }
  122. - (void)testCachedRealmOtherThreadCompactOnLaunch {
  123. // Test that the compaction block never gets called if the Realm is open on a different thread
  124. // Access Realm before opening it with a compaction block
  125. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  126. configuration.fileURL = RLMTestRealmURL();
  127. dispatch_semaphore_t failedCompactTestCompleteSema = dispatch_semaphore_create(0);
  128. dispatch_semaphore_t bgRealmClosedSema = dispatch_semaphore_create(0);
  129. XCTestExpectation *realmOpenedExpectation = [self expectationWithDescription:@"Realm was opened on background thread"];
  130. [self dispatchAsync:^{
  131. @autoreleasepool {
  132. __unused RLMRealm *firstRealm = [RLMRealm realmWithConfiguration:configuration error:nil];
  133. [realmOpenedExpectation fulfill];
  134. dispatch_semaphore_wait(failedCompactTestCompleteSema, DISPATCH_TIME_FOREVER);
  135. }
  136. dispatch_semaphore_signal(bgRealmClosedSema);
  137. }];
  138. [self waitForExpectationsWithTimeout:2 handler:nil];
  139. @autoreleasepool {
  140. // Configure the Realm to compact on launch
  141. RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy];
  142. __block BOOL compactBlockInvoked = NO;
  143. configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  144. compactBlockInvoked = YES;
  145. // Always attempt to compact
  146. return YES;
  147. };
  148. // Confirm expected sizes before and after opening the Realm
  149. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  150. __unused RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil];
  151. XCTAssertFalse(compactBlockInvoked);
  152. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  153. dispatch_semaphore_signal(failedCompactTestCompleteSema);
  154. }
  155. dispatch_semaphore_wait(bgRealmClosedSema, DISPATCH_TIME_FOREVER);
  156. // Configure the Realm to compact on launch
  157. RLMRealmConfiguration *configurationWithCompactBlock = [configuration copy];
  158. __block BOOL compactBlockInvoked = NO;
  159. configurationWithCompactBlock.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
  160. // Confirm expected sizes
  161. XCTAssertEqual(totalBytes, _expectedTotalBytesBefore);
  162. XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin));
  163. // Compact if the file is over 500KB in size and less than 20% 'used'
  164. // In practice, users might want to use values closer to 100MB and 50%
  165. NSUInteger fiveHundredKB = 500 * 1024;
  166. BOOL shouldCompact = (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2;
  167. compactBlockInvoked = YES;
  168. return shouldCompact;
  169. };
  170. // Confirm expected sizes before and after opening the Realm
  171. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  172. RLMRealm *realm = [RLMRealm realmWithConfiguration:configurationWithCompactBlock error:nil];
  173. XCTAssertTrue(compactBlockInvoked);
  174. XCTAssertLessThan([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  175. // Validate that the file still contains what it should
  176. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  177. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  178. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  179. }
  180. - (void)testReturnNoCompactOnLaunch {
  181. // Configure the Realm to compact on launch
  182. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  183. configuration.fileURL = RLMTestRealmURL();
  184. configuration.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
  185. // Confirm expected sizes
  186. XCTAssertEqual(totalBytes, _expectedTotalBytesBefore);
  187. XCTAssertTrue((usedBytes < totalBytes) && (usedBytes > expectedUsedBytesBeforeMin));
  188. // Don't compact.
  189. return NO;
  190. };
  191. // Confirm expected sizes before and after opening the Realm
  192. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  193. RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil];
  194. XCTAssertEqual([self fileSize:configuration.fileURL], _expectedTotalBytesBefore);
  195. // Validate that the file still contains what it should
  196. XCTAssertEqual([[StringObject allObjectsInRealm:realm] count], count + 2);
  197. XCTAssertEqualObjects(@"A", [[StringObject allObjectsInRealm:realm].firstObject stringCol]);
  198. XCTAssertEqualObjects(@"B", [[StringObject allObjectsInRealm:realm].lastObject stringCol]);
  199. }
  200. - (void)testCompactOnLaunchValidation {
  201. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  202. configuration.readOnly = YES;
  203. BOOL (^compactBlock)(NSUInteger, NSUInteger) = ^BOOL(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  204. return NO;
  205. };
  206. RLMAssertThrowsWithReasonMatching(configuration.shouldCompactOnLaunch = compactBlock,
  207. @"Cannot set `shouldCompactOnLaunch` when `readOnly` is set.");
  208. configuration.readOnly = NO;
  209. configuration.shouldCompactOnLaunch = compactBlock;
  210. RLMAssertThrowsWithReasonMatching(configuration.readOnly = YES,
  211. @"Cannot set `readOnly` when `shouldCompactOnLaunch` is set.");
  212. }
  213. - (void)testAccessDeniedOnTemporaryFile {
  214. RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
  215. configuration.fileURL = RLMTestRealmURL();
  216. configuration.shouldCompactOnLaunch = ^(__unused NSUInteger totalBytes, __unused NSUInteger usedBytes){
  217. return YES;
  218. };
  219. NSURL *tmpURL = [configuration.fileURL URLByAppendingPathExtension:@"tmp_compaction_space"];
  220. [NSData.data writeToURL:tmpURL atomically:NO];
  221. [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @YES} ofItemAtPath:tmpURL.path error:nil];
  222. RLMAssertThrowsWithReason([RLMRealm realmWithConfiguration:configuration error:nil],
  223. @"unlink() failed: Operation not permitted");
  224. [NSFileManager.defaultManager setAttributes:@{NSFileImmutable: @NO} ofItemAtPath:tmpURL.path error:nil];
  225. XCTAssertNoThrow([RLMRealm realmWithConfiguration:configuration error:nil]);
  226. }
  227. @end