PageRenderTime 102ms CodeModel.GetById 1ms app.highlight 96ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://macfuse.googlecode.com/
Objective C | 570 lines | 408 code | 103 blank | 59 comment | 13 complexity | 5eecb1b252e21252c3347db7f855b286 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 "KSInstallAction.h"
 17
 18#import "KSActionPipe.h"
 19#import "KSActionProcessor.h"
 20#import "KSCommandRunner.h"
 21#import "KSExistenceChecker.h"
 22#import "KSTicket.h"
 23
 24
 25@interface KSInstallActionTest : SenTestCase {
 26 @private
 27  NSString *successDMGPath_;
 28  NSString *failureDMGPath_;
 29  NSString *tryAgainDMGPath_;
 30  NSString *envVarDMGPath_;
 31  NSString *stderrDMGPath_;
 32}
 33@end
 34
 35
 36@interface KSInstallAction (Friend)
 37- (NSString *)mountPoint;
 38- (NSMutableDictionary *)environment;
 39@end
 40
 41// Log Writer to make sure that strings we expect to be logged actually get
 42// logged, in the correct order.
 43@interface ExpectLogWriter : NSObject <GTMLogWriter> {
 44  NSArray *expectedStrings_;
 45  int currentIndex_;
 46}
 47
 48// Initialize a new log writer.  |xstrings| is an array of strings that
 49// are expected to be found in the stuff that's logged.
 50- (id)initWithExpectedStrings:(NSArray *)xstrings;
 51
 52// Verifies that the expected strings were found, in the correct order.
 53// That is, the first string was seen.  Then once seen, the next one is
 54// looked for.
 55// Returns YES if all of the expected strings were seen, NO otherwise.
 56- (BOOL)verify;
 57@end
 58
 59
 60// ----------------------------------------------------------------
 61// Implement KSDownloadActionDelegateMethods.  Keep track of progress.
 62@interface KSInstallProgressCounter : NSObject {
 63  NSMutableArray *progressArray_;
 64}
 65+ (id)counter;
 66- (NSArray *)progressArray;
 67@end
 68
 69@implementation KSInstallProgressCounter
 70
 71+ (id)counter {
 72  return [[[self alloc] init] autorelease];
 73}
 74
 75- (id)init {
 76  if ((self = [super init])) {
 77    progressArray_ = [[NSMutableArray array] retain];
 78  }
 79  return self;
 80}
 81
 82- (void)dealloc {
 83  [progressArray_ release];
 84  [super dealloc];
 85}
 86
 87- (NSArray *)progressArray {
 88  return progressArray_;
 89}
 90
 91- (void)installAction:(KSInstallAction *)action
 92             progress:(NSNumber *)progress {
 93  [progressArray_ addObject:progress];
 94}
 95
 96@end
 97
 98// -----------------------------------------------------
 99
