FIRCoreDiagnosticsTest.m 13 KB

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