FIRCoreDiagnosticsTest.m 14 KB

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