GULOSLoggerTest.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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 "GULOSLogger.h"
  15. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  16. #import <GoogleUtilities/GULLogger.h>
  17. #import <GoogleUtilities/GULSwizzler.h>
  18. #import <os/log.h>
  19. #import <OCMock/OCMock.h>
  20. #import <XCTest/XCTest.h>
  21. NS_ASSUME_NONNULL_BEGIN
  22. static NSString *const kService = @"my service";
  23. static NSString *const kCode = @"I-COR000001";
  24. static NSTimeInterval const kTimeout = 1.0;
  25. // Expectation that contains the information needed to see if the correct parameters were used in an
  26. // os_log_with_type call.
  27. @interface GULOSLoggerExpectation : XCTestExpectation
  28. @property(nonatomic, nullable) os_log_t log;
  29. @property(nonatomic) os_log_type_t type;
  30. @property(nonatomic) NSString *message;
  31. - (instancetype)initWithLog:(nullable os_log_t)log
  32. type:(os_log_type_t)type
  33. message:(NSString *)message;
  34. @end
  35. @implementation GULOSLoggerExpectation
  36. - (instancetype)initWithLog:(nullable os_log_t)log
  37. type:(os_log_type_t)type
  38. message:(NSString *)message {
  39. self = [super
  40. initWithDescription:[NSString
  41. stringWithFormat:@"os_log_with_type(%@, %iu, %@) was not called.",
  42. log, type, message]];
  43. if (self) {
  44. _log = log;
  45. _type = type;
  46. _message = message;
  47. }
  48. return self;
  49. }
  50. @end
  51. // List of expectations that may be fulfilled in the current test.
  52. static NSMutableArray<GULOSLoggerExpectation *> *sExpectations;
  53. // Function that will be called by GULOSLogger instead of os_log_with_type.
  54. void GULTestOSLogWithType(os_log_t log, os_log_type_t type, char *s, ...) {
  55. // Grab the first variable argument.
  56. va_list args;
  57. va_start(args, s);
  58. NSString *message = [NSString stringWithUTF8String:va_arg(args, char *)];
  59. va_end(args);
  60. // Look for an expectation that meets these parameters.
  61. for (GULOSLoggerExpectation *expectation in sExpectations) {
  62. if ((expectation.log == nil || expectation.log == log) && expectation.type == type &&
  63. [message containsString:expectation.message]) {
  64. [expectation fulfill];
  65. return; // Only fulfill one expectation per call.
  66. }
  67. }
  68. }
  69. #pragma mark -
  70. // Redefine class property as readwrite for testing.
  71. @interface GULLogger (ForTesting)
  72. @property(nonatomic, nullable, class, readwrite) id<GULLoggerSystem> logger;
  73. @end
  74. // Surface osLog and dispatchQueues for tests.
  75. @interface GULOSLogger (ForTesting)
  76. @property(nonatomic) NSMutableDictionary<NSString *, os_log_t> *categoryLoggers;
  77. @property(nonatomic) dispatch_queue_t dispatchQueue;
  78. @property(nonatomic, unsafe_unretained) void (*logFunction)(os_log_t, os_log_type_t, char *, ...);
  79. @end
  80. #pragma mark -
  81. @interface GULOSLoggerTest : XCTestCase
  82. @property(nonatomic, nullable) GULOSLogger *osLogger;
  83. @property(nonatomic, nullable) id mock;
  84. @property(nonatomic) BOOL appStoreWasSwizzled;
  85. @end
  86. @implementation GULOSLoggerTest
  87. - (void)setAppStoreTo:(BOOL)fromAppStore {
  88. [GULSwizzler swizzleClass:[GULAppEnvironmentUtil class]
  89. selector:@selector(isFromAppStore)
  90. isClassSelector:YES
  91. withBlock:^BOOL() {
  92. return fromAppStore;
  93. }];
  94. self.appStoreWasSwizzled = YES;
  95. }
  96. - (void)partialMockLogger {
  97. // Add the ability to intercept calls to the instance under test
  98. self.mock = OCMPartialMock(self.osLogger);
  99. GULLogger.logger = self.mock;
  100. }
  101. - (void)setUp {
  102. [super setUp];
  103. // Setup globals and create the instance under test.
  104. sExpectations = [[NSMutableArray<GULOSLoggerExpectation *> alloc] init];
  105. self.osLogger = [[GULOSLogger alloc] init];
  106. self.osLogger.logFunction = &GULTestOSLogWithType;
  107. }
  108. - (void)tearDown {
  109. // Clear globals
  110. sExpectations = nil;
  111. GULLogger.logger = nil;
  112. [self.mock stopMocking];
  113. self.mock = nil;
  114. if (self.appStoreWasSwizzled) {
  115. [GULSwizzler unswizzleClass:[GULAppEnvironmentUtil class]
  116. selector:@selector(isFromAppStore)
  117. isClassSelector:YES];
  118. self.appStoreWasSwizzled = NO;
  119. }
  120. [super tearDown];
  121. }
  122. #pragma mark Tests
  123. - (void)testInit {
  124. // First, there are no loggers created.
  125. XCTAssertNil(self.osLogger.categoryLoggers);
  126. // After initializeLogger, there should be an empty dictionary ready, for loggers.
  127. [self.osLogger initializeLogger];
  128. NSDictionary *loggers = self.osLogger.categoryLoggers;
  129. XCTAssertNotNil(loggers);
  130. // Calling initializeLogger logger again, should change the dictionary instance.
  131. [self.osLogger initializeLogger];
  132. XCTAssertEqual(loggers, self.osLogger.categoryLoggers);
  133. }
  134. - (void)testSetLogLevelValid {
  135. // Setting the log level to something valid should not result in an error message.
  136. #pragma clang diagnostic push
  137. #pragma clang diagnostic ignored "-Wformat-security"
  138. OCMReject([self.mock logWithLevel:GULLoggerLevelError
  139. withService:OCMOCK_ANY
  140. isForced:NO
  141. withCode:OCMOCK_ANY
  142. withMessage:OCMOCK_ANY]);
  143. #pragma clang diagnostic pop
  144. self.osLogger.logLevel = GULLoggerLevelWarning;
  145. OCMVerifyAll(self.mock);
  146. }
  147. - (void)testSetLogLevelInvalid {
  148. // The logger should log an error for invalid levels.
  149. #pragma clang diagnostic push
  150. #pragma clang diagnostic ignored "-Wformat-security"
  151. OCMExpect([[self.mock stub] logWithLevel:GULLoggerLevelError
  152. withService:OCMOCK_ANY
  153. isForced:YES
  154. withCode:OCMOCK_ANY
  155. withMessage:OCMOCK_ANY]);
  156. self.osLogger.logLevel = GULLoggerLevelMin - 1;
  157. OCMExpect([[self.mock stub] logWithLevel:GULLoggerLevelError
  158. withService:OCMOCK_ANY
  159. isForced:YES
  160. withCode:OCMOCK_ANY
  161. withMessage:OCMOCK_ANY]);
  162. #pragma clang diagnostic push
  163. self.osLogger.logLevel = GULLoggerLevelMax + 1;
  164. OCMVerifyAll(self.mock);
  165. }
  166. - (void)testLogLevelAppStore {
  167. // When not from the App Store, all log levels should be allowed.
  168. [self setAppStoreTo:NO];
  169. self.osLogger.logLevel = GULLoggerLevelMin;
  170. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelMin);
  171. self.osLogger.logLevel = GULLoggerLevelMax;
  172. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelMax);
  173. // When from the App store, levels that are Notice or above, should be silently ignored.
  174. [self setAppStoreTo:YES];
  175. self.osLogger.logLevel = GULLoggerLevelError;
  176. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelError);
  177. self.osLogger.logLevel = GULLoggerLevelWarning;
  178. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning);
  179. self.osLogger.logLevel = GULLoggerLevelNotice;
  180. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning);
  181. self.osLogger.logLevel = GULLoggerLevelInfo;
  182. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning);
  183. self.osLogger.logLevel = GULLoggerLevelDebug;
  184. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning);
  185. }
  186. - (void)testForceDebug {
  187. [self partialMockLogger];
  188. [self setAppStoreTo:NO];
  189. XCTAssertFalse(self.osLogger.forcedDebug);
  190. GULLoggerForceDebug();
  191. XCTAssertTrue(self.osLogger.forcedDebug);
  192. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelDebug);
  193. }
  194. - (void)testForceDebugAppStore {
  195. [self partialMockLogger];
  196. [self setAppStoreTo:YES];
  197. self.osLogger.logLevel = GULLoggerLevelWarning;
  198. XCTAssertFalse(self.osLogger.forcedDebug);
  199. GULLoggerForceDebug();
  200. XCTAssertFalse(self.osLogger.forcedDebug);
  201. XCTAssertEqual(self.osLogger.logLevel, GULLoggerLevelWarning);
  202. }
  203. - (void)testLoggingValidNoVarArgs {
  204. [self.osLogger initializeLogger];
  205. XCTAssert(self.osLogger.categoryLoggers.count == 0);
  206. NSString *message = [NSUUID UUID].UUIDString;
  207. GULOSLoggerExpectation *expectation =
  208. [[GULOSLoggerExpectation alloc] initWithLog:nil type:OS_LOG_TYPE_DEFAULT message:message];
  209. [sExpectations addObject:expectation];
  210. [self.osLogger logWithLevel:GULLoggerLevelNotice
  211. withService:kService
  212. isForced:NO
  213. withCode:kCode
  214. withMessage:message];
  215. [self waitForExpectations:sExpectations timeout:kTimeout];
  216. }
  217. - (void)testLoggingValidWithVarArgs {
  218. [self.osLogger initializeLogger];
  219. XCTAssert(self.osLogger.categoryLoggers.count == 0);
  220. NSString *message = [NSUUID UUID].UUIDString;
  221. GULOSLoggerExpectation *expectation =
  222. [[GULOSLoggerExpectation alloc] initWithLog:nil type:OS_LOG_TYPE_DEFAULT message:message];
  223. [sExpectations addObject:expectation];
  224. [self.osLogger logWithLevel:GULLoggerLevelNotice
  225. withService:kService
  226. isForced:NO
  227. withCode:kCode
  228. withMessage:@"%@", message];
  229. [self waitForExpectations:sExpectations timeout:kTimeout];
  230. }
  231. @end
  232. NS_ASSUME_NONNULL_END