2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 #import <XCTest/XCTest.h>
28 #import "keychain/ckks/CKKSGroupOperation.h"
29 #import "keychain/ckks/CKKSCondition.h"
32 @interface CKKSResultCancelOperation : CKKSResultOperation
33 - (instancetype) init;
36 @implementation CKKSResultCancelOperation
37 - (instancetype)init {
38 if(self = [super init]) {
39 __weak __typeof(self) weakSelf = self;
40 [self addExecutionBlock:^{
50 @interface CKKSResultErrorOperation : CKKSResultOperation
51 - (instancetype) init;
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];
67 @interface CKKSOperationTests : XCTestCase
68 @property NSOperationQueue* queue;
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
76 @implementation CKKSOperationTests
81 self.queue = [[NSOperationQueue alloc] init];
85 [self.queue cancelAllOperations];
91 - (void)testIsPending {
92 NSBlockOperation* run = [NSBlockOperation blockOperationWithBlock:^{}];
93 NSBlockOperation* cancel = [NSBlockOperation blockOperationWithBlock:^{}];
94 NSBlockOperation* pending = [NSBlockOperation blockOperationWithBlock:^{}];
96 [self.queue addOperation: run];
98 [self.queue waitUntilAllOperationsAreFinished];
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");
105 - (void)testResultOperation {
106 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
107 __weak __typeof(op) weakOp = op;
109 [op addExecutionBlock:^{
110 weakOp.error = [NSError errorWithDomain:@"test domain" code:0 userInfo:nil];
113 [self.queue addOperation: op];
115 [op waitUntilFinished];
117 XCTAssertNotNil(op.error, "errors can persist");
120 - (void)testResultSuccessDependency {
121 __block bool firstRun = false;
122 __block bool secondRun = false;
124 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
125 [first addExecutionBlock:^{
129 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
130 [second addExecutionBlock:^{
131 XCTAssertTrue(firstRun);
134 [second addSuccessDependency: first];
136 [self.queue addOperation: second];
137 [self.queue addOperation: first];
139 [self.queue waitUntilAllOperationsAreFinished];
141 XCTAssertTrue(firstRun);
142 XCTAssertTrue(secondRun);
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");
150 - (void)testResultSuccessDependencyCancel {
151 CKKSResultCancelOperation* first = [[CKKSResultCancelOperation alloc] init];
153 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
154 [second addExecutionBlock:^{
155 XCTFail("Second operation should never run");
157 [second addSuccessDependency: first];
159 [self.queue addOperation: second];
160 [self.queue addOperation: first];
162 [self.queue waitUntilAllOperationsAreFinished];
164 XCTAssertTrue(first.finished, "First operation finished");
165 XCTAssertTrue(first.cancelled, "First operation is canceled (as requested)");
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");
173 - (void)testResultSuccessDependencyError {
174 CKKSResultErrorOperation* first = [[CKKSResultErrorOperation alloc] init];
176 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
177 [second addExecutionBlock:^{
178 XCTFail("Second operation should never run");
180 [second addSuccessDependency: first];
182 [self.queue addOperation: second];
183 [self.queue addOperation: first];
185 [self.queue waitUntilAllOperationsAreFinished];
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");
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");
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");
200 - (void)testResultTimeout {
201 __block bool firstRun = false;
202 __block bool secondRun = false;
204 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
205 [first addExecutionBlock:^{
209 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
210 [second addExecutionBlock:^{
211 XCTAssertTrue(firstRun);
214 [second addDependency: first];
216 [self.queue addOperation: [second timeout:(50)* NSEC_PER_MSEC]];
217 [self.queue waitUntilAllOperationsAreFinished];
219 XCTAssertFalse(firstRun);
220 XCTAssertFalse(secondRun);
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");
232 - (void)testResultTimeoutWithUnderlyingError {
233 __block bool firstRun = false;
234 __block bool secondRun = false;
236 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
237 [first addExecutionBlock:^{
240 first.descriptionErrorCode = 604;
242 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
243 [second addExecutionBlock:^{
244 XCTAssertTrue(firstRun);
247 [second addDependency: first];
249 [self.queue addOperation: [second timeout:(50)* NSEC_PER_MSEC]];
250 [self.queue waitUntilAllOperationsAreFinished];
252 XCTAssertFalse(firstRun);
253 XCTAssertFalse(secondRun);
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");
268 - (void)testResultNoTimeout {
269 __block bool firstRun = false;
270 __block bool secondRun = false;
272 CKKSResultOperation* first = [[CKKSResultOperation alloc] init];
273 [first addExecutionBlock:^{
277 CKKSResultOperation* second = [[CKKSResultOperation alloc] init];
278 [second addExecutionBlock:^{
279 XCTAssertTrue(firstRun);
282 [second addDependency: first];
284 [self.queue addOperation: [second timeout:(100)* NSEC_PER_MSEC]];
285 [self.queue addOperation: first];
286 [self.queue waitUntilAllOperationsAreFinished];
288 XCTAssertTrue(firstRun);
289 XCTAssertTrue(secondRun);
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");
298 - (void)testResultFinishDate
300 CKKSResultOperation* operation = [[CKKSResultOperation alloc] init];
301 XCTAssertNil(operation.finishDate, "Result operation does not have a finish date before it is run");
303 [operation addExecutionBlock:^{}];
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");
313 - (void)testGroupOperation {
314 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
316 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
317 [group runBeforeGroupFinished: op1];
319 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
320 [group runBeforeGroupFinished: op2];
322 [self.queue addOperation: group];
324 [self.queue waitUntilAllOperationsAreFinished];
326 XCTAssertEqual(op1.finished, YES, "First operation finished");
327 XCTAssertEqual(op2.finished, YES, "Second operation finished");
328 XCTAssertEqual(group.finished, YES, "Group operation finished");
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");
334 XCTAssertNil(op1.error, "First operation: no error");
335 XCTAssertNil(op2.error, "Second operation: no error");
336 XCTAssertNil(group.error, "Group operation: no error");
339 - (void)testGroupOperationRunBlock {
340 XCTestExpectation* operationRun = [self expectationWithDescription:@"operation run with named:withBlock:"];
341 CKKSGroupOperation* group = [CKKSGroupOperation named:@"asdf" withBlock: ^{
342 [operationRun fulfill];
344 [self.queue addOperation:group];
345 [self waitForExpectations: @[operationRun] timeout:5];
347 operationRun = [self expectationWithDescription:@"operation run with named:withBlockTakingSelf:"];
348 group = [CKKSGroupOperation named:@"asdf" withBlockTakingSelf:^(CKKSGroupOperation *strongOp) {
349 [operationRun fulfill];
351 [self.queue addOperation:group];
352 [self waitForExpectations: @[operationRun] timeout:5];
355 - (void)testGroupOperationSubOperationCancel {
356 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
358 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
359 [group runBeforeGroupFinished: op1];
361 CKKSResultCancelOperation* op2 = [[CKKSResultCancelOperation alloc] init];
362 [group runBeforeGroupFinished: op2];
364 [self.queue addOperation: group];
366 [self.queue waitUntilAllOperationsAreFinished];
368 XCTAssertEqual(op1.finished, YES, "First operation finished");
369 XCTAssertEqual(op2.finished, YES, "Second operation finished");
370 XCTAssertEqual(group.finished, YES, "Group operation finished");
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");
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");
382 - (void)testGroupOperationCancel {
383 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
385 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
386 [group runBeforeGroupFinished: op1];
389 [self.queue addOperation: group];
391 [self.queue waitUntilAllOperationsAreFinished];
393 XCTAssertEqual(op1.finished, YES, "First operation finished");
394 XCTAssertEqual(group.finished, YES, "Group operation finished");
396 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
397 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
399 XCTAssertNil(op1.error, "First operation: no error");
400 XCTAssertNil(group.error, "Group operation: no error");
403 - (void)testGroupOperationCancelAfterAdd {
404 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
406 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
407 [group runBeforeGroupFinished: op1];
409 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
410 [group addDependency: never];
412 [self.queue addOperation: group];
416 // Both of these should finish. Wait for that.
417 [op1 waitUntilFinished];
418 [group waitUntilFinished];
420 XCTAssertEqual(op1.finished, YES, "First operation finished");
421 XCTAssertEqual(group.finished, YES, "Group operation finished");
423 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
424 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
426 XCTAssertNil(op1.error, "First operation: no error");
427 XCTAssertNil(group.error, "Group operation: no error");
430 - (void)testGroupOperationCancelWhileRunning {
431 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
432 group.name = @"operation-under-test";
434 XCTestExpectation* groupStarted = [self expectationWithDescription: @"group started"];
435 XCTestExpectation* cancelOccurs = [self expectationWithDescription: @"cancel occurs"];
437 CKKSCondition* everythingFinished = [[CKKSCondition alloc] init];
438 CKKSResultOperation* op1 = [CKKSResultOperation named:@"op1" withBlock:^{
439 [groupStarted fulfill];
441 [self waitForExpectations:@[cancelOccurs] timeout:8.0];
443 // 'do some work'. Will wait 200msec.
444 [everythingFinished wait:200*NSEC_PER_MSEC];
446 [group runBeforeGroupFinished: op1];
447 [self.queue addOperation: group];
449 [self waitForExpectations:@[groupStarted] timeout:8.0];
451 [cancelOccurs fulfill];
453 [group waitUntilFinished];
455 XCTAssertEqual(op1.finished, YES, "First operation finished");
456 XCTAssertEqual(group.finished, YES, "Group operation finished");
458 [everythingFinished fulfill];
460 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
461 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
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");
468 - (void)testGroupOperationWithDependsOn {
469 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
471 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
472 [group dependOnBeforeGroupFinished:op1];
475 [self.queue addOperation: group];
477 [self.queue waitUntilAllOperationsAreFinished];
479 XCTAssertEqual(op1.finished, NO, "First operation not finished");
480 XCTAssertEqual(group.finished, YES, "Group operation finished");
482 XCTAssertEqual(op1.cancelled, NO, "First operation not cancelled");
483 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
485 XCTAssertNil(op1.error, "First operation: no error");
486 XCTAssertNil(group.error, "Group operation: no error");
489 - (void)testGroupOperationTimeout {
490 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
492 __block bool run1 = false;
493 CKKSResultOperation* op1 = [CKKSResultOperation operationWithBlock: ^{
496 [group runBeforeGroupFinished: op1];
498 __block bool run2 = false;
499 CKKSResultOperation* op2 = [CKKSResultOperation operationWithBlock: ^{
502 [group runBeforeGroupFinished: op2];
504 CKKSResultOperation* never = [[CKKSResultOperation alloc] init];
505 [group addDependency: never];
507 [group timeout:10*NSEC_PER_MSEC];
508 [self.queue addOperation: group];
510 [self.queue waitUntilAllOperationsAreFinished];
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];
516 XCTAssertEqual(op1.finished, YES, "First operation finished");
517 XCTAssertEqual(op2.finished, YES, "Second operation finished");
518 XCTAssertEqual(group.finished, YES, "Group operation finished");
520 XCTAssertEqual(op1.cancelled, YES, "First operation cancelled");
521 XCTAssertEqual(op2.cancelled, YES, "Second operation cancelled");
522 XCTAssertEqual(group.cancelled, YES, "Group operation cancelled");
524 XCTAssertFalse(run1, "First operation did not run");
525 XCTAssertFalse(run2, "Second operation did not run");
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");
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];
538 [self.queue addOperation: g];
539 [self.queue waitUntilAllOperationsAreFinished];
543 - (void)testGroupOperationError {
544 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
546 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
547 [group runBeforeGroupFinished: op1];
549 CKKSResultErrorOperation* op2 = [[CKKSResultErrorOperation alloc] init];
550 [group runBeforeGroupFinished: op2];
552 [self.queue addOperation: group];
554 [self.queue waitUntilAllOperationsAreFinished];
556 XCTAssertEqual(op1.finished, YES, "First operation finished");
557 XCTAssertEqual(op2.finished, YES, "Second operation finished");
558 XCTAssertEqual(group.finished, YES, "Group operation finished");
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");
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");
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");
574 - (void)testGroupOperationPending {
575 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
577 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
578 [group runBeforeGroupFinished: op1];
580 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
581 [group addDependency: op2];
583 [self.queue addOperation: group];
585 XCTAssertTrue([group isPending], "group operation hasn't started yet");
587 [self.queue addOperation: op2];
588 [self.queue waitUntilAllOperationsAreFinished];
589 XCTAssertFalse([group isPending], "group operation has started");
591 XCTAssertEqual(op1.finished, YES, "First operation finished");
592 XCTAssertEqual(op2.finished, YES, "Second operation finished");
593 XCTAssertEqual(group.finished, YES, "Group operation finished");
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");
599 XCTAssertNil(op1.error, "First operation: no error");
600 XCTAssertNil(op2.error, "Second operation: no error");
601 XCTAssertNil(group.error, "Group operation: no error");
604 - (void)testGroupOperationPendingAfterCancel {
605 CKKSGroupOperation* group = [[CKKSGroupOperation alloc] init];
607 CKKSResultOperation* op1 = [[CKKSResultOperation alloc] init];
608 [group runBeforeGroupFinished: op1];
610 CKKSResultOperation* op2 = [[CKKSResultOperation alloc] init];
611 [group addDependency: op2];
615 XCTAssertFalse([group isPending], "group operation isn't pending, as it's cancelled");