FIRCoreDiagnosticsTest.m 14 KB

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