FABTestExpectations.m 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. // Copyright 2019 Google
  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. #import "Crashlytics/UnitTests/FABOperation/FABTestExpectations.h"
  15. #import <XCTest/XCTest.h>
  16. #import "Crashlytics/Shared/FIRCLSOperation/FIRCLSOperation.h"
  17. #import "Crashlytics/UnitTests/FABOperation/FABTestAsyncOperation.h"
  18. void *FABOperationPreFlightCancellationTestKVOContext =
  19. &FABOperationPreFlightCancellationTestKVOContext;
  20. @interface FABTestExpectationObserver ()
  21. @property(strong, nonatomic) FIRCLSFABAsyncOperation *observedOperation;
  22. @end
  23. @implementation FABTestExpectationObserver
  24. - (void)dealloc {
  25. [self.observedOperation removeObserver:self
  26. forKeyPath:NSStringFromSelector(@selector(isExecuting))];
  27. }
  28. - (void)observeValueForKeyPath:(NSString *)keyPath
  29. ofObject:(id)object
  30. change:(NSDictionary<NSString *, id> *)change
  31. context:(void *)context {
  32. if (context != FABOperationPreFlightCancellationTestKVOContext) {
  33. return;
  34. }
  35. if (![object isKindOfClass:[FABTestAsyncOperation class]]) {
  36. return;
  37. }
  38. if (![keyPath isEqualToString:NSStringFromSelector(@selector(isExecuting))]) {
  39. return;
  40. }
  41. if (![change[@"new"] boolValue]) {
  42. return;
  43. }
  44. if (self.assertionBlock) {
  45. self.assertionBlock();
  46. }
  47. }
  48. @end
  49. @implementation FABTestExpectations
  50. + (void)
  51. addInFlightCancellationCompletionExpectationsToOperation:(FIRCLSFABAsyncOperation *)operation
  52. testCase:(XCTestCase *)testCase
  53. assertionBlock:
  54. (FABAsyncCompletionAssertionBlock)assertionBlock {
  55. XCTestExpectation *syncCompletionExpectation = [testCase
  56. expectationWithDescription:[NSString stringWithFormat:@"%@ syncCompletionExpectation",
  57. operation.name]];
  58. operation.completionBlock = ^{
  59. [syncCompletionExpectation fulfill];
  60. };
  61. XCTestExpectation *asyncCompletionExpectation = [testCase
  62. expectationWithDescription:[NSString stringWithFormat:@"%@ asyncCompletionExpectation",
  63. operation.name]];
  64. NSString *operationName = [operation.name copy];
  65. operation.asyncCompletion = ^(NSError *error) {
  66. [asyncCompletionExpectation fulfill];
  67. assertionBlock(operationName, error);
  68. };
  69. }
  70. + (void)addInFlightCancellationKVOExpectationsToOperation:(FIRCLSFABAsyncOperation *)operation
  71. testCase:(XCTestCase *)testCase {
  72. for (NSString *selector in @[
  73. NSStringFromSelector(@selector(isCancelled)), NSStringFromSelector(@selector(isFinished)),
  74. NSStringFromSelector(@selector(isExecuting))
  75. ]) {
  76. BOOL (^handler)(NSOperation *observedOperation, NSDictionary *change) =
  77. ^(NSOperation *observedOperation, NSDictionary *change) {
  78. if ([selector isEqualToString:NSStringFromSelector(@selector(isExecuting))]) {
  79. if (!observedOperation.isCancelled && !observedOperation.isFinished &&
  80. ![change[@"old"] boolValue] && [change[@"new"] boolValue]) {
  81. return YES;
  82. }
  83. } else if ([selector isEqualToString:NSStringFromSelector(@selector(isCancelled))]) {
  84. if (observedOperation.isExecuting && !observedOperation.isFinished &&
  85. ![change[@"old"] boolValue] && [change[@"new"] boolValue]) {
  86. return YES;
  87. }
  88. } else if ([selector isEqualToString:NSStringFromSelector(@selector(isFinished))]) {
  89. if (observedOperation.isCancelled && !observedOperation.isExecuting &&
  90. ![change[@"old"] boolValue] && [change[@"new"] boolValue]) {
  91. return YES;
  92. }
  93. }
  94. return NO;
  95. };
  96. [testCase keyValueObservingExpectationForObject:operation keyPath:selector handler:handler];
  97. }
  98. }
  99. + (void)addPreFlightCancellationCompletionExpectationsToOperation:
  100. (FIRCLSFABAsyncOperation *)operation
  101. testCase:(XCTestCase *)testCase
  102. asyncAssertionBlock:(FABAsyncCompletionAssertionBlock)
  103. asyncAssertionBlock {
  104. // we expect the synchronous, standard completionBlock to execute. Per Apple's documentation, it
  105. // always executes when isFinished is set to true, regardless of whether by cancellation or
  106. // finishing execution
  107. XCTestExpectation *syncCompletionExpectation =
  108. [testCase expectationWithDescription:@"syncCompletionExpectation"];
  109. operation.completionBlock = ^{
  110. [syncCompletionExpectation fulfill];
  111. };
  112. // call block containing XCTest assertions in asyncCompletion which will fail the test, it's just
  113. // more convenient to pass the block in containing them because of the way the macros work: they
  114. // use 'self' which must resolve to the XCTestCase instance
  115. NSString *operationName = [operation.name copy];
  116. operation.asyncCompletion = ^(NSError *error) {
  117. asyncAssertionBlock(operationName, error);
  118. };
  119. }
  120. + (FABTestExpectationObserver *)
  121. addPreFlightCancellationKVOExpectationsToOperation:(FIRCLSFABAsyncOperation *)operation
  122. testCase:(XCTestCase *)testCase {
  123. // add an expectation that isFinished is set to true, isCancelled is true and isExecuting is false
  124. BOOL (^handler)(NSOperation *observedOperation, NSDictionary *change) =
  125. ^(NSOperation *observedOperation, NSDictionary *change) {
  126. if (observedOperation.isCancelled && !observedOperation.isExecuting &&
  127. ![change[@"old"] boolValue] && [change[@"new"] boolValue]) {
  128. return YES;
  129. }
  130. return NO;
  131. };
  132. [testCase keyValueObservingExpectationForObject:operation
  133. keyPath:NSStringFromSelector(@selector(isFinished))
  134. handler:handler];
  135. // add key-value observing for isExecuting. if isExecuting ever changes to true for this
  136. // operation, we want to *fail* the test. We can't do this with expectations, so we use plain KVO.
  137. FABTestExpectationObserver *observer = [[FABTestExpectationObserver alloc] init];
  138. [operation addObserver:observer
  139. forKeyPath:NSStringFromSelector(@selector(isExecuting))
  140. options:NSKeyValueObservingOptionNew
  141. context:FABOperationPreFlightCancellationTestKVOContext];
  142. observer.observedOperation = operation;
  143. return observer;
  144. }
  145. @end