| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- /*
- * Copyright 2019 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <XCTest/XCTest.h>
- #if TARGET_OS_IOS || TARGET_OS_TV
- #import <UIKit/UIKit.h>
- #endif // TARGET_OS_IOS || TARGET_OS_TV
- #import <GoogleUtilities/GULAppEnvironmentUtil.h>
- #import <GoogleUtilities/GULHeartbeatDateStorage.h>
- #import <GoogleUtilities/GULUserDefaults.h>
- #import <OCMock/OCMock.h>
- #import <nanopb/pb_decode.h>
- #import <nanopb/pb_encode.h>
- #import "GoogleDataTransport/GDTCORLibrary/Internal/GoogleDataTransportInternal.h"
- #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"
- #import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"
- #import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"
- extern NSString *const kFIRLastCheckinDateKey;
- static NSString *const kGoogleAppID = @"1:123:ios:123abc";
- static NSString *const kBundleID = @"com.google.FirebaseSDKTests";
- static NSString *const kLibraryVersionID = @"1.2.3";
- static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";
- #pragma mark - Testing interfaces
- @interface FIRCoreDiagnostics : NSObject
- // Initialization.
- + (instancetype)sharedInstance;
- - (instancetype)initWithTransport:(GDTCORTransport *)transport
- heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage;
- // Properties.
- @property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;
- @property(nonatomic, readonly) GDTCORTransport *transport;
- @property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;
- // Install string helpers.
- + (NSString *)installString;
- + (BOOL)writeString:(NSString *)string toURL:(NSURL *)filePathURL;
- + (NSString *)stringAtURL:(NSURL *)filePathURL;
- // Metadata helpers.
- + (NSString *)deviceModel;
- // nanopb helper functions.
- extern pb_bytes_array_t *FIREncodeString(NSString *string);
- extern pb_bytes_array_t *FIREncodeData(NSData *data);
- extern logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum(
- NSString *serviceString);
- // Proto population functions.
- extern void FIRPopulateProtoWithInfoFromUserInfoParams(
- logs_proto_mobilesdk_ios_ICoreConfiguration *config,
- NSDictionary<NSString *, id> *diagnosticObjects);
- extern void FIRPopulateProtoWithCommonInfoFromApp(
- logs_proto_mobilesdk_ios_ICoreConfiguration *config,
- NSDictionary<NSString *, id> *diagnosticObjects);
- extern void FIRPopulateProtoWithInstalledServices(
- logs_proto_mobilesdk_ios_ICoreConfiguration *config);
- extern void FIRPopulateProtoWithNumberOfLinkedFrameworks(
- logs_proto_mobilesdk_ios_ICoreConfiguration *config);
- extern void FIRPopulateProtoWithInfoPlistValues(
- logs_proto_mobilesdk_ios_ICoreConfiguration *config);
- // FIRCoreDiagnosticsInterop.
- + (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
- - (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData;
- @end
- #pragma mark - Testing classes
- @interface FIRCoreDiagnosticsTestData : NSObject <FIRCoreDiagnosticsData>
- @end
- @implementation FIRCoreDiagnosticsTestData
- @synthesize diagnosticObjects = _diagnosticObjects;
- - (instancetype)init {
- self = [super init];
- if (self) {
- _diagnosticObjects =
- @{kFIRCDGoogleAppIDKey : kGoogleAppID, @"BUNDLE_ID" : kBundleID, kFIRCDllAppsCountKey : @1};
- }
- return self;
- }
- @end
- @interface FIRCoreDiagnosticsLog : NSObject <GDTCOREventDataObject>
- @property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config;
- - (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config;
- @end
- #pragma mark - Tests
- @interface FIRCoreDiagnosticsTest : XCTestCase
- @property(nonatomic) id optionsInstanceMock;
- @property(nonatomic) NSDictionary<NSString *, id> *expectedUserInfo;
- @property(nonatomic) FIRCoreDiagnostics *diagnostics;
- @property(nonatomic) id mockDateStorage;
- @property(nonatomic) id mockTransport;
- @end
- @implementation FIRCoreDiagnosticsTest
- - (void)setUp {
- [super setUp];
- self.mockTransport = OCMClassMock([GDTCORTransport class]);
- OCMStub([self.mockTransport eventForTransport])
- .andReturn([[GDTCOREvent alloc] initWithMappingID:@"111" target:2]);
- self.mockDateStorage = OCMClassMock([GULHeartbeatDateStorage class]);
- self.diagnostics = [[FIRCoreDiagnostics alloc] initWithTransport:self.mockTransport
- heartbeatDateStorage:self.mockDateStorage];
- }
- - (void)tearDown {
- self.diagnostics = nil;
- self.mockTransport = nil;
- self.mockDateStorage = nil;
- [super tearDown];
- }
- /** Tests populating the proto correctly. */
- - (void)testProtoPopulation {
- logs_proto_mobilesdk_ios_ICoreConfiguration icoreConfiguration =
- logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
- FIRPopulateProtoWithCommonInfoFromApp(&icoreConfiguration, @{
- kFIRCDllAppsCountKey : @1,
- kFIRCDGoogleAppIDKey : kGoogleAppID,
- kFIRCDBundleIDKey : kBundleID,
- kFIRCDLibraryVersionIDKey : kLibraryVersionID,
- kFIRCDUsingOptionsFromDefaultPlistKey : @YES
- });
- icoreConfiguration.using_gdt = 1;
- icoreConfiguration.has_using_gdt = 1;
- FIRPopulateProtoWithInfoPlistValues(&icoreConfiguration);
- icoreConfiguration.configuration_type =
- logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
- logs_proto_mobilesdk_ios_ICoreConfiguration icoreExpectedConfiguration =
- logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;
- [self populateProto:&icoreExpectedConfiguration];
- FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreConfiguration];
- FIRCoreDiagnosticsLog *expectedLog =
- [[FIRCoreDiagnosticsLog alloc] initWithConfig:icoreExpectedConfiguration];
- XCTAssert([[log transportBytes] isEqualToData:[expectedLog transportBytes]]);
- // A pb_release here should not be necessary here, as FIRCoreDiagnosticsLog should do it.
- }
- // Populates the ICoreConfiguration proto.
- - (void)populateProto:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config {
- NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
- NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @"";
- NSString *sdkVersion = info[@"DTSDKBuild"] ?: @"";
- NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion];
- config->using_gdt = 1;
- config->has_using_gdt = 1;
- config->configuration_type = logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;
- config->icore_version = FIREncodeString(kLibraryVersionID);
- config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE;
- config->has_pod_name = 1;
- config->app_id = FIREncodeString(kGoogleAppID);
- config->bundle_id = FIREncodeString(kBundleID);
- config->device_model = FIREncodeString([GULAppEnvironmentUtil deviceModel]);
- config->os_version = FIREncodeString([GULAppEnvironmentUtil systemVersion]);
- config->app_count = 1;
- config->has_app_count = 1;
- config->use_default_app = 1;
- config->has_use_default_app = 1;
- config->has_dynamic_framework_count = 0; // Removed from payload.
- config->apple_framework_version = FIREncodeString(combinedVersions);
- NSString *minVersion = [[NSBundle mainBundle] infoDictionary][@"MinimumOSVersion"];
- if (minVersion) {
- config->min_supported_ios_version = FIREncodeString(minVersion);
- }
- config->using_zip_file = 0;
- config->has_using_zip_file = 1;
- config->deployment_type = logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS;
- config->has_deployment_type = 1;
- config->deployed_in_app_store = 0;
- config->has_deployed_in_app_store = 1;
- config->swizzling_enabled = 1;
- config->has_swizzling_enabled = 1;
- }
- #pragma mark - Heartbeats
- - (void)testHeartbeatNotSentTheSameDay {
- NSCalendar *calendar = [NSCalendar currentCalendar];
- NSCalendarUnit unitFlags = NSCalendarUnitDay | NSCalendarUnitYear | NSCalendarUnitMonth;
- NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:[NSDate date]];
- // Verify start of the day
- NSDate *startOfTheDay = [calendar dateFromComponents:dateComponents];
- OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
- .andReturn(startOfTheDay);
- OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- [self assertEventSentWithHeartbeat:NO];
- // Verify middle of the day
- dateComponents.hour = 12;
- NSDate *middleOfTheDay = [calendar dateFromComponents:dateComponents];
- OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
- .andReturn(middleOfTheDay);
- OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- [self assertEventSentWithHeartbeat:NO];
- // Verify end of the day
- dateComponents.hour = 0;
- dateComponents.day += 1;
- NSDate *startOfNextDay = [calendar dateFromComponents:dateComponents];
- NSDate *endOfTheDay = [startOfNextDay dateByAddingTimeInterval:-1];
- OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
- .andReturn(endOfTheDay);
- OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- [self assertEventSentWithHeartbeat:NO];
- }
- - (void)testHeartbeatSentNoPreviousCheckin {
- OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
- .andReturn(nil);
- OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- [self assertEventSentWithHeartbeat:YES];
- }
- - (void)testHeartbeatSentNextDayDefaultApp {
- NSDate *startOfToday = [[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]];
- NSDate *endOfYesterday = [startOfToday dateByAddingTimeInterval:-1];
- OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
- .andReturn(endOfYesterday);
- OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- [self assertEventSentWithHeartbeat:YES];
- }
- #pragma mark - Singleton
- - (void)testSharedInstanceDateStorageProperlyInitialized {
- FIRCoreDiagnostics *sharedInstance = [FIRCoreDiagnostics sharedInstance];
- XCTAssertNotNil(sharedInstance.heartbeatDateStorage);
- XCTAssert([sharedInstance.heartbeatDateStorage isKindOfClass:[GULHeartbeatDateStorage class]]);
- NSDate *date = [NSDate date];
- XCTAssertTrue([sharedInstance.heartbeatDateStorage
- setHearbeatDate:date
- forTag:kFIRCoreDiagnosticsHeartbeatTag]);
- XCTAssertEqualObjects(
- [sharedInstance.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag],
- date);
- }
- #pragma mark - Helpers
- - (void)assertEventSentWithHeartbeat:(BOOL)isHeartbeat {
- [self expectEventToBeSentToTransportWithHeartbeat:isHeartbeat];
- [self.diagnostics sendDiagnosticsData:[[FIRCoreDiagnosticsTestData alloc] init]];
- OCMVerifyAllWithDelay(self.mockTransport, 0.5);
- OCMVerifyAllWithDelay(self.mockDateStorage, 0.5);
- }
- - (void)expectEventToBeSentToTransportWithHeartbeat:(BOOL)isHeartbeat {
- id eventValidation = [OCMArg checkWithBlock:^BOOL(GDTCOREvent *obj) {
- XCTAssert([obj isKindOfClass:[GDTCOREvent class]]);
- FIRCoreDiagnosticsLog *dataObject = obj.dataObject;
- XCTAssert([dataObject isKindOfClass:[FIRCoreDiagnosticsLog class]]);
- BOOL isSentEventHeartbeat =
- dataObject.config.sdk_name == logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;
- isSentEventHeartbeat = isSentEventHeartbeat && dataObject.config.has_sdk_name;
- XCTAssertEqual(isSentEventHeartbeat, isHeartbeat);
- return YES;
- }];
- OCMExpect([self.mockTransport sendTelemetryEvent:eventValidation]);
- }
- - (BOOL)isDate:(NSDate *)date1 approximatelyEqual:(NSDate *)date2 {
- NSTimeInterval precision = 10;
- NSTimeInterval diff = ABS([date1 timeIntervalSinceDate:date2]);
- return diff <= precision;
- }
- - (id)OCMArgToCheckDateEqualTo:(NSDate *)date {
- return [OCMArg checkWithBlock:^BOOL(id obj) {
- XCTAssert([obj isKindOfClass:[NSDate class]], "%@", self.name);
- return [self isDate:obj approximatelyEqual:date];
- }];
- }
- @end
|