PageRenderTime 158ms CodeModel.GetById 12ms app.highlight 139ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/update-engine/Common/KSActionProcessorTest.m

http://macfuse.googlecode.com/
Objective C | 697 lines | 474 code | 140 blank | 83 comment | 29 complexity | acb6728fd436318853ea1635172e5fed 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 "KSActionProcessor.h"
 17#import "KSAction.h"
 18#import "GTMLogger.h"
 19
 20
 21@interface KSActionProcessorTest : SenTestCase
 22@end
 23
 24
 25// Unit test notes
 26// ---------------
 27// We're testing the KSActionProcessor here. We test it by creating two
 28// KSAction subclasses: one that performs an asynchronous action and one that
 29// performs an action synchronously. Each of these classes will create more
 30// actions that will be added to the KSActionProcessor's queue while the action
 31// is running.
 32//
 33// The unit tests will make sure that all the actions get executed in the right
 34// order, and that all the delegate callbacks happen correctly.
 35
 36
 37// Simple do-nothing action, for testing.
 38@interface NOPAction : KSAction
 39@end
 40
 41@implementation NOPAction
 42
 43- (void)performAction {
 44  [[self processor] finishedProcessing:self successfully:YES]; 
 45}
 46
 47@end  // NOPAction
 48
 49
 50// A sample KSAction subclass that runs an asynchronous action. This action will
 51// also add other TestAsyncAction instances to the action queue (up to 10).
 52@interface TestAsyncAction : KSAction {
 53  int num_;
 54}
 55@end
 56
 57@implementation TestAsyncAction
 58
 59- (id)initWithNum:(int)num {
 60  if ((self = [super init])) {
 61    num_ = num;
 62  }
 63  return self;
 64}
 65
 66- (void)performAction {
 67  GTMLoggerInfo(@"num = %d, processor = %@", num_, [self processor]);
 68  [NSTimer scheduledTimerWithTimeInterval:0.1
 69                                   target:self
 70                                 selector:@selector(fire:)
 71                                 userInfo:nil
 72                                  repeats:NO];
 73}
 74
 75- (void)fire:(NSTimer *)timer {
 76  GTMLoggerInfo(@"num = %d", num_);
 77  if (num_ < 10) {
 78    TestAsyncAction *newAction = [[[TestAsyncAction alloc] initWithNum:++num_] autorelease];
 79    [[self processor] enqueueAction:newAction];
 80  }
 81  [[self processor] finishedProcessing:self successfully:YES];
 82}
 83
 84@end  // TestAsyncAction
 85
 86
 87// Test action for sending progress callbacks
 88@interface ProgressAction : KSAction {
 89  int totalCalls_;
 90  int callsMade_;
 91  NSTimeInterval interval_;
 92  float progress_;
 93  NSTimer *timer_;
 94}
 95- (id)initWithCalls:(int)interval;
 96@end
 97
 98@implementation ProgressAction
 99
