PageRenderTime 96ms CodeModel.GetById 15ms app.highlight 76ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://macfuse.googlecode.com/
Objective C | 428 lines | 304 code | 60 blank | 64 comment | 30 complexity | 0062d6dcaa7f06ea3508d2928db83779 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 <SenTestingKit/SenTestingKit.h>
 16#import "KSUpdateCheckAction.h"
 17#import "KSActionConstants.h"
 18#import "KSActionPipe.h"
 19#import "KSActionProcessor.h"
 20#import "KSExistenceChecker.h"
 21#import "KSMockFetcherFactory.h"
 22#import "KSPlistServer.h"
 23#import "KSTicket.h"
 24#import "KSUpdateInfo.h"
 25
 26
 27@interface KSUpdateCheckActionTest : SenTestCase {
 28 @private
 29  KSActionProcessor *processor_;
 30  NSURL *url_;
 31  KSServer *splitServer_;
 32  KSServer *singleServer_;
 33  NSArray *twoTickets_;
 34  NSArray *lottaTickets_;
 35  // variables set by an action's callback for which we are the delegate
 36  int delegatedStatus_;
 37  NSData *delegatedData_;
 38  NSError *delegatedError_;
 39}
 40@end
 41
 42
 43/* --------------------------------------------------------------- */
 44// Helper classes for mocking.
 45
 46// A mock KSServer which creates one request (and needs one fetcher) for each
 47// ticket.  The request data (HTTPBody) is a string-based number for each
 48// ticket; e.g. "0\0", "1\0".  The resultsForResponse creates a dictionary with
 49// the given data, reading the int from the request data.
 50@interface KSSplitMockServer : KSServer
 51@end
 52
 53// A mock KSServer which creates one request (and only one fetcher) for ALL
 54// tickets.  The request data (HTTPBody) is a string-based number which is the
 55// count of tickets; e.g. "8\0".  The resultsForResponse creates a count of
 56// result dictionaries with a key of "NumberKey" and a value of the value from
 57// the response.
 58@interface KSSingleMockServer : KSServer
 59@end
 60
 61
 62/* --------------------------------------------------------------- */
 63@implementation KSSplitMockServer
 64
 65// One request per ticket to create many fetchers
 66- (NSArray *)requestsForTickets:(NSArray *)tickets {
 67  NSMutableArray *array = [NSMutableArray arrayWithCapacity:[tickets count]];
 68  for (int i = 0; i < [tickets count]; i++) {
 69    NSString *str = [NSString stringWithFormat:@"%d", i];
 70    NSData *data = [NSData dataWithBytes:[str UTF8String] length:[str length]];
 71    NSURL *url = [NSURL URLWithString:@"file://foo"];
 72    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
 73    [request setHTTPMethod:@"POST"];
 74    [request setHTTPBody:data];
 75    [array addObject:request];
 76  }
 77  return array;
 78}
 79
 80// One action, ever.
 81- (NSArray *)updateInfosForResponse:(NSURLResponse *)response
 82                               data:(NSData *)data
 83                      outOfBandData:(NSDictionary **)oob {
 84  if (oob) *oob = nil;
 85  int x = 0;
 86  sscanf([data bytes], "%d", &x);
 87  NSDictionary *result = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:x]
 88                                                     forKey:@"NumberKey"];
 89  NSArray *array = [NSArray arrayWithObject:result];
 90  return array;
 91}
 92
 93@end
 94
 95
 96/* --------------------------------------------------------------- */
 97@implementation KSSingleMockServer
 98
 99// Only one request for all tickets
