FIRCoreDiagnosticsTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #if TARGET_OS_IOS || TARGET_OS_TV
  18. #import <UIKit/UIKit.h>
  19. #endif // TARGET_OS_IOS || TARGET_OS_TV
  20. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  21. #import <GoogleUtilities/GULHeartbeatDateStorage.h>
  22. #import <GoogleUtilities/GULUserDefaults.h>
  23. #import <OCMock/OCMock.h>
  24. #import <nanopb/pb_decode.h>
  25. #import <nanopb/pb_encode.h>
  26. #import "GoogleDataTransport/GDTCORLibrary/Internal/GoogleDataTransportInternal.h"
  27. #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"
  28. #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"
  29. #import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"
  30. extern NSString *const kFIRLastCheckinDateKey;
  31. static NSString *const kGoogleAppID = @"1:123:ios:123abc";
  32. static NSString *const kBundleID = @"com.google.FirebaseSDKTests";
  33. static NSString *const kLibraryVersionID = @"1.2.3";
  34. static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";
  35. #pragma mark - Testing interfaces
  36. @interface FIRCoreDiagnostics : NSObject
  37. // Initialization.
  38. + (instancetype)sharedInstance;
  39. - (instancetype)initWithTransport:(GDTCORTransport *)transport
  40. heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage;
  41. // Properties.
  42. @property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;
  43. @property(nonatomic, readonly) GDTCORTransport *transport;
  44. @property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;
  45. // Install string helpers.
  46. + (NSString *)installString;
  47. + (BOOL)writeString:(NSString *)string toURL:(NSURL *)filePathURL;
  48. + (NSString *)stringAtURL:(NSURL *)filePathURL;
  49. // Metadata helpers.
  50. + (NSString *)deviceModel;
  51. // nanopb helper functions.
  52. extern pb_bytes_array_t *FIREncodeString(NSString *string);
  53. extern pb_bytes_array_t *FIREncodeData(NSData *data);
  54. extern logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum(
  55. NSString *serviceString);
  56. // Proto population functions.
  57. extern void FIRPopulateProtoWithInfoFromUserInfoParams(
  58. logs_proto_mobilesdk_ios_ICoreConfiguration *config,
  59. NSDictionary<NSString *, id> *diagnosticObjects);
  60. extern void FIRPopulateProtoWithCommonInfoFromApp(
  61. logs_proto_mobilesdk_ios_ICoreConfiguration *config,
  62. NSDictionary<NSString *, id> *diagnosticObjects);
  63. extern void FIRPopulateProtoWithInstalledServices(
  64. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  65. extern void FIRPopulateProtoWithNumberOfLinkedFrameworks(
  66. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  67. extern void FIRPopulateProtoWithInfoPlistValues(
  68. logs_proto_mobilesdk_ios_ICoreConfiguration *config);
  69. // FIRCoreDiagnosticsInterop.
  70. + (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
  71. - (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
  72. @end
  73. #pragma mark - Testing classes
  74. @interface FIRCoreDiagnosticsTestData : NSObject <FIRCoreDiagnosticsData>
  75. @end
  76. @implementation FIRCoreDiagnosticsTestData
  77. @synthesize diagnosticObjects = _diagnosticObjects;
  78. - (instancetype)init {
  79. self = [super init];
  80. if (self) {
  81. _diagnosticObjects =
  82. @{kFIRCDGoogleAppIDKey : kGoogleAppID, @"BUNDLE_ID" : kBundleID, kFIRCDllAppsCountKey : @1};
  83. }
  84. return self;
  85. }
  86. @end
  87. @interface FIRCoreDiagnosticsLog : NSObject <GDTCOREventDataObject>
  88. @property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config;
  89. - (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config;
  90. @end
  91. #pragma mark - Tests
  92. @interface FIRCoreDiagnosticsTest : XCTestCase
  93. @property(nonatomic) id optionsInstanceMock;
  94. @property(nonatomic) NSDictionary<NSString *, id> *expectedUserInfo;
  95. @property(nonatomic) FIRCoreDiagnostics *diagnostics;
  96. @property(nonatomic) id mockDateStorage;
  97. @property(nonatomic) id mockTransport;
  98. @end
  99. @implementation FIRCoreDiagnosticsTest
  100. - (void)setUp {
  101. [super setUp];
  102. self.mockTransport = OCMClassMock([GDTCORTransport class]);
  103. OCMStub([self.mockTransport eventForTransport])
  104. .andReturn([[GDTCOREvent alloc] initWithMappingID:@"111" target:2]);
  105. self.mockDateStorage = OCMClassMock([GULHeartbeatDateStorage class]);
  106. self.diagnostics = [[FIRCoreDiagnostics alloc] initWithTransport:self.mockTransport
  107. heartbeatDateStorage:self.mockDateStorage];
  108. }
  109. - (void)tearDown {
  110. self.diagnostics = nil;
  111. self.mockTransport = nil;
  112. self.mockDateStorage = nil;
  113. [super tearDown];
  114. }
  115. /** Tests populating the proto correctly. */
  116. - (void)testProtoPopulation {
  117. logs_proto_mobilesdk_ios_ICoreConfiguration icoreConfiguration =
  118. logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
  119. FIRPopulateProtoWithCommonInfoFromApp(&icoreConfiguration, @{
  120. kFIRCDllAppsCountKey : @1,
  121. kFIRCDGoogleAppIDKey : kGoogleAppID,
  122. kFIRCDBundleIDKey : kBundleID,
  123. kFIRCDLibraryVersionIDKey : kLibraryVersionID,
  124. kFIRCDUsingOptionsFromDefaultPlistKey : @YES
  125. });
  126. icoreConfiguration.using_gdt = 1;
  127. icoreConfiguration.has_using_gdt = 1;
  128. FIRPopulateProtoWithNumberOfLinkedFrameworks(&icoreConfiguration);
  129. FIRPopulateProtoWithInfoPlistValues(&icoreConfiguration);
  130. icoreConfiguration.configuration_type =
  131. logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
  132. logs_proto_mobilesdk_ios_ICoreConfiguration icoreExpectedConfiguration =
  133. logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
  134. [self populateProto:&icoreExpectedConfiguration];
  135. FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreConfiguration];
  136. FIRCoreDiagnosticsLog *expectedLog =
  137. [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreExpectedConfiguration];
  138. XCTAssert([[log transportBytes] isEqualToData:[expectedLog transportBytes]]);
  139. // A pb_release here should not be necessary here, as FIRCoreDiagnosticsLog should do it.
  140. }
  141. // Populates the ICoreConfiguration proto.
  142. - (void)populateProto:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config {
  143. NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
  144. NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @"";
  145. NSString *sdkVersion = info[@"DTSDKBuild"] ?: @"";
  146. NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion];
  147. config->using_gdt = 1;
  148. config->has_using_gdt = 1;
  149. config->configuration_type = logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
  150. config->icore_version = FIREncodeString(kLibraryVersionID);
  151. config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE;
  152. config->has_pod_name = 1;
  153. config->app_id = FIREncodeString(kGoogleAppID);
  154. config->bundle_id = FIREncodeString(kBundleID);
  155. config->device_model = FIREncodeString([GULAppEnvironmentUtil deviceModel]);
  156. config->os_version = FIREncodeString([GULAppEnvironmentUtil systemVersion]);
  157. config->app_count = 1;
  158. config->has_app_count = 1;
  159. config->use_default_app = 1;
  160. config->has_use_default_app = 1;
  161. int numFrameworks = -1; // Subtract the app binary itself.
  162. unsigned int numImages;
  163. const char **imageNames = objc_copyImageNames(&numImages);
  164. for (unsigned int i = 0; i < numImages; i++) {
  165. NSString *imageName = [NSString stringWithUTF8String:imageNames[i]];
  166. if ([imageName rangeOfString:@"System/Library"].length != 0 // Apple .frameworks
  167. || [imageName rangeOfString:@"Developer/Library"].length != 0 // Xcode debug .frameworks
  168. || [imageName rangeOfString:@"usr/lib"].length != 0) { // Public .dylibs
  169. continue;
  170. }
  171. numFrameworks++;
  172. }
  173. free(imageNames);
  174. config->dynamic_framework_count = numFrameworks;
  175. config->has_dynamic_framework_count = 1;
  176. config->apple_framework_version = FIREncodeString(combinedVersions);
  177. NSString *minVersion = [[NSBundle mainBundle] infoDictionary][@"MinimumOSVersion"];
  178. if (minVersion) {
  179. config->min_supported_ios_version = FIREncodeString(minVersion);
  180. }
  181. config->using_zip_file = 0;
  182. config->has_using_zip_file = 1;
  183. config->deployment_type = logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS;
  184. config->has_deployment_type = 1;
  185. config->deployed_in_app_store = 0;
  186. config->has_deployed_in_app_store = 1;
  187. config->swizzling_enabled = 1;
  188. config->has_swizzling_enabled = 1;
  189. }
  190. #pragma mark - Heartbeats
  191. - (void)testHeartbeatNotSentTheSameDay {
  192. NSCalendar *calendar = [NSCalendar currentCalendar];
  193. NSCalendarUnit unitFlags = NSCalendarUnitDay | NSCalendarUnitYear | NSCalendarUnitMonth;
  194. NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:[NSDate date]];
  195. // Verify start of the day
  196. NSDate *startOfTheDay = [calendar dateFromComponents:dateComponents];
  197. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  198. .andReturn(startOfTheDay);
  199. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  200. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  201. [self assertEventSentWithHeartbeat:NO];
  202. // Verify middle of the day
  203. dateComponents.hour = 12;
  204. NSDate *middleOfTheDay = [calendar dateFromComponents:dateComponents];
  205. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  206. .andReturn(middleOfTheDay);
  207. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  208. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  209. [self assertEventSentWithHeartbeat:NO];
  210. // Verify end of the day
  211. dateComponents.hour = 0;
  212. dateComponents.day += 1;
  213. NSDate *startOfNextDay = [calendar dateFromComponents:dateComponents];
  214. NSDate *endOfTheDay = [startOfNextDay dateByAddingTimeInterval:-1];
  215. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  216. .andReturn(endOfTheDay);
  217. OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
  218. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  219. [self assertEventSentWithHeartbeat:NO];
  220. }
  221. - (void)testHeartbeatSentNoPreviousCheckin {
  222. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  223. .andReturn(nil);
  224. OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
  225. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  226. [self assertEventSentWithHeartbeat:YES];
  227. }
  228. - (void)testHeartbeatSentNextDayDefaultApp {
  229. NSDate *startOfToday = [[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]];
  230. NSDate *endOfYesterday = [startOfToday dateByAddingTimeInterval:-1];
  231. OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
  232. .andReturn(endOfYesterday);
  233. OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
  234. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  235. [self assertEventSentWithHeartbeat:YES];
  236. }
  237. #pragma mark - Singleton
  238. - (void)testSharedInstanceDateStorageProperlyInitialized {
  239. FIRCoreDiagnostics *sharedInstance = [FIRCoreDiagnostics sharedInstance];
  240. XCTAssertNotNil(sharedInstance.heartbeatDateStorage);
  241. XCTAssert([sharedInstance.heartbeatDateStorage isKindOfClass:[GULHeartbeatDateStorage class]]);
  242. NSDate *date = [NSDate date];
  243. XCTAssertTrue([sharedInstance.heartbeatDateStorage
  244. setHearbeatDate:date
  245. forTag:kFIRCoreDiagnosticsHeartbeatTag]);
  246. XCTAssertEqualObjects(
  247. [sharedInstance.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag],
  248. date);
  249. }
  250. #pragma mark - Helpers
  251. - (void)assertEventSentWithHeartbeat:(BOOL)isHeartbeat {
  252. [self expectEventToBeSentToTransportWithHeartbeat:isHeartbeat];
  253. [self.diagnostics sendDiagnosticsData:[[FIRCoreDiagnosticsTestData alloc] init]];
  254. OCMVerifyAllWithDelay(self.mockTransport, 0.5);
  255. OCMVerifyAllWithDelay(self.mockDateStorage, 0.5);
  256. }
  257. - (void)expectEventToBeSentToTransportWithHeartbeat:(BOOL)isHeartbeat {
  258. id eventValidation = [OCMArg checkWithBlock:^BOOL(GDTCOREvent *obj) {
  259. XCTAssert([obj isKindOfClass:[GDTCOREvent class]]);
  260. FIRCoreDiagnosticsLog *dataObject = obj.dataObject;
  261. XCTAssert([dataObject isKindOfClass:[FIRCoreDiagnosticsLog class]]);
  262. BOOL isSentEventHeartbeat =
  263. dataObject.config.sdk_name == logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;
  264. isSentEventHeartbeat = isSentEventHeartbeat && dataObject.config.has_sdk_name;
  265. XCTAssertEqual(isSentEventHeartbeat, isHeartbeat);
  266. return YES;
  267. }];
  268. OCMExpect([self.mockTransport sendTelemetryEvent:eventValidation]);
  269. }
  270. - (BOOL)isDate:(NSDate *)date1 approximatelyEqual:(NSDate *)date2 {
  271. NSTimeInterval precision = 10;
  272. NSTimeInterval diff = ABS([date1 timeIntervalSinceDate:date2]);
  273. return diff <= precision;
  274. }
  275. - (id)OCMArgToCheckDateEqualTo:(NSDate *)date {
  276. return [OCMArg checkWithBlock:^BOOL(id obj) {
  277. XCTAssert([obj isKindOfClass:[NSDate class]], "%@", self.name);
  278. return [self isDate:obj approximatelyEqual:date];
  279. }];
  280. }
  281. @end