5 // Created by Ben Williamson on 6/1/17.
19 @property (nonatomic, strong) Config *config;
21 // Names that we could choose from when adding a new item.
22 // When we add an item its name is moved to usedNames.
23 @property (nonatomic, strong) NSMutableArray<NSString *> *freeNames;
25 // Names and persistent references of the items we have written, which we can update or delete.
26 // When we delete an item its name is moved to freeNames.
27 @property (nonatomic, strong) NSMutableArray<NSMutableArray *> *usedNames;
32 @implementation Monkey
34 - (instancetype)initWithConfig:(Config *)config
36 if (self = [super init]) {
38 _freeNames = [NSMutableArray arrayWithCapacity:config.distinctNames];
39 _usedNames = [NSMutableArray arrayWithCapacity:config.distinctNames];
40 for (unsigned i = 0; i < config.distinctNames; i++) {
42 [_freeNames addObject:[NSString stringWithFormat:@"item-%@-%u", config.name, i]];
44 [_freeNames addObject:[NSString stringWithFormat:@"item-%u", i]];
51 - (void)advanceOneStep
53 static const int tryLimit = 1000;
54 for (int tries = 0; tries < tryLimit; tries++) {
55 if ([self tryAdvanceOneStep]) {
60 printf("Chose %d random actions but none of them made sense, giving up!\n", tryLimit);
61 printf("This is an internal failure of the manifeststresstest tool, not the system under test.\n");
62 NSLog(@"Chose %d random actions but none of them made sense, giving up!", tryLimit);
66 // return YES if it actually took a step, NO if the randomly chosen action was invalid
67 - (BOOL)tryAdvanceOneStep
69 unsigned totalWeight =
70 self.config.addItemWeight +
71 self.config.updateNameWeight +
72 self.config.updateDataWeight +
73 self.config.updateNameAndDataWeight +
74 self.config.deleteItemWeight;
76 if (totalWeight == 0) {
77 printf("Invalid manifeststresstest configuration:\n"
78 "At least one weight must be nonzero, or else we cannot choose any action.\n");
81 if (totalWeight > RAND_MAX) {
82 printf("Invalid manifeststresstest configuration:\n"
83 "The total of the weights must not exceed RAND_MAX == %d.\n", RAND_MAX);
86 unsigned r = random() % totalWeight;
88 if (r < self.config.addItemWeight) {
89 return [self addItem];
91 r -= self.config.addItemWeight;
93 if (r < self.config.updateNameWeight) {
94 return [self updateNameAndValue:NO];
96 r -= self.config.updateNameWeight;
98 if (r < self.config.updateDataWeight) {
99 return [self updateValue];
101 r -= self.config.updateDataWeight;
103 if (r < self.config.updateNameAndDataWeight) {
104 return [self updateNameAndValue:YES];
106 r -= self.config.updateNameAndDataWeight;
108 if (r < self.config.deleteItemWeight) {
109 return [self deleteItem];
111 NSAssert(false, @"Chosen random number was beyond totalWeight?!?");
115 - (NSString *)randomValue
117 if (0 == self.config.distinctValues) {
118 printf("Invalid manifeststresstest configuration:\n"
119 "Must allow a nonzero number of distinct values.\n");
122 unsigned n = random() % self.config.distinctValues;
123 return [NSString stringWithFormat:@"data-%u", n];
128 return [NSString stringWithFormat:@"[step:%d items:%d dup:%d gone:%d]",
131 self.addDuplicateCounter,
132 self.notFoundCounter];
135 - (void)unexpectedError:(OSStatus)status
137 NSLog(@"Unexpected error %d at step %d", (int)status, self.step);
143 if ([self.usedNames count] >= self.config.maxItems) {
146 NSUInteger freeCount = [self.freeNames count];
147 if (freeCount == 0) {
150 NSUInteger index = random() % freeCount;
151 NSString *name = self.freeNames[index];
152 NSString *value = [self randomValue];
154 NSLog(@"%@ addItem in %@ %@ = %@", [self prefix], self.config.view, name, value);
155 NSArray *itemRef = nil;
156 OSStatus status = [self.keychain addItem:name value:value view:self.config.view pRef:&itemRef];
159 [self.freeNames removeObjectAtIndex:index];
160 [self.usedNames addObject:[NSMutableArray arrayWithArray:@[name, itemRef]]];
162 unsigned usedCount = (unsigned)[self.usedNames count];
163 if (self.peakItems < usedCount) {
164 self.peakItems = usedCount;
167 case errSecDuplicateItem:
168 self.addDuplicateCounter++;
171 [self unexpectedError:status];
176 - (BOOL)updateNameAndValue:(BOOL)updateValue
178 NSUInteger freeCount = [self.freeNames count];
179 NSUInteger usedCount = [self.usedNames count];
180 if (usedCount == 0 || freeCount == 0) {
183 NSUInteger usedIndex = random() % usedCount;
184 NSUInteger freeIndex = random() % freeCount;
185 NSMutableArray *oldItem = self.usedNames[usedIndex];
186 NSArray *oldRef = oldItem[1];
187 NSString *oldName = oldItem[0];
188 NSString *newName = self.freeNames[freeIndex];
192 NSString *value = [self randomValue];
193 NSLog(@"%@ updateNameAndValue %@(%@) -> %@ = %@\n", [self prefix], oldName, oldRef, newName, value);
194 status = [self.keychain updateItem:oldRef newName:newName newValue:value];
196 NSLog(@"%@ updateName %@(%@) -> %@\n", [self prefix], oldName, oldRef, newName);
197 status = [self.keychain updateItem:oldRef newName:newName];
201 /* Update tracking arrays */
202 [self.freeNames removeObject:newName];
203 [self.freeNames addObject:oldName];
204 self.usedNames[usedIndex][0] = newName;
206 case errSecItemNotFound:
207 /* Update tracking arrays (someone else deleted this item) */
208 [self.usedNames removeObject:oldItem];
209 [self.freeNames addObject:oldName];
210 self.notFoundCounter++;
212 case errSecDuplicateItem:
213 self.addDuplicateCounter++;
214 // newName already exists, which means our attempted updateItem operation
215 // did not rename oldName to newName, so the item still has oldName.
216 // No action required.
219 [self unexpectedError:status];
226 NSUInteger usedCount = [self.usedNames count];
227 if (usedCount == 0) {
230 NSUInteger usedIndex = random() % usedCount;
231 NSArray *item = self.usedNames[usedIndex];
233 NSString *value = [self randomValue];
235 NSLog(@"%@ updateValue %@(%@) = %@", [self prefix], item[0], item[1], value);
236 OSStatus status = [self.keychain updateItem:item[1] newValue:value];
240 case errSecItemNotFound:
241 self.notFoundCounter++;
244 [self unexpectedError:status];
251 NSUInteger usedCount = [self.usedNames count];
252 if (usedCount == 0) {
255 NSUInteger usedIndex = random() % usedCount;
256 NSArray *item = self.usedNames[usedIndex];
258 NSLog(@"%@ deleteItem %@(%@)", [self prefix], item[0], item[1]);
259 OSStatus status = [self.keychain deleteItem:item[1]];
261 case errSecItemNotFound:
262 /* someone else deleted this item */
263 self.notFoundCounter++;
266 [self.usedNames removeObjectAtIndex:usedIndex];
267 [self.freeNames addObject:item[0]];
270 [self unexpectedError:status];
275 - (unsigned)itemCount
277 return (unsigned)[self.usedNames count];
282 for (NSArray *item in self.usedNames) {
283 NSLog(@"cleanup deleting %@(%@)", item[0], item[1]);
284 [self.keychain deleteItem:item[1]];