100- (NSArray *)requestsForTickets:(NSArray *)tickets {
101  NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
102  NSString *countString = [NSString stringWithFormat:@"%d", [tickets count]];
103  NSData *data = [countString dataUsingEncoding:NSUTF8StringEncoding];
104  NSURL *url = [NSURL URLWithString:@"file://foo"];
105  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
106  [request setHTTPMethod:@"POST"];
107  [request setHTTPBody:data];
108  [array addObject:request];
109  return array;
110}
111
112// N KSActions, where N is the number embedded in data.
113- (NSArray *)updateInfosForResponse:(NSURLResponse *)response
114                               data:(NSData *)data
115                      outOfBandData:(NSDictionary **)oob {
116  if (oob) *oob = nil;
117  int x = 0;
118  sscanf([data bytes], "%d", &x);
119  NSMutableArray *array = [NSMutableArray array];
120  for (int count = 0; count < x; count++) {
121    NSDictionary *result = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:count]
122                                                       forKey:@"NumberKey"];
123    [array addObject:result];
124  }
125  return array;
126}
127
128@end
129
130
131/* --------------------------------------------------------------- */
132@implementation KSUpdateCheckActionTest
133
134- (NSArray *)createTickets:(int)count forServer:(KSServer *)server {
135  NSMutableArray *tickets = [[[NSMutableArray alloc] init] autorelease];
136  for (int x = 0; x < count; x++) {
137    NSString *productid = [NSString stringWithFormat:@"{guid-%d}", x];
138    KSExistenceChecker *xc = [KSExistenceChecker falseChecker];
139    [tickets addObject:[KSTicket ticketWithProductID:productid
140                                 version:@"1.0"
141                                 existenceChecker:xc
142                                 serverURL:url_]];
143  }
144  return tickets;
145}
146
147- (void)setUp {
148  processor_ = [[KSActionProcessor alloc] init];
149
150  url_ = [NSURL URLWithString:@"file://foo"];
151  splitServer_ = [[KSSplitMockServer alloc] initWithURL:url_];
152  singleServer_ = [[KSSingleMockServer alloc] initWithURL:url_];
153  twoTickets_ = [[self createTickets:2 forServer:splitServer_] retain];
154  lottaTickets_ = [[self createTickets:8 forServer:singleServer_] retain];
155}
156
157- (void)tearDown {
158  [processor_ release];
159  [splitServer_ release];
160  [singleServer_ release];
161  [twoTickets_ release];
162  [lottaTickets_ release];
163  [delegatedData_ release];
164  [delegatedError_ release];
165}
166
167- (void)loopUntilEmpty {
168  STAssertNotNil(processor_, nil);
169  int count = 10;
170  while (([processor_ isProcessing] == YES) && (count > 0)) {
171    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
172    count--;
173  }
174  STAssertTrue(count > 0, nil);  // make sure we didn't time out
175}
176
177- (void)confirmNoErrors {
178  STAssertTrue(delegatedStatus_ == 0, nil);
179  STAssertTrue(delegatedData_ == 0, nil);
180  STAssertTrue(delegatedError_ == 0, nil);
181}
182
183- (void)runAction:(KSAction *)action {
184  STAssertTrue([action isRunning] == NO, nil);
185  [processor_ startProcessing];
186  STAssertTrue([action isRunning] == YES, nil);
187  [self loopUntilEmpty];
188  STAssertTrue([action isRunning] == NO, nil);
189}
190
191- (void)testBasics {
192  KSUpdateCheckAction *action = [KSUpdateCheckAction checkerWithServer:splitServer_
193                                                     tickets:twoTickets_];
194  STAssertNotNil(action, nil);
195  STAssertTrue([[action description] length] > 1, nil);
196  STAssertTrue([action outstandingRequests] == 0, nil);
197  STAssertTrue([action isRunning] == NO, nil);
198
199  action = [KSUpdateCheckAction checkerWithServer:splitServer_
200                                          tickets:nil];
201  STAssertNil(action, nil);
202
203  action = [KSUpdateCheckAction checkerWithServer:splitServer_
204                                          tickets:[NSArray array]];
205  STAssertNil(action, nil);
206
207  action = [[[KSUpdateCheckAction alloc] init] autorelease];
208  STAssertNil(action, nil);
209}
210
211// Uses a real GDataHTTPFetcher and a KSPlistServer to fetch a canned server
212// response, and ensures that the returned "result" dictionary from the
213// KSUpdateCheckAction matches what we're expecting.
214- (void)testServerResultDictionary {
215  NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
216  NSString *serverPlist = [mainBundle pathForResource:@"ServerSuccess"
217                                               ofType:@"plist"];
218  STAssertNotNil(serverPlist, nil);
219  NSURL *serverURL = [NSURL fileURLWithPath:serverPlist];
220
221  // Creates a KSServer for our "Plist server" that is really a text file
222  // containing Plist-style XML
223  KSServer *server = [KSPlistServer serverWithURL:serverURL];
224  STAssertNotNil(server, nil);
225
226  // Create a ticket that matches the one product in our plist server file
227  KSTicket *ticket = [KSTicket ticketWithProductID:@"COM.GOOGLE.UPDATEENGINE.KSUPDATEENGINE_TEST"
228                                           version:@"0"
229                                  existenceChecker:[KSPathExistenceChecker checkerWithPath:@"/"]
230                                         serverURL:serverURL];
231  STAssertNotNil(ticket, nil);
232
233  KSUpdateCheckAction *action =
234    [KSUpdateCheckAction checkerWithServer:server
235                                   tickets:[NSArray arrayWithObject:ticket]];
236  STAssertNotNil(action, nil);
237  STAssertTrue([action outstandingRequests] == 0, nil);
238  STAssertTrue([action isRunning] == NO, nil);
239
240  [processor_ enqueueAction:action];
241  [self runAction:action];
242  [self confirmNoErrors];
243
244  NSDictionary *dict =
245    [NSDictionary dictionaryWithObjectsAndKeys:
246     @"TyWAiay1UCIV0gqGbfjF4R009mg=", kServerCodeHash,
247     @"TyWAiay1UCIV0gqGbfjF4R009mg=", @"Hash",
248     [NSNumber numberWithInt:69042], kServerCodeSize,
249     @"69042", @"Size",
250     [NSURL URLWithString:@"file:///tmp/Test-SUCCESS.dmg"], kServerCodebaseURL,
251     @"file:///tmp/Test-SUCCESS.dmg", @"Codebase",
252     @"COM.GOOGLE.UPDATEENGINE.KSUPDATEENGINE_TEST", kServerProductID,
253     @"COM.GOOGLE.UPDATEENGINE.KSUPDATEENGINE_TEST", @"ProductID",
254     @"TRUEPREDICATE", @"Predicate",
255     nil];
256
257  // This is what the KSOutOfBandDataAction emits.
258  NSArray *updateInfos = [NSArray arrayWithObject:dict];
259  NSDictionary *expect =
260    [NSDictionary dictionaryWithObjectsAndKeys:
261                  serverURL, KSActionServerURLKey,
262                  updateInfos, KSActionUpdateInfosKey,
263                  nil];
264  STAssertEqualObjects([[action outPipe] contents], expect, nil);
265}
266
267- (id)resultsFromMockTestWithServer:(KSServer *)server tickets:(NSArray *)tickets {
268  NSString *bytes = [NSString stringWithFormat:@"%d", [tickets count]];
269  NSData *data = [NSData dataWithBytes:[bytes UTF8String] length:[bytes length]];
270  KSFetcherFactory *factory = [KSMockFetcherFactory alwaysFinishWithData:data];
271  KSUpdateCheckAction *action = [[[KSUpdateCheckAction alloc]
272                                   initWithFetcherFactory:factory
273                                   server:server
274                                   tickets:tickets] autorelease];
275  STAssertNotNil(data, nil);
276  STAssertNotNil(factory, nil);
277  STAssertNotNil(action, nil);
278  [processor_ enqueueAction:action];
279
280  [self runAction:action];
281  [self confirmNoErrors];
282
283  return [[action outPipe] contents];
284}
285
286// Lots of mocks in this one which hide a bit of complexity.  The fetchers from
287// the fetcher factory used here always claims to finish correctly, returning
288// the data (as "results") passed into the factory.  The splitServer_ is a
289// KSSplitServer, which has a seperate fetcher for each ticket ("split").  A
290// KSSplitServer creates a string-encoded number (as NSData) for each ticket as
291// requests (ignored by the fetcher).  Since the server uses the mock fetcher,
292// the net result here is that we get a dictionary with "NumberKey" => X, where
293// X is the number embedded in the data passed in to the factory (e.g. "2" for
294// two tickets).
295//
296// In short, 2 tickets --> 2 fetchers --> 2 (exactly the same) results
297- (void)testSplitServer {
298  NSDictionary *results = [self resultsFromMockTestWithServer:splitServer_
299                                                      tickets:twoTickets_];
300  NSArray *updateInfos = [results objectForKey:KSActionUpdateInfosKey];
301
302  STAssertTrue([updateInfos count] == 1, nil);
303
304  NSDictionary *expect =
305    [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:2]
306                                forKey:@"NumberKey"];
307
308  STAssertEqualObjects([updateInfos objectAtIndex:0], expect, nil);
309}
310
311// Similar to testSplitServer above, but we only use one fetcher for several
312// tickets.  (The fetcher was specified on server creation.) Unlike the
313// KSSplitServer, the KSSingleServer creates a result dictionary with a value of
314// X, where X increments from 0 to to the value in the data passed into the
315// factory (which is the number of tickets).
316//
317// In short, 8 tickets --> 1 fetcher --> 8 (unique) actions.
318- (void)testSingleFetcher {
319  NSDictionary *results = [self resultsFromMockTestWithServer:singleServer_
320                                                      tickets:lottaTickets_];
321  NSArray *updateInfos = [results objectForKey:KSActionUpdateInfosKey];
322
323  STAssertTrue([updateInfos count] == [lottaTickets_ count], nil);
324  for (int i = 0; i < [updateInfos count]; i++) {
325    NSDictionary *expect =
326      [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:i]
327                                  forKey:@"NumberKey"];
328    STAssertTrue([[updateInfos objectAtIndex:i] isEqual:expect], nil);
329  }
330}
331
332// Test termination of a KSUpdateCheckAction.
333- (void)testTermination {
334  NSData *data = [NSData dataWithBytes:"hi" length:2];
335  KSFetcherFactory *factory = [KSMockFetcherFactory alwaysFinishWithData:data];
336  KSUpdateCheckAction *action = [[[KSUpdateCheckAction alloc]
337                                   initWithFetcherFactory:factory
338                                   server:splitServer_
339                                   tickets:twoTickets_] autorelease];
340  STAssertNotNil(data, nil);
341  STAssertNotNil(factory, nil);
342  STAssertNotNil(action, nil);
343  [processor_ enqueueAction:action];
344
345  // can't use runAction routine; we don't want to run the runloop.
346  STAssertTrue([processor_ actionsCompleted] == 0, nil);
347  STAssertTrue([action isRunning] == NO, nil);
348  STAssertTrue([action outstandingRequests] == 0, nil);
349  [processor_ startProcessing];
350  STAssertTrue([action isRunning] == YES, nil);
351  STAssertTrue([action outstandingRequests] > 0, nil);
352
353  // intentionally no running of the run loop here so this action
354  // can't finish.  (If it finished we couldn't cancel it!)
355
356  [self confirmNoErrors];
357  [processor_ stopProcessing];  // should call terminateAction
358  STAssertTrue([action isRunning] == NO, nil);
359  STAssertTrue([action outstandingRequests] == 0, nil);
360  STAssertTrue([processor_ actionsCompleted] == 0, nil);
361}
362
363// Make sure bad input (tickets point to different servers) gets caught.
364- (void)testBadTickets {
365  NSMutableArray *mixedTickets = [NSMutableArray array];
366  [mixedTickets addObjectsFromArray:twoTickets_];
367
368  NSString *productid = [NSString stringWithFormat:@"{guid-%d}", 102];
369  KSExistenceChecker *xc = [KSExistenceChecker falseChecker];
370  NSURL *altURL = [NSURL URLWithString:@"file://foo/alt/bar"];
371  [mixedTickets addObject:[KSTicket ticketWithProductID:productid
372                               version:@"1.0"
373                               existenceChecker:xc
374                               serverURL:altURL]];
375
376  NSData *data = [NSData dataWithBytes:"hi" length:2];
377  KSFetcherFactory *factory = [KSMockFetcherFactory alwaysFinishWithData:data];
378  STAssertNotNil(data, nil);
379  STAssertNotNil(factory, nil);
380
381  KSUpdateCheckAction *action1 = [[[KSUpdateCheckAction alloc]
382                                    initWithFetcherFactory:factory
383                                                    server:splitServer_
384                                                   tickets:mixedTickets]
385                                   autorelease];
386  STAssertNil(action1, nil);
387  KSUpdateCheckAction *action2 = [[[KSUpdateCheckAction alloc]
388                                    initWithFetcherFactory:factory
389                                                    server:singleServer_
390                                                   tickets:mixedTickets]
391                                   autorelease];
392  STAssertNil(action2, nil);
393}
394
395// Again, a funny fetcher factory which is supposed to fail
396- (void)testFailedWithError {
397  NSError *err = [NSError errorWithDomain:@"domain" code:55789 userInfo:nil];
398  KSFetcherFactory *factory = [KSMockFetcherFactory alwaysFailWithError:err];
399  KSUpdateCheckAction *action = [[[KSUpdateCheckAction alloc]
400                                   initWithFetcherFactory:factory
401                                   server:splitServer_
402                                   tickets:twoTickets_] autorelease];
403  STAssertNotNil(err, nil);
404  STAssertNotNil(factory, nil);
405  STAssertNotNil(action, nil);
406  [action setDelegate:self];
407  [processor_ enqueueAction:action];
408
409  [self confirmNoErrors];
410  [self runAction:action];
411
412  // make sure the errors are exactly what we expected
413  STAssertTrue(delegatedStatus_ == 0, nil);
414  STAssertTrue(delegatedData_ == 0, nil);
415  STAssertTrue([delegatedError_ isEqual:err], nil);
416}
417
418- (void)fetcher:(GDataHTTPFetcher *)fetcher failedWithStatus:(int)status
419           data:(NSData *)data {
420  delegatedStatus_ = status;
421  delegatedData_ = [data retain];
422}
423
424- (void)fetcher:(GDataHTTPFetcher *)fetcher failedWithError:(NSError *)error {
425  delegatedError_ = [error retain];
426}
427
428@end