100// !!!internal knowledge!!!
101@interface KSInstallAction (PrivateMethods)
102- (void)markProgress:(float)progress;
103@end
104
105
106@implementation KSInstallActionTest
107
108- (void)setUp {
109  NSBundle *mainBundle = [NSBundle bundleForClass:[self class]];
110
111  successDMGPath_ = [[mainBundle pathForResource:@"Test-SUCCESS"
112                                          ofType:@"dmg"] retain];
113  failureDMGPath_ = [[mainBundle pathForResource:@"Test-FAILURE"
114                                          ofType:@"dmg"] retain];
115  tryAgainDMGPath_ = [[mainBundle pathForResource:@"Test-TRYAGAIN"
116                                           ofType:@"dmg"] retain];
117  envVarDMGPath_ = [[mainBundle pathForResource:@"Test-ENVVAR"
118                                           ofType:@"dmg"] retain];
119  stderrDMGPath_ = [[mainBundle pathForResource:@"Test-STDERR"
120                                           ofType:@"dmg"] retain];
121  STAssertNotNil(successDMGPath_, nil);
122  STAssertNotNil(failureDMGPath_, nil);
123  STAssertNotNil(tryAgainDMGPath_, nil);
124  STAssertNotNil(envVarDMGPath_, nil);
125  STAssertNotNil(stderrDMGPath_, nil);
126
127  // Make sure we're always using the default script prefix
128  [KSInstallAction setInstallScriptPrefix:nil];
129}
130
131- (void)tearDown {
132  [successDMGPath_ release];
133  [failureDMGPath_ release];
134  [tryAgainDMGPath_ release];
135  [envVarDMGPath_ release];
136  [stderrDMGPath_ release];
137}
138
139- (void)testScriptPrefix {
140  // Verify that setting a nil prefix falls back to the default
141  [KSInstallAction setInstallScriptPrefix:nil];
142  NSString *prefix = [KSInstallAction installScriptPrefix];
143  STAssertNotNil(prefix, nil);
144  STAssertEqualObjects(prefix, @".engine", nil);
145
146  STAssertEqualObjects([KSInstallAction preinstallScriptName],
147                       @".engine_preinstall", nil);
148  STAssertEqualObjects([KSInstallAction installScriptName],
149                       @".engine_install", nil);
150  STAssertEqualObjects([KSInstallAction postinstallScriptName],
151                       @".engine_postinstall", nil);
152
153
154  // Now, verify that setting a different prefix works correctly
155  [KSInstallAction setInstallScriptPrefix:@".foo"];
156  prefix = [KSInstallAction installScriptPrefix];
157  STAssertNotNil(prefix, nil);
158  STAssertEqualObjects(prefix, @".foo", nil);
159
160  STAssertEqualObjects([KSInstallAction preinstallScriptName],
161                       @".foo_preinstall", nil);
162  STAssertEqualObjects([KSInstallAction installScriptName],
163                       @".foo_install", nil);
164  STAssertEqualObjects([KSInstallAction postinstallScriptName],
165                       @".foo_postinstall", nil);
166
167  // Reset the class back to the default script prefix
168  [KSInstallAction setInstallScriptPrefix:nil];
169}
170
171- (void)testCreation {
172  KSInstallAction *action = nil;
173
174  action = [[[KSInstallAction alloc] init] autorelease];
175  STAssertNil(action, nil);
176
177  action = [[[KSInstallAction alloc] initWithDMGPath:nil
178                                              runner:nil
179                                       userInitiated:NO
180                                          updateInfo:nil] autorelease];
181  STAssertNil(action, nil);
182
183  action = [KSInstallAction actionWithDMGPath:nil runner:nil userInitiated:NO];
184  STAssertNil(action, nil);
185
186  action = [KSInstallAction actionWithDMGPath:@"blah"
187                                       runner:@"foo"
188                                userInitiated:NO];
189  STAssertNotNil(action, nil);
190  STAssertTrue([[action description] length] > 1, nil);
191
192  STAssertEqualObjects([action dmgPath], @"blah", nil);
193  STAssertEqualObjects([action runner], @"foo", nil);
194  STAssertTrue([action userInitiated] == NO, nil);
195}
196
197- (void)testSuccess {
198  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
199  STAssertNotNil(runner, nil);
200
201  KSInstallAction *action = nil;
202  action = [KSInstallAction actionWithDMGPath:successDMGPath_
203                                       runner:runner
204                                userInitiated:NO];
205  STAssertNotNil(action, nil);
206
207  // Create an action processor and run the action
208  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
209  STAssertNotNil(ap, nil);
210
211  [ap enqueueAction:action];
212  [ap startProcessing];  // Runs the whole action because our action is sync.
213
214  STAssertFalse([action isRunning], nil);
215  STAssertEqualObjects([[action outPipe] contents],
216                       [NSNumber numberWithInt:0],
217                       nil);
218}
219
220- (void)testFailure {
221  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
222  STAssertNotNil(runner, nil);
223
224  KSInstallAction *action = nil;
225  action = [KSInstallAction actionWithDMGPath:failureDMGPath_
226                                       runner:runner
227                                userInitiated:NO];
228  STAssertNotNil(action, nil);
229
230  // Create an action processor and run the action
231  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
232  STAssertNotNil(ap, nil);
233
234  [ap enqueueAction:action];
235  [ap startProcessing];  // Runs the whole action because our action is sync.
236
237  STAssertFalse([action isRunning], nil);
238
239  // Make sure we get a script failure code (not zero) from the action.
240  int rc = [[[action outPipe] contents] intValue];
241  STAssertTrue(rc != 0, nil);
242}
243
244- (void)testTryAgain {
245  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
246  STAssertNotNil(runner, nil);
247
248  KSInstallAction *action = nil;
249  action = [KSInstallAction actionWithDMGPath:tryAgainDMGPath_
250                                       runner:runner
251                                userInitiated:NO];
252  STAssertNotNil(action, nil);
253
254  // Create an action processor and run the action
255  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
256  STAssertNotNil(ap, nil);
257
258  [ap enqueueAction:action];
259  [ap startProcessing];  // Runs the whole action because our action is sync.
260
261  STAssertFalse([action isRunning], nil);
262  STAssertEqualObjects([[action outPipe] contents],
263                       [NSNumber numberWithInt:KS_INSTALL_TRY_AGAIN_LATER],
264                       nil);
265}
266
267- (void)testBogusPath {
268  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
269  STAssertNotNil(runner, nil);
270
271  KSInstallAction *action = nil;
272
273  // This should certainly fail since /etc/pass is clearly not a path to a DMG
274  action = [KSInstallAction actionWithDMGPath:@"/etc/passwd"
275                                       runner:runner
276                                userInitiated:NO];
277  STAssertNotNil(action, nil);
278
279  // Create an action processor and run the action
280  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
281  STAssertNotNil(ap, nil);
282
283  [ap enqueueAction:action];
284  [ap startProcessing];  // Runs the whole action because our action is sync.
285
286  STAssertFalse([action isRunning], nil);
287  // Make sure we get a script failure code (not zero) from the action.
288  int rc = [[[action outPipe] contents] intValue];
289  STAssertTrue(rc != 0, nil);
290}
291
292- (void)testWithNonExistentPath {
293  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
294  STAssertNotNil(runner, nil);
295
296  KSInstallAction *action = nil;
297
298  // This should certainly fail since /etc/pass is clearly not a path to a DMG
299  action = [KSInstallAction actionWithDMGPath:@"/path/to/fake/file"
300                                       runner:runner
301                                userInitiated:NO];
302  STAssertNotNil(action, nil);
303
304  // Create an action processor and run the action
305  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
306  STAssertNotNil(ap, nil);
307
308  [ap enqueueAction:action];
309  [ap startProcessing];  // Runs the whole action because our action is sync.
310
311  STAssertFalse([action isRunning], nil);
312
313  // Make sure we get a script failure code (not zero) from the action.
314  int rc = [[[action outPipe] contents] intValue];
315  STAssertTrue(rc != 0, nil);
316}
317
318- (void)testEnvironmentVariables {
319  KSTicket *ticket =
320      [KSTicket ticketWithProductID:@"com.google.hasselhoff"
321                            version:@"3.14.15.9"
322                   existenceChecker:[KSPathExistenceChecker
323                                      checkerWithPath:@"/oombly/foombly"]
324                          serverURL:[NSURL URLWithString:@"http://google.com"]];
325  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
326
327  // Make sure the environemnt variables dictionary used by the install
328  // action is sane.
329  KSUpdateInfo *info;
330  info = [NSDictionary dictionaryWithObjectsAndKeys:
331            @"com.google.hasselhoff", kServerProductID,
332            [NSURL URLWithString:@"a://a"], kServerCodebaseURL,
333            [NSNumber numberWithInt:2], kServerCodeSize,
334            @"zzz", kServerCodeHash,
335            @"a://b", kServerMoreInfoURLString,
336            [NSNumber numberWithBool:YES], kServerPromptUser,
337            [NSNumber numberWithBool:YES], kServerRequireReboot,
338            @"/Hassel/Hoff", kServerLocalizationBundle,
339            @"1.3.2 (with pudding)", kServerDisplayVersion,
340            ticket, kTicket,
341            nil];
342
343  KSInstallAction *action = nil;
344  action = [KSInstallAction actionWithDMGPath:@""
345                                       runner:runner
346                                userInitiated:NO
347                                   updateInfo:info];
348  STAssertNotNil(action, nil);
349
350  NSDictionary *env = [action environment];
351  // Make sure everything is set.
352  STAssertEqualObjects([env objectForKey:@"KS_SUPPORTS_TAG"],
353                       @"YES", nil);
354  STAssertEqualObjects([env objectForKey:@"KS_TICKET_PRODUCT_ID"],
355                       @"com.google.hasselhoff", nil);
356  STAssertEqualObjects([env objectForKey:@"KS_TICKET_VERSION"],
357                       @"3.14.15.9", nil);
358  STAssertEqualObjects([env objectForKey:@"KS_TICKET_SERVER_URL"],
359                       @"http://google.com", nil);
360  STAssertEqualObjects([env objectForKey:@"KS_TICKET_XC_PATH"],
361                       @"/oombly/foombly", nil);
362  STAssertEqualObjects([env objectForKey:@"KS_INTERACTIVE"],
363                       @"YES", nil);
364  STAssertEqualObjects([env objectForKey:@"KS_USER_INITIATED"],
365                       @"NO", nil);
366  STAssertEqualObjects([env objectForKey:@"KS_kServerCodeHash"],
367                       @"zzz", nil);
368  STAssertEqualObjects([env objectForKey:@"KS_kServerCodeSize"],
369                       @"2", nil);
370  STAssertEqualObjects([env objectForKey:@"KS_kServerCodebaseURL"],
371                       @"a://a", nil);
372  STAssertEqualObjects([env objectForKey:@"KS_kServerDisplayVersion"],
373                       @"1.3.2 (with pudding)", nil);
374  STAssertEqualObjects([env objectForKey:@"KS_kServerLocalizationBundle"],
375                       @"/Hassel/Hoff", nil);
376  STAssertEqualObjects([env objectForKey:@"KS_kServerMoreInfoURLString"],
377                       @"a://b", nil);
378  STAssertEqualObjects([env objectForKey:@"KS_kServerProductID"],
379                       @"com.google.hasselhoff", nil);
380  STAssertEqualObjects([env objectForKey:@"KS_kServerPromptUser"],
381                       @"1", nil);
382  STAssertEqualObjects([env objectForKey:@"KS_kServerRequireReboot"],
383                       @"1", nil);
384  NSString *path = [env objectForKey:@"PATH"];
385  STAssertTrue([path length] != 0, nil);
386  STAssertTrue([path rangeOfString:@"/bin"].location != NSNotFound, nil);
387
388  // Now exercise the KS_INTERACTIVE values.  It should be YES if
389  // the server says to prompt, or the update is user initiated.
390  typedef struct TestSettings {
391    BOOL prompt_;
392    BOOL userInitiated_;
393    NSString *expected_;
394  } TestSettings;
395
396  TestSettings settings[] = {
397    { YES,  NO, @"YES" },
398    {  NO, YES, @"YES" },
399    { YES, YES, @"YES" },  // oh, yes
400    {  NO,  NO,  @"NO" },
401  };
402
403  TestSettings *scan = settings;
404  TestSettings *stop = scan + sizeof(settings) / sizeof(*settings);
405
406  while (scan < stop) {
407    info = [NSDictionary dictionaryWithObjectsAndKeys:
408                         [NSNumber numberWithBool:scan->prompt_],
409                         kServerPromptUser,
410                         nil];
411    action = [KSInstallAction actionWithDMGPath:@""
412                                         runner:runner
413                                  userInitiated:scan->userInitiated_
414                                   updateInfo:info];
415    env = [action environment];
416    STAssertEqualObjects([env objectForKey:@"KS_INTERACTIVE"],
417                         scan->expected_, nil);
418    scan++;
419  }
420}
421
422- (void)testRunningEnvironmentVariables {
423  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
424  STAssertNotNil(runner, nil);
425
426  // Construct a ticket and update info, and then check the values
427  // in the three scripts on the disk image.
428  // It's the scripts on the disk image which check the values and in
429  // the case of error will complain to standard out (which will then
430  // get printed by KSInstallAction) and return a non-zero value from
431  // the script.
432  KSTicket *ticket =
433      [KSTicket ticketWithProductID:@"com.google.hasselhoff"
434                            version:@"3.14.15.9"
435                   existenceChecker:[KSPathExistenceChecker
436                                      checkerWithPath:@"/oombly/foombly"]
437                          serverURL:[NSURL URLWithString:@"http://google.com"]];
438
439  KSUpdateInfo *info;
440  info = [NSDictionary dictionaryWithObjectsAndKeys:
441            @"com.google.hasselhoff", kServerProductID,
442            [NSURL URLWithString:@"a://a"], kServerCodebaseURL,
443            [NSNumber numberWithInt:2], kServerCodeSize,
444            @"zzz", kServerCodeHash,
445            @"a://b", kServerMoreInfoURLString,
446            [NSNumber numberWithBool:YES], kServerPromptUser,
447            [NSNumber numberWithBool:YES], kServerRequireReboot,
448            @"/Hassel/Hoff", kServerLocalizationBundle,
449            @"1.3.2 (with pudding)", kServerDisplayVersion,
450            ticket, kTicket,
451            nil];
452
453  KSInstallAction *action = nil;
454  action = [KSInstallAction actionWithDMGPath:envVarDMGPath_
455                                       runner:runner
456                                userInitiated:NO
457                                   updateInfo:info];
458  STAssertNotNil(action, nil);
459
460  // Create an action processor and run the action
461  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
462  STAssertNotNil(ap, nil);
463
464  [ap enqueueAction:action];
465  [ap startProcessing];  // Runs the whole action because our action is sync.
466
467  STAssertFalse([action isRunning], nil);
468  STAssertEqualObjects([[action outPipe] contents], [NSNumber numberWithInt:0],
469                       nil);
470}
471
472- (void)testMountPointGeneration {
473  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
474  STAssertNotNil(runner, nil);
475
476  NSDictionary *fakeUpdateInfo = [NSDictionary dictionaryWithObjectsAndKeys:
477                                  @"product", @"kServerProductID",
478                                  @"hash", @"kServerCodeHash", nil];
479
480  KSInstallAction *action = nil;
481  action = [KSInstallAction actionWithDMGPath:successDMGPath_
482                                       runner:runner
483                                userInitiated:NO
484                                   updateInfo:fakeUpdateInfo];
485  STAssertEqualObjects([action mountPoint], @"/Volumes/product-hash", nil);
486
487  // Now try the test w/ a huge product ID and it should be truncated to 50 cols
488  // This test also uses a real hash to test that we replace "/" w/ "_"
489  fakeUpdateInfo = [NSDictionary dictionaryWithObjectsAndKeys:
490                    @"12345678901234567890123456789012345678901234567890"  // 50
491                    @"ABCDEFG...", @"kServerProductID",
492                    @"l7HuEd/xMLeYU+ZmWvUsHyZTHpE=", @"kServerCodeHash", nil];
493  action = [KSInstallAction actionWithDMGPath:successDMGPath_
494                                       runner:runner
495                                userInitiated:NO
496                                   updateInfo:fakeUpdateInfo];
497  STAssertEqualObjects([action mountPoint], @"/Volumes/"
498                       @"12345678901234567890123456789012345678901234567890"  // 50
499                       @"-l7HuEd_xMLeYU+ZmWvUsHyZTHpE=", nil);  // realHash
500                                                                // s,/,_,g
501}
502
503- (void)testStderrLogging {
504  // These strings are printed to stderr from the install scripts.
505  NSArray *expectedStrings =
506    [NSArray arrayWithObjects:@"preinstall to stderr", @"install to stderr",
507             @"postinstall to stderr", nil];
508
509  // Install our intercepting log writer.
510  ExpectLogWriter *expectWriter =
511    [[ExpectLogWriter alloc] initWithExpectedStrings:expectedStrings];
512  id logger = [GTMLogger sharedLogger];
513  id<GTMLogWriter> originalWriter = [logger writer];
514  [logger setWriter:expectWriter];
515
516  // Run the "update"
517  id<KSCommandRunner> runner = [KSTaskCommandRunner commandRunner];
518  STAssertNotNil(runner, nil);
519
520  KSInstallAction *action = nil;
521  action = [KSInstallAction actionWithDMGPath:stderrDMGPath_
522                                       runner:runner
523                                userInitiated:NO];
524  STAssertNotNil(action, nil);
525
526  KSActionProcessor *ap = [[[KSActionProcessor alloc] init] autorelease];
527  STAssertNotNil(ap, nil);
528
529  [ap enqueueAction:action];
530  [ap startProcessing];  // Runs the whole action because our action is sync.
531
532  STAssertFalse([action isRunning], nil);
533
534  // Make sure we saw what we were expecting.
535  STAssertTrue([expectWriter verify], nil);
536
537  // Restore the original log writer.
538  [logger setWriter:originalWriter];
539}
540
541@end
542
543
544@implementation ExpectLogWriter
545
546- (id)initWithExpectedStrings:(NSArray *)xstrings {
547  if ((self = [super init])) {
548    expectedStrings_ = [xstrings retain];
549  }
550  return self;
551}
552
553- (void)dealloc {
554  [expectedStrings_ release];
555  [super dealloc];
556}
557
558- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
559  if (currentIndex_ < [expectedStrings_ count]) {
560    NSRange range =
561      [msg rangeOfString:[expectedStrings_ objectAtIndex:currentIndex_]];
562    if (range.location != NSNotFound) currentIndex_++;
563  }
564}
565
566- (BOOL)verify {
567  return currentIndex_ == [expectedStrings_ count];
568}
569
570@end