PageRenderTime 114ms CodeModel.GetById 18ms app.highlight 90ms RepoModel.GetById 1ms app.codeStats 1ms

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