]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSOperationTests.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSOperationTests.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import <XCTest/XCTest.h>
27
28 #import "keychain/ckks/CKKSGroupOperation.h"
29 #import "keychain/ckks/CKKSCondition.h"
30
31 // Helper Operations
32 @interface CKKSResultCancelOperation : CKKSResultOperation
33 - (instancetype) init;
34 @end
35
36 @implementation CKKSResultCancelOperation
37 - (instancetype)init {
38 if(self = [super init]) {
39 __weak __typeof(self) weakSelf = self;
40 [self addExecutionBlock:^{
41 [weakSelf cancel];
42 }];
43 }
44 return self;
45 }
46 @end
47
48
49
50 @interface CKKSResultErrorOperation : CKKSResultOperation
51 - (instancetype) init;
52 @end
53
54 @implementation CKKSResultErrorOperation
55 - (instancetype)init {
56 if(self = [super init]) {
57 __weak __typeof(self) weakSelf = self;
58 [self addExecutionBlock:^{
59 weakSelf.error = [NSError errorWithDomain:@"test domain" code:5 userInfo:nil];
60 }];
61 }
62 return self;
63 }
64 @end
65
66
67 @interface CKKSOperationTests : XCTestCase
68 @property NSOperationQueue* queue;
69 @end
70
71 // Remaining tests to write:
72 // TODO: subclass of CKKSResultOperation implementing main() respects addSuccessDependency without any special code
73 // TODO: chain of automatic dependencies
74 // TODO: test showing that CKKSGroupOperations don't start if they success-depend on a failed CKKSResultOperation
75
76 @implementation CKKSOperationTests
77
78 - (void)setUp {
79 [super setUp];
80
81 self.queue = [[NSOperationQueue alloc] init];
82 }
83
84 - (void)tearDown {
85 [self.queue cancelAllOperations];
86 self.queue = nil;
87
88 [super tearDown];
89 }
90
91 - (void)testIsPending {
92 NSBlockOperation* run = [NSBlockOperation blockOperationWithBlock:^{}];
93 NSBlockOperation* cancel = [NSBlockOperation blockOperationWithBlock:^{}];
94 NSBlockOperation* pending = [NSBlockOperation blockOperationWithBlock:^{}];
95
96 [self.queue addOperation: run];
97 [cancel cancel];
98 [self.queue waitUntilAllOperationsAreFinished];
99
100 XCTAssertTrue( [pending isPending], @"Pending operation should be pending");
101 XCTAssertFalse([run isPending], @"run operation should not be pending");
102 XCTAssertFalse([cancel isPending], @"Cancelled operation should not be pending");
103 }
104
105 - (void)testResultOperation {
106 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
107 __weak __typeof(op) weakOp = op;
108
109 [op addExecutionBlock:^{
110 weakOp.error = [NSError errorWithDomain:@"test domain" code:0 userInfo:nil];
111 }];
112
113 [self.queue addOperation: op];
114
115 [op waitUntilFinished];
116
117 XCTAssertNotNil(op.error, "errors can persist");
118 }
119
120 - (void)testResultSuccessDependency {
121 __block bool firstRun = false;
122 __block bool secondRun = false;
123
124 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
125 [first addExecutionBlock:^{
126 firstRun = true;
127 }];
128
129 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
130 [second addExecutionBlock:^{
131 XCTAssertTrue(firstRun);
132 secondRun = true;
133 }];
134 [second addSuccessDependency: first];
135
136 [self.queue addOperation: second];
137 [self.queue addOperation: first];
138
139 [self.queue waitUntilAllOperationsAreFinished];
140
141 XCTAssertTrue(firstRun);
142 XCTAssertTrue(secondRun);
143
144 XCTAssertTrue(first.finished, "First operation finished");
145 XCTAssertFalse(first.cancelled, "First operation not cancelled");
146 XCTAssertTrue(second.finished, "Second operation finished");
147 XCTAssertFalse(second.cancelled, "Second operation not cancelled");
148 }
149
150 - (void)testResultSuccessDependencyCancel {
151 CKKSResultCancelOperation* first = [[CKKSResultCancelOperation alloc] init];
152
153 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
154 [second addExecutionBlock:^{
155 XCTFail("Second operation should never run");
156 }];
157 [second addSuccessDependency: first];
158
159 [self.queue addOperation: second];
160 [self.queue addOperation: first];
161
162 [self.queue waitUntilAllOperationsAreFinished];
163
164 XCTAssertTrue(first.finished, "First operation finished");
165 XCTAssertTrue(first.cancelled, "First operation is canceled (as requested)");
166
167 XCTAssertTrue(second.cancelled, "Second operation is canceled");
168 XCTAssertTrue(second.finished, "Second operation finished");
169 XCTAssertNotNil(second.error, "Error is generated when CKKSResultOperation is cancelled");
170 XCTAssertEqual(second.error.code, CKKSResultSubresultCancelled, "Error code is CKKSResultSubresultCancelled");
171 }
172
173 - (void)testResultSuccessDependencyError {
174 CKKSResultErrorOperation* first = [[CKKSResultErrorOperation alloc] init];
175
176 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
177 [second addExecutionBlock:^{
178 XCTFail("Second operation should never run");
179 }];
180 [second addSuccessDependency: first];
181
182 [self.queue addOperation: second];
183 [self.queue addOperation: first];
184
185 [self.queue waitUntilAllOperationsAreFinished];
186
187 XCTAssertTrue(first.finished, "First operation finished");
188 XCTAssertFalse(first.cancelled, "First operation is not canceled");
189 XCTAssertNotNil(first.error, "First operation has an error");
190
191 XCTAssertTrue(second.cancelled, "Second operation is canceled");
192 XCTAssertTrue(second.finished, "Second operation finished");
193 XCTAssertNotNil(second.error, "Error is generated when dependent CKKSResultOperation has an error");
194 XCTAssertEqual(second.error.code, CKKSResultSubresultError, "Error code is CKKSResultSubresultError");
195
196 XCTAssertNotNil(second.error.userInfo[NSUnderlyingErrorKey], "Passed up the error from the first operation");
197 XCTAssertEqual([second.error.userInfo[NSUnderlyingErrorKey] code], 5, "Passed up the right error from the first operation");
198 }
199
200 - (void)testResultTimeout {
201 __block bool firstRun = false;
202 __block bool secondRun = false;
203
204 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
205 [first addExecutionBlock:^{
206 firstRun = true;
207 }];
208
209 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
210 [second addExecutionBlock:^{
211 XCTAssertTrue(firstRun);
212 secondRun = true;
213 }];
214 [second addDependency: first];
215
216 [self.queue addOperation: [second timeout:(50)* NSEC_PER_MSEC]];
217 [self.queue waitUntilAllOperationsAreFinished];
218
219 XCTAssertFalse(firstRun);
220 XCTAssertFalse(secondRun);
221
222 XCTAssertFalse(first.finished, "First operation not finished");
223 XCTAssertFalse(first.cancelled, "First operation not cancelled");
224 XCTAssertTrue(second.finished, "Second operation finished");
225 XCTAssertTrue(second.cancelled, "Second operation cancelled");
226 XCTAssertNotNil(second.error, "Second operation has an error");
227 XCTAssertEqual(second.error.code, CKKSResultTimedOut, "Second operation error is good");
228 NSError* underlying = second.error.userInfo[NSUnderlyingErrorKey];
229 XCTAssertNil(underlying, "Second operation's error doesn't have an underlying explanation");
230 }
231
232 - (void)testResultTimeoutWithUnderlyingError {
233 __block bool firstRun = false;
234 __block bool secondRun = false;
235
236 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
237 [first addExecutionBlock:^{
238 firstRun = true;
239 }];
240 first.descriptionErrorCode = 604;
241
242 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
243 [second addExecutionBlock:^{
244 XCTAssertTrue(firstRun);
245 secondRun = true;
246 }];
247 [second addDependency: first];
248
249 [self.queue addOperation: [second timeout:(50)* NSEC_PER_MSEC]];
250 [self.queue waitUntilAllOperationsAreFinished];
251
252 XCTAssertFalse(firstRun);
253 XCTAssertFalse(secondRun);
254
255 XCTAssertFalse(first.finished, "First operation not finished");
256 XCTAssertFalse(first.cancelled, "First operation not cancelled");
257 XCTAssertTrue(second.finished, "Second operation finished");
258 XCTAssertTrue(second.cancelled, "Second operation cancelled");
259 XCTAssertNotNil(second.error, "Second operation has an error");
260 XCTAssertEqual(second.error.code, CKKSResultTimedOut, "Second operation error is good");
261 NSError* underlying = second.error.userInfo[NSUnderlyingErrorKey];
262 XCTAssertNotNil(underlying, "second operation's error has an underlying reason");
263 XCTAssertEqualObjects(underlying.domain, CKKSResultDescriptionErrorDomain, "second operation's underlying error's domain should be CKKSResultDescriptionErrorDomain");
264 XCTAssertEqual(underlying.code, 604, "second operation's underlying error's domain should be first's description");
265 }
266
267
268 - (void)testResultNoTimeout {
269 __block bool firstRun = false;
270 __block bool secondRun = false;
271
272 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
273 [first addExecutionBlock:^{
274 firstRun = true;
275 }];
276
277 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
278 [second addExecutionBlock:^{
279 XCTAssertTrue(firstRun);
280 secondRun = true;
281 }];
282 [second addDependency: first];
283
284 [self.queue addOperation: [second timeout:(100)* NSEC_PER_MSEC]];
285 [self.queue addOperation: first];
286 [self.queue waitUntilAllOperationsAreFinished];
287
288 XCTAssertTrue(firstRun);
289 XCTAssertTrue(secondRun);
290
291 XCTAssertTrue(first.finished, "First operation finished");
292 XCTAssertFalse(first.cancelled, "First operation not cancelled");
293 XCTAssertTrue(second.finished, "Second operation finished");
294 XCTAssertFalse(second.cancelled, "Second operation not cancelled");
295 XCTAssertNil(second.error, "Second operation has no error");
296 }
297
298 - (void)testResultFinishDate
299 {
300 CKKSResultOperation* operation = [[CKKSResultOperation alloc] init];
301 XCTAssertNil(operation.finishDate, "Result operation does not have a finish date before it is run");
302
303 [operation addExecutionBlock:^{}];
304
305 [self.queue addOperation:operation];
306 [self.queue waitUntilAllOperationsAreFinished];
307 sleep(0.1); // wait for the completion block to have time to fire
308 XCTAssertNotNil(operation.finishDate, "Result operation has a finish date after everything is done");
309 NSTimeInterval timeIntervalSinceFinishDate = [[NSDate date] timeIntervalSinceDate:operation.finishDate];
310 XCTAssertTrue(timeIntervalSinceFinishDate >= 0.0 && timeIntervalSinceFinishDate <= 10.0, "Result operation finish datelooks reasonable");
311 }
312
313 - (void)testGroupOperation {
314 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
315
316 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
317 [group runBeforeGroupFinished: op1];
318
319 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
320 [group runBeforeGroupFinished: op2];
321
322 [self.queue addOperation: group];
323
324 [self.queue waitUntilAllOperationsAreFinished];
325
326 XCTAssertEqual(op1.finished, YES, "First operation finished");
327 XCTAssertEqual(op2.finished, YES, "Second operation finished");
328 XCTAssertEqual(group.finished, YES, "Group operation finished");
329
330 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
331 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
332 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
333
334 XCTAssertNil(op1.error, "First operation: no error");
335 XCTAssertNil(op2.error, "Second operation: no error");
336 XCTAssertNil(group.error, "Group operation: no error");
337 }
338
339 - (void)testGroupOperationRunBlock {
340 XCTestExpectation* operationRun = [self expectationWithDescription:@"operation run with named:withBlock:"];
341 CKKSGroupOperation* group = [CKKSGroupOperation named:@"asdf" withBlock: ^{
342 [operationRun fulfill];
343 }];
344 [self.queue addOperation:group];
345 [self waitForExpectations: @[operationRun] timeout:5];
346
347 operationRun = [self expectationWithDescription:@"operation run with named:withBlockTakingSelf:"];
348 group = [CKKSGroupOperation named:@"asdf" withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
349 [operationRun fulfill];
350 }];
351 [self.queue addOperation:group];
352 [self waitForExpectations: @[operationRun] timeout:5];
353 }
354
355 - (void)testGroupOperationSubOperationCancel {
356 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
357
358 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
359 [group runBeforeGroupFinished: op1];
360
361 CKKSResultCancelOperation* op2 = [[CKKSResultCancelOperation alloc] init];
362 [group runBeforeGroupFinished: op2];
363
364 [self.queue addOperation: group];
365
366 [self.queue waitUntilAllOperationsAreFinished];
367
368 XCTAssertEqual(op1.finished, YES, "First operation finished");
369 XCTAssertEqual(op2.finished, YES, "Second operation finished");
370 XCTAssertEqual(group.finished, YES, "Group operation finished");
371
372 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
373 XCTAssertEqual(op2.cancelled, YES, "Second operation not cancelled");
374 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
375
376 XCTAssertNil(op1.error, "First operation: no error");
377 XCTAssertNil(op2.error, "Second operation: no error");
378 XCTAssertNotNil(group.error, "Group operation: has an error");
379 XCTAssertEqual(group.error.code, CKKSResultSubresultCancelled, "Error code is CKKSResultSubresultCancelled");
380 }
381
382 - (void)testGroupOperationCancel {
383 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
384
385 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
386 [group runBeforeGroupFinished: op1];
387
388 [group cancel];
389 [self.queue addOperation: group];
390
391 [self.queue waitUntilAllOperationsAreFinished];
392
393 XCTAssertEqual(op1.finished, YES, "First operation finished");
394 XCTAssertEqual(group.finished, YES, "Group operation finished");
395
396 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
397 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
398
399 XCTAssertNil(op1.error, "First operation: no error");
400 XCTAssertNil(group.error, "Group operation: no error");
401 }
402
403 - (void)testGroupOperationCancelAfterAdd {
404 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
405
406 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
407 [group runBeforeGroupFinished: op1];
408
409 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
410 [group addDependency: never];
411
412 [self.queue addOperation: group];
413
414 [group cancel];
415
416 // Both of these should finish. Wait for that.
417 [op1 waitUntilFinished];
418 [group waitUntilFinished];
419
420 XCTAssertEqual(op1.finished, YES, "First operation finished");
421 XCTAssertEqual(group.finished, YES, "Group operation finished");
422
423 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
424 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
425
426 XCTAssertNil(op1.error, "First operation: no error");
427 XCTAssertNil(group.error, "Group operation: no error");
428 }
429
430 - (void)testGroupOperationCancelWhileRunning {
431 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
432 group.name = @"operation-under-test";
433
434 XCTestExpectation* groupStarted = [self expectationWithDescription: @"group started"];
435 XCTestExpectation* cancelOccurs = [self expectationWithDescription: @"cancel occurs"];
436
437 CKKSCondition* everythingFinished = [[CKKSCondition alloc] init];
438 CKKSResultOperation* op1 = [CKKSResultOperation named:@"op1" withBlock:^{
439 [groupStarted fulfill];
440
441 [self waitForExpectations:@[cancelOccurs] timeout:8.0];
442
443 // 'do some work'. Will wait 200msec.
444 [everythingFinished wait:200*NSEC_PER_MSEC];
445 }];
446 [group runBeforeGroupFinished: op1];
447 [self.queue addOperation: group];
448
449 [self waitForExpectations:@[groupStarted] timeout:8.0];
450 [group cancel];
451 [cancelOccurs fulfill];
452
453 [group waitUntilFinished];
454
455 XCTAssertEqual(op1.finished, YES, "First operation finished");
456 XCTAssertEqual(group.finished, YES, "Group operation finished");
457
458 [everythingFinished fulfill];
459
460 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
461 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
462
463 XCTAssertNil(op1.error, "First operation: no error");
464 XCTAssertNotNil(group.error, "Group operation: has an error");
465 XCTAssertEqual(group.error.code, CKKSResultSubresultCancelled, "Error code is CKKSResultSubresultCancelled");
466 }
467
468 - (void)testGroupOperationWithDependsOn {
469 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
470
471 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
472 [group dependOnBeforeGroupFinished:op1];
473
474 [group cancel];
475 [self.queue addOperation: group];
476
477 [self.queue waitUntilAllOperationsAreFinished];
478
479 XCTAssertEqual(op1.finished, NO, "First operation not finished");
480 XCTAssertEqual(group.finished, YES, "Group operation finished");
481
482 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
483 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
484
485 XCTAssertNil(op1.error, "First operation: no error");
486 XCTAssertNil(group.error, "Group operation: no error");
487 }
488
489 - (void)testGroupOperationTimeout {
490 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
491
492 __block bool run1 = false;
493 CKKSResultOperation* op1 = [CKKSResultOperation operationWithBlock: ^{
494 run1 = true;
495 }];
496 [group runBeforeGroupFinished: op1];
497
498 __block bool run2 = false;
499 CKKSResultOperation* op2 = [CKKSResultOperation operationWithBlock: ^{
500 run2 = true;
501 }];
502 [group runBeforeGroupFinished: op2];
503
504 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
505 [group addDependency: never];
506
507 [group timeout:10*NSEC_PER_MSEC];
508 [self.queue addOperation: group];
509
510 [self.queue waitUntilAllOperationsAreFinished];
511
512 // Shouldn't be necessary, but I'm not sure the NSOperation's finished property vs. dependency triggering is thread-safe
513 [op1 waitUntilFinished];
514 [op2 waitUntilFinished];
515
516 XCTAssertEqual(op1.finished, YES, "First operation finished");
517 XCTAssertEqual(op2.finished, YES, "Second operation finished");
518 XCTAssertEqual(group.finished, YES, "Group operation finished");
519
520 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
521 XCTAssertEqual(op2.cancelled, YES, "Second operation cancelled");
522 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
523
524 XCTAssertFalse(run1, "First operation did not run");
525 XCTAssertFalse(run2, "Second operation did not run");
526
527 XCTAssertNil(op1.error, "First operation: no error");
528 XCTAssertNil(op2.error, "Second operation: no error");
529 XCTAssertNotNil(group.error, "Group operation: error");
530 XCTAssertEqual(group.error.code, CKKSResultTimedOut, "Error code is CKKSResultTimedOut");
531
532 // Try a few more times, just in case
533 for(int i = 0; i < 100; i++) {
534 CKKSGroupOperation* g = [[CKKSGroupOperation alloc] init];
535 [g addDependency: never];
536 [g timeout:((i%20)+1)*NSEC_PER_MSEC];
537
538 [self.queue addOperation: g];
539 [self.queue waitUntilAllOperationsAreFinished];
540 }
541 }
542
543 - (void)testGroupOperationError {
544 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
545
546 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
547 [group runBeforeGroupFinished: op1];
548
549 CKKSResultErrorOperation* op2 = [[CKKSResultErrorOperation alloc] init];
550 [group runBeforeGroupFinished: op2];
551
552 [self.queue addOperation: group];
553
554 [self.queue waitUntilAllOperationsAreFinished];
555
556 XCTAssertEqual(op1.finished, YES, "First operation finished");
557 XCTAssertEqual(op2.finished, YES, "Second operation finished");
558 XCTAssertEqual(group.finished, YES, "Group operation finished");
559
560 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
561 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
562 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
563
564 XCTAssertNil(op1.error, "First operation: no error");
565 XCTAssertNotNil(op2.error, "Second operation: error (as expected)");
566 XCTAssertEqual(op2.error.code, 5, "Rght error from the erroring operation");
567
568 XCTAssertNotNil(group.error, "Error is generated when dependent CKKSResultOperation has an error");
569 XCTAssertEqual(group.error.code, CKKSResultSubresultError, "Error code is CKKSResultSubresultError");
570 XCTAssertNotNil(group.error.userInfo[NSUnderlyingErrorKey], "Passed up the error from the first operation");
571 XCTAssertEqual([group.error.userInfo[NSUnderlyingErrorKey] code], 5, "Passed up the right error from the first operation");
572 }
573
574 - (void)testGroupOperationPending {
575 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
576
577 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
578 [group runBeforeGroupFinished: op1];
579
580 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
581 [group addDependency: op2];
582
583 [self.queue addOperation: group];
584
585 XCTAssertTrue([group isPending], "group operation hasn't started yet");
586
587 [self.queue addOperation: op2];
588 [self.queue waitUntilAllOperationsAreFinished];
589 XCTAssertFalse([group isPending], "group operation has started");
590
591 XCTAssertEqual(op1.finished, YES, "First operation finished");
592 XCTAssertEqual(op2.finished, YES, "Second operation finished");
593 XCTAssertEqual(group.finished, YES, "Group operation finished");
594
595 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
596 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
597 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
598
599 XCTAssertNil(op1.error, "First operation: no error");
600 XCTAssertNil(op2.error, "Second operation: no error");
601 XCTAssertNil(group.error, "Group operation: no error");
602 }
603
604 - (void)testGroupOperationPendingAfterCancel {
605 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
606
607 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
608 [group runBeforeGroupFinished: op1];
609
610 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
611 [group addDependency: op2];
612
613 [group cancel];
614
615 XCTAssertFalse([group isPending], "group operation isn't pending, as it's cancelled");
616 }
617
618
619 @end
620
621 #endif /* OCTAGON */