/OHHTTPStubs/UnitTests/Async Tests Helper/XCTestExpectation+OHRetroCompat.m

https://github.com/AliSoftware/OHHTTPStubs · Objective C · 211 lines · 139 code · 39 blank · 33 comment · 12 complexity · 5c81ea5d04c03a31873b05f6e8b2bd56 MD5 · raw file

  1. /***********************************************************************************
  2. *
  3. * Copyright (c) 2012 Olivier Halligon
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. *
  23. ***********************************************************************************/
  24. /*----------------------------------------------------------------------------------
  25. * NOTE
  26. *
  27. * This file mirror the new XCTestExpectation API from Xcode 6's XCTest framework
  28. * (at least part of it) so that we can use the same API in older Xcode versions
  29. ----------------------------------------------------------------------------------*/
  30. #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < 80000) \
  31. || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED < 101000)
  32. #import "XCTestExpectation+OHRetroCompat.h"
  33. #import <Foundation/Foundation.h>
  34. #import <libkern/OSAtomic.h>
  35. static NSTimeInterval const kRunLoopSamplingInterval = 0.01;
  36. /////////////////////////////////////////////////////////
  37. @interface XCTestExpectation()
  38. @property(strong) NSString* descritionString;
  39. @property(weak) XCTestCase* associatedTestCase;
  40. @property(readonly) BOOL fulfilled;
  41. #if XCTestExpectation_OHRetroCompat_BETTER_FAILURE_LOCATIONS
  42. @property(assign) const char* sourceFile;
  43. @property(assign) NSUInteger sourceLine;
  44. #endif
  45. @end
  46. @interface XCTestCaseAsync()
  47. {
  48. NSMutableArray* _expectations;
  49. OSSpinLock _expectationsLock;
  50. int32_t _unfulfilledExpectationsCount;
  51. }
  52. @end
  53. /////////////////////////////////////////////////////////
  54. @implementation XCTestCaseAsync
  55. #if XCTestExpectation_OHRetroCompat_BETTER_FAILURE_LOCATIONS
  56. #undef expectationWithDescription
  57. #undef waitForExpectationsWithTimeout
  58. #endif
  59. - (void)setUp
  60. {
  61. [super setUp];
  62. _expectations = [NSMutableArray new];
  63. _unfulfilledExpectationsCount = 0;
  64. }
  65. - (void)tearDown
  66. {
  67. [super tearDown];
  68. if (!OSAtomicCompareAndSwap32(0, 0, &_unfulfilledExpectationsCount)) // if unfulfilledExpectationsCount != 0
  69. {
  70. OSSpinLockLock(&_expectationsLock);
  71. [_expectations filterUsingPredicate:[NSPredicate predicateWithFormat:@"fulfilled == NO"]];
  72. #if XCTestExpectation_OHRetroCompat_BETTER_FAILURE_LOCATIONS
  73. // Locate Xcode test failures at the exact line of each expectation, if we have thie FILE+LINE information
  74. for(XCTestExpectation* expectation in _expectations)
  75. {
  76. _XCTFailureHandler(self, YES, expectation.sourceFile, expectation.sourceLine, _XCTFailureDescription(_XCTAssertion_Fail, 0), @"Failed due to unwaited expectation.");
  77. }
  78. #else
  79. XCTFail(@"Failed due to unwaited expectations: %@.", [_expectations componentsJoinedByString:@", "]);
  80. #endif
  81. [_expectations removeAllObjects];
  82. OSSpinLockUnlock(&_expectationsLock);
  83. }
  84. }
  85. - (XCTestExpectation *)expectationWithDescription:(NSString *)description
  86. {
  87. XCTestExpectation* expectation = [XCTestExpectation new];
  88. expectation.associatedTestCase = self;
  89. expectation.descritionString = description;
  90. OSSpinLockLock(&_expectationsLock);
  91. {
  92. [_expectations addObject:expectation];
  93. OSAtomicIncrement32(&_unfulfilledExpectationsCount);
  94. }
  95. OSSpinLockUnlock(&_expectationsLock);
  96. return expectation;
  97. }
  98. - (void)decrementUnfulfilledExpectationsCount
  99. {
  100. OSAtomicDecrement32(&_unfulfilledExpectationsCount);
  101. }
  102. - (void)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(XCWaitCompletionHandler)handlerOrNil
  103. {
  104. [self __file:NULL line:0 waitForExpectationsWithTimeout:timeout handler:handlerOrNil];
  105. }
  106. - (void)__file:(const char*)_caller_source_file line:(NSUInteger)_caller_source_line waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(XCWaitCompletionHandler)handlerOrNil
  107. {
  108. NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
  109. while ([timeoutDate timeIntervalSinceNow]>0)
  110. {
  111. CFRunLoopRunInMode(kCFRunLoopDefaultMode, kRunLoopSamplingInterval, YES);
  112. if (OSAtomicCompareAndSwap32(0, 0, &_unfulfilledExpectationsCount)) break; // if all expectations fulfilled, break
  113. }
  114. NSError* error = nil;
  115. if (!OSAtomicCompareAndSwap32(0, 0, &_unfulfilledExpectationsCount)) // if unfulfilledExpectationsCount != 0
  116. {
  117. error = [NSError errorWithDomain:@"com.apple.XCTestErrorDomain" code:0 userInfo:nil];
  118. }
  119. if (handlerOrNil)
  120. {
  121. handlerOrNil(error);
  122. }
  123. if (error)
  124. {
  125. OSSpinLockLock(&_expectationsLock);
  126. [_expectations filterUsingPredicate:[NSPredicate predicateWithFormat:@"fulfilled == NO"]];
  127. NSString* expectationsList = [_expectations componentsJoinedByString:@", "];
  128. [_expectations removeAllObjects];
  129. OSAtomicCompareAndSwap32(_unfulfilledExpectationsCount, 0, &_unfulfilledExpectationsCount); // reset to 0
  130. OSSpinLockUnlock(&_expectationsLock);
  131. #if XCTestExpectation_OHRetroCompat_BETTER_FAILURE_LOCATIONS
  132. _XCTFailureHandler(self, YES, _caller_source_file, _caller_source_line, _XCTFailureDescription(_XCTAssertion_Fail, 0),
  133. @"Asynchronous wait failed: Exceeded timeout of %g seconds, with unfulfilled expectations: %@.",
  134. timeout, expectationsList);
  135. #else
  136. XCTFail(@"Asynchronous wait failed: Exceeded timeout of %g seconds, with unfulfilled expectations: %@.",
  137. timeout, expectationsList);
  138. #endif
  139. }
  140. }
  141. #if XCTestExpectation_OHRetroCompat_BETTER_FAILURE_LOCATIONS
  142. - (XCTestExpectation *)__file:(const char*)_caller_source_file line:(NSUInteger)_caller_source_line
  143. expectationWithDescription:(NSString *)description
  144. {
  145. XCTestExpectation* expectation = [self expectationWithDescription:description];
  146. expectation.sourceFile = _caller_source_file;
  147. expectation.sourceLine = _caller_source_line;
  148. return expectation;
  149. }
  150. #endif
  151. @end
  152. /////////////////////////////////////////////////////////
  153. @implementation XCTestExpectation
  154. - (void)fulfill
  155. {
  156. if(!_associatedTestCase)
  157. {
  158. [NSException raise:NSInternalInconsistencyException format:@"The test case associated with this XCTestExpectation %@ has already finished!", self];
  159. }
  160. if (_fulfilled)
  161. {
  162. [NSException raise:NSInternalInconsistencyException format:@"The XCTestExpectation %@ has already been fulfilled!", self];
  163. }
  164. _fulfilled = YES;
  165. [_associatedTestCase decrementUnfulfilledExpectationsCount];
  166. }
  167. - (NSString*)description
  168. {
  169. return [NSString stringWithFormat:@"\"%@\"", _descritionString];
  170. }
  171. @end
  172. #endif