Files
firebase-ios-sdk/Example/CoreDiagnostics/Tests/FIRCoreDiagnosticsTest.m
T

350 lines
13 KiB
Objective-C
Raw Normal View History

2019-07-18 09:54:06 -07:00
/*
* 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>
2019-07-18 09:54:06 -07:00
#import <OCMock/OCMock.h>
#import <nanopb/pb_decode.h>
#import <nanopb/pb_encode.h>
2020-07-31 11:38:53 -07:00
#import "GoogleDataTransport/GDTCORLibrary/Internal/GoogleDataTransportInternal.h"
#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"
#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"
2019-07-18 09:54:06 -07:00
2020-08-10 07:46:19 -07:00
#import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"
2019-07-18 09:54:06 -07:00
extern NSString *const kFIRAppDiagnosticsNotification;
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";
2019-11-14 15:22:20 -05:00
static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";
2019-07-18 09:54:06 -07:00
#pragma mark - Testing interfaces
@interface FIRCoreDiagnostics : NSObject
// Initialization.
+ (instancetype)sharedInstance;
2019-09-09 15:21:29 -07:00
- (instancetype)initWithTransport:(GDTCORTransport *)transport
2019-11-14 15:22:20 -05:00
heartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage;
2019-07-18 09:54:06 -07:00
// Properties.
@property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;
2019-09-09 15:21:29 -07:00
@property(nonatomic, readonly) GDTCORTransport *transport;
2019-11-14 15:22:20 -05:00
@property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;
2019-07-18 09:54:06 -07:00
// 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
2019-09-09 15:21:29 -07:00
@interface FIRCoreDiagnosticsLog : NSObject <GDTCOREventDataObject>
2019-07-18 09:54:06 -07:00
@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];
2019-09-09 15:21:29 -07:00
self.mockTransport = OCMClassMock([GDTCORTransport class]);
2019-07-18 09:54:06 -07:00
OCMStub([self.mockTransport eventForTransport])
2019-09-09 15:21:29 -07:00
.andReturn([[GDTCOREvent alloc] initWithMappingID:@"111" target:2]);
2019-07-18 09:54:06 -07:00
2019-11-14 15:22:20 -05:00
self.mockDateStorage = OCMClassMock([GULHeartbeatDateStorage class]);
2019-07-18 09:54:06 -07:00
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 {
2019-07-18 09:54:06 -07:00
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;
FIRPopulateProtoWithNumberOfLinkedFrameworks(&icoreConfiguration);
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([FIRCoreDiagnostics 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;
int numFrameworks = -1; // Subtract the app binary itself.
unsigned int numImages;
const char **imageNames = objc_copyImageNames(&numImages);
for (unsigned int i = 0; i < numImages; i++) {
NSString *imageName = [NSString stringWithUTF8String:imageNames[i]];
if ([imageName rangeOfString:@"System/Library"].length != 0 // Apple .frameworks
|| [imageName rangeOfString:@"Developer/Library"].length != 0 // Xcode debug .frameworks
|| [imageName rangeOfString:@"usr/lib"].length != 0) { // Public .dylibs
continue;
}
numFrameworks++;
}
free(imageNames);
config->dynamic_framework_count = numFrameworks;
config->has_dynamic_framework_count = 1;
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];
2019-11-14 15:22:20 -05:00
OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
.andReturn(startOfTheDay);
OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
2019-07-18 09:54:06 -07:00
[self assertEventSentWithHeartbeat:NO];
// Verify middle of the day
dateComponents.hour = 12;
NSDate *middleOfTheDay = [calendar dateFromComponents:dateComponents];
2019-11-14 15:22:20 -05:00
OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
.andReturn(middleOfTheDay);
OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
2019-07-18 09:54:06 -07:00
[self assertEventSentWithHeartbeat:NO];
// Verify end of the day
dateComponents.hour = 0;
dateComponents.day += 1;
NSDate *startOfNextDay = [calendar dateFromComponents:dateComponents];
NSDate *endOfTheDay = [startOfNextDay dateByAddingTimeInterval:-1];
2019-11-14 15:22:20 -05:00
OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
.andReturn(endOfTheDay);
OCMReject([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[OCMArg any]]
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
2019-07-18 09:54:06 -07:00
[self assertEventSentWithHeartbeat:NO];
}
- (void)testHeartbeatSentNoPreviousCheckin {
2019-11-14 15:22:20 -05:00
OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
.andReturn(nil);
OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
2019-07-18 09:54:06 -07:00
[self assertEventSentWithHeartbeat:YES];
}
- (void)testHeartbeatSentNextDayDefaultApp {
NSDate *startOfToday = [[NSCalendar currentCalendar] startOfDayForDate:[NSDate date]];
NSDate *endOfYesterday = [startOfToday dateByAddingTimeInterval:-1];
2019-11-14 15:22:20 -05:00
OCMExpect([self.mockDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag])
.andReturn(endOfYesterday);
OCMExpect([self.mockDateStorage setHearbeatDate:[self OCMArgToCheckDateEqualTo:[NSDate date]]
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
2019-07-18 09:54:06 -07:00
[self assertEventSentWithHeartbeat:YES];
}
#pragma mark - Singleton
- (void)testSharedInstanceDateStorageProperlyInitialized {
FIRCoreDiagnostics *sharedInstance = [FIRCoreDiagnostics sharedInstance];
XCTAssertNotNil(sharedInstance.heartbeatDateStorage);
2019-11-14 15:22:20 -05:00
XCTAssert([sharedInstance.heartbeatDateStorage isKindOfClass:[GULHeartbeatDateStorage class]]);
2019-07-18 09:54:06 -07:00
NSDate *date = [NSDate date];
2019-11-14 15:22:20 -05:00
XCTAssertTrue([sharedInstance.heartbeatDateStorage
setHearbeatDate:date
forTag:kFIRCoreDiagnosticsHeartbeatTag]);
XCTAssertEqualObjects(
[sharedInstance.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag],
date);
2019-07-18 09:54:06 -07:00
}
#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 {
2019-09-09 15:21:29 -07:00
id eventValidation = [OCMArg checkWithBlock:^BOOL(GDTCOREvent *obj) {
XCTAssert([obj isKindOfClass:[GDTCOREvent class]]);
2019-07-18 09:54:06 -07:00
FIRCoreDiagnosticsLog *dataObject = obj.dataObject;
XCTAssert([dataObject isKindOfClass:[FIRCoreDiagnosticsLog class]]);
BOOL isSentEventHeartbeat =
dataObject.config.sdk_name == logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;
2019-07-25 09:34:25 -04:00
isSentEventHeartbeat = isSentEventHeartbeat && dataObject.config.has_sdk_name;
2019-07-18 09:54:06 -07:00
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