PageRenderTime 75ms CodeModel.GetById 2ms app.highlight 69ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/Core/KSTicketStoreTest.m

http://macfuse.googlecode.com/
Objective C | 373 lines | 249 code | 67 blank | 57 comment | 15 complexity | f4052115fb332e04020623a458bfed64 MD5 | raw file
  1// Copyright 2008 Google Inc.
  2//
  3// Licensed under the Apache License, Version 2.0 (the "License");
  4// you may not use this file except in compliance with the License.
  5// You may obtain a copy of the License at
  6//
  7//     http://www.apache.org/licenses/LICENSE-2.0
  8//
  9// Unless required by applicable law or agreed to in writing, software
 10// distributed under the License is distributed on an "AS IS" BASIS,
 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12// See the License for the specific language governing permissions and
 13// limitations under the License.
 14
 15#import "KSTicketStoreTest.h"
 16#import "KSTicketStore.h"
 17#import "KSTicket.h"
 18#import "KSExistenceChecker.h"
 19#import "KSUUID.h"
 20#import "GTMLogger.h"
 21
 22
 23static KSTicket *GenerateTicketWithXC(KSExistenceChecker *xc) {
 24  NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
 25  KSTicket *t = [KSTicket ticketWithProductID:[KSUUID uuidString]
 26                                 version:@"1.1"
 27                        existenceChecker:xc
 28                               serverURL:url];
 29  return t;
 30}
 31
 32static KSTicket *GenerateTicket(void) {
 33  return GenerateTicketWithXC([KSExistenceChecker falseChecker]);
 34}
 35
 36static KSTicket *GenerateTicketWithXCPath(NSString *path) {
 37  return GenerateTicketWithXC([KSPathExistenceChecker checkerWithPath:path]);
 38}
 39
 40
 41// This class simply houses a -threadMain: method for testing concurrently
 42// accessing a ticket store. See the -threadMain: comments for more details.
 43//
 44// This class is only used in the -testLocking unit test method below.
 45@interface Threader : NSObject 
 46- (void)threadMain:(id)info;
 47@end
 48
 49static volatile int gFailedThreaders = 0;  // The total number of threads who failed.
 50static volatile int gStoppedThreads = 0;
 51static NSString *kHugeLock = @"lock";
 52
 53@implementation Threader
 54// This method is called in a separate thread and it's passed an NSDictionary
 55// with a ticket store ("store") and an array of tickets ("tickets") that
 56// *should be* in the store at all times. All we do is loop through N times and
 57// re-add the tickets that should already be in the store to the store again. 
 58// Then we read the tickets back out of the store to guarantee that the store
 59// has what we expect.
 60//
 61// The point is that the invariant of this test is that the |store| *always* has
 62// the tickets that are specified in the |tickets| array. Even though multiple
 63// threads are modifying the store (albeit, with the same tickets), each one
 64// always gets a consistent snapshot.
 65- (void)threadMain:(id)info {
 66  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 67  
 68  KSTicketStore *store = [info objectForKey:@"store"];
 69  NSArray *tickets = [info objectForKey:@"tickets"];
 70  
 71  // We actually pack the tickets into a set because we don't care about order
 72  NSSet *ticketSet = [NSSet setWithArray:tickets];
 73  
 74  for (int i = 0; i < 100; ++i) {
 75    //
 76    // Store all the |tickets| into the |store| (again)
 77    //
 78    KSTicket *t = nil;
 79    NSEnumerator *ticketEnumerator = [tickets objectEnumerator];
 80    while ((t = [ticketEnumerator nextObject])) {
 81      if (![store storeTicket:t])
 82        GTMLoggerError(@"!!!!!!!! Failed to store ticket %@", t);  // COV_NF_LINE
 83    }
 84    
 85    //
 86    // Read tickets back out and make sure it's all consistent
 87    //
 88    NSArray *allTickets = [store tickets];
 89    NSSet *allTicketSet = [NSSet setWithArray:allTickets];
 90    if (![ticketSet isEqual:allTicketSet]) {
 91      // COV_NF_START
 92      GTMLoggerError(@"failed to read tickets out of store. Expected %@, "
 93                     @"but got %@", tickets, allTickets);
 94      @synchronized (kHugeLock) {
 95        ++gFailedThreaders;  // Count our failed threader
 96      }
 97      // COV_NF_END
 98    }
 99  }
100  
101  [pool release];
102  @synchronized (kHugeLock) {
103    ++gStoppedThreads;
104  }
105}
106@end
107
108static NSString *const kTicketStorePath = @"/tmp/KSTicketStoreTest.ticketstore";
109
110@implementation KSTicketStoreTest
111
112- (void)setUp {
113  // Reset these to 0 because if anything subclasses us these statics may no
114  // longer be 0 initialized.
115  gStoppedThreads = 0;
116  gFailedThreaders = 0;
117
118  // This ivar may not be nil if a subclass has overridden this method and 
119  // has already assigned to store_ before calling this method via super.
120  if (store_ == nil) {
121    [[NSFileManager defaultManager] removeFileAtPath:kTicketStorePath handler:nil];
122    NSString *lock = [kTicketStorePath stringByAppendingPathExtension:@"lock"];
123    [[NSFileManager defaultManager] removeFileAtPath:lock handler:nil];
124    store_ = [[KSTicketStore alloc] initWithPath:kTicketStorePath];
125  }
126  // Basic sanity tests
127  STAssertNotNil(store_, nil);
128  STAssertNotNil([store_ path], nil);
129  STAssertNotNil([store_ tickets], nil);
130  STAssertEquals(0, [store_ ticketCount], nil);
131  STAssertTrue([[store_ description] length] > 1, nil);
132}
133
134- (void)tearDown {
135  NSString *path = [store_ path];
136  STAssertNotNil(path, nil);
137  [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];
138  NSString *lock = [path stringByAppendingPathExtension:@"lock"];
139  [[NSFileManager defaultManager] removeFileAtPath:lock handler:nil];
140  [store_ release];
141  store_ = nil;
142}
143
144- (void)testTicketStore {
145  KSTicketStore *ts = [[[KSTicketStore alloc] init] autorelease];
146  STAssertNil(ts, nil);
147    
148  KSTicket *t1 = GenerateTicket();
149  STAssertNotNil(t1, nil);
150  
151  STAssertEquals([store_ ticketCount], 0, nil);
152  STAssertTrue([store_ storeTicket:t1], nil);
153  STAssertEquals(1, [store_ ticketCount], nil);
154
155  STAssertTrue([store_ deleteTicket:t1], nil);
156  STAssertEquals(0, [store_ ticketCount], nil);
157  STAssertNotNil([store_ tickets], nil);
158
159  // Re-adding the same ticket should not increase the ticket count because
160  // tickets should be unique.
161  for (int i = 1; i < 5; i++) {
162    STAssertTrue([store_ storeTicket:t1], nil);
163    STAssertEquals(1, [store_ ticketCount], nil);
164  }
165
166  // Adding new tickets should increase the ticket count.
167  for (int i = 1; i < 5; i++) {
168    KSTicket *newTicket = GenerateTicket();
169    STAssertTrue([store_ storeTicket:newTicket], nil);
170    STAssertEquals(1 + i, [store_ ticketCount], nil);
171  }
172  STAssertEquals(5, [store_ ticketCount], nil);
173
174  // Now, remove our orginal ticket, and see the count drop back down.
175  STAssertTrue([store_ deleteTicket:t1], nil);
176  STAssertEquals(4, [store_ ticketCount], nil);
177
178}
179
180- (void)testTicketRetrieval {
181  KSTicket *t1 = GenerateTicket();
182  KSTicket *t2 = GenerateTicket();
183  KSTicket *t3 = GenerateTicket();
184
185  STAssertNotNil(t1, nil);
186  STAssertNotNil(t2, nil);
187  STAssertNotNil(t3, nil);
188
189  STAssertTrue([store_ storeTicket:t1], nil);
190  STAssertEquals(1, [store_ ticketCount], nil);
191
192  STAssertTrue([store_ storeTicket:t2], nil);
193  STAssertEquals(2, [store_ ticketCount], nil);
194
195  STAssertTrue([store_ storeTicket:t3], nil);
196  STAssertEquals(3, [store_ ticketCount], nil);
197
198  KSTicket *found = nil;
199
200  found = [store_ ticketForProductID:[t1 productID]];
201  STAssertEqualObjects(t1, found, nil);
202
203  found = [store_ ticketForProductID:[t2 productID]];
204  STAssertEqualObjects(t2, found, nil);
205
206  found = [store_ ticketForProductID:[t3 productID]];
207  STAssertEqualObjects(t3, found, nil);
208  
209  STAssertTrue([store_ deleteTicketForProductID:[t3 productID]], nil);
210  found = [store_ ticketForProductID:[t3 productID]];
211  STAssertNil(found, nil);
212  
213  // Test a PRODUCTID that is not in the ticket store
214  KSTicket *t4 = GenerateTicket();
215  found = [store_ ticketForProductID:[t4 productID]];
216  STAssertNil(found, nil);
217}
218
219- (void)testUniqueness {
220  KSExistenceChecker *xc = [KSExistenceChecker falseChecker];
221  NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
222  KSTicket *t1 = [KSTicket ticketWithProductID:@"{PRODUCTID}"
223                                  version:@"1.1"
224                         existenceChecker:xc
225                                serverURL:url];
226
227  STAssertNotNil(t1, nil);
228  STAssertEquals(0, [store_ ticketCount], nil);
229
230  // Re-adding the same ticket should not increase the ticket count because
231  // tickets should be unique.
232  for (int i = 1; i < 5; i++) {
233    STAssertTrue([store_ storeTicket:t1], nil);
234    STAssertEquals(1, [store_ ticketCount], nil);
235  }
236
237  KSTicket *t2 = nil;
238
239  t2 = [KSTicket ticketWithProductID:@"{productid}"
240                          version:@"1.1"
241               existenceChecker:xc
242                      serverURL:url];
243  STAssertTrue([store_ storeTicket:t2], nil);
244  STAssertEquals(1, [store_ ticketCount], nil);
245  STAssertEqualObjects(t2, [store_ ticketForProductID:[t2 productID]], nil);
246
247  t2 = [KSTicket ticketWithProductID:@"{pRoDUCtID}"
248                        version:@"1.1!"
249               existenceChecker:xc
250                      serverURL:url];
251  STAssertTrue([store_ storeTicket:t2], nil);
252  STAssertEquals(1, [store_ ticketCount], nil);
253  STAssertEqualObjects(t2, [store_ ticketForProductID:[t2 productID]], nil);
254
255  t2 = [KSTicket ticketWithProductID:@"{productID}"
256                        version:@"1.1!"
257               existenceChecker:xc
258                      serverURL:url];
259  STAssertTrue([store_ storeTicket:t2], nil);
260  STAssertEquals(1, [store_ ticketCount], nil);
261  STAssertEqualObjects(t2, [store_ ticketForProductID:[t2 productID]], nil);
262
263  // Now that the PRODUCTID is changing, the ticket count should actually increase
264  t2 = [KSTicket ticketWithProductID:@"{PRODUCTID}!"
265                        version:@"1.1!"
266               existenceChecker:xc
267                      serverURL:url];
268  STAssertTrue([store_ storeTicket:t2], nil);
269  STAssertEquals(2, [store_ ticketCount], nil);
270}
271
272- (void)testEncodeDecode {
273  STAssertTrue([store_ storeTicket:GenerateTicket()], nil);
274  STAssertTrue([store_ storeTicket:GenerateTicket()], nil);
275  STAssertTrue([store_ storeTicket:GenerateTicketWithXCPath(@"/tmp")], nil);
276  STAssertTrue([store_ storeTicket:GenerateTicketWithXCPath(@"/Foo/Bar")], nil);
277
278  // store2 is now a new ticket store which originated from disk.
279  KSTicketStore *store2 = [KSTicketStore ticketStoreWithPath:[store_ path]];
280  STAssertNotNil(store2, nil);
281
282  // compare store_ with store2
283  STAssertEquals([store_ ticketCount], [store2 ticketCount], nil);
284  NSArray *ticketArray = [store_ tickets];
285  NSEnumerator *tenum = [ticketArray objectEnumerator];
286  KSTicket *ticket1 = nil; // from store1
287  while ((ticket1 = [tenum nextObject])) {
288    KSTicket *ticket2 = [store2 ticketForProductID:[ticket1 productID]];  // from store2
289    STAssertNotNil(ticket2, nil);
290    STAssertEqualObjects(ticket1, ticket2, nil);
291  }
292}
293
294- (void)testQuerying {
295  NSURL *url1 = [NSURL URLWithString:@"http://blah"];
296  NSURL *url2 = [NSURL URLWithString:@"http://quux"];
297  
298  KSTicket *t1 = [KSTicket ticketWithProductID:@"foo"
299                                       version:@"1.0"
300                              existenceChecker:[KSExistenceChecker falseChecker]
301                                     serverURL:url1];
302  KSTicket *t2 = [KSTicket ticketWithProductID:@"bar"
303                                       version:@"2.0"
304                              existenceChecker:[KSExistenceChecker falseChecker]
305                                     serverURL:url1];
306  KSTicket *t3 = [KSTicket ticketWithProductID:@"baz"
307                                       version:@"3.0"
308                              existenceChecker:[KSExistenceChecker falseChecker]
309                                     serverURL:url2];
310  
311  STAssertNotNil(t1, nil);
312  STAssertNotNil(t2, nil);
313  STAssertNotNil(t3, nil);
314
315  STAssertNil([[store_ tickets] ticketsByURL], nil);
316  
317  STAssertTrue([store_ storeTicket:t1], nil);
318  STAssertTrue([store_ storeTicket:t2], nil);
319  STAssertTrue([store_ storeTicket:t3], nil);
320  
321  NSDictionary *truth = [NSDictionary dictionaryWithObjectsAndKeys:
322                         [NSArray arrayWithObjects:t1, t2, nil], url1,
323                         [NSArray arrayWithObject:t3], url2, nil];
324  
325  NSDictionary *byURL = [[store_ tickets] ticketsByURL];
326  STAssertNotNil(byURL, nil);
327  
328  STAssertEqualObjects(byURL, truth, nil);
329}
330
331- (void)testLocking {
332  KSTicket *t1 = GenerateTicket();
333  STAssertNotNil(t1, nil);
334  
335  KSTicket *t2 = GenerateTicket();
336  STAssertNotNil(t2, nil);
337  
338  STAssertTrue([store_ storeTicket:t1], nil);
339  STAssertTrue([store_ storeTicket:t2], nil);
340  
341  NSArray *tickets = [NSArray arrayWithObjects:t1, t2, nil];
342  STAssertNotNil(tickets, nil);
343  
344  // Package up some parameters that we want to give to each NSThread
345  NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
346                        tickets, @"tickets",
347                        store_, @"store", nil];
348  
349  static const int kNumThreads = 10;
350  
351  // Create 10 NSThreads, give each one the |info| dictionary, and tell each
352  // one to run their -threadMain:
353  for (int i = 0; i < kNumThreads; ++i) {
354    Threader *threader = [[[Threader alloc] init] autorelease];
355    [NSThread detachNewThreadSelector:@selector(threadMain:)
356                             toTarget:threader
357                           withObject:info];
358  }
359  
360  // Wait for threads to finish
361  while (1) {
362    NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:0.2];
363    [[NSRunLoop currentRunLoop] runUntilDate:quick]; 
364    @synchronized (kHugeLock) {
365      if (gStoppedThreads == kNumThreads) break;
366    }
367  }
368
369  // This ensures that none of the threads failed to access the ticket store
370  STAssertTrue(gFailedThreaders == 0, nil);
371}
372
373@end