/core/externals/update-engine/externals/google-toolbox-for-mac/Foundation/GTMFileSystemKQueueTest.m

http://macfuse.googlecode.com/ · Objective C · 521 lines · 359 code · 83 blank · 79 comment · 8 complexity · c98a367db06e15d542a12928710555ea MD5 · raw file

  1. //
  2. // GTMFileSystemKQueueTest.m
  3. //
  4. // Copyright 2008 Google Inc.
  5. //
  6. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7. // use this file except in compliance with the License. You may obtain a copy
  8. // of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  15. // License for the specific language governing permissions and limitations under
  16. // the License.
  17. //
  18. #import "GTMSenTestCase.h"
  19. #import "GTMFileSystemKQueue.h"
  20. #import "GTMUnitTestDevLog.h"
  21. // Private methods of GTMFileSystemKQueue we use for some tests
  22. @interface GTMFileSystemKQueue (PrivateMethods)
  23. - (void)unregisterWithKQueue;
  24. @end
  25. @interface GTMFileSystemKQueueTest : GTMTestCase {
  26. @private
  27. NSString *testPath_;
  28. NSString *testPath2_;
  29. }
  30. @end
  31. // Helper class to serve as callback target of the kqueue test
  32. @interface GTMFSKQTestHelper : NSObject {
  33. @private
  34. int writes_, renames_, deletes_;
  35. __weak GTMFileSystemKQueue *queue_;
  36. }
  37. @end
  38. @implementation GTMFSKQTestHelper
  39. - (void)callbackForQueue:(GTMFileSystemKQueue *)queue
  40. events:(GTMFileSystemKQueueEvents)event {
  41. // Can't use standard ST macros here because our helper
  42. // is not a subclass of GTMTestCase. This is intentional.
  43. if (queue != queue_) {
  44. NSString *file = [NSString stringWithUTF8String:__FILE__];
  45. NSException *exception
  46. = [NSException failureInEqualityBetweenObject:queue
  47. andObject:queue_
  48. inFile:file
  49. atLine:__LINE__
  50. withDescription:nil];
  51. [exception raise];
  52. }
  53. if (event & kGTMFileSystemKQueueWriteEvent) {
  54. ++writes_;
  55. }
  56. if (event & kGTMFileSystemKQueueDeleteEvent) {
  57. ++deletes_;
  58. }
  59. if (event & kGTMFileSystemKQueueRenameEvent) {
  60. ++renames_;
  61. }
  62. }
  63. - (int)totals {
  64. return writes_ + renames_ + deletes_;
  65. }
  66. - (int)writes {
  67. return writes_;
  68. }
  69. - (int)renames {
  70. return renames_;
  71. }
  72. - (int)deletes {
  73. return deletes_;
  74. }
  75. - (void)setKQueue:(GTMFileSystemKQueue *)queue {
  76. queue_ = queue;
  77. }
  78. @end
  79. @implementation GTMFileSystemKQueueTest
  80. - (void)setUp {
  81. NSString *temp = NSTemporaryDirectory();
  82. testPath_
  83. = [[temp stringByAppendingPathComponent:
  84. @"GTMFileSystemKQueueTest.testfile"] retain];
  85. testPath2_ = [[testPath_ stringByAppendingPathExtension:@"2"] retain];
  86. // make sure the files aren't in the way of the test
  87. NSFileManager *fm = [NSFileManager defaultManager];
  88. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  89. NSError *error = nil;
  90. [fm removeItemAtPath:testPath_ error:&error];
  91. [fm removeItemAtPath:testPath2_ error:&error];
  92. #else
  93. [fm removeFileAtPath:testPath_ handler:nil];
  94. [fm removeFileAtPath:testPath2_ handler:nil];
  95. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  96. }
  97. - (void)tearDown {
  98. // make sure we clean up the files from a failed test
  99. NSFileManager *fm = [NSFileManager defaultManager];
  100. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  101. NSError *error = nil;
  102. [fm removeItemAtPath:testPath_ error:&error];
  103. [fm removeItemAtPath:testPath2_ error:&error];
  104. #else
  105. [fm removeFileAtPath:testPath_ handler:nil];
  106. [fm removeFileAtPath:testPath2_ handler:nil];
  107. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  108. [testPath_ release];
  109. testPath_ = nil;
  110. [testPath2_ release];
  111. testPath2_ = nil;
  112. }
  113. - (void)testInit {
  114. GTMFileSystemKQueue *testKQ;
  115. GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
  116. STAssertNotNil(helper, nil);
  117. // init should fail
  118. [GTMUnitTestDevLog expectString:@"Don't call init, use "
  119. @"initWithPath:forEvents:acrossReplace:target:action:"];
  120. testKQ = [[[GTMFileSystemKQueue alloc] init] autorelease];
  121. STAssertNil(testKQ, nil);
  122. // no path
  123. testKQ
  124. = [[[GTMFileSystemKQueue alloc] initWithPath:nil
  125. forEvents:kGTMFileSystemKQueueAllEvents
  126. acrossReplace:YES
  127. target:helper
  128. action:@selector(callbackForQueue:events:)] autorelease];
  129. STAssertNil(testKQ, nil);
  130. // not events
  131. testKQ
  132. = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
  133. forEvents:0
  134. acrossReplace:YES
  135. target:helper
  136. action:@selector(callbackForQueue:events:)] autorelease];
  137. STAssertNil(testKQ, nil);
  138. // no target
  139. testKQ
  140. = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
  141. forEvents:kGTMFileSystemKQueueAllEvents
  142. acrossReplace:YES
  143. target:nil
  144. action:@selector(callbackForQueue:events:)] autorelease];
  145. STAssertNil(testKQ, nil);
  146. // no handler
  147. testKQ
  148. = [[[GTMFileSystemKQueue alloc] initWithPath:@"/var/log/system.log"
  149. forEvents:0
  150. acrossReplace:YES
  151. target:helper
  152. action:nil] autorelease];
  153. STAssertNil(testKQ, nil);
  154. // path that doesn't exist
  155. testKQ
  156. = [[[GTMFileSystemKQueue alloc] initWithPath:@"/path/that/does/not/exist"
  157. forEvents:kGTMFileSystemKQueueAllEvents
  158. acrossReplace:YES
  159. target:helper
  160. action:@selector(callbackForQueue:events:)] autorelease];
  161. STAssertNil(testKQ, nil);
  162. }
  163. - (void)spinForEvents:(GTMFSKQTestHelper *)helper {
  164. // Spin the runloop for a second so that the helper callbacks fire
  165. unsigned int attempts = 0;
  166. int initialTotals = [helper totals];
  167. while (([helper totals] == initialTotals) && (attempts < 10)) { // Try for up to 2s
  168. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
  169. attempts++;
  170. }
  171. }
  172. - (void)testWriteAndDelete {
  173. NSFileManager *fm = [NSFileManager defaultManager];
  174. GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
  175. STAssertNotNil(helper, nil);
  176. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  177. NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  178. STAssertNotNil(testFH, nil);
  179. // Start monitoring the file
  180. GTMFileSystemKQueue *testKQ
  181. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  182. forEvents:kGTMFileSystemKQueueAllEvents
  183. acrossReplace:YES
  184. target:helper
  185. action:@selector(callbackForQueue:events:)];
  186. STAssertNotNil(testKQ, nil);
  187. STAssertEqualObjects([testKQ path], testPath_, nil);
  188. [helper setKQueue:testKQ];
  189. // Write to the file
  190. [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
  191. // Spin the runloop for a second so that the helper callbacks fire
  192. [self spinForEvents:helper];
  193. STAssertEquals([helper totals], 1, nil);
  194. // Close and delete
  195. [testFH closeFile];
  196. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  197. NSError *error = nil;
  198. STAssertTrue([fm removeItemAtPath:testPath_ error:&error], @"Err: %@", error);
  199. #else
  200. STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
  201. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  202. [self spinForEvents:helper];
  203. STAssertEquals([helper totals], 2, nil);
  204. // Clean up the kqueue
  205. [testKQ release];
  206. testKQ = nil;
  207. STAssertEquals([helper writes], 1, nil);
  208. STAssertEquals([helper deletes], 1, nil);
  209. STAssertEquals([helper renames], 0, nil);
  210. }
  211. - (void)testWriteAndDeleteAndWrite {
  212. // One will pass YES to |acrossReplace|, the other will pass NO.
  213. NSFileManager *fm = [NSFileManager defaultManager];
  214. GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
  215. STAssertNotNil(helper, nil);
  216. GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
  217. STAssertNotNil(helper, nil);
  218. // Create a temp file path
  219. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  220. NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  221. STAssertNotNil(testFH, nil);
  222. // Start monitoring the file
  223. GTMFileSystemKQueue *testKQ
  224. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  225. forEvents:kGTMFileSystemKQueueAllEvents
  226. acrossReplace:YES
  227. target:helper
  228. action:@selector(callbackForQueue:events:)];
  229. STAssertNotNil(testKQ, nil);
  230. STAssertEqualObjects([testKQ path], testPath_, nil);
  231. [helper setKQueue:testKQ];
  232. GTMFileSystemKQueue *testKQ2
  233. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  234. forEvents:kGTMFileSystemKQueueAllEvents
  235. acrossReplace:NO
  236. target:helper2
  237. action:@selector(callbackForQueue:events:)];
  238. STAssertNotNil(testKQ2, nil);
  239. STAssertEqualObjects([testKQ2 path], testPath_, nil);
  240. [helper2 setKQueue:testKQ2];
  241. // Write to the file
  242. [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
  243. // Spin the runloop for a second so that the helper callbacks fire
  244. [self spinForEvents:helper];
  245. STAssertEquals([helper totals], 1, nil);
  246. STAssertEquals([helper2 totals], 1, nil);
  247. // Close and delete
  248. [testFH closeFile];
  249. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  250. NSError *error = nil;
  251. STAssertTrue([fm removeItemAtPath:testPath_ error:&error], @"Err: %@", error);
  252. #else
  253. STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
  254. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  255. // Recreate
  256. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  257. testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  258. STAssertNotNil(testFH, nil);
  259. [testFH writeData:[@"ha!" dataUsingEncoding:NSUnicodeStringEncoding]];
  260. // Spin the runloop for a second so that the helper callbacks fire
  261. [self spinForEvents:helper];
  262. STAssertEquals([helper totals], 2, nil);
  263. STAssertEquals([helper2 totals], 2, nil);
  264. // Write to it again
  265. [testFH writeData:[@"continued..." dataUsingEncoding:NSUnicodeStringEncoding]];
  266. // Spin the runloop for a second so that the helper callbacks fire
  267. [self spinForEvents:helper];
  268. STAssertEquals([helper totals], 3, nil);
  269. STAssertEquals([helper2 totals], 2, nil);
  270. // Close and delete
  271. [testFH closeFile];
  272. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  273. STAssertTrue([fm removeItemAtPath:testPath_ error:&error], @"Err: %@", error);
  274. #else
  275. STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
  276. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  277. // Spin the runloop for a second so that the helper callbacks fire
  278. [self spinForEvents:helper];
  279. STAssertEquals([helper totals], 4, nil);
  280. STAssertEquals([helper2 totals], 2, nil);
  281. // Clean up the kqueue
  282. [testKQ release];
  283. testKQ = nil;
  284. [testKQ2 release];
  285. testKQ2 = nil;
  286. STAssertEquals([helper writes], 2, nil);
  287. STAssertEquals([helper deletes], 2, nil);
  288. STAssertEquals([helper renames], 0, nil);
  289. STAssertEquals([helper2 writes], 1, nil);
  290. STAssertEquals([helper2 deletes], 1, nil);
  291. STAssertEquals([helper2 renames], 0, nil);
  292. }
  293. - (void)testWriteAndRenameAndWrite {
  294. // One will pass YES to |acrossReplace|, the other will pass NO.
  295. NSFileManager *fm = [NSFileManager defaultManager];
  296. GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
  297. STAssertNotNil(helper, nil);
  298. GTMFSKQTestHelper *helper2 = [[[GTMFSKQTestHelper alloc] init] autorelease];
  299. STAssertNotNil(helper2, nil);
  300. // Create a temp file path
  301. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  302. NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  303. STAssertNotNil(testFH, nil);
  304. // Start monitoring the file
  305. GTMFileSystemKQueue *testKQ
  306. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  307. forEvents:kGTMFileSystemKQueueAllEvents
  308. acrossReplace:YES
  309. target:helper
  310. action:@selector(callbackForQueue:events:)];
  311. STAssertNotNil(testKQ, nil);
  312. STAssertEqualObjects([testKQ path], testPath_, nil);
  313. [helper setKQueue:testKQ];
  314. GTMFileSystemKQueue *testKQ2
  315. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  316. forEvents:kGTMFileSystemKQueueAllEvents
  317. acrossReplace:NO
  318. target:helper2
  319. action:@selector(callbackForQueue:events:)];
  320. STAssertNotNil(testKQ2, nil);
  321. STAssertEqualObjects([testKQ2 path], testPath_, nil);
  322. [helper2 setKQueue:testKQ2];
  323. // Write to the file
  324. [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
  325. // Spin the runloop for a second so that the helper callbacks fire
  326. [self spinForEvents:helper];
  327. STAssertEquals([helper totals], 1, nil);
  328. STAssertEquals([helper2 totals], 1, nil);
  329. // Move it and create the file again
  330. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  331. NSError *error = nil;
  332. STAssertTrue([fm moveItemAtPath:testPath_ toPath:testPath2_ error:&error],
  333. @"Error: %@", error);
  334. #else
  335. STAssertTrue([fm movePath:testPath_ toPath:testPath2_ handler:nil], nil);
  336. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  337. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  338. NSFileHandle *testFHPrime
  339. = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  340. STAssertNotNil(testFHPrime, nil);
  341. [testFHPrime writeData:[@"eh?" dataUsingEncoding:NSUnicodeStringEncoding]];
  342. // Spin the runloop for a second so that the helper callbacks fire
  343. [self spinForEvents:helper];
  344. STAssertEquals([helper totals], 2, nil);
  345. STAssertEquals([helper2 totals], 2, nil);
  346. // Write to the new file
  347. [testFHPrime writeData:[@"continue..." dataUsingEncoding:NSUnicodeStringEncoding]];
  348. // Spin the runloop for a second so that the helper callbacks fire
  349. [self spinForEvents:helper];
  350. STAssertEquals([helper totals], 3, nil);
  351. STAssertEquals([helper2 totals], 2, nil);
  352. // Write to the old file
  353. [testFH writeData:[@"continue old..." dataUsingEncoding:NSUnicodeStringEncoding]];
  354. // Spin the runloop for a second so that the helper callbacks fire
  355. [self spinForEvents:helper];
  356. STAssertEquals([helper totals], 3, nil);
  357. STAssertEquals([helper2 totals], 3, nil);
  358. // and now close old
  359. [testFH closeFile];
  360. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  361. STAssertTrue([fm removeItemAtPath:testPath2_ error:&error], @"Err: %@", error);
  362. #else
  363. STAssertTrue([fm removeFileAtPath:testPath2_ handler:nil], nil);
  364. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  365. // Spin the runloop for a second so that the helper callbacks fire
  366. [self spinForEvents:helper];
  367. STAssertEquals([helper totals], 3, nil);
  368. STAssertEquals([helper2 totals], 4, nil);
  369. // and now close new
  370. [testFHPrime closeFile];
  371. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  372. STAssertTrue([fm removeItemAtPath:testPath_ error:&error], @"Err: %@", error);
  373. #else
  374. STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
  375. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  376. // Spin the runloop for a second so that the helper callbacks fire
  377. [self spinForEvents:helper];
  378. STAssertEquals([helper totals], 4, nil);
  379. STAssertEquals([helper2 totals], 4, nil);
  380. // Clean up the kqueue
  381. [testKQ release];
  382. testKQ = nil;
  383. [testKQ2 release];
  384. testKQ2 = nil;
  385. STAssertEquals([helper writes], 2, nil);
  386. STAssertEquals([helper deletes], 1, nil);
  387. STAssertEquals([helper renames], 1, nil);
  388. STAssertEquals([helper2 writes], 2, nil);
  389. STAssertEquals([helper2 deletes], 1, nil);
  390. STAssertEquals([helper2 renames], 1, nil);
  391. }
  392. - (void)testNoSpinHang {
  393. // This case tests a specific historically problematic interaction of
  394. // GTMFileSystemKQueue and the runloop. GTMFileSystemKQueue uses the CFSocket
  395. // notifications (and thus the runloop) for monitoring, however, you can
  396. // dealloc the instance (and thus unregister the underlying kevent descriptor)
  397. // prior to any runloop spin. The unregister removes the pending notifications
  398. // from the monitored main kqueue file descriptor that CFSocket has previously
  399. // noticed but not yet called back. At that point a kevent() call in the
  400. // socket callback without a timeout would hang the runloop.
  401. // Warn this may hang
  402. NSLog(@"%s on failure this will hang.", __PRETTY_FUNCTION__);
  403. NSFileManager *fm = [NSFileManager defaultManager];
  404. GTMFSKQTestHelper *helper = [[[GTMFSKQTestHelper alloc] init] autorelease];
  405. STAssertNotNil(helper, nil);
  406. STAssertTrue([fm createFileAtPath:testPath_ contents:nil attributes:nil], nil);
  407. NSFileHandle *testFH = [NSFileHandle fileHandleForWritingAtPath:testPath_];
  408. STAssertNotNil(testFH, nil);
  409. // Start monitoring the file
  410. GTMFileSystemKQueue *testKQ
  411. = [[GTMFileSystemKQueue alloc] initWithPath:testPath_
  412. forEvents:kGTMFileSystemKQueueAllEvents
  413. acrossReplace:YES
  414. target:helper
  415. action:@selector(callbackForQueue:events:)];
  416. STAssertNotNil(testKQ, nil);
  417. STAssertEqualObjects([testKQ path], testPath_, nil);
  418. [helper setKQueue:testKQ];
  419. // Write to the file
  420. [testFH writeData:[@"doh!" dataUsingEncoding:NSUnicodeStringEncoding]];
  421. // Close and delete
  422. [testFH closeFile];
  423. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  424. NSError *error = nil;
  425. STAssertTrue([fm removeItemAtPath:testPath_ error:&error], @"Err: %@", error);
  426. #else
  427. STAssertTrue([fm removeFileAtPath:testPath_ handler:nil], nil);
  428. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  429. // Now destroy the queue, with events outstanding from the CFSocket, but
  430. // unconsumed.
  431. STAssertEquals([testKQ retainCount], (NSUInteger)1, nil);
  432. [testKQ release];
  433. testKQ = nil;
  434. // Spin the runloop, no events were delivered (and we should not hang)
  435. [self spinForEvents:helper];
  436. STAssertEquals([helper totals], 0, nil);
  437. }
  438. @end