/core/externals/update-engine/Core/KSInstallActionTest.m
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