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