]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSOperationTests.m
Security-59306.11.20.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
308 XCTAssertEqual([operation.completionHandlerDidRunCondition wait:4 * NSEC_PER_SEC], 0, "Completion block should fire in a reasonable amount of time");
309 XCTAssertNotNil(operation.finishDate, "Result operation has a finish date after everything is done");
310 NSTimeInterval timeIntervalSinceFinishDate = [[NSDate date] timeIntervalSinceDate:operation.finishDate];
311 XCTAssertTrue(timeIntervalSinceFinishDate >= 0.0 && timeIntervalSinceFinishDate <= 10.0, "Result operation finish datelooks reasonable");
312 }
313
314 - (void)testGroupOperation {
315 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
316
317 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
318 [group runBeforeGroupFinished: op1];
319
320 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
321 [group runBeforeGroupFinished: op2];
322
323 [self.queue addOperation: group];
324
325 [self.queue waitUntilAllOperationsAreFinished];
326
327 XCTAssertEqual(op1.finished, YES, "First operation finished");
328 XCTAssertEqual(op2.finished, YES, "Second operation finished");
329 XCTAssertEqual(group.finished, YES, "Group operation finished");
330
331 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
332 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
333 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
334
335 XCTAssertNil(op1.error, "First operation: no error");
336 XCTAssertNil(op2.error, "Second operation: no error");
337 XCTAssertNil(group.error, "Group operation: no error");
338 }
339
340 - (void)testGroupOperationRunBlock {
341 XCTestExpectation* operationRun = [self expectationWithDescription:@"operation run with named:withBlock:"];
342 CKKSGroupOperation* group = [CKKSGroupOperation named:@"asdf" withBlock: ^{
343 [operationRun fulfill];
344 }];
345 [self.queue addOperation:group];
346 [self waitForExpectations: @[operationRun] timeout:5];
347
348 operationRun = [self expectationWithDescription:@"operation run with named:withBlockTakingSelf:"];
349 group = [CKKSGroupOperation named:@"asdf" withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
350 [operationRun fulfill];
351 }];
352 [self.queue addOperation:group];
353 [self waitForExpectations: @[operationRun] timeout:5];
354 }
355
356 - (void)testGroupOperationSubOperationCancel {
357 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
358
359 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
360 [group runBeforeGroupFinished: op1];
361
362 CKKSResultCancelOperation* op2 = [[CKKSResultCancelOperation alloc] init];
363 [group runBeforeGroupFinished: op2];
364
365 [self.queue addOperation: group];
366
367 [self.queue waitUntilAllOperationsAreFinished];
368
369 XCTAssertEqual(op1.finished, YES, "First operation finished");
370 XCTAssertEqual(op2.finished, YES, "Second operation finished");
371 XCTAssertEqual(group.finished, YES, "Group operation finished");
372
373 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
374 XCTAssertEqual(op2.cancelled, YES, "Second operation not cancelled");
375 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
376
377 XCTAssertNil(op1.error, "First operation: no error");
378 XCTAssertNil(op2.error, "Second operation: no error");
379 XCTAssertNotNil(group.error, "Group operation: has an error");
380 XCTAssertEqual(group.error.code, CKKSResultSubresultCancelled, "Error code is CKKSResultSubresultCancelled");
381 }
382
383 - (void)testGroupOperationCancel {
384 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
385
386 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
387 [group runBeforeGroupFinished: op1];
388
389 [group cancel];
390 [self.queue addOperation: group];
391
392 [self.queue waitUntilAllOperationsAreFinished];
393
394 XCTAssertEqual(op1.finished, YES, "First operation finished");
395 XCTAssertEqual(group.finished, YES, "Group operation finished");
396
397 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
398 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
399
400 XCTAssertNil(op1.error, "First operation: no error");
401 XCTAssertNil(group.error, "Group operation: no error");
402 }
403
404 - (void)testGroupOperationCancelAfterAdd {
405 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
406
407 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
408 [group runBeforeGroupFinished: op1];
409
410 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
411 [group addDependency: never];
412
413 [self.queue addOperation: group];
414
415 [group cancel];
416
417 // Both of these should finish. Wait for that.
418 [op1 waitUntilFinished];
419 [group waitUntilFinished];
420
421 XCTAssertEqual(op1.finished, YES, "First operation finished");
422 XCTAssertEqual(group.finished, YES, "Group operation finished");
423
424 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
425 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
426
427 XCTAssertNil(op1.error, "First operation: no error");
428 XCTAssertNil(group.error, "Group operation: no error");
429 }
430
431 - (void)testGroupOperationCancelWhileRunning {
432 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
433 group.name = @"operation-under-test";
434
435 XCTestExpectation* groupStarted = [self expectationWithDescription: @"group started"];
436 XCTestExpectation* cancelOccurs = [self expectationWithDescription: @"cancel occurs"];
437
438 CKKSCondition* everythingFinished = [[CKKSCondition alloc] init];
439 CKKSResultOperation* op1 = [CKKSResultOperation named:@"op1" withBlock:^{
440 [groupStarted fulfill];
441
442 [self waitForExpectations:@[cancelOccurs] timeout:8.0];
443
444 // 'do some work'. Will wait 200msec.
445 [everythingFinished wait:200*NSEC_PER_MSEC];
446 }];
447 [group runBeforeGroupFinished: op1];
448 [self.queue addOperation: group];
449
450 [self waitForExpectations:@[groupStarted] timeout:8.0];
451 [group cancel];
452 [cancelOccurs fulfill];
453
454 [group waitUntilFinished];
455
456 XCTAssertEqual(op1.finished, YES, "First operation finished");
457 XCTAssertEqual(group.finished, YES, "Group operation finished");
458
459 [everythingFinished fulfill];
460
461 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
462 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
463
464 XCTAssertNil(op1.error, "First operation: no error");
465 XCTAssertNotNil(group.error, "Group operation: has an error");
466 XCTAssertEqual(group.error.code, CKKSResultSubresultCancelled, "Error code is CKKSResultSubresultCancelled");
467 }
468
469 - (void)testGroupOperationWithDependsOn {
470 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
471
472 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
473 [group dependOnBeforeGroupFinished:op1];
474
475 [group cancel];
476 [self.queue addOperation: group];
477
478 [self.queue waitUntilAllOperationsAreFinished];
479
480 XCTAssertEqual(op1.finished, NO, "First operation not finished");
481 XCTAssertEqual(group.finished, YES, "Group operation finished");
482
483 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
484 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
485
486 XCTAssertNil(op1.error, "First operation: no error");
487 XCTAssertNil(group.error, "Group operation: no error");
488 }
489
490 - (void)testGroupOperationTimeout {
491 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
492
493 __block bool run1 = false;
494 CKKSResultOperation* op1 = [CKKSResultOperation operationWithBlock: ^{
495 run1 = true;
496 }];
497 [group runBeforeGroupFinished: op1];
498
499 __block bool run2 = false;
500 CKKSResultOperation* op2 = [CKKSResultOperation operationWithBlock: ^{
501 run2 = true;
502 }];
503 [group runBeforeGroupFinished: op2];
504
505 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
506 [group addDependency: never];
507
508 [group timeout:10*NSEC_PER_MSEC];
509 [self.queue addOperation: group];
510
511 [self.queue waitUntilAllOperationsAreFinished];
512
513 // Shouldn't be necessary, but I'm not sure the NSOperation's finished property vs. dependency triggering is thread-safe
514 [op1 waitUntilFinished];
515 [op2 waitUntilFinished];
516
517 XCTAssertEqual(op1.finished, YES, "First operation finished");
518 XCTAssertEqual(op2.finished, YES, "Second operation finished");
519 XCTAssertEqual(group.finished, YES, "Group operation finished");
520
521 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
522 XCTAssertEqual(op2.cancelled, YES, "Second operation cancelled");
523 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
524
525 XCTAssertFalse(run1, "First operation did not run");
526 XCTAssertFalse(run2, "Second operation did not run");
527
528 XCTAssertNil(op1.error, "First operation: no error");
529 XCTAssertNil(op2.error, "Second operation: no error");
530 XCTAssertNotNil(group.error, "Group operation: error");
531 XCTAssertEqual(group.error.code, CKKSResultTimedOut, "Error code is CKKSResultTimedOut");
532
533 // Try a few more times, just in case
534 for(int i = 0; i < 100; i++) {
535 CKKSGroupOperation* g = [[CKKSGroupOperation alloc] init];
536 [g addDependency: never];
537 [g timeout:((i%20)+1)*NSEC_PER_MSEC];
538
539 [self.queue addOperation: g];
540 [self.queue waitUntilAllOperationsAreFinished];
541 }
542 }
543
544 - (void)testGroupOperationError {
545 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
546
547 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
548 [group runBeforeGroupFinished: op1];
549
550 CKKSResultErrorOperation* op2 = [[CKKSResultErrorOperation alloc] init];
551 [group runBeforeGroupFinished: op2];
552
553 [self.queue addOperation: group];
554
555 [self.queue waitUntilAllOperationsAreFinished];
556
557 XCTAssertEqual(op1.finished, YES, "First operation finished");
558 XCTAssertEqual(op2.finished, YES, "Second operation finished");
559 XCTAssertEqual(group.finished, YES, "Group operation finished");
560
561 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
562 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
563 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
564
565 XCTAssertNil(op1.error, "First operation: no error");
566 XCTAssertNotNil(op2.error, "Second operation: error (as expected)");
567 XCTAssertEqual(op2.error.code, 5, "Rght error from the erroring operation");
568
569 XCTAssertNotNil(group.error, "Error is generated when dependent CKKSResultOperation has an error");
570 XCTAssertEqual(group.error.code, CKKSResultSubresultError, "Error code is CKKSResultSubresultError");
571 XCTAssertNotNil(group.error.userInfo[NSUnderlyingErrorKey], "Passed up the error from the first operation");
572 XCTAssertEqual([group.error.userInfo[NSUnderlyingErrorKey] code], 5, "Passed up the right error from the first operation");
573 }
574
575 - (void)testGroupOperationPending {
576 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
577
578 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
579 [group runBeforeGroupFinished: op1];
580
581 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
582 [group addDependency: op2];
583
584 [self.queue addOperation: group];
585
586 XCTAssertTrue([group isPending], "group operation hasn't started yet");
587
588 [self.queue addOperation: op2];
589 [self.queue waitUntilAllOperationsAreFinished];
590 XCTAssertFalse([group isPending], "group operation has started");
591
592 XCTAssertEqual(op1.finished, YES, "First operation finished");
593 XCTAssertEqual(op2.finished, YES, "Second operation finished");
594 XCTAssertEqual(group.finished, YES, "Group operation finished");
595
596 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
597 XCTAssertEqual(op2.cancelled, NO, "Second operation cancelled");
598 XCTAssertEqual(group.cancelled, NO, "Group operation not cancelled");
599
600 XCTAssertNil(op1.error, "First operation: no error");
601 XCTAssertNil(op2.error, "Second operation: no error");
602 XCTAssertNil(group.error, "Group operation: no error");
603 }
604
605 - (void)testGroupOperationPendingAfterCancel {
606 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
607
608 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
609 [group runBeforeGroupFinished: op1];
610
611 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
612 [group addDependency: op2];
613
614 [group cancel];
615
616 XCTAssertFalse([group isPending], "group operation isn't pending, as it's cancelled");
617 }
618
619
620 @end
621
622 #endif /* OCTAGON */