// Copyright 2021 Google LLC // // 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 #import "FirebasePerformance/Sources/FIRPerformance+Internal.h" #import "FirebasePerformance/Sources/FPRDataUtils.h" #import "FirebasePerformance/Sources/FPRNanoPbUtils.h" #import "FirebasePerformance/Sources/Public/FirebasePerformance/FIRPerformance.h" #import "FirebasePerformance/Sources/Common/FPRConstants.h" #import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace+Private.h" #import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h" #import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h" #import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h" #import "FirebasePerformance/Sources/Gauges/CPU/FPRCPUGaugeData.h" #import "FirebasePerformance/Sources/Gauges/Memory/FPRMemoryGaugeData.h" #import "FirebasePerformance/Tests/Unit/FPRTestCase.h" #import "FirebasePerformance/Tests/Unit/FPRTestUtils.h" #import @interface FPRNanoPbUtilsTest : FPRTestCase @end @implementation FPRNanoPbUtilsTest - (void)setUp { [super setUp]; FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:YES]; } - (void)tearDown { [super tearDown]; FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setDataCollectionEnabled:NO]; } /** Validates that a firebase_perf_v1_PerfMetric creation is successful. */ - (void)testPerfMetricMessageCreation { NSString *appID = @"RandomAppID"; firebase_perf_v1_PerfMetric perfMetric = FPRGetPerfMetricMessage(appID); XCTAssertEqualObjects(FPRDecodeString(perfMetric.application_info.google_app_id), appID); } /** Tests if the application information is populated when creating a firebase_perf_v1_PerfMetric. */ - (void)testApplicationInfoMessage { firebase_perf_v1_PerfMetric event = FPRGetPerfMetricMessage(@"appid"); firebase_perf_v1_ApplicationInfo appInfo = event.application_info; XCTAssertEqualObjects(FPRDecodeString(appInfo.google_app_id), @"appid"); XCTAssertTrue(appInfo.ios_app_info.sdk_version != NULL); XCTAssertTrue(appInfo.has_ios_app_info); XCTAssertTrue(appInfo.ios_app_info.bundle_short_version != NULL); XCTAssertTrue(appInfo.ios_app_info.mcc_mnc == NULL || appInfo.ios_app_info.mcc_mnc->size == 6); XCTAssertTrue(appInfo.ios_app_info.has_network_connection_info); XCTAssertTrue(appInfo.ios_app_info.network_connection_info.has_network_type); XCTAssertTrue(appInfo.ios_app_info.network_connection_info.network_type != firebase_perf_v1_NetworkConnectionInfo_NetworkType_NONE); if (appInfo.ios_app_info.network_connection_info.network_type == firebase_perf_v1_NetworkConnectionInfo_NetworkType_MOBILE) { XCTAssertTrue(appInfo.ios_app_info.network_connection_info.mobile_subtype != firebase_perf_v1_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE); } } /** Validates that ApplicationInfoMessage carries global attributes. */ - (void)testApplicationInfoMessageWithAttributes { FIRPerformance *performance = [FIRPerformance sharedInstance]; [performance setValue:@"bar1" forAttribute:@"foo1"]; [performance setValue:@"bar2" forAttribute:@"foo2"]; firebase_perf_v1_PerfMetric event = FPRGetPerfMetricMessage(@"appid"); firebase_perf_v1_ApplicationInfo appInfo = event.application_info; XCTAssertEqual(appInfo.custom_attributes_count, 2); NSDictionary *attributes = FPRDecodeStringToStringMap( (StringToStringMap *)appInfo.custom_attributes, appInfo.custom_attributes_count); XCTAssertEqualObjects(attributes[@"foo1"], @"bar1"); XCTAssertEqualObjects(attributes[@"foo2"], @"bar2"); [performance removeAttribute:@"foo1"]; [performance removeAttribute:@"foo2"]; } /** Validates that a valid FIRTrace object to firebase_perf_v1_TraceMetric conversion is successful. */ - (void)testTraceMetricMessageCreation { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; [trace startStageNamed:@"1"]; [trace startStageNamed:@"2"]; [trace incrementMetric:@"c1" byInt:2]; [trace setValue:@"bar" forAttribute:@"foo"]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertEqualObjects(FPRDecodeString(traceMetric.name), @"Random"); XCTAssertEqual(traceMetric.subtraces_count, 2); XCTAssertEqual(traceMetric.counters_count, 1); NSDictionary *counters = FPRDecodeStringToNumberMap((StringToNumberMap *)traceMetric.counters, traceMetric.counters_count); XCTAssertEqual([counters[@"c1"] intValue], 2); XCTAssertEqualObjects(FPRDecodeString(traceMetric.subtraces[0].name), @"1"); XCTAssertEqualObjects(FPRDecodeString(traceMetric.subtraces[1].name), @"2"); XCTAssertTrue(traceMetric.custom_attributes != NULL); XCTAssertEqual(traceMetric.custom_attributes_count, 1); NSDictionary *attributes = FPRDecodeStringToStringMap( (StringToStringMap *)traceMetric.custom_attributes, traceMetric.custom_attributes_count); XCTAssertEqualObjects(attributes[@"foo"], @"bar"); } /** Validates that a valid FIRTrace object to firebase_perf_v1_TraceMetric conversion has required * fields. */ - (void)testTraceMetricMessageCreationHasRequiredFields { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; [trace incrementMetric:@"c1" byInt:2]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertTrue(traceMetric.name != NULL); XCTAssertTrue(traceMetric.has_client_start_time_us); XCTAssertTrue(traceMetric.has_duration_us); XCTAssertTrue(traceMetric.has_is_auto); } /** Validates the session details inside trace metric. */ - (void)testTraceMetricMessageHasSessionDetails { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; [trace incrementMetric:@"c1" byInt:2]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsGauges]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertTrue(traceMetric.perf_sessions != NULL); XCTAssertTrue(traceMetric.perf_sessions_count >= 2); } /** Validates that the FPRNetworkTrace object to firebase_perf_v1_NetworkRequestMetric conversion is * successful. */ - (void)testNetworkTraceMetricMessage { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertEqualObjects(FPRDecodeString(networkMetric.url), URL.absoluteString); XCTAssertEqual(networkMetric.http_method, firebase_perf_v1_NetworkRequestMetric_HttpMethod_GET); XCTAssertEqual( networkMetric.network_client_error_reason, firebase_perf_v1_NetworkRequestMetric_NetworkClientErrorReason_GENERIC_CLIENT_ERROR); XCTAssertEqual(networkMetric.http_response_code, 404); XCTAssertEqualObjects(FPRDecodeString(networkMetric.response_content_type), @"text/json"); } /** Validates that the FPRNetworkTrace object to Proto conversion has required fields for a valid * response. */ - (void)testNetworkTraceMetricMessageHasAllRequiredFields { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertTrue(networkMetric.url != NULL); XCTAssertTrue(networkMetric.has_client_start_time_us); XCTAssertTrue(networkMetric.has_http_method); XCTAssertTrue(networkMetric.has_response_payload_bytes); XCTAssertTrue(networkMetric.has_network_client_error_reason); XCTAssertTrue(networkMetric.has_http_response_code); XCTAssertTrue(networkMetric.response_content_type != NULL); XCTAssertTrue(networkMetric.has_time_to_response_completed_us); } /** Validates that application process state conversion to firebase_perf_v1_ApplicationProcessState * enum type is successful. */ - (void)testApplicationProcessStateConversion { XCTAssertEqual(firebase_perf_v1_ApplicationProcessState_BACKGROUND, FPRApplicationProcessState(FPRTraceStateBackgroundOnly)); XCTAssertEqual(firebase_perf_v1_ApplicationProcessState_FOREGROUND, FPRApplicationProcessState(FPRTraceStateForegroundOnly)); XCTAssertEqual(firebase_perf_v1_ApplicationProcessState_FOREGROUND_BACKGROUND, FPRApplicationProcessState(FPRTraceStateBackgroundAndForeground)); XCTAssertEqual(firebase_perf_v1_ApplicationProcessState_APPLICATION_PROCESS_STATE_UNKNOWN, FPRApplicationProcessState(FPRTraceStateUnknown)); // Try with some random value should say the application state is unknown. XCTAssertEqual(firebase_perf_v1_ApplicationProcessState_APPLICATION_PROCESS_STATE_UNKNOWN, FPRApplicationProcessState(100)); } #ifdef TARGET_HAS_MOBILE_CONNECTIVITY /** Validates if network object creation works. */ - (void)testNetworkInfoObjectCreation { XCTAssertNotNil(FPRNetworkInfo()); } #endif /** Validates the session details inside trace metric. */ - (void)testNetworkRequestMetricMessageHasSessionDetails { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsGauges]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertTrue(networkMetric.perf_sessions != NULL); XCTAssertTrue(networkMetric.perf_sessions_count >= 2); } /** Validates the gauge metric proto packaging works with proper conversions. */ - (void)testMemoryMetricProtoConversion { NSMutableArray *gauges = [[NSMutableArray alloc] init]; NSDate *date = [NSDate date]; FPRMemoryGaugeData *memoryData = [[FPRMemoryGaugeData alloc] initWithCollectionTime:date heapUsed:5 * 1024 heapAvailable:10 * 1024]; [gauges addObject:memoryData]; firebase_perf_v1_GaugeMetric gaugeMetric = FPRGetGaugeMetric(gauges, @"abc"); XCTAssertEqual(gaugeMetric.cpu_metric_readings_count, 0); XCTAssertEqual(gaugeMetric.ios_memory_readings_count, 1); XCTAssertEqual(gaugeMetric.ios_memory_readings[0].used_app_heap_memory_kb, 5); XCTAssertEqual(gaugeMetric.ios_memory_readings[0].free_app_heap_memory_kb, 10); } /** Validates the gauge metric proto packaging works. */ - (void)testGaugeMetricProtoPacking { NSMutableArray *gauges = [[NSMutableArray alloc] init]; for (int i = 0; i < 5; i++) { NSDate *date = [NSDate date]; FPRCPUGaugeData *cpuData = [[FPRCPUGaugeData alloc] initWithCollectionTime:date systemTime:100 userTime:200]; FPRMemoryGaugeData *memoryData = [[FPRMemoryGaugeData alloc] initWithCollectionTime:date heapUsed:100 heapAvailable:200]; [gauges addObject:cpuData]; [gauges addObject:memoryData]; } firebase_perf_v1_GaugeMetric gaugeMetric = FPRGetGaugeMetric(gauges, @"abc"); XCTAssertEqual(gaugeMetric.cpu_metric_readings_count, 5); XCTAssertEqual(gaugeMetric.ios_memory_readings_count, 5); } /** Validates if the first session is a verbose session for a trace. */ - (void)testOrderingOfSessionsForTrace { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsGauges]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertTrue(traceMetric.perf_sessions != NULL); XCTAssertTrue(traceMetric.perf_sessions_count >= 2); firebase_perf_v1_PerfSession perfSession = traceMetric.perf_sessions[0]; XCTAssertEqual(perfSession.session_verbosity[0], firebase_perf_v1_SessionVerbosity_GAUGES_AND_SYSTEM_EVENTS); XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"b"); } /** Validates the verbosity ordering when no sessions are verbose. */ - (void)testOrderingOfNonVerboseSessionsForTrace { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsNone]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertTrue(traceMetric.perf_sessions != NULL); XCTAssertTrue(traceMetric.perf_sessions_count >= 2); firebase_perf_v1_PerfSession perfSession = traceMetric.perf_sessions[0]; XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"a"); XCTAssertEqual(perfSession.session_verbosity_count, 0); } /** Validates if a session is not verbose, do not populate the session verbosity array. */ - (void)testVerbosityArrayEmptyWhenTheSessionIsNotVerbose { FIRTrace *trace = [[FIRTrace alloc] initWithName:@"Random"]; [trace start]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; trace.activeSessions = [@[ session1 ] mutableCopy]; [trace stop]; firebase_perf_v1_TraceMetric traceMetric = FPRGetTraceMetric(trace); XCTAssertTrue(traceMetric.perf_sessions != NULL); XCTAssertTrue(traceMetric.perf_sessions_count >= 1); firebase_perf_v1_PerfSession perfSession = traceMetric.perf_sessions[0]; XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"a"); XCTAssertEqual(perfSession.session_verbosity_count, 0); } /** Validates if the first session is a verbose session for a network trace. */ - (void)testOrderingOfSessionsForNetworkTrace { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsGauges]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertTrue(networkMetric.perf_sessions != NULL); XCTAssertTrue(networkMetric.perf_sessions_count >= 2); firebase_perf_v1_PerfSession perfSession = networkMetric.perf_sessions[0]; XCTAssertEqual(perfSession.session_verbosity[0], firebase_perf_v1_SessionVerbosity_GAUGES_AND_SYSTEM_EVENTS); XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"b"); } /** Validates the verbosity ordering when no sessions are verbose for a network trace. */ - (void)testOrderingOfNonVerboseSessionsForNetworkTrace { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; FPRSessionDetails *session2 = [[FPRSessionDetails alloc] initWithSessionId:@"b" options:FPRSessionOptionsNone]; trace.activeSessions = [@[ session1, session2 ] mutableCopy]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertTrue(networkMetric.perf_sessions != NULL); XCTAssertTrue(networkMetric.perf_sessions_count >= 2); firebase_perf_v1_PerfSession perfSession = networkMetric.perf_sessions[0]; XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"a"); XCTAssertEqual(perfSession.session_verbosity_count, 0); } /** Validates if a session is not verbose, do not populate the session verbosity array for network * trace. */ - (void)testVerbosityArrayEmptyWhenTheSessionIsNotVerboseForNetworkTrace { NSURL *URL = [NSURL URLWithString:@"https://abc.com"]; NSURLRequest *URLRequest = [NSURLRequest requestWithURL:URL]; FPRNetworkTrace *trace = [[FPRNetworkTrace alloc] initWithURLRequest:URLRequest]; [trace start]; [trace checkpointState:FPRNetworkTraceCheckpointStateInitiated]; [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived]; NSDictionary *headerFields = @{@"Content-Type" : @"text/json"}; NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URLRequest.URL statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:headerFields]; NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-200 userInfo:nil]; FPRSessionDetails *session1 = [[FPRSessionDetails alloc] initWithSessionId:@"a" options:FPRSessionOptionsNone]; trace.activeSessions = [@[ session1 ] mutableCopy]; [trace didReceiveData:[NSData data]]; [trace didCompleteRequestWithResponse:response error:error]; firebase_perf_v1_NetworkRequestMetric networkMetric = FPRGetNetworkRequestMetric(trace); XCTAssertTrue(networkMetric.perf_sessions != NULL); XCTAssertTrue(networkMetric.perf_sessions_count >= 1); firebase_perf_v1_PerfSession perfSession = networkMetric.perf_sessions[0]; XCTAssertEqualObjects(FPRDecodeString(perfSession.session_id), @"a"); XCTAssertEqual(perfSession.session_verbosity_count, 0); } @end