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