100- (id)initWithCalls:(int)calls {
101  if ((self = [super init])) {
102    totalCalls_ = calls;
103    interval_ = (float)1/calls;
104  }
105  return self;
106}
107
108- (void)performAction {
109  GTMLoggerInfo(@"interval_ = %f, processor = %@", interval_, [self processor]);
110  timer_ = [[NSTimer scheduledTimerWithTimeInterval:interval_
111                                             target:self
112                                           selector:@selector(fire:)
113                                           userInfo:nil
114                                            repeats:YES] retain];
115}
116
117- (void)fire:(NSTimer *)timer {
118  ++callsMade_;
119  progress_ += interval_;
120  GTMLoggerInfo(@"TEST: progress_ = %f, interval = %f", progress_, interval_);
121  [[self processor] runningAction:self progress:progress_];
122  
123  if (callsMade_ >= totalCalls_) {
124    [timer_ invalidate];
125    [timer_ release];
126    timer_ = nil;
127    [[self processor] finishedProcessing:self successfully:YES];
128  }
129}
130
131@end  // ProgressAction
132
133
134// A sample KSAction that runs an action synchronously. This action will also
135// add other KSActions to the action queue (up to 10). The type of action added
136// is configurable through the toggle parameter. If |toggle| is YES, then the
137// actions added will alternate between TestAction and TestAsyncAction.
138@interface TestAction : KSAction {
139  int num_;
140  BOOL toggle_;
141}
142@end
143
144@implementation TestAction
145
146- (id)initWithNum:(int)num toggle:(BOOL)toggle {
147  if ((self = [super init])) {
148    num_ = num;
149    toggle_ = toggle;
150  }
151  return self;
152}
153
154- (void)performAction {
155  GTMLoggerInfo(@"num = %d, processor = %@", num_, [self processor]);
156  if (num_ < 10) {
157    TestAction *newAction = nil;
158
159    if (toggle_ && (num_ % 2) == 0)
160      newAction = [[[TestAsyncAction alloc] initWithNum:++num_] autorelease];
161    else
162      newAction = [[[TestAction alloc] initWithNum:++num_ toggle:NO] autorelease];
163
164    [[self processor] enqueueAction:newAction];
165  }
166  [[self processor] finishedProcessing:self successfully:YES];
167}
168
169@end  // TestAction
170
171
172// This is a KSActionProcessor delegate. It simply records the number of times
173// a delegate method is called, and the order in which they're called. This
174// is used to verify that delegate methods happend correctly.
175@interface MethodCounter : NSObject {
176  NSMutableDictionary *methodCalls_;
177  NSMutableArray *callOrder_;
178}
179@end
180
181@implementation MethodCounter
182
183+ (id)counter {
184  return [[[self alloc] init] autorelease];
185}
186
187- (id)init {
188  if ((self = [super init])) {
189    methodCalls_ = [[NSMutableDictionary alloc] init];
190    callOrder_ = [[NSMutableArray alloc] init];
191  }
192  return self;
193}
194
195- (void)dealloc {
196  [methodCalls_ release];
197  [callOrder_ release];
198  [super dealloc];
199}
200
201- (NSDictionary *)methodCalls {
202  return methodCalls_;
203}
204
205- (NSArray *)callOrder {
206  return callOrder_;
207}
208
209// Sent when processing is started.
210- (void)processingStarted:(KSActionProcessor *)processor {
211  NSString *key = NSStringFromSelector(_cmd);
212  NSNumber *count = [methodCalls_ objectForKey:key];
213  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
214                   forKey:key];
215  [callOrder_ addObject:key];
216  GTMLoggerInfo(@"%@", processor);
217}
218
219// Sent when the action queue is empty.
220- (void)processingDone:(KSActionProcessor *)processor {
221  NSString *key = NSStringFromSelector(_cmd);
222  NSNumber *count = [methodCalls_ objectForKey:key];
223  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
224                   forKey:key];
225  [callOrder_ addObject:key];
226  GTMLoggerInfo(@"%@", processor);
227}
228
229// Sent when processing is stopped by via the -stopProcessing message. This
230// does not imply that the action queue is empty.
231- (void)processingStopped:(KSActionProcessor *)processor {
232  NSString *key = NSStringFromSelector(_cmd);
233  NSNumber *count = [methodCalls_ objectForKey:key];
234  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
235                   forKey:key];
236  [callOrder_ addObject:key];
237  GTMLoggerInfo(@"%@", processor);
238}
239
240// Called after an action has been enqueued by the KSActionProcessor.
241- (void)processor:(KSActionProcessor *)processor
242   enqueuedAction:(KSAction *)action {
243  NSString *key = NSStringFromSelector(_cmd);
244  NSNumber *count = [methodCalls_ objectForKey:key];
245  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
246                   forKey:key];
247  [callOrder_ addObject:key];
248  GTMLoggerInfo(@"%@ %@", processor, action);
249}
250
251// Called right before the KSActionProcessor starts the KSAction by sending it
252// the -performAction: message.
253- (void)processor:(KSActionProcessor *)processor
254   startingAction:(KSAction *)action {
255  NSString *key = NSStringFromSelector(_cmd);
256  NSNumber *count = [methodCalls_ objectForKey:key];
257  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
258                   forKey:key];
259  [callOrder_ addObject:key];
260  GTMLoggerInfo(@"%@ %@", processor, action);
261}
262
263// Called once the KSAction informs the KSActionProcessor that the action has
264// finished.
265- (void)processor:(KSActionProcessor *)processor
266   finishedAction:(KSAction *)action
267     successfully:(BOOL)wasOK {
268  NSString *key = NSStringFromSelector(_cmd);
269  NSNumber *count = [methodCalls_ objectForKey:key];
270  [methodCalls_ setObject:[NSNumber numberWithInt:(1 + [count intValue])]
271                   forKey:key];
272  [callOrder_ addObject:key];
273  GTMLoggerInfo(@"%@ %@", processor, action);
274}
275
276@end  // MethodCounter
277
278
279@interface ProgressRecorder : NSObject {
280  NSMutableArray *calls_;
281}
282- (NSArray *)calls;
283@end
284
285@implementation ProgressRecorder
286
287- (id)init {
288  if ((self = [super init])) {
289    calls_ = [[NSMutableArray alloc] init];
290  }
291  return self;
292}
293
294- (void)dealloc {
295  [calls_ release];
296  [super dealloc];
297}
298
299- (NSArray *)calls {
300  return calls_;
301}
302
303- (void)processor:(KSActionProcessor *)processor
304    runningAction:(KSAction *)action
305         progress:(float)progress {
306  NSString *call = [NSString stringWithFormat:@"action:%.02f,processor:%.02f",
307                    progress, [processor progress]];
308  [calls_ addObject:call];
309}
310
311@end
312
313
314@interface KSActionProcessor (InternalPrivateMethods)
315- (void)updateProgressWithFraction:(float)fraction;
316@end
317
318//
319// Begin unit test code
320//
321
322@implementation KSActionProcessorTest
323
324- (void)testBasic {
325  KSActionProcessor *ap = nil;
326
327  ap = [[[KSActionProcessor alloc] initWithDelegate:nil] autorelease];
328  STAssertNotNil(ap, nil);
329
330  ap = [[[KSActionProcessor alloc] initWithDelegate:@"blah"] autorelease];
331  STAssertNotNil(ap, nil);
332
333  ap = [[[KSActionProcessor alloc] init] autorelease];
334  STAssertNotNil(ap, nil);
335
336  STAssertTrue([[ap actions] count] == 0, nil);
337  [ap enqueueAction:nil];
338  STAssertTrue([[ap actions] count] == 0, nil);
339
340  [ap enqueueAction:[[[KSAction alloc] init] autorelease]];
341  [ap enqueueAction:[[[KSAction alloc] init] autorelease]];
342  [ap enqueueAction:[[[KSAction alloc] init] autorelease]];
343
344  STAssertTrue([[ap actions] count] == 3, nil);
345  STAssertTrue([ap actionsCompleted] == 0, nil);
346
347  STAssertNil([ap delegate], nil);
348  [ap setDelegate:@"blah"];
349  STAssertNotNil([ap delegate], nil);
350
351  STAssertTrue([[ap description] length] > 1, nil);
352}
353
354- (void)verifyMethodCounter:(MethodCounter *)counter {
355  //
356  // Make sure each delegate method was called the correct number of times
357  //
358
359  NSDictionary *calls = [counter methodCalls];
360  STAssertNotNil(calls, nil);
361  // There are 7 delegate methods total, but MethodCounter only counts 6 of
362  // them. The one that's not counted is processor:runningAction:progress:,
363  // because that method can be called a number of times 
364  STAssertTrue([calls count] == 6, nil);
365
366  STAssertEqualObjects([calls objectForKey:@"processingStarted:"],
367                       [NSNumber numberWithInt:1], nil);
368
369  STAssertEqualObjects([calls objectForKey:@"processingDone:"],
370                       [NSNumber numberWithInt:1], nil);
371
372  STAssertEqualObjects([calls objectForKey:@"processingStopped:"],
373                       [NSNumber numberWithInt:1], nil);
374
375  STAssertEqualObjects([calls objectForKey:@"processor:enqueuedAction:"],
376                       [NSNumber numberWithInt:10], nil);
377
378  STAssertEqualObjects([calls objectForKey:@"processor:startingAction:"],
379                       [NSNumber numberWithInt:10], nil);
380
381  STAssertEqualObjects([calls objectForKey:@"processor:finishedAction:successfully:"],
382                       [NSNumber numberWithInt:10], nil);
383
384  //
385  // Make sure the methods were called in the correct order.
386  //
387
388  NSArray *order = [counter callOrder];
389  STAssertNotNil(order, nil);
390  STAssertTrue([order count] == 33, nil);  // 33 calls total
391
392  NSArray *correctOrder = [NSArray arrayWithObjects:
393                           @"processor:enqueuedAction:",
394                           @"processingStarted:",
395                           @"processor:startingAction:",
396                           @"processor:enqueuedAction:",
397                           @"processor:finishedAction:successfully:",
398                           @"processor:startingAction:",
399                           @"processor:enqueuedAction:",
400                           @"processor:finishedAction:successfully:",
401                           @"processor:startingAction:",
402                           @"processor:enqueuedAction:",
403                           @"processor:finishedAction:successfully:",
404                           @"processor:startingAction:",
405                           @"processor:enqueuedAction:",
406                           @"processor:finishedAction:successfully:",
407                           @"processor:startingAction:",
408                           @"processor:enqueuedAction:",
409                           @"processor:finishedAction:successfully:",
410                           @"processor:startingAction:",
411                           @"processor:enqueuedAction:",
412                           @"processor:finishedAction:successfully:",
413                           @"processor:startingAction:",
414                           @"processor:enqueuedAction:",
415                           @"processor:finishedAction:successfully:",
416                           @"processor:startingAction:",
417                           @"processor:enqueuedAction:",
418                           @"processor:finishedAction:successfully:",
419                           @"processor:startingAction:",
420                           @"processor:enqueuedAction:",
421                           @"processor:finishedAction:successfully:",
422                           @"processor:startingAction:",
423                           @"processor:finishedAction:successfully:",
424                           @"processingDone:",
425                           @"processingStopped:",
426                           nil];
427
428  STAssertTrue([order count] == [correctOrder count], nil);
429  STAssertEqualObjects(order, correctOrder, nil);
430
431  // Useful for debugging
432  // GTMLoggerInfo(@"methodCalls = %@", calls);
433  // GTMLoggerInfo(@"callOrder = %@", [counter callOrder]);
434}
435
436// The guts of a sync test but allow the caller to specify the number
437// of times [ap startProcessing] is called.
438- (void)commonTestSynchronousWithStarts:(int)starts {
439  // Create an action processor delegate that counts the delegate calls
440  MethodCounter *counter = [MethodCounter counter];
441  STAssertNotNil(counter, nil);
442
443  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:counter] autorelease];
444  STAssertNotNil(ap, nil);
445
446  // Create some simple action to start the ball rolling. This action will
447  // create other actions that will be appended to the action processor queue.
448  // A total of 10 actions should be used overall (in this test).
449  KSAction *action = [[[TestAction alloc] initWithNum:1 toggle:NO] autorelease];
450  STAssertNotNil(action, nil);
451  STAssertTrue([ap actionsCompleted] == 0, nil);
452
453  // Add our one action to kick things off, then start processing the queue
454  [ap enqueueAction:action];
455
456  // INTERNAL KNOWLEDGE:
457  // startProcessing: calls processHead: which asserts if the
458  // currentAction_ isn't nil.  The currentAction_ becomes non-nil as
459  // a result of a valid call to processHead:.
460  // Thus, if >1 of these calls don't throw an assert, we're fine.
461  for (int i = 0; i < starts; i++)
462    [ap startProcessing];
463
464  // Since this is the synchronous test, all actions will be done by this point,
465  // so we can verify that all the delegate methods worked correctly.
466  // Only call verifyMethodCounter on the "normal" case, since the
467  // trap mechanism doesn't understand excessive starts.
468  if (starts == 1)
469    [self verifyMethodCounter:counter];
470
471  STAssertTrue([ap actionsCompleted] > 1, nil);
472}
473
474- (void)testSynchronous {
475  [self commonTestSynchronousWithStarts:1];
476}
477
478- (void)testExcessiveStartProcessing {
479  [self commonTestSynchronousWithStarts:8];
480}
481
482- (void)testAsynchronous {
483  MethodCounter *counter = [MethodCounter counter];
484  STAssertNotNil(counter, nil);
485
486  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:counter] autorelease];
487  STAssertNotNil(ap, nil);
488
489  // Create an action that does its stuff asynchronously.
490  KSAction *action = [[[TestAsyncAction alloc] initWithNum:1] autorelease];
491  STAssertNotNil(action, nil);
492
493  // Add our one action to kick things off, then start processing the queue
494  [ap enqueueAction:action];
495  [ap startProcessing];
496
497  // Since we're testing asynchronous actions, we need to spin the runloop for
498  // a bit to make sure all of our actions complete. Each action uses a 0.1
499  // second timer, so spinning for 2 seconds should be more than enough time
500  // for everything to complete.
501  NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:2];
502  [[NSRunLoop currentRunLoop] runUntilDate:quick];
503
504  [self verifyMethodCounter:counter];
505}
506
507- (void)testBoth {
508  MethodCounter *counter = [MethodCounter counter];
509  STAssertNotNil(counter, nil);
510
511  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:counter] autorelease];
512  STAssertNotNil(ap, nil);
513
514  // Create an action that will create sync and async actions in the same Q
515  KSAction *action = [[[TestAction alloc] initWithNum:1 toggle:YES] autorelease];
516  STAssertNotNil(action, nil);
517
518  // Add our one action to kick things off, then start processing the queue
519  [ap enqueueAction:action];
520  STAssertTrue([ap actionsCompleted] == 0, nil);
521  [ap startProcessing];
522
523  NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:1];
524  [[NSRunLoop currentRunLoop] runUntilDate:quick];
525
526  [self verifyMethodCounter:counter];
527  STAssertTrue([ap actionsCompleted] > 3, nil);
528}
529
530- (void)testUpdateProgress {
531  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:nil] autorelease];
532  KSAction *nop = [[[NOPAction alloc] init] autorelease];
533  [ap enqueueAction:nop];
534  STAssertNotNil(ap, nil);
535  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
536  
537  [ap updateProgressWithFraction:0.1];
538  STAssertEqualsWithAccuracy([ap progress], 0.1f, 0.01, nil);
539  
540  [ap updateProgressWithFraction:0.2];
541  STAssertEqualsWithAccuracy([ap progress], 0.2f, 0.01, nil);
542
543  [ap updateProgressWithFraction:0.3];
544  STAssertEqualsWithAccuracy([ap progress], 0.3f, 0.01, nil);
545  
546  [ap updateProgressWithFraction:1.0];
547  STAssertEqualsWithAccuracy([ap progress], 1.0f, 0.01, nil);
548  
549  [ap updateProgressWithFraction:0.0];
550  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
551}
552
553- (void)testProgressSingleAction {
554  ProgressRecorder *recorder = [[[ProgressRecorder alloc] init] autorelease];
555  STAssertNotNil(recorder, nil);
556  
557  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:recorder] autorelease];
558  STAssertNotNil(ap, nil);
559  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
560  
561  KSAction *action = [[[ProgressAction alloc] initWithCalls:10] autorelease];
562  STAssertNotNil(action, nil);
563  
564  [ap enqueueAction:action];
565  STAssertTrue([ap actionsCompleted] == 0, nil);
566  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
567
568  [ap startProcessing];
569  
570  NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:1];
571  [[NSRunLoop currentRunLoop] runUntilDate:quick];
572  
573  STAssertFalse([ap isProcessing], nil);
574  STAssertEquals([ap actionsCompleted], 1, nil);
575  STAssertEqualsWithAccuracy([ap progress], 1.0f, 0.01, nil);
576  
577  NSArray *progressCalls = [recorder calls];
578  STAssertTrue([progressCalls count] == 10, nil);
579  
580  NSArray *expectedCalls = [NSArray arrayWithObjects:
581                            @"action:0.10,processor:0.10",
582                            @"action:0.20,processor:0.20",
583                            @"action:0.30,processor:0.30",
584                            @"action:0.40,processor:0.40",
585                            @"action:0.50,processor:0.50",
586                            @"action:0.60,processor:0.60",
587                            @"action:0.70,processor:0.70",
588                            @"action:0.80,processor:0.80",
589                            @"action:0.90,processor:0.90",
590                            @"action:1.00,processor:1.00",
591                            nil];
592  STAssertEqualObjects(progressCalls, expectedCalls, nil);
593}
594
595- (void)testProgressMultipleActions {
596  ProgressRecorder *recorder = [[[ProgressRecorder alloc] init] autorelease];
597  STAssertNotNil(recorder, nil);
598  
599  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:recorder] autorelease];
600  STAssertNotNil(ap, nil);
601  
602  KSAction *action1 = [[[ProgressAction alloc] initWithCalls:10] autorelease];
603  STAssertNotNil(action1, nil);
604  
605  KSAction *action2 = [[[ProgressAction alloc] initWithCalls:10] autorelease];
606  STAssertNotNil(action2, nil);
607  
608  [ap enqueueAction:action1];
609  [ap enqueueAction:action2];
610  STAssertTrue([ap actionsCompleted] == 0, nil);
611  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
612
613  [ap startProcessing];
614  
615  NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:2.2];
616  [[NSRunLoop currentRunLoop] runUntilDate:quick];
617  
618  STAssertFalse([ap isProcessing], nil);
619  STAssertEquals([ap actionsCompleted], 2, nil);
620  STAssertEqualsWithAccuracy([ap progress], 1.0f, 0.01, nil);
621  
622  NSArray *progressCalls = [recorder calls];
623  STAssertTrue([progressCalls count] == 20, nil);
624  
625  NSArray *expectedCalls = [NSArray arrayWithObjects:
626                            @"action:0.10,processor:0.05",
627                            @"action:0.20,processor:0.10",
628                            @"action:0.30,processor:0.15",
629                            @"action:0.40,processor:0.20",
630                            @"action:0.50,processor:0.25",
631                            @"action:0.60,processor:0.30",
632                            @"action:0.70,processor:0.35",
633                            @"action:0.80,processor:0.40",
634                            @"action:0.90,processor:0.45",
635                            @"action:1.00,processor:0.50",
636                            @"action:0.10,processor:0.55",
637                            @"action:0.20,processor:0.60",
638                            @"action:0.30,processor:0.65",
639                            @"action:0.40,processor:0.70",
640                            @"action:0.50,processor:0.75",
641                            @"action:0.60,processor:0.80",
642                            @"action:0.70,processor:0.85",
643                            @"action:0.80,processor:0.90",
644                            @"action:0.90,processor:0.95",
645                            @"action:1.00,processor:1.00",
646                            nil];
647  STAssertEqualObjects(progressCalls, expectedCalls, nil);
648}
649
650- (void)testProgressMixedActions {
651  ProgressRecorder *recorder = [[[ProgressRecorder alloc] init] autorelease];
652  STAssertNotNil(recorder, nil);
653  
654  KSActionProcessor *ap = [[[KSActionProcessor alloc] initWithDelegate:recorder] autorelease];
655  STAssertNotNil(ap, nil);
656  
657  KSAction *action1 = [[[ProgressAction alloc] initWithCalls:10] autorelease];
658  STAssertNotNil(action1, nil);
659  
660  KSAction *action2 = [[[NOPAction alloc] init] autorelease];
661  STAssertNotNil(action2, nil);
662  
663  [ap enqueueAction:action1];
664  [ap enqueueAction:action2];
665  STAssertTrue([ap actionsCompleted] == 0, nil);
666  STAssertEqualsWithAccuracy([ap progress], 0.0f, 0.01, nil);
667  
668  [ap startProcessing];
669  
670  NSDate *quick = [NSDate dateWithTimeIntervalSinceNow:2.2];
671  [[NSRunLoop currentRunLoop] runUntilDate:quick];
672  
673  STAssertFalse([ap isProcessing], nil);
674  STAssertEquals([ap actionsCompleted], 2, nil);
675  
676  // Verify that the progress now is 1.0
677  STAssertEqualsWithAccuracy([ap progress], 1.0f, 0.01, nil);
678  
679  NSArray *progressCalls = [recorder calls];
680  STAssertTrue([progressCalls count] == 10, nil);
681  
682  NSArray *expectedCalls = [NSArray arrayWithObjects:
683                            @"action:0.10,processor:0.05",
684                            @"action:0.20,processor:0.10",
685                            @"action:0.30,processor:0.15",
686                            @"action:0.40,processor:0.20",
687                            @"action:0.50,processor:0.25",
688                            @"action:0.60,processor:0.30",
689                            @"action:0.70,processor:0.35",
690                            @"action:0.80,processor:0.40",
691                            @"action:0.90,processor:0.45",
692                            @"action:1.00,processor:0.50",
693                            nil];
694  STAssertEqualObjects(progressCalls, expectedCalls, nil);
695}
696
697@end