From bfc1839ee3cbb014b5b27b8dd290049ec96c2402 Mon Sep 17 00:00:00 2001 From: Gran Date: Thu, 18 Mar 2021 13:06:08 -0700 Subject: [PATCH 001/131] Add test coverage workflow (#8) * Add code coverage workflow. --- .github/workflows/test_coverage.yml | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/test_coverage.yml diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml new file mode 100644 index 00000000..4fd3a1c2 --- /dev/null +++ b/.github/workflows/test_coverage.yml @@ -0,0 +1,79 @@ +name: test_coverage + +on: + pull_request: + # open will be triggered when a pull request is created. + # synchronize will be triggered when a pull request has new commits. + # closed will be triggered when a pull request is closed. + types: [opened, synchronize, closed] + +env: + METRICS_SERVICE_SECRET: ${{ secrets.GHASecretsGPGPassphrase1 }} + +jobs: + pod_lib_lint: + if: github.repository == 'google/GoogleDataTransport' && (github.event.action == 'synchronize' || github.event.action == 'opened' || github.event.pull_request.merged) + runs-on: macos-latest + strategy: + matrix: + target: [ios] + steps: + - uses: actions/checkout@v2 + - name: Setup Scripts Directory + run: ./setup-scripts.sh + - name: Setup Bundler + run: ./scripts/setup_bundler.sh + - name: PodLibLint DataTransport + run: | + scripts/code_coverage_report/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec="Tests-Unit" + - uses: actions/upload-artifact@v2 + with: + name: codecoverage + path: /Users/runner/*.xcresult + manage_test_results: + if: github.repository == 'google/GoogleDataTransport' && (github.event.action == 'synchronize' || github.event.action == 'opened' || github.event.pull_request.merged) + needs: pod_lib_lint + runs-on: macOS-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/download-artifact@v2 + id: download + with: + path: /Users/runner/test + - name: Setup Scripts Directory + run: ./setup-scripts.sh + - name: Access to Metrics Service + if: ${{ env.METRICS_SERVICE_SECRET }} + run: | + # Install gcloud sdk + curl https://sdk.cloud.google.com > install.sh + bash install.sh --disable-prompts + echo "${HOME}/google-cloud-sdk/bin/" >> $GITHUB_PATH + export PATH="${HOME}/google-cloud-sdk/bin/:${PATH}" + + # Activate the service account for Metrics Service. + scripts/decrypt_gha_secret.sh scripts/gha-encrypted/metrics_service_access.json.gpg \ + metrics-access.json "${{ env.METRICS_SERVICE_SECRET }}" + gcloud auth activate-service-account --key-file metrics-access.json + - name: Build code coverage tool + run: | + cd firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/ + swift build + - name: Generate report + if: github.event_name == 'pull_request' && ${{ env.METRICS_SERVICE_SECRET }} + env: + pr_branch: ${{ github.event.pull_request.head.ref }} + run: | + common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) + GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) + if [ -d "${{steps.download.outputs.download-path}}" ]; then + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "Google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" + fi + - name: Update New Coverage Data + if: github.event.pull_request.merged == true && ${{ env.METRICS_SERVICE_SECRET }} + run: | + if [ -d "${{steps.download.outputs.download-path}}" ]; then + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "Google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" --branch "${GITHUB_REF##*/}" + fi From c654af7fa7a4b50df690ea9adc53f68e893c87ff Mon Sep 17 00:00:00 2001 From: Gran Date: Thu, 18 Mar 2021 15:38:55 -0700 Subject: [PATCH 002/131] Update typoes. (#9) * Update typos. --- .github/workflows/test_coverage.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 4fd3a1c2..f82c696a 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -62,18 +62,18 @@ jobs: cd firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/ swift build - name: Generate report - if: github.event_name == 'pull_request' && ${{ env.METRICS_SERVICE_SECRET }} + if: github.event.pull_request.merged != true && ${{ env.METRICS_SERVICE_SECRET }} env: pr_branch: ${{ github.event.pull_request.head.ref }} run: | common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "Google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" fi - name: Update New Coverage Data if: github.event.pull_request.merged == true && ${{ env.METRICS_SERVICE_SECRET }} run: | if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "Google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/firebase/firebase-ios-sdk/actions/runs/${GITHUB_RUN_ID}" --branch "${GITHUB_REF##*/}" + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --branch "${GITHUB_REF##*/}" fi From 3615a4ca10961d82ed2b5459cd9a5cb5ebc03ff2 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Wed, 24 Mar 2021 15:02:54 -0400 Subject: [PATCH 003/131] Fix diagnostics warnings (#11) * Diagnostics issues fixes * Comments fixes from #3 * Fix GDTCORUploadCoordinatorTest * Fix tests * Fix GDTCCTUploadOperation cancel synchronization * Cleanup * Fix URLSession retain cycle. * Bump SDK version to 8.3.1 * Changelog --- GoogleDataTransport.podspec | 7 +- GoogleDataTransport/CHANGELOG.md | 3 + .../GDTCCTLibrary/GDTCCTUploadOperation.m | 88 ++++++++++++++----- .../GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m | 4 + .../Private/GDTCCTUploadOperation.h | 4 +- .../GDTCORFlatFileStorage+Promises.m | 6 +- .../Internal/GDTCORStorageProtocol.h | 2 +- .../Unit/GDTCORUploadCoordinatorTest.m | 11 ++- 8 files changed, 89 insertions(+), 36 deletions(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 68ca0df4..326168aa 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '8.3.0' + s.version = '8.3.1' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -58,6 +58,7 @@ Shared library for iOS SDK data transport needs. }.merge(header_search_paths) common_test_sources = ['GoogleDataTransport/GDTCORTests/Common/**/*.{h,m}'] + common_cct_test_sources = ['GoogleDataTransport/GDTCCTTests/Common/**/*.{h,m}'] # Test app specs if ENV['GDT_DEV'] && ENV['GDT_DEV'] == '1' then @@ -92,7 +93,7 @@ Shared library for iOS SDK data transport needs. test_spec.scheme = { :code_coverage => true } test_spec.platforms = {:ios => ios_deployment_target, :osx => osx_deployment_target, :tvos => tvos_deployment_target} test_spec.requires_app_host = false - test_spec.source_files = ['GoogleDataTransport/GDTCORTests/Unit/**/*.{h,m}'] + common_test_sources + test_spec.source_files = ['GoogleDataTransport/GDTCORTests/Unit/**/*.{h,m}'] + common_test_sources + common_cct_test_sources test_spec.pod_target_xcconfig = header_search_paths end @@ -146,8 +147,6 @@ Shared library for iOS SDK data transport needs. end end - common_cct_test_sources = ['GoogleDataTransport/GDTCCTTests/Common/**/*.{h,m}'] - # Test specs s.test_spec 'CCT-Tests-Unit' do |test_spec| test_spec.scheme = { :code_coverage => true } diff --git a/GoogleDataTransport/CHANGELOG.md b/GoogleDataTransport/CHANGELOG.md index fd7a29d2..ee50654e 100644 --- a/GoogleDataTransport/CHANGELOG.md +++ b/GoogleDataTransport/CHANGELOG.md @@ -1,3 +1,6 @@ +# v8.3.1 +- Fix thread sanitizer and undefined behavior diagnostics warnings. ( https://github.com/firebase/firebase-ios-sdk/issues/7771) +- URLSession delegate retain cycle. (https://github.com/firebase/firebase-ios-sdk/issues/7780) # v8.3.0 - Upload logic was refactored to use [Promises library](https://github.com/google/promises) to improve readability and maintainability of the async code. - Fix some race conditions and edge cases when multiple targets have events ready for upload. diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index fd9507e3..41460284 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -69,6 +69,9 @@ @interface GDTCCTUploadOperation () @property(nonatomic, readonly) id storage; @property(nonatomic, readonly) id metadataProvider; +/** The URL session that will attempt upload. */ +@property(nonatomic) NSURLSession *uploaderSession; + /// NSOperation state properties implementation. @property(nonatomic, readwrite, getter=isExecuting) BOOL executing; @property(nonatomic, readwrite, getter=isFinished) BOOL finished; @@ -88,10 +91,6 @@ - (instancetype)initWithTarget:(GDTCORTarget)target self = [super init]; if (self) { _uploaderQueue = queue; - NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - _uploaderSession = [NSURLSession sessionWithConfiguration:config - delegate:self - delegateQueue:nil]; _target = target; _conditions = conditions; _uploadURL = uploadURL; @@ -101,6 +100,16 @@ - (instancetype)initWithTarget:(GDTCORTarget)target return self; } +- (NSURLSession *)uploaderSessionCreateIfNeeded { + if (_uploaderSession == nil) { + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _uploaderSession = [NSURLSession sessionWithConfiguration:config + delegate:self + delegateQueue:nil]; + } + return _uploaderSession; +} + - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions { __block GDTCORBackgroundIdentifier backgroundTaskID = GDTCORBackgroundIdentifierInvalid; @@ -256,8 +265,22 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions .thenOn(self.uploaderQueue, ^FBLPromise *(NSURLRequest *request) { // 2. Send URL request. - return [self.uploaderSession gul_dataTaskPromiseWithRequest:request]; - }); + return + [[self uploaderSessionCreateIfNeeded] gul_dataTaskPromiseWithRequest:request]; + }) + .thenOn(self.uploaderQueue, + ^GULURLSessionDataResponse *(GULURLSessionDataResponse *response) { + // Invalidate session to release the delegate (which is `self`) to break the retain + // cycle. + [self.uploaderSession finishTasksAndInvalidate]; + return response; + }) + .recoverOn(self.uploaderQueue, ^id(NSError *error) { + // Invalidate session to release the delegate (which is `self`) to break the retain cycle. + [self.uploaderSession finishTasksAndInvalidate]; + // Re-throw the error. + return error; + }); } /** Parses server response and update next upload time for the specified target based on it. */ @@ -488,26 +511,42 @@ - (void)URLSession:(NSURLSession *)session @synthesize executing = _executing; @synthesize finished = _finished; +- (BOOL)isFinished { + @synchronized(self) { + return _finished; + } +} + +- (BOOL)isExecuting { + @synchronized(self) { + return _executing; + } +} + - (BOOL)isAsynchronous { return YES; } - (void)startOperation { - [self willChangeValueForKey:@"isExecuting"]; - [self willChangeValueForKey:@"isFinished"]; - _executing = YES; - _finished = NO; - [self didChangeValueForKey:@"isExecuting"]; - [self didChangeValueForKey:@"isFinished"]; + @synchronized(self) { + [self willChangeValueForKey:@"isExecuting"]; + [self willChangeValueForKey:@"isFinished"]; + self->_executing = YES; + self->_finished = NO; + [self didChangeValueForKey:@"isExecuting"]; + [self didChangeValueForKey:@"isFinished"]; + } } - (void)finishOperation { - [self willChangeValueForKey:@"isExecuting"]; - [self willChangeValueForKey:@"isFinished"]; - _executing = NO; - _finished = YES; - [self didChangeValueForKey:@"isExecuting"]; - [self didChangeValueForKey:@"isFinished"]; + @synchronized(self) { + [self willChangeValueForKey:@"isExecuting"]; + [self willChangeValueForKey:@"isFinished"]; + self->_executing = NO; + self->_finished = YES; + [self didChangeValueForKey:@"isExecuting"]; + [self didChangeValueForKey:@"isFinished"]; + } } - (void)main { @@ -518,13 +557,14 @@ - (void)main { } - (void)cancel { - GDTCORLogDebug(@"Upload operation cancelled: %@", self); - [super cancel]; + @synchronized(self) { + [super cancel]; - // If the operation hasn't been started we can set `isFinished = YES` straight away. - if (!_executing) { - _executing = NO; - _finished = YES; + // If the operation hasn't been started we can set `isFinished = YES` straight away. + if (!_executing) { + _executing = NO; + _finished = YES; + } } } diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m index 00a0f1b4..1c77b4cb 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m @@ -130,6 +130,10 @@ - (nullable NSData *)networkConnectionInfoData { options:0 error:&error]; NSString *base64Data = bytesDict[GDTCCTNetworkConnectionInfo]; + if (base64Data == nil) { + return nil; + } + NSData *networkConnectionInfoData = [[NSData alloc] initWithBase64EncodedString:base64Data options:0]; if (error) { diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h index be4ab399..3f636440 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h @@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN * @param storage A storage object to fetch events for upload. * @param metadataProvider An object to retrieve/update data shared between different upload * operations. + * @return An instance of GDTCCTUploadOperation ready to be added to an NSOperationQueue. */ - (instancetype)initWithTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions @@ -67,9 +68,6 @@ NS_ASSUME_NONNULL_BEGIN /** The queue on which all CCT uploading will occur. */ @property(nonatomic, readonly) dispatch_queue_t uploaderQueue; -/** The URL session that will attempt upload. */ -@property(nonatomic, readonly) NSURLSession *uploaderSession; - /** The current upload task. */ @property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m index e27e2af6..c3a22a8e 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m @@ -58,7 +58,11 @@ @implementation GDTCORFlatFileStorage (Promises) deleteEvents:(BOOL)deleteEvents { return [self batchIDsForTarget:target].thenOn(self.storageQueue, ^id(NSSet *batchIDs) { - return [self removeBatchesWithIDs:batchIDs deleteEvents:NO]; + if (batchIDs.count == 0) { + return [FBLPromise resolvedWith:[NSNull null]]; + } else { + return [self removeBatchesWithIDs:batchIDs deleteEvents:NO]; + } }); } diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index dcb451e9..ffccfdba 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -125,7 +125,7 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, // TODO: Consider complete replacing block based API by promise API. -/** Promise based version of API defined in GDTCORStorageProtocol. See API docs for corresponding +/** Promise based version of API defined in GDTCORStorageProtocol. See API docs for corresponding * methods in GDTCORStorageProtocol. */ @protocol GDTCORStoragePromiseProtocol diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORUploadCoordinatorTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORUploadCoordinatorTest.m index 44df2286..f6a0f7f0 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORUploadCoordinatorTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORUploadCoordinatorTest.m @@ -24,7 +24,7 @@ #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.h" #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORUploadCoordinator+Testing.h" -#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h" +#import "GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCOREventGenerator.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORTestUploader.h" @@ -32,7 +32,7 @@ @interface GDTCORUploadCoordinatorTest : GDTCORTestCase /** A storage fake to inject into GDTCORUploadCoordinator. */ -@property(nonatomic) GDTCORStorageFake *storageFake; +@property(nonatomic) GDTCCTTestStorage *storageFake; /** A test uploader. */ @property(nonatomic) GDTCORTestUploader *uploader; @@ -43,7 +43,7 @@ @implementation GDTCORUploadCoordinatorTest - (void)setUp { [super setUp]; - self.storageFake = [[GDTCORStorageFake alloc] init]; + self.storageFake = [[GDTCCTTestStorage alloc] init]; self.uploader = [[GDTCORTestUploader alloc] init]; [[GDTCORRegistrar sharedInstance] registerUploader:_uploader target:kGDTCORTargetTest]; @@ -112,6 +112,8 @@ - (void)testThatAFailedUploadResultsInAnEventualRetry { enumerateObjectsUsingBlock:^(GDTCOREvent *_Nonnull obj, BOOL *_Nonnull stop) { [self.storageFake storeEvent:obj onComplete:nil]; }]; + + XCTestExpectation *eventsBatched = [self expectationWithDescription:@"Events batched"]; __block NSNumber *batchID; [storage batchWithEventSelector:[GDTCORStorageEventSelector eventSelectorForTarget:kGDTCORTargetTest] @@ -119,7 +121,10 @@ - (void)testThatAFailedUploadResultsInAnEventualRetry { onComplete:^(NSNumber *_Nullable newBatchID, NSSet *_Nullable events) { batchID = newBatchID; + [eventsBatched fulfill]; }]; + + [self waitForExpectations:@[ eventsBatched ] timeout:0.5]; self.uploader.uploadWithConditionsBlock = ^(GDTCORTarget target, GDTCORUploadConditions conditions) { [storage removeBatchWithID:batchID deleteEvents:NO onComplete:nil]; From c2fdda22801dec8d9e57ab3f614e62762956ef1c Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 29 Mar 2021 13:18:48 -0700 Subject: [PATCH 004/131] Update to nanopb 0.3.9.8 (#15) Co-authored-by: varconst --- GoogleDataTransport.podspec | 4 ++-- GoogleDataTransport/CHANGELOG.md | 4 ++++ GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile | 1 + GoogleDataTransport/GDTWatchOSTestApp/Podfile | 1 + GoogleDataTransport/ProtoSupport/generate_cct_protos.sh | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 326168aa..dd2f7feb 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '8.3.1' + s.version = '8.4.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -40,7 +40,7 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] s.dependency 'GoogleUtilities/Environment', '~> 7.2' - s.dependency 'nanopb', '~> 2.30907.0' + s.dependency 'nanopb', '~> 2.30908.0' s.dependency 'PromisesObjC', '~> 1.2' header_search_paths = { diff --git a/GoogleDataTransport/CHANGELOG.md b/GoogleDataTransport/CHANGELOG.md index ee50654e..3330ba8b 100644 --- a/GoogleDataTransport/CHANGELOG.md +++ b/GoogleDataTransport/CHANGELOG.md @@ -1,6 +1,10 @@ +# v8.4.0 +- Update to nanopb 0.3.9.8. (https://github.com/firebase/firebase-ios-sdk/issues/7787) + # v8.3.1 - Fix thread sanitizer and undefined behavior diagnostics warnings. ( https://github.com/firebase/firebase-ios-sdk/issues/7771) - URLSession delegate retain cycle. (https://github.com/firebase/firebase-ios-sdk/issues/7780) + # v8.3.0 - Upload logic was refactored to use [Promises library](https://github.com/google/promises) to improve readability and maintainability of the async code. - Fix some race conditions and edge cases when multiple targets have events ready for upload. diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile b/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile index 30f67f01..420e55a7 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile @@ -1,5 +1,6 @@ use_frameworks! +source 'https://github.com/firebase/SpecsDev.git' source 'https://github.com/firebase/SpecsStaging.git' source 'https://cdn.cocoapods.org/' diff --git a/GoogleDataTransport/GDTWatchOSTestApp/Podfile b/GoogleDataTransport/GDTWatchOSTestApp/Podfile index 54ddf699..d7d49663 100644 --- a/GoogleDataTransport/GDTWatchOSTestApp/Podfile +++ b/GoogleDataTransport/GDTWatchOSTestApp/Podfile @@ -1,3 +1,4 @@ +source 'https://github.com/firebase/SpecsDev.git' source 'https://github.com/firebase/SpecsStaging.git' source 'https://cdn.cocoapods.org/' diff --git a/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh b/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh index 27880b5e..4f2b789c 100755 --- a/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh +++ b/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh @@ -23,7 +23,7 @@ readonly DIR="$( git rev-parse --show-toplevel )" # Current release of nanopb being used to build the CCT protos -readonly NANOPB_VERSION="0.3.9.7" +readonly NANOPB_VERSION="0.3.9.8" readonly NANOPB_TEMPDIR="${DIR}/GoogleDataTransport/nanopb_temp" readonly LIBRARY_DIR="${DIR}/GoogleDataTransport/GDTCCTLibrary/" From ee9779ab3dc98ff6714d9bd2944a8c51fe47989c Mon Sep 17 00:00:00 2001 From: Gran Date: Mon, 29 Mar 2021 17:18:37 -0700 Subject: [PATCH 005/131] Add CCT-Tests-Unit to Test Coverage (#12) * Add CCT-Tests-Unit to Test Coverage * Update trigger condition. --- .github/workflows/test_coverage.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index f82c696a..313eb81a 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -25,7 +25,7 @@ jobs: run: ./scripts/setup_bundler.sh - name: PodLibLint DataTransport run: | - scripts/code_coverage_report/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec="Tests-Unit" + scripts/code_coverage_report/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec=Tests-Unit,CCT-Tests-Unit - uses: actions/upload-artifact@v2 with: name: codecoverage @@ -61,18 +61,20 @@ jobs: run: | cd firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/ swift build + cat $GITHUB_EVENT_PATH - name: Generate report - if: github.event.pull_request.merged != true && ${{ env.METRICS_SERVICE_SECRET }} + if: github.event.pull_request.merged != true && env.METRICS_SERVICE_SECRET env: pr_branch: ${{ github.event.pull_request.head.ref }} run: | + echo $(git merge-base remotes/origin/${pr_branch} remotes/origin/main) common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) if [ -d "${{steps.download.outputs.download-path}}" ]; then firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" fi - name: Update New Coverage Data - if: github.event.pull_request.merged == true && ${{ env.METRICS_SERVICE_SECRET }} + if: github.event.pull_request.merged == true && env.METRICS_SERVICE_SECRET run: | if [ -d "${{steps.download.outputs.download-path}}" ]; then firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --branch "${GITHUB_REF##*/}" From 369716b8d7518a530ce3c3a251436d72546debd8 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 30 Mar 2021 16:14:19 -0700 Subject: [PATCH 006/131] Update nanopb version (#16) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 957c8fee..0d3bfc8e 100644 --- a/Package.swift +++ b/Package.swift @@ -30,7 +30,7 @@ let package = Package( .package( name: "nanopb", url: "https://github.com/firebase/nanopb.git", - "2.30907.0" ..< "2.30908.0" + "2.30908.0" ..< "2.30909.0" ), .package( name: "GoogleUtilities", From e844aa8f82d834ccfeecb0bf154820897bd26b28 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 13 Apr 2021 12:07:56 -0400 Subject: [PATCH 007/131] Update README.md (#17) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a8625db..cab1faa3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ After the CI is green: ## To develop -- Run `generate_project.sh` after installing the prereqs +- Run `./GoogleDataTransport/generate_project.sh` after installing the prereqs ## When adding new logging endpoint From 0afaad7819fe7cd66d772ddebb093b89eea3b2a9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 13 Apr 2021 19:00:17 -0400 Subject: [PATCH 008/131] Rename transform: API to transformGDTEvent: (#18) * Rename transform: API to transformGDTEvent: * Bump GDT podspec from 8.4.0 to 9.0.0 --- GoogleDataTransport.podspec | 2 +- GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m | 6 +++--- .../GDTCORLibrary/Private/GDTCORTransformer.h | 3 ++- .../Public/GoogleDataTransport/GDTCORConsoleLogger.h | 3 ++- .../Public/GoogleDataTransport/GDTCOREventTransformer.h | 2 +- .../GDTCORTests/Integration/GDTCORIntegrationTest.m | 4 ++-- .../GDTCORTests/Unit/GDTCORTransformerTest.m | 4 ++-- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index dd2f7feb..862e7acf 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '8.4.0' + s.version = '9.0.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m b/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m index fba182ae..b6d02497 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m @@ -78,16 +78,16 @@ - (void)transformEvent:(GDTCOREvent *)event dispatch_async(_eventWritingQueue, ^{ GDTCOREvent *transformedEvent = event; for (id transformer in transformers) { - if ([transformer respondsToSelector:@selector(transform:)]) { + if ([transformer respondsToSelector:@selector(transformGDTEvent:)]) { GDTCORLogDebug(@"Applying a transformer to event %@", event); - transformedEvent = [transformer transform:transformedEvent]; + transformedEvent = [transformer transformGDTEvent:event]; if (!transformedEvent) { completionWrapper(NO, nil); return; } } else { GDTCORLogError(GDTCORMCETransformerDoesntImplementTransform, - @"Transformer doesn't implement transform: %@", transformer); + @"Transformer doesn't implement transformGDTEvent: %@", transformer); completionWrapper(NO, nil); return; } diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h index 2868306a..ccca6283 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h @@ -38,7 +38,8 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; -/** Writes the result of applying the given transformers' -transform method on the given event. +/** Writes the result of applying the given transformers' `transformGDTEvent:` method on the given + * event. * * @note If the app is suspended, a background task will be created to complete work in-progress, * but this method will not send any further events until the app is resumed. diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h index d306f182..1fdf7325 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h @@ -68,7 +68,8 @@ typedef NS_ENUM(NSInteger, GDTCORMessageCode) { /** For warning messages concerning the reading of a event file. */ GDTCORMCWFileReadError = 6, - /** For error messages concerning transform: not being implemented by an event transformer. */ + /** For error messages concerning transformGDTEvent: not being implemented by an event + transformer. */ GDTCORMCETransformerDoesntImplementTransform = 1000, /** For error messages concerning the creation of a directory failing. */ diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h index 29f95924..80dee7d4 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN * @param event The event to transform. * @return A transformed event, or nil if the transformation dropped the event. */ -- (nullable GDTCOREvent *)transform:(GDTCOREvent *)event; +- (nullable GDTCOREvent *)transformGDTEvent:(GDTCOREvent *)event; @end diff --git a/GoogleDataTransport/GDTCORTests/Integration/GDTCORIntegrationTest.m b/GoogleDataTransport/GDTCORTests/Integration/GDTCORIntegrationTest.m index 80a3338d..9b32ff42 100644 --- a/GoogleDataTransport/GDTCORTests/Integration/GDTCORIntegrationTest.m +++ b/GoogleDataTransport/GDTCORTests/Integration/GDTCORIntegrationTest.m @@ -52,8 +52,8 @@ @interface GDTCORIntegrationTestTransformer : NSObject @implementation GDTCORIntegrationTestTransformer -- (nullable GDTCOREvent *)transform:(GDTCOREvent *)event { - // drop half the events during transforming. +- (nullable GDTCOREvent *)transformGDTEvent:(GDTCOREvent *)event { + // Drop half the events during transforming. if (arc4random_uniform(2) == 0) { event = nil; } diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransformerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransformerTest.m index 0c1bf5f5..cb23f738 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransformerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransformerTest.m @@ -37,7 +37,7 @@ @interface GDTCORTransformerTestNilingTransformer : NSObject Date: Thu, 15 Apr 2021 10:05:58 -0400 Subject: [PATCH 009/131] Update README.md (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update README.md • Update Integration Testing directions to match updates made to GoogleUtils's README. • Added some syntax highlighting to existing code blocks. * Move CHANGELOG to root * Modify google3 command * Differentiate when to push to dev vs staging * Don't skip tests when pushing to dev * Don't skip tests when pushing to staging * Fix typo in copybara command * Generalize copybara command * Revert "Don't skip tests when pushing to dev" This reverts commit 476fd4380abb82f4a7aed01f17add3b129698e27. * Revert "Don't skip tests when pushing to staging" This reverts commit 7fce014284c3510f1aed192040dfe04eef18c223. * Remove some whitespace * Remove some elusive whitespace --- .../CHANGELOG.md => CHANGELOG.md | 0 README.md | 63 +++++++++++++++---- 2 files changed, 50 insertions(+), 13 deletions(-) rename GoogleDataTransport/CHANGELOG.md => CHANGELOG.md (100%) diff --git a/GoogleDataTransport/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from GoogleDataTransport/CHANGELOG.md rename to CHANGELOG.md diff --git a/README.md b/README.md index cab1faa3..a7953299 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,51 @@ These instructions apply to minor and patch version updates. Major versions need a customized adaptation. After the CI is green: - * Update the version in the podspec - * Add the CocoaPods tag - * `git tag CocoaPods-{version}` - * `git push origin CocoaPods-{version}` - * Push the podspec to SpecsStaging - * `pod repo push staging GoogleDataTransport.podspec` - * Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile` - * On google3, copybara and run a global TAP which should kick off automatically after each PR. +* Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md) +* Checkout the `main` branch and ensure it is up to date. + ```console + git checkout main + git pull + ``` +* Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleDataTransport.podspec#L3)) + ```console + git tag CocoaPods-{version} + git push origin CocoaPods-{version} + ``` +* Push the podspec to the designated repo + * If this version of GDT is intended to launch **before or with** the next Firebase release: +
+ Push to SpecsStaging + + ```console + pod repo push --skip-tests staging GoogleDataTransport.podspec + ``` + + If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with: + ```console + pod repo add staging git@github.com:firebase/SpecsStaging.git + ``` +
+ * Otherwise: +
+ Push to SpecsDev + + ```console + pod repo push --skip-tests dev GoogleDataTransport.podspec + ``` + + If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with: + ```console + pod repo add dev git@github.com:firebase/SpecsDev.git + ``` +
+* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`. +* On google3, create a workspace and new CL. Then copybara and run a global TAP. +
+  /google/data/ro/teams/copybara/copybara third_party/firebase/ios/Releases/GoogleDataTransport/copy.bara.sky \
+  --piper-description-behavior=OVERWRITE \
+  --destination-cl=YOUR_CL gdt
+  
## Publishing * Add a version tag for Swift PM @@ -35,21 +72,21 @@ After the CI is green: ### Swift - Import `GoogleDataTransport` module: - ``` + ```swift import GoogleDataTransport ``` - Set logging level global variable to the desired value before calling `FirebaseApp.config()`: - ``` + ```swift GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue ``` ### Objective-C - Import `GoogleDataTransport`: - ``` + ```objective-c #import ``` - Set logging level global variable to the desired value before calling `-[FIRApp config]`: - ``` + ```objective-c GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug; ``` @@ -116,7 +153,7 @@ before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: -``` +```console brew install clang-format@11 brew install mint ``` From 4ced162bf822ef488308aeb830aa080dddcd8fc9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 15 Apr 2021 14:31:44 -0400 Subject: [PATCH 010/131] Update Package.swift (#20) --- Package.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Package.swift b/Package.swift index 0d3bfc8e..bc6c7c38 100644 --- a/Package.swift +++ b/Package.swift @@ -48,8 +48,6 @@ let package = Package( ], path: "GoogleDataTransport", exclude: [ - "CHANGELOG.md", - "README.md", "generate_project.sh", "GDTCCTWatchOSTestApp/", "GDTWatchOSTestApp/", From 51404421a5b05696bb2936ba53540a34b2e75111 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:11:31 -0400 Subject: [PATCH 011/131] Update CHANGELOG.md for GDT 9.0 (#23) * Update CHANGELOG.md for GDT 9.0 * Tag PR # instead of issue # * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3330ba8b..f4915b7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.0.0 +- Rename `transform:` API to `transformGDTEvent:`. (#19) + # v8.4.0 - Update to nanopb 0.3.9.8. (https://github.com/firebase/firebase-ios-sdk/issues/7787) From 7e8647af0b6bbc31525590810ae4abb9a0f96130 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 20 Apr 2021 18:30:25 -0400 Subject: [PATCH 012/131] Update repo for clang-format12 (#22) --- .github/workflows/check.yml | 2 +- GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m | 5 +++-- README.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7ff802fd..f32d2f73 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -30,7 +30,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@11 + brew install clang-format@12 brew install mint mint bootstrap diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m index 14462aea..e8f95ad5 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m @@ -24,8 +24,9 @@ GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) { if (assertionBlockSEL) { IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL]; if (assertionBlockIMP) { - GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)( - GDTCORAssertClass, assertionBlockSEL); + GDTCORAssertionBlock assertionBlock = + ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDTCORAssertClass, + assertionBlockSEL); if (assertionBlock) { return assertionBlock; } diff --git a/README.md b/README.md index a7953299..6e40d9d6 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@11 +brew install clang-format@12 brew install mint ``` From 83af7c9040df991ee7d63fdf70ec5af8df5fe572 Mon Sep 17 00:00:00 2001 From: Gran Date: Thu, 27 May 2021 10:18:14 -0700 Subject: [PATCH 013/131] Update workflow. (#25) --- .github/workflows/test_coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 313eb81a..1e1c4751 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -71,11 +71,11 @@ jobs: common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" fi - name: Update New Coverage Data if: github.event.pull_request.merged == true && env.METRICS_SERVICE_SECRET run: | if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --branch "${GITHUB_REF##*/}" + firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --source-branch "${{ github.base_ref }}" fi From e5440fded3a1dab4eb8013f332064315857a2f57 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 3 Jun 2021 06:44:43 -0700 Subject: [PATCH 014/131] Fix Xcode 12.5 analyze errors (#26) --- CHANGELOG.md | 3 +++ .../GDTCORLibrary/GDTCORFlatFileStorage.m | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4915b7e..d590cdb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.0.1 +- Fix Xcode 12.5 analyze warnings. (#26) + # v9.0.0 - Rename `transform:` API to `transformGDTEvent:`. (#19) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m index 42cca001..6e326d84 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m @@ -296,7 +296,7 @@ - (void)batchIDsForTarget:(GDTCORTarget)target NSDictionary *components = [self batchComponentsFromFilename:path]; NSNumber *targetNumber = components[kGDTCORBatchComponentsTargetKey]; NSNumber *batchID = components[kGDTCORBatchComponentsBatchIDKey]; - if (targetNumber.intValue == target) { + if (batchID != nil && targetNumber.intValue == target) { [batchIDs addObject:batchID]; } } @@ -474,7 +474,7 @@ - (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete { * cases when the app is terminated while uploading a batch. */ - (nullable NSArray *)batchDirPathsForBatchID:(NSNumber *)batchID - error:(NSError **)outError { + error:(NSError **_Nonnull)outError { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; NSArray *batches = @@ -503,7 +503,7 @@ - (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete { /** Makes a copy of the contents of a directory to a directory at the specified path.*/ - (BOOL)moveContentsOfDirectoryAtPath:(NSString *)sourcePath to:(NSString *)destinationPath - error:(NSError **)outError { + error:(NSError **_Nonnull)outError { NSFileManager *fileManager = [NSFileManager defaultManager]; NSError *error; @@ -566,14 +566,19 @@ - (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID } else { NSString *batchDirName = [batchDirPath lastPathComponent]; NSDictionary *components = [self batchComponentsFromFilename:batchDirName]; - NSNumber *target = components[kGDTCORBatchComponentsTargetKey]; - NSString *destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath] - stringByAppendingPathComponent:target.stringValue]; + NSString *targetValue = [components[kGDTCORBatchComponentsTargetKey] stringValue]; + NSString *destinationPath; + if (targetValue) { + destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath] + stringByAppendingPathComponent:targetValue]; + } // `- [NSFileManager moveItemAtPath:toPath:error:]` method fails if an item by the // destination path already exists (which usually is the case for the current method). Move // the events one by one instead. - if ([self moveContentsOfDirectoryAtPath:batchDirPath to:destinationPath error:&error]) { + if (destinationPath && [self moveContentsOfDirectoryAtPath:batchDirPath + to:destinationPath + error:&error]) { GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath, destinationPath); } else { @@ -581,7 +586,7 @@ - (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID } // Even if not all events where moved back to the storage, there is not much can be done at - // this point, so cleanup batch directory now to avoid clattering. + // this point, so cleanup batch directory now to avoid cluttering. removeBatchDir(batchDirPath); } } From cad598c0338e8b203b94378dff0d4d819fa141cf Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 3 Jun 2021 08:28:20 -0700 Subject: [PATCH 015/131] Update podspec for a 9.0.1 release (#27) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 862e7acf..ba7193cd 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.0.0' + s.version = '9.0.1' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From c875664c78c75db0bbd80aa0b8cbe809fb8d63e8 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 4 Jun 2021 08:20:29 -0700 Subject: [PATCH 016/131] More detail for Publish instructions (#28) --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e40d9d6..81f559e5 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,20 @@ After the CI is green: * Add a version tag for Swift PM * `git tag {version}` * `git push origin {version}` - * `pod trunk push GoogleDataTransport.podspec` - * Clean up SpecsStaging + * `pod trunk push GoogleDataTransport.podspec --skip-tests` + * Clean up SpecsStaging +
+ + ```console + git clone git@github.com:firebase/SpecsStaging.git + cd SpecsStaging/ + git rm -rf GoogleDataTransport/ + git commit -m "Post publish cleanup" + git push origin master + ``` +
+ * [Create GitHub Release](https://github.com/google/GoogleDataTransport/releases/new) + * [Template content](https://github.com/google/GoogleDataTransport/releases/edit/9.0.1) ## Set logging level From 7080c10917e0322262dbe8b8dc9d6ecc19bd5a40 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 24 Jun 2021 11:40:20 -0400 Subject: [PATCH 017/131] Update release instructions (#29) * Update release instructions * whitespaces --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 81f559e5..b76e58a8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ These instructions apply to minor and patch version updates. Major versions need a customized adaptation. After the CI is green: +* Determine the next version for release by checking the + [tagged releases](https://github.com/google/GoogleDataTransport/tags). + Ensure that the next release version keeps the Swift PM and CocoaPods versions in sync. +* Verify that the releasing version is the latest entry in the [CHANGELOG.md](CHANGELOG.md), + updating it if necessary. * Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md) * Checkout the `main` branch and ensure it is up to date. ```console @@ -61,23 +66,61 @@ After the CI is green: ## Publishing +The release process is as follows: +1. [Tag and release for Swift PM](#swift-package-manager) +2. [Publish to CocoaPods](#cocoapods) +3. [Create GitHub Release](#create-github-release) +4. [Perform post release cleanup](#post-release-cleanup) + +### Swift Package Manager + By creating and [pushing a tag](https://github.com/google/GoogleDataTransport/tags) + for Swift PM, the newly tagged version will be immediately released for public use. + Given this, please verify the intended time of release for Swift PM. * Add a version tag for Swift PM - * `git tag {version}` - * `git push origin {version}` - * `pod trunk push GoogleDataTransport.podspec --skip-tests` - * Clean up SpecsStaging -
+ ```console + git tag {version} + git push origin {version} + ``` + *Note: Ensure that any inflight PRs that depend on the new `GoogleDataTransport` version are updated to point to the + newly tagged version rather than a checksum.* - ```console - git clone git@github.com:firebase/SpecsStaging.git - cd SpecsStaging/ - git rm -rf GoogleDataTransport/ - git commit -m "Post publish cleanup" - git push origin master - ``` -
- * [Create GitHub Release](https://github.com/google/GoogleDataTransport/releases/new) - * [Template content](https://github.com/google/GoogleDataTransport/releases/edit/9.0.1) +### CocoaPods +* Publish the newly versioned pod to CocoaPods + + It's recommended to point to the `GoogleDataTransport.podspec` in `staging` to make sure the correct spec is being published. + ```console + pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport.podspec --skip-tests + ``` + + The pod push was successful if the above command logs: `🚀 GoogleDataTransport ({version}) successfully published`. + In addition, a new commit that publishes the new version (co-authored by [CocoaPodsAtGoogle](https://github.com/CocoaPodsAtGoogle)) + should appear in the [CocoaPods specs repo](https://github.com/CocoaPods/Specs). Last, the latest version should be displayed + on [GoogleDataTransport's CocoaPods page](https://cocoapods.org/pods/GoogleDataTransport). + +### [Create GitHub Release](https://github.com/google/GoogleDataTransport/releases/new/) + Update the [release template](https://github.com/google/GoogleDataTransport/releases/new/)'s **Tag version** and **Release title** + fields with the latest version. In addition, reference the [Release Notes](./CHANGELOG.md) in the release's description. + + See [this release](https://github.com/google/GoogleDataTransport/releases/edit/9.0.1) for an example. + + *Don't forget to perform the [post release cleanup](#post-release-cleanup)!* + +### Post Release Cleanup +
+ Clean up SpecsStaging + + ```console + pwd=$(pwd) + mkdir -p /tmp/release-cleanup && cd $_ + git clone git@github.com:firebase/SpecsStaging.git + cd SpecsStaging/ + git rm -rf GoogleDataTransport/ + git commit -m "Post publish cleanup" + git push origin master + rm -rf /tmp/release-cleanup + cd $pwd + ``` +
## Set logging level @@ -87,7 +130,7 @@ After the CI is green: ```swift import GoogleDataTransport ``` -- Set logging level global variable to the desired value before calling `FirebaseApp.config()`: +- Set logging level global variable to the desired value before calling `FirebaseApp.configure()`: ```swift GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue ``` @@ -97,7 +140,7 @@ After the CI is green: ```objective-c #import ``` -- Set logging level global variable to the desired value before calling `-[FIRApp config]`: +- Set logging level global variable to the desired value before calling `-[FIRApp configure]`: ```objective-c GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug; ``` From 9e79fd8deddbef5646fbb5dbb3eaf963e338f007 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 8 Jul 2021 17:18:51 -0400 Subject: [PATCH 018/131] Bump promises dependency (#31) * Bump promises dependency * Fix podspec * Include Promises in Package.swift * Bump Promises in Package.swift * Bump podspec minor version * CHANGELOG * Bump to 9.1.0 * Fix CHANGELOG --- CHANGELOG.md | 3 +++ GoogleDataTransport.podspec | 4 ++-- Package.swift | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d590cdb7..6e5bd207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.1.0 +- Bump Promises dependency. (#8334) + # v9.0.1 - Fix Xcode 12.5 analyze warnings. (#26) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index ba7193cd..e5c187de 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.0.1' + s.version = '9.1.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -41,7 +41,7 @@ Shared library for iOS SDK data transport needs. s.dependency 'GoogleUtilities/Environment', '~> 7.2' s.dependency 'nanopb', '~> 2.30908.0' - s.dependency 'PromisesObjC', '~> 1.2' + s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' header_search_paths = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/"' diff --git a/Package.swift b/Package.swift index bc6c7c38..95e750bb 100644 --- a/Package.swift +++ b/Package.swift @@ -32,6 +32,11 @@ let package = Package( url: "https://github.com/firebase/nanopb.git", "2.30908.0" ..< "2.30909.0" ), + .package( + name: "Promises", + url: "https://github.com/google/promises.git", + "1.2.8" ..< "3.0.0" + ), .package( name: "GoogleUtilities", url: "https://github.com/google/GoogleUtilities.git", @@ -44,6 +49,7 @@ let package = Package( name: "GoogleDataTransport", dependencies: [ .product(name: "nanopb", package: "nanopb"), + .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), ], path: "GoogleDataTransport", From 1568ed6b7cc40ceec3c924eb6cc9d240c5942129 Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Thu, 15 Jul 2021 11:45:14 -0400 Subject: [PATCH 019/131] Issue template with redirection to Firebase repo (#34) * Issue template with redirection to Firebase repo * Line break --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ .github/ISSUE_TEMPLATE/issue_report.md | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/issue_report.md diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..c47c4c3a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Google Data Transport + url: https://github.com/firebase/firebase-ios-sdk/issues/new/choose + about: Google Data Transport is not supported for direct usage. Please file with the product library that depends on Google Data Transport. diff --git a/.github/ISSUE_TEMPLATE/issue_report.md b/.github/ISSUE_TEMPLATE/issue_report.md new file mode 100644 index 00000000..82769f38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_report.md @@ -0,0 +1,8 @@ +--- +name: 🐞 Internal Issue +about: Googlers may file issues here or to track with [Firebase](https://github.com/firebase/firebase-ios-sdk/issues/new/choose) +--- + \ No newline at end of file From ff7179f416ac5c76c57f40cfed083b0fcb9a633a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 16 Jul 2021 09:12:40 -0400 Subject: [PATCH 020/131] Update pod trunk command (#32) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b76e58a8..f7648c21 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ The release process is as follows: It's recommended to point to the `GoogleDataTransport.podspec` in `staging` to make sure the correct spec is being published. ```console - pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport.podspec --skip-tests + pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport/{version}/GoogleDataTransport.podspec --skip-tests ``` The pod push was successful if the above command logs: `🚀 GoogleDataTransport ({version}) successfully published`. From 1c00aed1b3428ea585903b31e4d04d717ccebdcd Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 16 Jul 2021 09:50:23 -0400 Subject: [PATCH 021/131] Apply clang-format version 12.0.1 style (#35) --- .../Unit/Helpers/GDTCCTEventGenerator.m | 4 +-- .../GDTCORLibrary/GDTCORAssert.m | 5 ++-- .../GDTCORLibrary/GDTCORPlatform.m | 29 +++++++++---------- .../Unit/GDTCORFlatFileStorageTest.m | 4 +-- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m index 932bc0a0..77719858 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m @@ -223,9 +223,7 @@ - (NSURL *)writeConsistentMessageToDisk:(NSString *)messageResource { [event.clockSnapshot setValue:@(1239567890) forKeyPath:@"uptimeNanoseconds"]; event.qosTier = GDTCOREventQoSTelemetry; NSError *error; - event.customBytes = [NSJSONSerialization dataWithJSONObject:@{ - @"customParam2" : @(34) - } + event.customBytes = [NSJSONSerialization dataWithJSONObject:@{@"customParam2" : @(34)} options:0 error:&error]; GDTCORAssert(error == nil, @"There shouldn't be an issue turning into JSON"); diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m index e8f95ad5..14462aea 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m @@ -24,9 +24,8 @@ GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) { if (assertionBlockSEL) { IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL]; if (assertionBlockIMP) { - GDTCORAssertionBlock assertionBlock = - ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDTCORAssertClass, - assertionBlockSEL); + GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)( + GDTCORAssertClass, assertionBlockSEL); if (assertionBlock) { return assertionBlock; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index a6207ec2..0b51577a 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -487,22 +487,21 @@ - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID); } [[self sharedNSProcessInfoForBackgroundTask] - performExpiringActivityWithReason:name - usingBlock:^(BOOL expired) { - if (expired) { - if (handler) { - handler(); - } - dispatch_semaphore_signal(semaphore); - GDTCORLogDebug( - @"Activity with name:%@ bgID:%ld on watchOS is expiring.", - name, (long)bgID); - } else { - dispatch_semaphore_wait( - semaphore, - dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); + performExpiringActivityWithReason:name + usingBlock:^(BOOL expired) { + if (expired) { + if (handler) { + handler(); } - }]; + dispatch_semaphore_signal(semaphore); + GDTCORLogDebug( + @"Activity with name:%@ bgID:%ld on watchOS is expiring.", name, + (long)bgID); + } else { + dispatch_semaphore_wait( + semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); + } + }]; #endif return bgID; } diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index f801e059..b5d2645f 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -286,8 +286,8 @@ - (void)testStoringEventsDuringTerminate { }]; } [NSNotificationCenter.defaultCenter - postNotificationName:kGDTCORApplicationWillTerminateNotification - object:nil]; + postNotificationName:kGDTCORApplicationWillTerminateNotification + object:nil]; } self.continueAfterFailure = originalValueOfContinueAfterFailure; } From 5119ccfc201387098960621eeb2fb3aaa3d82f7e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 30 Jul 2021 17:10:52 -0400 Subject: [PATCH 022/131] Fix Xcode13b4 Catalyst build (#36) * Fix Xcode13b4 Catalyst build * Add explicit * Style * Update CHANGELOG for 'Unreleased' release --- CHANGELOG.md | 3 +++ Package.swift | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e5bd207..38d38b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Fix Xcode13b4 Catalyst build (#36) + # v9.1.0 - Bump Promises dependency. (#8334) diff --git a/Package.swift b/Package.swift index 95e750bb..9c69e907 100644 --- a/Package.swift +++ b/Package.swift @@ -76,8 +76,8 @@ let package = Package( .define("PB_ENABLE_MALLOC", to: "1"), ], linkerSettings: [ - .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS])), - .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS])), + .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS, .catalyst])), + .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .catalyst])), ] ), .testTarget( @@ -100,3 +100,13 @@ let package = Package( cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 ) + +extension Platform { + static var catalyst: Self { + #if swift(>=5.5) + return Self.macCatalyst + #else + return Self.macOS + #endif // swift(>=5.5) + } +} From ce71be3f0da0fca59c447617525c171022e9fb3a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 31 Aug 2021 14:20:26 -0400 Subject: [PATCH 023/131] Fix paths in test_coverage.yml (#37) --- .github/workflows/test_coverage.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 1e1c4751..37306e5f 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -25,7 +25,7 @@ jobs: run: ./scripts/setup_bundler.sh - name: PodLibLint DataTransport run: | - scripts/code_coverage_report/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec=Tests-Unit,CCT-Tests-Unit + scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec=Tests-Unit,CCT-Tests-Unit - uses: actions/upload-artifact@v2 with: name: codecoverage @@ -59,8 +59,7 @@ jobs: gcloud auth activate-service-account --key-file metrics-access.json - name: Build code coverage tool run: | - cd firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/ - swift build + swift build --package-path firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/ cat $GITHUB_EVENT_PATH - name: Generate report if: github.event.pull_request.merged != true && env.METRICS_SERVICE_SECRET @@ -71,11 +70,11 @@ jobs: common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" + firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" fi - name: Update New Coverage Data if: github.event.pull_request.merged == true && env.METRICS_SERVICE_SECRET run: | if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/code_coverage_report/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --source-branch "${{ github.base_ref }}" + firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --source-branch "${{ github.base_ref }}" fi From 14716b52767c75a10fa7e54b9f8d63a4cb6b9789 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 2 Sep 2021 14:51:23 -0400 Subject: [PATCH 024/131] Update CHANGELOG for SPM 9.1.1 (#38) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d38b8e..ce3897d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# v9.1.1 (Swift PM) - Fix Xcode13b4 Catalyst build (#36) # v9.1.0 From 7fb27ea49414b9c5483503cd06baa821c8654d1e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:04:49 -0400 Subject: [PATCH 025/131] Bump podspec to 9.1.1 (not to be released) (#39) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index e5c187de..de6dd9a1 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.1.0' + s.version = '9.1.1' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From 886452d867a4f2f44eadfbfb17da24c355ffd36a Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Fri, 15 Oct 2021 16:26:18 -0400 Subject: [PATCH 026/131] Update clang-format version to 13 (#42) --- .github/workflows/check.yml | 2 +- .../Unit/Helpers/GDTCCTEventGenerator.m | 4 ++- .../GDTCORLibrary/GDTCORPlatform.m | 29 ++++++++++--------- .../Unit/GDTCORFlatFileStorageTest.m | 4 +-- README.md | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f32d2f73..52d1db50 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -30,7 +30,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@12 + brew install clang-format@13 brew install mint mint bootstrap diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m index 77719858..932bc0a0 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m @@ -223,7 +223,9 @@ - (NSURL *)writeConsistentMessageToDisk:(NSString *)messageResource { [event.clockSnapshot setValue:@(1239567890) forKeyPath:@"uptimeNanoseconds"]; event.qosTier = GDTCOREventQoSTelemetry; NSError *error; - event.customBytes = [NSJSONSerialization dataWithJSONObject:@{@"customParam2" : @(34)} + event.customBytes = [NSJSONSerialization dataWithJSONObject:@{ + @"customParam2" : @(34) + } options:0 error:&error]; GDTCORAssert(error == nil, @"There shouldn't be an issue turning into JSON"); diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index 0b51577a..a6207ec2 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -487,21 +487,22 @@ - (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID); } [[self sharedNSProcessInfoForBackgroundTask] - performExpiringActivityWithReason:name - usingBlock:^(BOOL expired) { - if (expired) { - if (handler) { - handler(); + performExpiringActivityWithReason:name + usingBlock:^(BOOL expired) { + if (expired) { + if (handler) { + handler(); + } + dispatch_semaphore_signal(semaphore); + GDTCORLogDebug( + @"Activity with name:%@ bgID:%ld on watchOS is expiring.", + name, (long)bgID); + } else { + dispatch_semaphore_wait( + semaphore, + dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); } - dispatch_semaphore_signal(semaphore); - GDTCORLogDebug( - @"Activity with name:%@ bgID:%ld on watchOS is expiring.", name, - (long)bgID); - } else { - dispatch_semaphore_wait( - semaphore, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC)); - } - }]; + }]; #endif return bgID; } diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index b5d2645f..f801e059 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -286,8 +286,8 @@ - (void)testStoringEventsDuringTerminate { }]; } [NSNotificationCenter.defaultCenter - postNotificationName:kGDTCORApplicationWillTerminateNotification - object:nil]; + postNotificationName:kGDTCORApplicationWillTerminateNotification + object:nil]; } self.continueAfterFailure = originalValueOfContinueAfterFailure; } diff --git a/README.md b/README.md index f7648c21..2633a79f 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@12 +brew install clang-format@13 brew install mint ``` From 15ccdfd25ac55b9239b82809531ff26605e7556e Mon Sep 17 00:00:00 2001 From: Maksym Malyhin Date: Mon, 18 Oct 2021 10:03:58 -0400 Subject: [PATCH 027/131] Use [NSOperation start] instead of main for async NSOperation (#41) * Use [NSOperation start] instead of main for async NSOperation * Bump version to 9.1.2 * Changelog --- CHANGELOG.md | 2 ++ GoogleDataTransport.podspec | 2 +- GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3897d3..99e7ac87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# v9.1.2 +- Fix crash in `GDTCCTUploadOperation` on older iOS versions. (#41) # v9.1.1 (Swift PM) - Fix Xcode13b4 Catalyst build (#36) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index de6dd9a1..3058dbf7 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.1.1' + s.version = '9.1.2' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 41460284..f3887dfc 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -549,7 +549,7 @@ - (void)finishOperation { } } -- (void)main { +- (void)start { [self startOperation]; GDTCORLogDebug(@"Upload operation started: %@", self); From ccea922fc03deb8e8c13529da349d9cf910c755e Mon Sep 17 00:00:00 2001 From: dmaclach Date: Tue, 9 Nov 2021 15:39:46 -0800 Subject: [PATCH 028/131] Fix up compilation with `-Woverriding-method-mismatch` (#44) --- .../GDTCORLibrary/GDTCORLifecycle.m | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m b/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m index 89da75dd..7e534569 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORLifecycle.m @@ -47,18 +47,17 @@ - (instancetype)init { if (self) { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self - selector:@selector(applicationDidEnterBackground:) + selector:@selector(applicationDidEnterBackgroundNotification:) name:kGDTCORApplicationDidEnterBackgroundNotification object:nil]; [notificationCenter addObserver:self - selector:@selector(applicationWillEnterForeground:) + selector:@selector(applicationWillEnterForegroundNotification:) name:kGDTCORApplicationWillEnterForegroundNotification object:nil]; - NSString *name = kGDTCORApplicationWillTerminateNotification; [notificationCenter addObserver:self - selector:@selector(applicationWillTerminate:) - name:name + selector:@selector(applicationWillTerminateNotification:) + name:kGDTCORApplicationWillTerminateNotification object:nil]; } return self; @@ -68,7 +67,7 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } -- (void)applicationDidEnterBackground:(NSNotification *)notification { +- (void)applicationDidEnterBackgroundNotification:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillBackground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is backgrounding."); @@ -84,7 +83,7 @@ - (void)applicationDidEnterBackground:(NSNotification *)notification { } } -- (void)applicationWillEnterForeground:(NSNotification *)notification { +- (void)applicationWillEnterForegroundNotification:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillForeground:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is foregrounding."); @@ -100,7 +99,7 @@ - (void)applicationWillEnterForeground:(NSNotification *)notification { } } -- (void)applicationWillTerminate:(NSNotification *)notification { +- (void)applicationWillTerminateNotification:(NSNotification *)notification { GDTCORApplication *application = [GDTCORApplication sharedApplication]; if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) { GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is terminating."); From 9e3d608515edce3cb7f63260be5cd765c0a27124 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sun, 12 Dec 2021 19:37:08 -0500 Subject: [PATCH 029/131] Fix `watchos-testapp` workflow's macOS image (#46) * Set workflow image to use macOS-10.15 * s/12.2/12 --- .github/workflows/datatransport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index 52f4a609..e406bbf9 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -34,7 +34,7 @@ jobs: run: scripts/third_party/travis/retry.sh scripts/test_catalyst.sh GoogleDataTransport build watchos-testapp: - runs-on: macOS-latest + runs-on: macOS-10.15 steps: - uses: actions/checkout@v2 - name: Xcode 12 From 25449cd3dc7f4583407c81748c880fbf433c95dc Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sun, 12 Dec 2021 19:52:06 -0500 Subject: [PATCH 030/131] Remove GDTCORLifecycle conformance to GDTCORApplicationDelegate (#45) --- GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h index 3d779707..9316e5cb 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h @@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN * library (GDTCORStorage and GDTCORUploadCoordinator instances) will deserialize themselves from * and to disk before and after every operation, respectively. */ -@interface GDTCORLifecycle : NSObject +@interface GDTCORLifecycle : NSObject @end From 5fe18c35562a2a2c463d69165e20f401d98f25b5 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 26 Jan 2022 18:14:27 -0500 Subject: [PATCH 031/131] Upgrade swift-format to match firebase-ios-sdk (#49) * Upgrade swift-format to match firebase-ios-sdk * Style * Fix watchOS failure * Fix CI failures * Fix CI failures 2 --- GoogleDataTransport/GDTCCTTestApp/app.swift | 42 +++++++++++++------ .../GDTCCTTestApp/gdthelpers.swift | 2 +- .../GDTCCTTestApp/globals.swift | 2 +- .../GDTCCTTestApp/viewcontroller.swift | 2 +- .../NotificationService.swift | 3 +- .../InterfaceController.swift | 3 +- .../InterfaceController.swift | 3 +- .../gdthelpers.swift | 2 +- .../GDTTestApp/EventCleanupPerfTest.swift | 4 +- GoogleDataTransport/GDTTestApp/app.swift | 14 +++++-- GoogleDataTransport/GDTTestApp/globals.swift | 2 +- .../GDTTestApp/viewcontroller.swift | 4 +- .../InterfaceController.swift | 3 +- Mintfile | 2 +- 14 files changed, 54 insertions(+), 34 deletions(-) diff --git a/GoogleDataTransport/GDTCCTTestApp/app.swift b/GoogleDataTransport/GDTCCTTestApp/app.swift index cf5af7e8..4a48dce0 100644 --- a/GoogleDataTransport/GDTCCTTestApp/app.swift +++ b/GoogleDataTransport/GDTCCTTestApp/app.swift @@ -32,12 +32,21 @@ import GoogleDataTransport } public class ViewController: UIViewController { - let cctTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.CCT)! - let fllTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.FLL)! - let cshTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.CSH)! + let cctTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.CCT + )! + let fllTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.FLL + )! + let cshTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.CSH + )! @IBOutlet var backendSwitch: UISegmentedControl? @@ -97,12 +106,21 @@ import GoogleDataTransport } public class ViewController: NSViewController { - let cctTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.CCT)! - let fllTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.FLL)! - let cshTransport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.CSH)! + let cctTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.CCT + )! + let fllTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.FLL + )! + let cshTransport = GDTCORTransport( + mappingID: "1018", + transformers: nil, + target: GDTCORTarget.CSH + )! @IBOutlet var backendSwitch: NSSegmentedControl? diff --git a/GoogleDataTransport/GDTCCTTestApp/gdthelpers.swift b/GoogleDataTransport/GDTCCTTestApp/gdthelpers.swift index 118da3b0..40c7a758 100644 --- a/GoogleDataTransport/GDTCCTTestApp/gdthelpers.swift +++ b/GoogleDataTransport/GDTCCTTestApp/gdthelpers.swift @@ -17,7 +17,7 @@ import GoogleDataTransport class FirelogTestMessageHolder: NSObject, GDTCOREventDataObject { - public var root: FirelogTestMessage = FirelogTestMessage() + public var root: FirelogTestMessage = .init() func transportBytes() -> Data { do { diff --git a/GoogleDataTransport/GDTCCTTestApp/globals.swift b/GoogleDataTransport/GDTCCTTestApp/globals.swift index 4519686e..6a226a3d 100644 --- a/GoogleDataTransport/GDTCCTTestApp/globals.swift +++ b/GoogleDataTransport/GDTCCTTestApp/globals.swift @@ -16,7 +16,7 @@ import Foundation -public struct Globals { +public enum Globals { public static var SharedViewController: ViewController? public static var IsMonkeyTesting: Bool = false diff --git a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift index dc40ba01..b92ef44a 100644 --- a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift @@ -122,7 +122,7 @@ public extension ViewController { print("Beginning monkey test") Globals.IsMonkeyTesting = true - let sema: DispatchSemaphore = DispatchSemaphore(value: 0) + let sema = DispatchSemaphore(value: 0) var generateEvents = true DispatchQueue.global().asyncAfter(deadline: .now() + Globals.MonkeyTestLength) { generateEvents = false diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift index 7373fbd0..a811aa6c 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift @@ -22,8 +22,7 @@ class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? - var transport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.FLL)! + var transport = GDTCORTransport(mappingID: "1018", transformers: nil, target: GDTCORTarget.FLL)! override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift index e7c45d2a..85d0a9bc 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift @@ -19,8 +19,7 @@ import Foundation import GoogleDataTransport class InterfaceController: WKInterfaceController { - var transport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.FLL)! + var transport = GDTCORTransport(mappingID: "1018", transformers: nil, target: GDTCORTarget.FLL)! override func awake(withContext context: Any?) { super.awake(withContext: context) diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift index 166423a3..26784d16 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift @@ -19,8 +19,7 @@ import Foundation import GoogleDataTransport class InterfaceController: WKInterfaceController { - var transport: GDTCORTransport = GDTCORTransport(mappingID: "1018", transformers: nil, - target: GDTCORTarget.FLL)! + var transport = GDTCORTransport(mappingID: "1018", transformers: nil, target: GDTCORTarget.FLL)! override func awake(withContext context: Any?) { super.awake(withContext: context) diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/gdthelpers.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/gdthelpers.swift index d1c4712f..779a1fd6 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/gdthelpers.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/gdthelpers.swift @@ -17,7 +17,7 @@ import GoogleDataTransport class FirelogTestMessageHolder: NSObject, GDTCOREventDataObject { - public var root: FirelogTestMessage = FirelogTestMessage() + public var root: FirelogTestMessage = .init() func transportBytes() -> Data { do { diff --git a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift index b0bc831d..e80f5bb4 100644 --- a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift +++ b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift @@ -22,7 +22,7 @@ import os.signpost /// The test actions to run under the profiler to measure performance of `GDTCORFlatFileStorage.checkForExpirations()` method. @available(iOS 12.0, *) -class EventCleanupPerfTest { +enum EventCleanupPerfTest { static let log = OSLog(subsystem: "GoogleDataTransport-TestApp", category: "EventCleanupPerfTest") static func run(completion: @escaping () -> Void) { @@ -44,7 +44,7 @@ class EventCleanupPerfTest { os_signpost(.begin, log: log, name: "generateTestEvents", signpostID: signpostID) - _ = (0 ..< count).compactMap { (_) -> GDTCOREvent? in + _ = (0 ..< count).compactMap { _ -> GDTCOREvent? in group.enter() let event = GDTCOREventGenerator.generateEvent(for: .test, qosTier: nil, mappingID: nil) GDTCORFlatFileStorage.sharedInstance().store(event) { _, _ in diff --git a/GoogleDataTransport/GDTTestApp/app.swift b/GoogleDataTransport/GDTTestApp/app.swift index eae281b9..b12865ee 100644 --- a/GoogleDataTransport/GDTTestApp/app.swift +++ b/GoogleDataTransport/GDTTestApp/app.swift @@ -33,8 +33,11 @@ import GoogleDataTransport } public class ViewController: UIViewController { - let transport: GDTCORTransport = GDTCORTransport(mappingID: "1234", transformers: nil, - target: GDTCORTarget.test)! + let transport = GDTCORTransport( + mappingID: "1234", + transformers: nil, + target: GDTCORTarget.test + )! @IBOutlet var statusLabel: UILabel! } @@ -49,7 +52,10 @@ import GoogleDataTransport } public class ViewController: NSViewController { - let transport: GDTCORTransport = GDTCORTransport(mappingID: "1234", transformers: nil, - target: GDTCORTarget.test)! + let transport = GDTCORTransport( + mappingID: "1234", + transformers: nil, + target: GDTCORTarget.test + )! } #endif diff --git a/GoogleDataTransport/GDTTestApp/globals.swift b/GoogleDataTransport/GDTTestApp/globals.swift index caccc8ea..1c35a355 100644 --- a/GoogleDataTransport/GDTTestApp/globals.swift +++ b/GoogleDataTransport/GDTTestApp/globals.swift @@ -16,7 +16,7 @@ import Foundation -public struct Globals { +public enum Globals { public static var SharedViewController: ViewController? public static let MonkeyTestLength: TimeInterval = 20.0 diff --git a/GoogleDataTransport/GDTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTTestApp/viewcontroller.swift index d9d9ea5d..b0b264fa 100644 --- a/GoogleDataTransport/GDTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTTestApp/viewcontroller.swift @@ -93,7 +93,7 @@ public extension ViewController { print("Beginning monkey test") let generateEventsQueue = DispatchQueue(label: "com.google.GDTTestApp.vc.generateEvents") - let sema: DispatchSemaphore = DispatchSemaphore(value: 0) + let sema = DispatchSemaphore(value: 0) var generateEvents = true DispatchQueue.global().asyncAfter(deadline: .now() + Globals.MonkeyTestLength) { generateEventsQueue.sync { @@ -115,7 +115,7 @@ public extension ViewController { generationFunctions[randomIndex](self) } RunLoop.current.run(until: Date(timeIntervalSinceNow: Double.random(in: 0 ..< 1.5))) - var shouldContinueGeneratingEvents: Bool = false + var shouldContinueGeneratingEvents = false generateEventsQueue.sync { shouldContinueGeneratingEvents = generateEvents } diff --git a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift index 06cffb6c..0544ef58 100644 --- a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift @@ -21,8 +21,7 @@ import Dispatch import GoogleDataTransport class InterfaceController: WKInterfaceController { - let transport: GDTCORTransport = GDTCORTransport(mappingID: "1234", transformers: nil, - target: GDTCORTarget.test)! + let transport = GDTCORTransport(mappingID: "1234", transformers: nil, target: GDTCORTarget.test)! override func awake(withContext context: Any?) { super.awake(withContext: context) diff --git a/Mintfile b/Mintfile index 30fa82e4..4e60f1d0 100644 --- a/Mintfile +++ b/Mintfile @@ -1 +1 @@ -nicklockwood/SwiftFormat@0.45.5 +nicklockwood/SwiftFormat@0.49.2 From 1a65c30db4fc841e06586441682c2dda036d9db3 Mon Sep 17 00:00:00 2001 From: Sam Edson Date: Thu, 10 Mar 2022 10:56:07 -0500 Subject: [PATCH 032/131] Make GoogleDataTransport use GoogleUtilities logging level (#54) --- GoogleDataTransport.podspec | 3 +- .../GDTCORLibrary/GDTCORConsoleLogger.m | 46 +++++++++++-------- .../GoogleDataTransport/GDTCORConsoleLogger.h | 5 -- Package.swift | 1 + README.md | 25 +++------- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 3058dbf7..169c7319 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -39,7 +39,8 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] - s.dependency 'GoogleUtilities/Environment', '~> 7.2' + s.dependency 'GoogleUtilities/Environment', '~> 7.7' + s.dependency 'GoogleUtilities/Logger', '~> 7.7' s.dependency 'nanopb', '~> 2.30908.0' s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m b/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m index 5eaee924..20a60903 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m @@ -16,40 +16,46 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" -volatile NSInteger GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelErrors; +#import /** The console logger prefix. */ static NSString *kGDTCORConsoleLogger = @"[GoogleDataTransport]"; NSString *GDTCORMessageCodeEnumToString(GDTCORMessageCode code) { - return [[NSString alloc] initWithFormat:@"I-GDTCOR%06ld", (long)code]; + return [[NSString alloc] initWithFormat:@"I-GDT%06ld", (long)code]; } void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *format, ...) { -// Don't log anything in not debug builds. #if !NDEBUG - if (logLevel >= GDTCORConsoleLoggerLoggingLevel) { - NSString *logFormat = [NSString stringWithFormat:@"%@[%@] %@", kGDTCORConsoleLogger, - GDTCORMessageCodeEnumToString(code), format]; - va_list args; - va_start(args, format); - NSLogv(logFormat, args); - va_end(args); + GULLoggerLevel gulLevel = GULLoggerLevelDebug; + switch (logLevel) { + case GDTCORLoggingLevelDebug: + gulLevel = GULLoggerLevelDebug; + break; + case GDTCORLoggingLevelVerbose: + gulLevel = GULLoggerLevelInfo; + break; + case GDTCORLoggingLevelWarnings: + gulLevel = GULLoggerLevelWarning; + break; + case GDTCORLoggingLevelErrors: + gulLevel = GULLoggerLevelError; + break; + default: + break; } + + va_list args; + va_start(args, format); + GULLogBasic(gulLevel, kGDTCORConsoleLogger, false, GDTCORMessageCodeEnumToString(code), format, + args); + va_end(args); #endif // !NDEBUG } void GDTCORLogAssert( BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) { -// Don't log anything in not debug builds. -#if !NDEBUG GDTCORMessageCode code = wasFatal ? GDTCORMCEFatalAssertion : GDTCORMCEGeneralError; - NSString *logFormat = - [NSString stringWithFormat:@"%@[%@] (%@:%ld) : %@", kGDTCORConsoleLogger, - GDTCORMessageCodeEnumToString(code), file, (long)line, format]; - va_list args; - va_start(args, format); - NSLogv(logFormat, args); - va_end(args); -#endif // !NDEBUG + + GDTCORLog(code, GDTCORLoggingLevelErrors, @"(%@:%ld) : %@", file, (long)line, format); } diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h index 1fdf7325..36a08c15 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h @@ -16,11 +16,6 @@ #import -/** The current logging level. This value and higher will be printed. Declared as volatile to make - * getting and setting atomic. - */ -FOUNDATION_EXPORT volatile NSInteger GDTCORConsoleLoggerLoggingLevel; - /** A list of logging levels that GDT supports. */ typedef NS_ENUM(NSInteger, GDTCORLoggingLevel) { diff --git a/Package.swift b/Package.swift index 9c69e907..ffffd33d 100644 --- a/Package.swift +++ b/Package.swift @@ -51,6 +51,7 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), + .product(name: "GULLogger", package: "GoogleUtilities"), ], path: "GoogleDataTransport", exclude: [ diff --git a/README.md b/README.md index 2633a79f..51a458be 100644 --- a/README.md +++ b/README.md @@ -124,26 +124,13 @@ The release process is as follows: ## Set logging level -### Swift +GoogleDataTransport will follow the logging level of the underlying +GoogleUtilities logger. If GoogleDataTransport is being used with a Firebase +App, you can enable debug logs by adding the command line argument +`-FIRDebugEnabled` to your app's Arguments Passed on Launch. You can disable +debug logs with `-FIRDebugDisabled`. -- Import `GoogleDataTransport` module: - ```swift - import GoogleDataTransport - ``` -- Set logging level global variable to the desired value before calling `FirebaseApp.configure()`: - ```swift - GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue - ``` -### Objective-C - -- Import `GoogleDataTransport`: - ```objective-c - #import - ``` -- Set logging level global variable to the desired value before calling `-[FIRApp configure]`: - ```objective-c - GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug; - ``` +For performance reasons, GoogleDataTransport will not log in release builds. ## Prereqs From b9e1f0edd62bd144e09f8576b70d1cd1afade234 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 3 May 2022 00:45:07 -0400 Subject: [PATCH 033/131] [Infra] Upgrade `clang-format` to `clang-format@14` (#58) * Bump to clang-format@14 * Fix whitespace * Remove deleted check * Review --- .github/workflows/check.yml | 7 +++---- .../GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c | 4 ++-- README.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 52d1db50..b4be8890 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,6 +2,8 @@ name: check on: pull_request: + paths: + - '.github/workflows/check.yml' push: branches: main @@ -30,7 +32,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@13 + brew install clang-format@14 brew install mint mint bootstrap @@ -46,8 +48,5 @@ jobs: - name: Copyrights run: scripts/check_copyright.sh - - name: Module imports - run: scripts/check_no_module_imports.sh - - name: Imports run: scripts/check_imports.swift diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c index 2f5327e5..2d92a15b 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c @@ -105,7 +105,7 @@ const pb_field_t gdt_cct_LogResponse_fields[3] = { #if !defined(PB_FIELD_32BIT) /* If you get an error here, it means that you need to define PB_FIELD_32BIT * compile-time option. You can do that in pb.h or on compiler command line. - * + * * The reason you need to do this is that some of your messages contain tag * numbers or field sizes that are larger than what can fit in 8 or 16 bit * field descriptors. @@ -116,7 +116,7 @@ PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 655 #if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) /* If you get an error here, it means that you need to define PB_FIELD_16BIT * compile-time option. You can do that in pb.h or on compiler command line. - * + * * The reason you need to do this is that some of your messages contain tag * numbers or field sizes that are larger than what can fit in the default * 8 bit descriptors. diff --git a/README.md b/README.md index 51a458be..9f34a1c3 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@13 +brew install clang-format@14 brew install mint ``` From 4073d04bf28943353b706f461d4ca2a89abb69c1 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 5 May 2022 10:39:54 -0700 Subject: [PATCH 034/131] Update to allow update to nanopb 0.3.9.9 (#59) --- CHANGELOG.md | 5 +++++ GoogleDataTransport.podspec | 4 ++-- GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile | 2 +- Package.swift | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99e7ac87..820e8862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ +# v9.1.3 +- Update to support both nanopb 0.3.9.8 and 0.3.9.9. (#59) +- GoogleDataTransport now uses the logging level set in GoogleUtilities. (#54) + # v9.1.2 - Fix crash in `GDTCCTUploadOperation` on older iOS versions. (#41) + # v9.1.1 (Swift PM) - Fix Xcode13b4 Catalyst build (#36) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 169c7319..a8016b65 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.1.2' + s.version = '9.1.3' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -41,7 +41,7 @@ Shared library for iOS SDK data transport needs. s.dependency 'GoogleUtilities/Environment', '~> 7.7' s.dependency 'GoogleUtilities/Logger', '~> 7.7' - s.dependency 'nanopb', '~> 2.30908.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' header_search_paths = { diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile b/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile index 420e55a7..4f8fce37 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/Podfile @@ -12,7 +12,7 @@ target 'GDTCCTWatchOSIndependentTestAppWatchKitExtension' do pod 'GoogleDataTransport', :path => '../../' pod 'FirebaseCore' pod 'FirebaseMessaging' - pod 'FirebaseStorage' + pod 'FirebaseStorage', '> 9.0' end diff --git a/Package.swift b/Package.swift index ffffd33d..10d3150c 100644 --- a/Package.swift +++ b/Package.swift @@ -30,7 +30,7 @@ let package = Package( .package( name: "nanopb", url: "https://github.com/firebase/nanopb.git", - "2.30908.0" ..< "2.30909.0" + "2.30908.0" ..< "2.30910.0" ), .package( name: "Promises", From b905c49326b72211531ed9d7baa02d724828a8dc Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 6 May 2022 13:06:08 -0400 Subject: [PATCH 035/131] Rollback #54 and update for 9.1.4 patch release (#60) This reverts commit 1a65c30db4fc841e06586441682c2dda036d9db3. --- CHANGELOG.md | 3 ++ GoogleDataTransport.podspec | 3 +- .../GDTCORLibrary/GDTCORConsoleLogger.m | 46 ++++++++----------- .../GoogleDataTransport/GDTCORConsoleLogger.h | 5 ++ Package.swift | 1 - README.md | 25 +++++++--- 6 files changed, 48 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820e8862..f74ea97d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.1.4 +- Rollback logging level changes from v9.1.3. (#60) + # v9.1.3 - Update to support both nanopb 0.3.9.8 and 0.3.9.9. (#59) - GoogleDataTransport now uses the logging level set in GoogleUtilities. (#54) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index a8016b65..a9c812ae 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.1.3' + s.version = '9.1.4' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -40,7 +40,6 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] s.dependency 'GoogleUtilities/Environment', '~> 7.7' - s.dependency 'GoogleUtilities/Logger', '~> 7.7' s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m b/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m index 20a60903..5eaee924 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORConsoleLogger.m @@ -16,46 +16,40 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" -#import +volatile NSInteger GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelErrors; /** The console logger prefix. */ static NSString *kGDTCORConsoleLogger = @"[GoogleDataTransport]"; NSString *GDTCORMessageCodeEnumToString(GDTCORMessageCode code) { - return [[NSString alloc] initWithFormat:@"I-GDT%06ld", (long)code]; + return [[NSString alloc] initWithFormat:@"I-GDTCOR%06ld", (long)code]; } void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *format, ...) { +// Don't log anything in not debug builds. #if !NDEBUG - GULLoggerLevel gulLevel = GULLoggerLevelDebug; - switch (logLevel) { - case GDTCORLoggingLevelDebug: - gulLevel = GULLoggerLevelDebug; - break; - case GDTCORLoggingLevelVerbose: - gulLevel = GULLoggerLevelInfo; - break; - case GDTCORLoggingLevelWarnings: - gulLevel = GULLoggerLevelWarning; - break; - case GDTCORLoggingLevelErrors: - gulLevel = GULLoggerLevelError; - break; - default: - break; + if (logLevel >= GDTCORConsoleLoggerLoggingLevel) { + NSString *logFormat = [NSString stringWithFormat:@"%@[%@] %@", kGDTCORConsoleLogger, + GDTCORMessageCodeEnumToString(code), format]; + va_list args; + va_start(args, format); + NSLogv(logFormat, args); + va_end(args); } - - va_list args; - va_start(args, format); - GULLogBasic(gulLevel, kGDTCORConsoleLogger, false, GDTCORMessageCodeEnumToString(code), format, - args); - va_end(args); #endif // !NDEBUG } void GDTCORLogAssert( BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) { +// Don't log anything in not debug builds. +#if !NDEBUG GDTCORMessageCode code = wasFatal ? GDTCORMCEFatalAssertion : GDTCORMCEGeneralError; - - GDTCORLog(code, GDTCORLoggingLevelErrors, @"(%@:%ld) : %@", file, (long)line, format); + NSString *logFormat = + [NSString stringWithFormat:@"%@[%@] (%@:%ld) : %@", kGDTCORConsoleLogger, + GDTCORMessageCodeEnumToString(code), file, (long)line, format]; + va_list args; + va_start(args, format); + NSLogv(logFormat, args); + va_end(args); +#endif // !NDEBUG } diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h index 36a08c15..1fdf7325 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h @@ -16,6 +16,11 @@ #import +/** The current logging level. This value and higher will be printed. Declared as volatile to make + * getting and setting atomic. + */ +FOUNDATION_EXPORT volatile NSInteger GDTCORConsoleLoggerLoggingLevel; + /** A list of logging levels that GDT supports. */ typedef NS_ENUM(NSInteger, GDTCORLoggingLevel) { diff --git a/Package.swift b/Package.swift index 10d3150c..7665088b 100644 --- a/Package.swift +++ b/Package.swift @@ -51,7 +51,6 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), - .product(name: "GULLogger", package: "GoogleUtilities"), ], path: "GoogleDataTransport", exclude: [ diff --git a/README.md b/README.md index 9f34a1c3..73bcde5a 100644 --- a/README.md +++ b/README.md @@ -124,13 +124,26 @@ The release process is as follows: ## Set logging level -GoogleDataTransport will follow the logging level of the underlying -GoogleUtilities logger. If GoogleDataTransport is being used with a Firebase -App, you can enable debug logs by adding the command line argument -`-FIRDebugEnabled` to your app's Arguments Passed on Launch. You can disable -debug logs with `-FIRDebugDisabled`. +### Swift -For performance reasons, GoogleDataTransport will not log in release builds. +- Import `GoogleDataTransport` module: + ```swift + import GoogleDataTransport + ``` +- Set logging level global variable to the desired value before calling `FirebaseApp.configure()`: + ```swift + GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue + ``` +### Objective-C + +- Import `GoogleDataTransport`: + ```objective-c + #import + ``` +- Set logging level global variable to the desired value before calling `-[FIRApp configure]`: + ```objective-c + GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug; + ``` ## Prereqs From d1b70206c02eab678c6fc4430b5c0f621e572675 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 20 May 2022 19:12:03 -0400 Subject: [PATCH 036/131] [skip ci] Regenerate with nanopb-0.3.9.9 (#62) --- .../GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c | 2 +- .../GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h | 2 +- GoogleDataTransport/ProtoSupport/generate_cct_protos.sh | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c index 2d92a15b..7c5f1e7c 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c @@ -15,7 +15,7 @@ */ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.3.9.7 */ +/* Generated by nanopb-0.3.9.9 */ #include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h index 05bdf589..334ddfe6 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h @@ -15,7 +15,7 @@ */ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.3.9.7 */ +/* Generated by nanopb-0.3.9.9 */ #ifndef PB_GDT_CCT_CCT_NANOPB_H_INCLUDED #define PB_GDT_CCT_CCT_NANOPB_H_INCLUDED diff --git a/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh b/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh index 4f2b789c..4e2ba4a4 100755 --- a/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh +++ b/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh @@ -22,8 +22,8 @@ readonly DIR="$( git rev-parse --show-toplevel )" -# Current release of nanopb being used to build the CCT protos -readonly NANOPB_VERSION="0.3.9.8" +# Current release of nanopb being used to build the CCT protos +readonly NANOPB_VERSION="0.3.9.9" readonly NANOPB_TEMPDIR="${DIR}/GoogleDataTransport/nanopb_temp" readonly LIBRARY_DIR="${DIR}/GoogleDataTransport/GDTCCTLibrary/" From 3755eb08c19b782f4383cd87f3134ab0c91c4e5a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 26 Jan 2022 18:59:02 -0500 Subject: [PATCH 037/131] [Metrics] Stub out base components (#48) --- .../GDTCORLibrary/GDTCORMetricsController.m | 55 +++++++++++++++++++ .../Internal/GDTCOREventDropReason.h | 28 ++++++++++ .../GDTCORMetricsControllerProtocol.h | 42 ++++++++++++++ .../Internal/GDTCORStorageProtocol.h | 10 ++++ .../Private/GDTCORMetricsController.h | 27 +++++++++ 5 files changed, 162 insertions(+) create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m create mode 100644 GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h create mode 100644 GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m new file mode 100644 index 00000000..d15aaadf --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +@implementation GDTCORMetricsController + +- (void)storage:(nonnull id)storage + didDropEvent:(nonnull GDTCOREvent *)event { + // TODO(nickcooke): Implement. +} + +- (void)storage:(nonnull id)storage + didRemoveExpiredEvent:(nonnull GDTCOREvent *)event { + // TODO(nickcooke): Implement. +} + +- (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason + eventCount:(NSUInteger)eventCount + mappingID:(nonnull NSString *)mappingID { + // TODO(nickcooke): Implement. +} + +- (nonnull FBLPromise *)metrics { + // TODO(nickcooke): Implement. + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)resetMetrics:(nonnull GDTCORMetrics *)metrics { + // TODO(nickcooke): Implement. + return [FBLPromise resolvedWith:nil]; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h new file mode 100644 index 00000000..13b8603f --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h @@ -0,0 +1,28 @@ +/* + * Copyright 2022 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 + +// TODO(nickcooke): Document. +typedef NS_ENUM(NSInteger, GDTCOREventDropReason) { + GDTCOREventDropReasonUnknown = 0, + GDTCOREventDropReasonMessageTooOld, + GDTCOREventDropReasonStorageFull, + GDTCOREventDropReasonPayloadTooBig, + GDTCOREventDropReasonMaxRetriesReached, + GDTCOREventDropReasonInvalidPayload, + GDTCOREventDropReasonServerError +}; diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h new file mode 100644 index 00000000..b5b41f4f --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" + +@class FBLPromise; +@class GDTCORMetrics; + +NS_ASSUME_NONNULL_BEGIN + +// TODO(nickcooke): Document. +@protocol GDTCORMetricsControllerProtocol +// TODO(nickcooke): Document. +- (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason + eventCount:(NSUInteger)eventCount + mappingID:(NSString *)mappingID; + +// TODO(nickcooke): Document. +- (FBLPromise *)metrics; + +// TODO(nickcooke): Document. +- (FBLPromise *)resetMetrics:(GDTCORMetrics *)metrics; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index ffccfdba..c09e07a5 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -168,4 +168,14 @@ FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target); +// TODO(ncooke3): Document. +@protocol GDTCORStorageDelegate +// TODO(ncooke3): Document. +- (void)storage:(id)storage + didRemoveExpiredEvent:(GDTCOREvent *)event; + +// TODO(ncooke3): Document. +- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event; +@end + NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h new file mode 100644 index 00000000..be89e04b --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h @@ -0,0 +1,27 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORMetricsController : NSObject + +@end + +NS_ASSUME_NONNULL_END From 3481f932e2112ca7b98f4143ebe6a9779a19aff1 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 27 Jan 2022 18:34:45 -0500 Subject: [PATCH 038/131] [Metrics] Add plumbing for metrics controller registration (#50) * Add plumbing for metrics controller registration * Style --- .../GDTCORLibrary/GDTCORMetricsController.m | 44 +++++++++++++++---- .../GDTCORLibrary/GDTCORRegistrar.m | 17 +++++++ .../Internal/GDTCOREventDropReason.h | 2 +- .../GDTCORMetricsControllerProtocol.h | 8 ++-- .../GDTCORLibrary/Internal/GDTCORRegistrar.h | 5 +++ .../Private/GDTCORRegistrar_Private.h | 4 ++ 6 files changed, 66 insertions(+), 14 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index d15aaadf..3ef29bfe 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -24,32 +24,58 @@ #import "FBLPromises.h" #endif +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" + @implementation GDTCORMetricsController -- (void)storage:(nonnull id)storage - didDropEvent:(nonnull GDTCOREvent *)event { - // TODO(nickcooke): Implement. ++ (void)load { + [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] + target:kGDTCORTargetCSH]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] + target:kGDTCORTargetFLL]; } -- (void)storage:(nonnull id)storage - didRemoveExpiredEvent:(nonnull GDTCOREvent *)event { - // TODO(nickcooke): Implement. ++ (instancetype)sharedInstance { + static id sharedInstance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // TODO(ncooke3): Implement. + } + return self; } - (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason eventCount:(NSUInteger)eventCount mappingID:(nonnull NSString *)mappingID { - // TODO(nickcooke): Implement. + // TODO(ncooke3): Implement. } - (nonnull FBLPromise *)metrics { - // TODO(nickcooke): Implement. + // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } - (nonnull FBLPromise *)resetMetrics:(nonnull GDTCORMetrics *)metrics { - // TODO(nickcooke): Implement. + // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } +- (void)storage:(nonnull id)storage + didDropEvent:(nonnull GDTCOREvent *)event { + // TODO(ncooke3): Implement. +} + +- (void)storage:(nonnull id)storage + didRemoveExpiredEvent:(nonnull GDTCOREvent *)event { + // TODO(ncooke3): Implement. +} + @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index d7e9b097..c200a082 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -35,11 +35,16 @@ } @implementation GDTCORRegistrar { + // TODO(ncooke3): Replace ivar declarations with @synthesize attributes. + /** Backing ivar for targetToUploader property. */ NSMutableDictionary> *_targetToUploader; /** Backing ivar for targetToStorage property. */ NSMutableDictionary> *_targetToStorage; + + // TODO(ncooke3): Document. + NSMutableDictionary> *_targetToMetricsController; } + (instancetype)sharedInstance { @@ -57,6 +62,7 @@ - (instancetype)init { _registrarQueue = dispatch_queue_create("com.google.GDTCORRegistrar", DISPATCH_QUEUE_SERIAL); _targetToUploader = [[NSMutableDictionary alloc] init]; _targetToStorage = [[NSMutableDictionary alloc] init]; + _targetToMetricsController = [[NSMutableDictionary alloc] init]; } return self; } @@ -83,6 +89,11 @@ - (void)registerStorage:(id)storage target:(GDTCORTarget) }); } +- (void)registerMetricsController:(id)metricsController + target:(GDTCORTarget)target { + // TODO(ncooke3): Implement. +} + - (NSMutableDictionary> *)targetToUploader { __block NSMutableDictionary> *targetToUploader; __weak GDTCORRegistrar *weakSelf = self; @@ -107,6 +118,12 @@ - (void)registerStorage:(id)storage target:(GDTCORTarget) return targetToStorage; } +- (NSMutableDictionary> *) + targetToMetricsController { + // TODO(ncooke3): Implement. + return [NSMutableDictionary dictionary]; +} + #pragma mark - GDTCORLifecycleProtocol - (void)appWillBackground:(nonnull GDTCORApplication *)app { diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h index 13b8603f..3bdf81a3 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h @@ -16,7 +16,7 @@ #import -// TODO(nickcooke): Document. +// TODO(ncooke3): Document. typedef NS_ENUM(NSInteger, GDTCOREventDropReason) { GDTCOREventDropReasonUnknown = 0, GDTCOREventDropReasonMessageTooOld, diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index b5b41f4f..87a96bb8 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -24,17 +24,17 @@ NS_ASSUME_NONNULL_BEGIN -// TODO(nickcooke): Document. +// TODO(ncooke3): Document. @protocol GDTCORMetricsControllerProtocol -// TODO(nickcooke): Document. +// TODO(ncooke3): Document. - (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason eventCount:(NSUInteger)eventCount mappingID:(NSString *)mappingID; -// TODO(nickcooke): Document. +// TODO(ncooke3): Document. - (FBLPromise *)metrics; -// TODO(nickcooke): Document. +// TODO(ncooke3): Document. - (FBLPromise *)resetMetrics:(GDTCORMetrics *)metrics; @end diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h index 3ea521b2..5d75eb6e 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h @@ -16,6 +16,7 @@ #import +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" @@ -45,6 +46,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)registerStorage:(id)storage target:(GDTCORTarget)target; +// TODO(ncooke3): Document. +- (void)registerMetricsController:(id)metricsController + target:(GDTCORTarget)target; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h index 26796110..c75e3ed7 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h @@ -30,6 +30,10 @@ NS_ASSUME_NONNULL_BEGIN @property(atomic, readonly) NSMutableDictionary> *targetToStorage; +// TODO(ncooke3): Document. +@property(atomic, readonly) + NSMutableDictionary> *targetToMetricsController; + @end NS_ASSUME_NONNULL_END From f9555bd95a083139667fe639a34353d7f288b200 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 1 Feb 2022 11:19:30 -0500 Subject: [PATCH 039/131] [Metrics] Add plumbing for passing metrics controller to upload operation (#51) * Add plumbing for passing metrics controller to upload operation * Assign passed in metrics controller * Fix build issues * Fix build issues 2 * Remove macro from .m file --- GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m | 5 ++++- GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m | 6 +++++- .../GDTCCTLibrary/Private/GDTCCTUploadOperation.h | 2 ++ GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m | 7 ++++++- .../Internal/GDTCORMetricsControllerProtocol.h | 4 ++++ 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index f3887dfc..894e4bb3 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -68,6 +68,7 @@ @interface GDTCCTUploadOperation () @property(nonatomic, readonly) NSURL *uploadURL; @property(nonatomic, readonly) id storage; @property(nonatomic, readonly) id metadataProvider; +@property(nonatomic, readonly, nullable) id metricsController; /** The URL session that will attempt upload. */ @property(nonatomic) NSURLSession *uploaderSession; @@ -87,7 +88,8 @@ - (instancetype)initWithTarget:(GDTCORTarget)target uploadURL:(NSURL *)uploadURL queue:(dispatch_queue_t)queue storage:(id)storage - metadataProvider:(id)metadataProvider { + metadataProvider:(id)metadataProvider + metricsController:(nullable id)metricsController { self = [super init]; if (self) { _uploaderQueue = queue; @@ -96,6 +98,7 @@ - (instancetype)initWithTarget:(GDTCORTarget)target _uploadURL = uploadURL; _storage = storage; _metadataProvider = metadataProvider; + _metricsController = metricsController; } return self; } diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m index d25dc542..d94d4146 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m @@ -89,13 +89,17 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions return; } + id metricsController = + GDTCORMetricsControllerInstanceForTarget(target); + GDTCCTUploadOperation *uploadOperation = [[GDTCCTUploadOperation alloc] initWithTarget:target conditions:conditions uploadURL:[[self class] serverURLForTarget:target] queue:self.uploadQueue storage:storage - metadataProvider:self]; + metadataProvider:self + metricsController:metricsController]; GDTCORLogDebug(@"Upload operation created: %@, target: %@", uploadOperation, @(target)); diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h index 3f636440..1061c76c 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h @@ -19,6 +19,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h" @protocol GDTCORStoragePromiseProtocol; +@protocol GDTCORMetricsControllerProtocol; NS_ASSUME_NONNULL_BEGIN @@ -59,6 +60,7 @@ NS_ASSUME_NONNULL_BEGIN queue:(dispatch_queue_t)queue storage:(id)storage metadataProvider:(id)metadataProvider + metricsController:(nullable id)metricsController NS_DESIGNATED_INITIALIZER; /** YES if a batch upload attempt was performed. NO otherwise. If NO for the finished operation, diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index c200a082..1f7312d7 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -23,7 +23,6 @@ return [GDTCORRegistrar sharedInstance].targetToStorage[@(target)]; } -FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target) { id storage = [GDTCORRegistrar sharedInstance].targetToStorage[@(target)]; @@ -34,6 +33,12 @@ } } +id _Nullable GDTCORMetricsControllerInstanceForTarget( + GDTCORTarget target) { + // TODO(ncooke3): Implement. + return nil; +} + @implementation GDTCORRegistrar { // TODO(ncooke3): Replace ivar declarations with @synthesize attributes. diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index 87a96bb8..f314e3bd 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -39,4 +39,8 @@ NS_ASSUME_NONNULL_BEGIN @end +FOUNDATION_EXPORT +id _Nullable GDTCORMetricsControllerInstanceForTarget( + GDTCORTarget target); + NS_ASSUME_NONNULL_END From 7275919e5eeb5109c245c24adc8d8030801143fc Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 14 Feb 2022 10:21:06 -0500 Subject: [PATCH 040/131] [Metrics] Add metrics to upload operation (#52) * Change GDTCORStorageDelegate * Add metrics to upload operation and add tests * Include missing code * Fix warnings * Reduce PR size * Do not use 'unknown' reason * Fix integration test * Review * Update `GDTCCTUploadOperation.m` comment. --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 183 +++++---- .../Integration/GDTCCTIntegrationTest.m | 2 +- .../GDTCCTTests/Unit/GDTCCTUploaderTest.m | 347 ++++++++++++++++++ .../GDTCORLibrary/GDTCORMetrics.m | 26 ++ .../GDTCORLibrary/GDTCORMetricsController.m | 40 +- .../GDTCORLibrary/GDTCORRegistrar.m | 32 +- .../GDTCORMetricsControllerProtocol.h | 14 +- .../GDTCORLibrary/Private/GDTCORMetrics.h | 26 ++ .../Categories/GDTCORRegistrar+Testing.m | 1 + .../Fakes/GDTCORMetricsControllerFake.h | 43 +++ .../Fakes/GDTCORMetricsControllerFake.m | 73 ++++ 11 files changed, 697 insertions(+), 90 deletions(-) create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h create mode 100644 GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h create mode 100644 GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 894e4bb3..08a91cdd 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -25,6 +25,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" @@ -71,7 +72,13 @@ @interface GDTCCTUploadOperation () @property(nonatomic, readonly, nullable) id metricsController; /** The URL session that will attempt upload. */ -@property(nonatomic) NSURLSession *uploaderSession; +@property(nonatomic, nullable) NSURLSession *uploaderSession; + +/// The metrics being uploaded by the operation. These metrics are fetched and included as an event +/// in the upload batch as part of the upload process. +/// +/// Metrics being uploaded are retained so they can be reset when upload is successful. +@property(nonatomic, nullable) GDTCORMetrics *currentMetrics; /// NSOperation state properties implementation. @property(nonatomic, readwrite, getter=isExecuting) BOOL executing; @@ -140,59 +147,66 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions // 1. Check if the conditions for the target are suitable. [self isReadyToUploadTarget:target conditions:conditions] + .validateOn(self.uploaderQueue, + ^BOOL(NSNull *result) { + // 2. Stop the operation if it has been cancelled. + return !self.isCancelled; + }) .thenOn(self.uploaderQueue, - ^id(id result) { - // 2. Remove previously attempted batches + ^FBLPromise *(NSNull *result) { + // 3. Remove previously attempted batches. return [storage removeAllBatchesForTarget:target deleteEvents:NO]; }) .thenOn(self.uploaderQueue, - ^FBLPromise *(id result) { + ^FBLPromise *(NSNull *result) { // There may be a big amount of events stored, so creating a batch may be an // expensive operation. - // 3. Do a lightweight check if there are any events for the target first to - // finish early if there are no. + // 4. Do a lightweight check if there are any events for the target first to + // finish early if there are none. return [storage hasEventsForTarget:target]; }) .validateOn(self.uploaderQueue, ^BOOL(NSNumber *hasEvents) { - // Stop operation if there are no events to upload. + // 5. Stop operation if there are no events to upload. return hasEvents.boolValue; }) .thenOn(self.uploaderQueue, - ^FBLPromise *(id result) { - if (self.isCancelled) { - return nil; - } - - // 4. Fetch events to upload. + ^FBLPromise *(NSNumber *result) { + // 6. Fetch events to upload. GDTCORStorageEventSelector *eventSelector = [self eventSelectorTarget:target withConditions:conditions]; return [storage batchWithEventSelector:eventSelector batchExpiration:[NSDate dateWithTimeIntervalSinceNow:600]]; }) + .thenOn(self.uploaderQueue, + ^FBLPromise *(GDTCORUploadBatch *batch) { + // 7. Add metrics to batch if metrics collection is supported. + if (![self.metricsController isMetricsCollectionSupportedForTarget:target]) { + return [FBLPromise resolvedWith:batch]; + } + + return [self batchByAddingMetricsEventToBatch:batch forTarget:target]; + }) .validateOn(self.uploaderQueue, - ^BOOL(GDTCORUploadBatch *batch) { - // 5. Validate batch. - return batch.batchID != nil && batch.events.count > 0; + ^BOOL(GDTCORUploadBatch *result) { + // 8. Stop the operation if it has been cancelled. + return !self.isCancelled; }) .thenOn(self.uploaderQueue, ^FBLPromise *(GDTCORUploadBatch *batch) { // A non-empty batch has been created, consider it as an upload attempt. self.uploadAttempted = YES; - // 6. Perform upload URL request. - return [self sendURLRequestWithBatch:batch target:target storage:storage]; + // 9. Perform upload. + return [self uploadBatch:batch toTarget:target storage:storage]; }) - .thenOn(self.uploaderQueue, - ^id(id result) { - // 7. Finish operation. - [self finishOperation]; - backgroundTaskCompletion(); - return nil; - }) - .catchOn(self.uploaderQueue, ^(NSError *error) { - // TODO: Maybe report the error to the client. + .catchOn(self.uploaderQueue, + ^(NSError *error){ + // TODO: Consider reporting the error to the client. + }) + .alwaysOn(self.uploaderQueue, ^{ + // 10. Finish operation. [self finishOperation]; backgroundTaskCompletion(); }); @@ -200,50 +214,65 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions #pragma mark - Upload implementation details -/** Sends URL request to upload the provided batch and handle the response. */ -- (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch - target:(GDTCORTarget)target - storage:(id)storage { - NSNumber *batchID = batch.batchID; - +/** TODO(ncooke3): Document. */ +- (FBLPromise *)uploadBatch:(GDTCORUploadBatch *)batch + toTarget:(GDTCORTarget)target + storage:(id)storage { // 1. Send URL request. return [self sendURLRequestWithBatch:batch target:target] - .thenOn( - self.uploaderQueue, - ^FBLPromise *(GULURLSessionDataResponse *response) { - // 2. Parse response and update the next upload time if can. - [self updateNextUploadTimeWithResponse:response forTarget:target]; - - // 3. Cleanup batch. - - // Only retry if one of these codes is returned: - // 429 - Too many requests; - // 5xx - Server errors. - NSInteger statusCode = response.HTTPResponse.statusCode; - if (statusCode == 429 || (statusCode >= 500 && statusCode < 600)) { - // Move the events back to the main storage to be uploaded on the next attempt. - return [storage removeBatchWithID:batchID deleteEvents:NO]; - } else { - if (statusCode >= 200 && statusCode <= 300) { - GDTCORLogDebug(@"CCT: batch %@ delivered", batchID); - } else { - GDTCORLogDebug( - @"CCT: batch %@ was rejected by the server and will be deleted with all events", - batchID); - } - - // The events are either delivered or unrecoverable broken, so remove the batch with - // events. - return [storage removeBatchWithID:batch.batchID deleteEvents:YES]; - } - }) + .thenOn(self.uploaderQueue, + ^FBLPromise *(GULURLSessionDataResponse *response) { + // 2. Update the next upload time and process response. + [self updateNextUploadTimeWithResponse:response forTarget:target]; + + return [self processResponse:response forBatch:batch storage:storage]; + }) .recoverOn(self.uploaderQueue, ^id(NSError *error) { - // In the case of a network error move the events back to the main storage to be uploaded on - // the next attempt. - return [storage removeBatchWithID:batchID deleteEvents:NO]; + // If a network error occurred, move the events back to the main + // storage so they can attempt to be uploaded in the next attempt. + return [storage removeBatchWithID:batch.batchID deleteEvents:NO]; }); } +/** TODO(ncooke3): Document. */ +- (FBLPromise *)processResponse:(GULURLSessionDataResponse *)response + forBatch:(GDTCORUploadBatch *)batch + storage:(id)storage { + // 1. Cleanup batch based on the response's status code. + NSInteger statusCode = response.HTTPResponse.statusCode; + BOOL isSuccess = statusCode >= 200 && statusCode < 300; + // Transient errors include "too many requests" (429) and server errors (5xx). + BOOL isTransientError = + statusCode == 429 || statusCode == 404 || (statusCode >= 500 && statusCode < 600); + + BOOL shouldDeleteEvents = isSuccess || !isTransientError; + + // Reset metrics if upload is successful. + GDTCORMetrics *uploadedMetrics = [self currentMetrics]; + if (uploadedMetrics) { + [self.metricsController confirmMetrics:uploadedMetrics wereUploaded:isSuccess]; + } + + if (isSuccess) { + GDTCORLogDebug(@"CCT: batch %@ uploaded. Batch will be deleted.", batch.batchID); + + } else if (isTransientError) { + GDTCORLogDebug(@"CCT: batch %@ upload failed. Batch will attempt to be uploaded later.", + batch.batchID); + + } else { + GDTCORLogDebug(@"CCT: batch %@ upload failed. Batch will be deleted.", batch.batchID); + + if (/* isInvalidPayloadError */ statusCode == 400) { + // Log events that will be dropped due to the upload error. + [self.metricsController logEventsDroppedForReason:GDTCOREventDropReasonInvalidPayload + events:batch.events]; + } + } + + return [storage removeBatchWithID:batch.batchID deleteEvents:shouldDeleteEvents]; +} + /** Composes and sends URL request. */ - (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch target:(GDTCORTarget)target { @@ -489,6 +518,30 @@ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target qosTiers:qosTiers]; } +// TODO(ncooke3): Consider another approach: Adding metrics property to `GDTCORUploadBatch`. +- (FBLPromise *)batchByAddingMetricsEventToBatch:(GDTCORUploadBatch *)batch + forTarget:(GDTCORTarget)target { + return [self.metricsController fetchMetrics].thenOn( + self.uploaderQueue, ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { + // TODO(ncooke3): Define kMetricEventMappingID. + // TODO(ncooke3): Consider if `fetchMetrics` should instead return + // the metrics in a `GDTCOREvent`. + GDTCOREvent *metricsEvent = [[GDTCOREvent alloc] initWithMappingID:@"kMetricEventMappingID" + target:target]; + + [metricsEvent setDataObject:metrics]; + + // Save the metrics so they can be reset later upon successful upload. + [self setCurrentMetrics:metrics]; + + GDTCORUploadBatch *batchWithMetricEvent = [[GDTCORUploadBatch alloc] + initWithBatchID:batch.batchID + events:[batch.events setByAddingObject:metricsEvent]]; + + return batchWithMetricEvent; + }); +} + #pragma mark - NSURLSessionDelegate - (void)URLSession:(NSURLSession *)session diff --git a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m index 55af6461..8fecb328 100644 --- a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m +++ b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m @@ -89,7 +89,7 @@ - (void)setUp { self.transport = [[GDTCORTransport alloc] initWithMappingID:@"1018" transformers:nil - target:kGDTCORTargetCSH]; + target:kGDTCORTargetCCT]; } - (void)tearDown { diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m index c2c8bbba..cbfcadeb 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m @@ -16,13 +16,17 @@ #import +#import "FBLPromise+Testing.h" + #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.h" +#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h" #import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.h" #import "GoogleDataTransport/GDTCCTTests/Unit/TestServer/GDTCCTTestServer.h" @@ -393,6 +397,349 @@ - (void)testUploadTarget_WhenThereAreNoEventsFirstThenEventsAdded_ThenUploadNewE [self waitForUploadOperationsToFinish:self.uploader]; } +- (void)testUploadTarget_WhenTargetSupportsMetricsCollection_ThenMetricsAreAddedToUploadBatch { + // Given + // - Generate and batch a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + [self batchEvents]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return YES; + }; + + GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + return [FBLPromise resolvedWith:dummyMetrics]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + [metricsConfirmationExpectation fulfill]; + XCTAssertTrue(uploaded); + XCTAssertEqualObjects(metrics, dummyMetrics); + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + droppedEventsAreLoggedExpectation.inverted = YES; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason _, NSSet *__) { + [droppedEventsAreLoggedExpectation fulfill]; + }; + + // - Set response expectations. + XCTestExpectation *responseSentExpectation = [self expectationTestServerSuccessRequestResponse]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + +- (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetricsAreSentOrLogged { + // Given + // - Generate and batch a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + [self batchEvents]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return NO; + }; + + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + fetchMetricsExpectation.inverted = YES; + metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + return [FBLPromise resolvedWith:nil]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsConfirmationExpectation.inverted = YES; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *_, BOOL __) { + [metricsConfirmationExpectation fulfill]; + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + droppedEventsAreLoggedExpectation.inverted = YES; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason _, NSSet *__) { + [droppedEventsAreLoggedExpectation fulfill]; + }; + + // - Set response expectations. + XCTestExpectation *responseSentExpectation = [self expectationTestServerSuccessRequestResponse]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + +- (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmedWithSuccess { + // Given + // - Generate and batch a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + [self batchEvents]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return YES; + }; + + GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + return [FBLPromise resolvedWith:dummyMetrics]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + [metricsConfirmationExpectation fulfill]; + XCTAssertTrue(uploaded); + XCTAssertEqualObjects(metrics, dummyMetrics); + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + droppedEventsAreLoggedExpectation.inverted = YES; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason _, NSSet *__) { + [droppedEventsAreLoggedExpectation fulfill]; + }; + + // - Set response expectations. + XCTestExpectation *responseSentExpectation = [self expectationTestServerSuccessRequestResponse]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + +- (void) + testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToTransientError_ThenMetricsAreConfirmedWithFailure { + // Given + // - Generate a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + self.testStorage.removeBatchAndDeleteEventsExpectation.inverted = YES; + self.testStorage.removeBatchWithoutDeletingEventsExpectation.inverted = NO; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return YES; + }; + + GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + return [FBLPromise resolvedWith:dummyMetrics]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + [metricsConfirmationExpectation fulfill]; + XCTAssertFalse(uploaded); + XCTAssertEqualObjects(metrics, dummyMetrics); + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + droppedEventsAreLoggedExpectation.inverted = YES; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason _, NSSet *__) { + [droppedEventsAreLoggedExpectation fulfill]; + }; + + // - Set response expectations. + XCTestExpectation *responseFailedExpectation = [self expectationTestServerResponseWithCode:500 + headers:nil]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseFailedExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + +- (void) + testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToNonTransientError_ThenMetricsAreConfirmedWithFailureAndDroppedBatchEventAreLogged { + // Given + // - Generate a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + self.testStorage.removeBatchAndDeleteEventsExpectation.inverted = NO; + self.testStorage.removeBatchWithoutDeletingEventsExpectation.inverted = YES; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return YES; + }; + + GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + return [FBLPromise resolvedWith:dummyMetrics]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + [metricsConfirmationExpectation fulfill]; + XCTAssertFalse(uploaded); + XCTAssertEqualObjects(metrics, dummyMetrics); + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason reason, NSSet *droppedEvents) { + [droppedEventsAreLoggedExpectation fulfill]; + XCTAssertEqual(reason, GDTCOREventDropReasonInvalidPayload); + XCTAssertEqual([droppedEvents count], 2); + }; + + // - Set response expectations. + XCTestExpectation *responseFailedExpectation = [self expectationTestServerResponseWithCode:400 + headers:nil]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseFailedExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + #pragma mark - Storage interaction tests - (void)testStorageSelectorWhenConditionsHighPriority { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m new file mode 100644 index 00000000..b38b7b67 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -0,0 +1,26 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" + +@implementation GDTCORMetrics + +- (nonnull NSData *)transportBytes { + // TODO(ncooke3): Implement. + return [NSData data]; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index 3ef29bfe..a7e055a0 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -52,30 +52,48 @@ - (instancetype)init { return self; } -- (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason - eventCount:(NSUInteger)eventCount - mappingID:(nonnull NSString *)mappingID { +- (nonnull FBLPromise *)confirmMetrics:(nonnull GDTCORMetrics *)metrics + wereUploaded:(BOOL)uploaded { // TODO(ncooke3): Implement. + return [FBLPromise resolvedWith:nil]; } -- (nonnull FBLPromise *)metrics { +- (nonnull FBLPromise *)fetchMetrics { // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } -- (nonnull FBLPromise *)resetMetrics:(nonnull GDTCORMetrics *)metrics { +- (nonnull FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason + events:(nonnull NSSet *)events { // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } -- (void)storage:(nonnull id)storage - didDropEvent:(nonnull GDTCOREvent *)event { - // TODO(ncooke3): Implement. +- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { + switch (target) { + // Only the Firelog backend supports metrics collection. + case kGDTCORTargetFLL: + case kGDTCORTargetCSH: + case kGDTCORTargetTest: + return YES; + + case kGDTCORTargetCCT: + case kGDTCORTargetINT: + return NO; + } + + NSAssert(NO, @"This code path shouldn't be reached."); } -- (void)storage:(nonnull id)storage - didRemoveExpiredEvent:(nonnull GDTCOREvent *)event { - // TODO(ncooke3): Implement. +#pragma mark - GDTCORStorageDelegate + +- (void)storage:(id)storage + didRemoveExpiredEvent:(GDTCOREvent *)event { + // TODO(ncooke): Implement. +} + +- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { + // TODO(ncooke): Implement. } @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index 1f7312d7..53ee320e 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -35,8 +35,7 @@ id _Nullable GDTCORMetricsControllerInstanceForTarget( GDTCORTarget target) { - // TODO(ncooke3): Implement. - return nil; + return [GDTCORRegistrar sharedInstance].targetToMetricsController[@(target)]; } @implementation GDTCORRegistrar { @@ -65,9 +64,9 @@ - (instancetype)init { self = [super init]; if (self) { _registrarQueue = dispatch_queue_create("com.google.GDTCORRegistrar", DISPATCH_QUEUE_SERIAL); - _targetToUploader = [[NSMutableDictionary alloc] init]; - _targetToStorage = [[NSMutableDictionary alloc] init]; - _targetToMetricsController = [[NSMutableDictionary alloc] init]; + _targetToUploader = [NSMutableDictionary dictionary]; + _targetToStorage = [NSMutableDictionary dictionary]; + _targetToMetricsController = [NSMutableDictionary dictionary]; } return self; } @@ -94,9 +93,18 @@ - (void)registerStorage:(id)storage target:(GDTCORTarget) }); } +// TODO(ncooke): Add `GDTCORRegistar` test for this API. - (void)registerMetricsController:(id)metricsController target:(GDTCORTarget)target { - // TODO(ncooke3): Implement. + __weak GDTCORRegistrar *weakSelf = self; + dispatch_async(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + GDTCORLogDebug(@"Registered metrics controller: %@ for target:%ld", metricsController, + (long)target); + strongSelf->_targetToMetricsController[@(target)] = metricsController; + } + }); } - (NSMutableDictionary> *)targetToUploader { @@ -125,8 +133,16 @@ - (void)registerMetricsController:(id)metricsCo - (NSMutableDictionary> *) targetToMetricsController { - // TODO(ncooke3): Implement. - return [NSMutableDictionary dictionary]; + __block NSMutableDictionary> + *targetToMetricsController; + __weak GDTCORRegistrar *weakSelf = self; + dispatch_sync(_registrarQueue, ^{ + GDTCORRegistrar *strongSelf = weakSelf; + if (strongSelf) { + targetToMetricsController = strongSelf->_targetToMetricsController; + } + }); + return targetToMetricsController; } #pragma mark - GDTCORLifecycleProtocol diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index f314e3bd..f674ffad 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -20,22 +20,26 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" @class FBLPromise; +@class GDTCOREvent; @class GDTCORMetrics; NS_ASSUME_NONNULL_BEGIN // TODO(ncooke3): Document. @protocol GDTCORMetricsControllerProtocol + +// TODO(ncooke3): Document. +- (FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason + events:(NSSet *)events; + // TODO(ncooke3): Document. -- (void)logEventsDroppedForReason:(GDTCOREventDropReason)reason - eventCount:(NSUInteger)eventCount - mappingID:(NSString *)mappingID; +- (FBLPromise *)fetchMetrics; // TODO(ncooke3): Document. -- (FBLPromise *)metrics; +- (FBLPromise *)confirmMetrics:(GDTCORMetrics *)metrics wereUploaded:(BOOL)uploaded; // TODO(ncooke3): Document. -- (FBLPromise *)resetMetrics:(GDTCORMetrics *)metrics; +- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target; @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h new file mode 100644 index 00000000..b10c7bb7 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -0,0 +1,26 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORMetrics : NSObject +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.m b/GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.m index a78c7b69..f1e5be2f 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.m +++ b/GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.m @@ -24,6 +24,7 @@ - (void)reset { // These calls occur on the queue. [self.targetToUploader removeAllObjects]; [self.targetToStorage removeAllObjects]; + [self.targetToMetricsController removeAllObjects]; dispatch_sync(self.registrarQueue, ^{ }); } diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h new file mode 100644 index 00000000..c06870b3 --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" + +@class FBLPromise; +@class GDTCOREvent; +@class GDTCORMetrics; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORMetricsControllerFake : NSObject + +@property(nonatomic, copy, nullable) void (^onLogEventsDroppedHandler) + (GDTCOREventDropReason reason, NSSet *events); + +@property(nonatomic, copy, nullable) FBLPromise * (^onFetchMetricsHandler)(void); + +@property(nonatomic, copy, nullable) void (^onConfirmMetricsHandler) + (GDTCORMetrics *metrics, BOOL uploaded); + +@property(nonatomic, copy, nullable) BOOL (^onTargetSupportsMetricsCollectionHandler)(GDTCORTarget); + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m new file mode 100644 index 00000000..0735e45b --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m @@ -0,0 +1,73 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +@implementation GDTCORMetricsControllerFake + +- (FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason + events:(NSSet *)events { + if (self.onLogEventsDroppedHandler) { + self.onLogEventsDroppedHandler(reason, events); + } else { + [self doesNotRecognizeSelector:_cmd]; + } + return [FBLPromise resolvedWith:nil]; +} + +- (FBLPromise *)fetchMetrics { + if (self.onFetchMetricsHandler) { + return self.onFetchMetricsHandler(); + } else { + [self doesNotRecognizeSelector:_cmd]; + return [FBLPromise resolvedWith:nil]; + } +} + +- (FBLPromise *)confirmMetrics:(GDTCORMetrics *)metrics wereUploaded:(BOOL)uploaded { + if (self.onConfirmMetricsHandler) { + self.onConfirmMetricsHandler(metrics, uploaded); + } else { + [self doesNotRecognizeSelector:_cmd]; + } + return [FBLPromise resolvedWith:nil]; +} + +- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { + if (self.onTargetSupportsMetricsCollectionHandler) { + return self.onTargetSupportsMetricsCollectionHandler(target); + } else { + [self doesNotRecognizeSelector:_cmd]; + return NO; + } +} + +- (void)storage:(id)storage + didRemoveExpiredEvent:(GDTCOREvent *)event { + // TODO(ncooke): Implement. +} + +- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { + // TODO(ncooke): Implement. +} + +@end From ae446dc7d2652bf0f4c13cea9ddc38bb4264e52a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 May 2022 13:48:47 -0400 Subject: [PATCH 041/131] [Metrics] Implement metrics controller (#53) * Implement metrics controller * [skip ci] Update GDTCOREventMetricsCounter.m * [skip ci] Update GDTCOREventMetricsCounter.m (2) * [skip ci] Update GDTCOREventMetricsCounter.m (3) * Address some TODOs * Stub implementation to fix CI * Rename API and fix warnings * Update API for better readability * Disable failing test * [skip ci] Review * Make logic flow more explicit --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 11 +- .../Common/TestStorage/GDTCCTTestStorage.m | 8 + .../GDTCCTTests/Unit/GDTCCTUploaderTest.m | 28 ++- .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 136 +++++++++++++++ .../GDTCORFlatFileStorage+Promises.m | 8 + .../GDTCORLibrary/GDTCORMetricsController.m | 137 +++++++++++++-- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 54 ++++++ .../GDTCORLibrary/GDTCORRegistrar.m | 2 +- .../GDTCORMetricsControllerProtocol.h | 25 ++- .../Internal/GDTCORStorageProtocol.h | 13 ++ .../Private/GDTCOREventMetricsCounter.h | 40 +++++ .../GDTCORLibrary/Private/GDTCORMetrics.h | 10 ++ .../Private/GDTCORMetricsMetadata.h | 43 +++++ .../Fakes/GDTCORMetricsControllerFake.h | 6 +- .../Fakes/GDTCORMetricsControllerFake.m | 14 +- .../Unit/GDTCORMetricsControllerTest.m | 161 ++++++++++++++++++ 16 files changed, 639 insertions(+), 57 deletions(-) create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h create mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 08a91cdd..ba6b7d36 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -247,10 +247,10 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions BOOL shouldDeleteEvents = isSuccess || !isTransientError; - // Reset metrics if upload is successful. + // Place metrics back in storage if upload failed. GDTCORMetrics *uploadedMetrics = [self currentMetrics]; - if (uploadedMetrics) { - [self.metricsController confirmMetrics:uploadedMetrics wereUploaded:isSuccess]; + if (uploadedMetrics && !isSuccess) { + [self.metricsController offerMetrics:uploadedMetrics]; } if (isSuccess) { @@ -518,14 +518,11 @@ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target qosTiers:qosTiers]; } -// TODO(ncooke3): Consider another approach: Adding metrics property to `GDTCORUploadBatch`. - (FBLPromise *)batchByAddingMetricsEventToBatch:(GDTCORUploadBatch *)batch forTarget:(GDTCORTarget)target { - return [self.metricsController fetchMetrics].thenOn( + return [self.metricsController getAndResetMetrics].thenOn( self.uploaderQueue, ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { // TODO(ncooke3): Define kMetricEventMappingID. - // TODO(ncooke3): Consider if `fetchMetrics` should instead return - // the metrics in a `GDTCOREvent`. GDTCOREvent *metricsEvent = [[GDTCOREvent alloc] initWithMappingID:@"kMetricEventMappingID" target:target]; diff --git a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m index 288ba0a4..33e49ed8 100644 --- a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m +++ b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m @@ -207,6 +207,14 @@ - (void)defaultBatchWithEventSelector:(nonnull GDTCORStorageEventSelector *)even }]; } +- (FBLPromise *) + fetchAndUpdateLibraryDataForKey:(NSString *)key + klass:(Class)klass + readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock { + // TODO(ncooke3): Implement. + return [FBLPromise resolvedWith:nil]; +} + - (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { return [NSError errorWithDomain:@"GDTCORFlatFileStorage" code:-1 diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m index cbfcadeb..cca1d7dc 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m @@ -424,17 +424,16 @@ - (void)testUploadTarget_WhenTargetSupportsMetricsCollection_ThenMetricsAreAdded GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; XCTestExpectation *fetchMetricsExpectation = [self expectationWithDescription:@"fetchMetricsExpectation"]; - metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { [fetchMetricsExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; XCTestExpectation *metricsConfirmationExpectation = [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + metricsConfirmationExpectation.inverted = YES; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { [metricsConfirmationExpectation fulfill]; - XCTAssertTrue(uploaded); - XCTAssertEqualObjects(metrics, dummyMetrics); }; XCTestExpectation *droppedEventsAreLoggedExpectation = @@ -491,7 +490,7 @@ - (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetrics XCTestExpectation *fetchMetricsExpectation = [self expectationWithDescription:@"fetchMetricsExpectation"]; fetchMetricsExpectation.inverted = YES; - metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { [fetchMetricsExpectation fulfill]; return [FBLPromise resolvedWith:nil]; }; @@ -499,7 +498,7 @@ - (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetrics XCTestExpectation *metricsConfirmationExpectation = [self expectationWithDescription:@"metricsConfirmationExpectation"]; metricsConfirmationExpectation.inverted = YES; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *_, BOOL __) { + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *_) { [metricsConfirmationExpectation fulfill]; }; @@ -557,17 +556,16 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; XCTestExpectation *fetchMetricsExpectation = [self expectationWithDescription:@"fetchMetricsExpectation"]; - metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { [fetchMetricsExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; XCTestExpectation *metricsConfirmationExpectation = [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + metricsConfirmationExpectation.inverted = YES; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { [metricsConfirmationExpectation fulfill]; - XCTAssertTrue(uploaded); - XCTAssertEqualObjects(metrics, dummyMetrics); }; XCTestExpectation *droppedEventsAreLoggedExpectation = @@ -627,16 +625,15 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; XCTestExpectation *fetchMetricsExpectation = [self expectationWithDescription:@"fetchMetricsExpectation"]; - metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { [fetchMetricsExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; XCTestExpectation *metricsConfirmationExpectation = [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { [metricsConfirmationExpectation fulfill]; - XCTAssertFalse(uploaded); XCTAssertEqualObjects(metrics, dummyMetrics); }; @@ -698,16 +695,15 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; XCTestExpectation *fetchMetricsExpectation = [self expectationWithDescription:@"fetchMetricsExpectation"]; - metricsControllerFake.onFetchMetricsHandler = ^FBLPromise * { + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { [fetchMetricsExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; XCTestExpectation *metricsConfirmationExpectation = [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics, BOOL uploaded) { + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { [metricsConfirmationExpectation fulfill]; - XCTAssertFalse(uploaded); XCTAssertEqualObjects(metrics, dummyMetrics); }; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m new file mode 100644 index 00000000..440da3a2 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -0,0 +1,136 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" + +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +typedef NSDictionary GDTCORDroppedEventCounter; + +@interface GDTCOREventMetricsCounter () + +// TODO(ncooke3): Document. +@property(nonatomic) + NSDictionary *droppedEventCounterByMappingID; + +@end + +@implementation GDTCOREventMetricsCounter + ++ (instancetype)counterWithEvents:(NSArray *)events + droppedForReason:(GDTCOREventDropReason)reason { + NSMutableDictionary *eventCounterByMappingID = + [NSMutableDictionary dictionary]; + + for (GDTCOREvent *event in events) { + // TODO(ncooke3): Should I use an autorelease pool? + // Dropped events with a `nil` or empty mapping ID (log source) are not recorded. + if (event.mappingID.length == 0) { + continue; + } + + // Get the dropped event counter for the event's mapping ID (log source). + // If the dropped event counter for this event's mapping ID is `nil`, + // an empty mutable counter is returned. + NSMutableDictionary *eventCounter = + [NSMutableDictionary dictionaryWithDictionary:eventCounterByMappingID[event.mappingID]]; + + // Increment the dropped event counter for the given reason. + NSInteger currentEventCountForReason = [eventCounter[@(reason)] integerValue]; + NSInteger updatedEventCountForReason = currentEventCountForReason + 1; + + eventCounter[@(reason)] = @(updatedEventCountForReason); + + // Update the mapping ID's (log source's) event counter with an immutable + // copy of the updated counter. + eventCounterByMappingID[event.mappingID] = [eventCounter copy]; + } + + return [[self alloc] initWithDroppedEventCounterByMappingID:[eventCounterByMappingID copy]]; +} + +- (instancetype)initWithDroppedEventCounterByMappingID: + (NSDictionary *)droppedEventCounterByMappingID { + self = [super init]; + if (self) { + _droppedEventCounterByMappingID = droppedEventCounterByMappingID; + } + return self; +} + +- (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter { + // Create a new counter by merging the current counter with the given counter. + NSDictionary *mergedEventCounterByMappingID = [[self + class] dictionaryByMergingDictionary:self.droppedEventCounterByMappingID + withOtherDictionary:counter.droppedEventCounterByMappingID + uniquingKeysWithBlock:^NSDictionary *(NSDictionary *eventCounter1, + NSDictionary *eventCounter2) { + return [[self class] + dictionaryByMergingDictionary:eventCounter1 + withOtherDictionary:eventCounter2 + uniquingKeysWithBlock:^NSNumber *(NSNumber *eventCount1, + NSNumber *eventCount2) { + return @(eventCount1.integerValue + eventCount2.integerValue); + }]; + }]; + + return + [[[self class] alloc] initWithDroppedEventCounterByMappingID:mergedEventCounterByMappingID]; +} + +/// Creates a new dictionary by merging together two given dictionaries. +/// @param dictionary A dictionary for merging. +/// @param otherDictionary Another dictionary for merging. +/// @param block A block that is called with the values for any duplicate keys that are encountered. +/// The block returns the desired value for the merged dictionary. ++ (NSDictionary *)dictionaryByMergingDictionary:(NSDictionary *)dictionary + withOtherDictionary:(NSDictionary *)otherDictionary + uniquingKeysWithBlock:(id (^)(id value1, id value2))block { + // TODO(ncooke3): Optimize so larger dictionary is passed in below. + NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary]; + + [otherDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if (mergedDictionary[key] != nil) { + // The key exists in both given dictionaries so combine the corresponding values with the + // given block. + id newValue = block(mergedDictionary[key], obj); + mergedDictionary[key] = newValue; + } else { + mergedDictionary[key] = obj; + } + }]; + + return [mergedDictionary copy]; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + // TODO(ncooke3): Implement +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + // TODO(ncooke3): Implement + return nil; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m index c3a22a8e..48a32ffb 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m @@ -94,6 +94,14 @@ @implementation GDTCORFlatFileStorage (Promises) }]; } +- (FBLPromise *) + fetchAndUpdateLibraryDataForKey:(NSString *)key + klass:(Class)klass + readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock { + // TODO(ncooke3): Implement. + return [FBLPromise resolvedWith:nil]; +} + // TODO: Move to a separate class/extension when needed in more places. - (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { return [NSError errorWithDomain:@"GDTCORFlatFileStorage" diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index a7e055a0..ecea6b3b 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -24,7 +24,25 @@ #import "FBLPromises.h" #endif +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" + +// TODO(ncooke3): Document. +static NSString *const kMetricsLibraryDataKey = @"metrics-library-data"; + +@interface GDTCORMetricsController () +// TODO(ncooke3): Document. +@property(nonatomic) id storage; + +@end @implementation GDTCORMetricsController @@ -39,34 +57,119 @@ + (instancetype)sharedInstance { static id sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] init]; + sharedInstance = [[self alloc] initWithStorage:[GDTCORFlatFileStorage sharedInstance]]; }); return sharedInstance; } -- (instancetype)init { +- (instancetype)initWithStorage:(id)storage { self = [super init]; if (self) { - // TODO(ncooke3): Implement. + _storage = storage; } return self; } -- (nonnull FBLPromise *)confirmMetrics:(nonnull GDTCORMetrics *)metrics - wereUploaded:(BOOL)uploaded { - // TODO(ncooke3): Implement. - return [FBLPromise resolvedWith:nil]; +- (nonnull FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason + events:(nonnull NSSet *)events { + GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( + GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + GDTCOREventMetricsCounter *metricsCounter = + [GDTCOREventMetricsCounter counterWithEvents:[events allObjects] droppedForReason:reason]; + + if (metricsMetadata) { + GDTCOREventMetricsCounter *droppedEventCounter = + [metricsMetadata.droppedEventCounter counterByMergingWithCounter:metricsCounter]; + + return [GDTCORMetricsMetadata + metadataWithCollectionStartDate:[metricsMetadata collectionStartDate] + eventMetricsCounter:droppedEventCounter]; + } else { + // There was an error (e.g. empty storage); `metricsMetadata` is nil. + GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); + return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:metricsCounter]; + } + }; + + return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey + klass:[GDTCORMetricsMetadata class] + readWriteBlock:readWriteblock] + .then(^id _Nullable(GDTCORLibraryData _Nullable value) { + return nil; + }); } -- (nonnull FBLPromise *)fetchMetrics { - // TODO(ncooke3): Implement. - return [FBLPromise resolvedWith:nil]; +- (nonnull FBLPromise *)getAndResetMetrics { + __block GDTCORMetricsMetadata *snapshottedMetricsMetadata = nil; + + GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( + GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + if (metricsMetadata) { + snapshottedMetricsMetadata = metricsMetadata; + } else { + GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); + } + return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:nil]; + }; + + return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey + klass:[GDTCORMetricsMetadata class] + readWriteBlock:readWriteblock] + .then(^id _Nullable(GDTCORLibraryData _Nullable value) { + // TODO(ncooke3): Create metrics object using snapshottedMetadata. + return nil; + }); } -- (nonnull FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason - events:(nonnull NSSet *)events { - // TODO(ncooke3): Implement. - return [FBLPromise resolvedWith:nil]; +- (nonnull FBLPromise *)offerMetrics:(nonnull GDTCORMetrics *)metrics { + GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( + GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + if (metricsMetadata) { + if (metrics.collectionStartDate <= metricsMetadata.collectionStartDate) { + // If the metrics to append are older than the metrics represented by the + // currently stored metrics, then return a new metadata object that + // incorporates the data from the given metrics. + return [GDTCORMetricsMetadata + metadataWithCollectionStartDate:[metricsMetadata collectionStartDate] + eventMetricsCounter: + [metricsMetadata.droppedEventCounter + counterByMergingWithCounter:metrics.droppedEventCounter]]; + } else { + // This catches an edge case where the given metrics to append are + // newer than metrics represented by the currently stored metrics + // metadata. In this case, return the existing metadata object as the + // given metrics are assumed to already be accounted for by the + // currenly metadata. + return metricsMetadata; + } + } else { + // There was an error (e.g. empty storage); `metricsMetadata` is nil. + GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); + + NSDate *now = [NSDate date]; + if (metrics.collectionStartDate <= now) { + // The given metrics are were recorded up until now. They wouldn't + // be offered if they were successfully uploaded so their + // corresponding metadata can be safely placed back in storage. + return [GDTCORMetricsMetadata metadataWithCollectionStartDate:metrics.collectionStartDate + eventMetricsCounter:metrics.droppedEventCounter]; + } else { + // This catches an edge case where the given metrics are from the + // future. If this occurs, ignore them and store an empty metadata + // object intended to track metrics metadata from this time forward. + return [GDTCORMetricsMetadata metadataWithCollectionStartDate:now eventMetricsCounter:nil]; + } + } + }; + + return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey + klass:[GDTCORMetricsMetadata class] + readWriteBlock:readWriteblock] + .then(^id _Nullable(GDTCORLibraryData _Nullable value) { + return nil; + }); } - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { @@ -89,11 +192,13 @@ - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { - (void)storage:(id)storage didRemoveExpiredEvent:(GDTCOREvent *)event { - // TODO(ncooke): Implement. + [self logEventsDroppedForReason:GDTCOREventDropReasonMessageTooOld + events:[NSSet setWithObject:event]]; } - (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { - // TODO(ncooke): Implement. + [self logEventsDroppedForReason:GDTCOREventDropReasonStorageFull + events:[NSSet setWithObject:event]]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m new file mode 100644 index 00000000..1d29b032 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -0,0 +1,54 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" + +@implementation GDTCORMetricsMetadata + ++ (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate + eventMetricsCounter: + (nullable GDTCOREventMetricsCounter *)eventMetricsCounter { + return [[self alloc] initWithCollectionStartDate:collectedSinceDate counter:eventMetricsCounter]; +} + +- (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate + counter:(nullable GDTCOREventMetricsCounter *)counter { + self = [super init]; + if (self) { + _collectionStartDate = collectionStartDate; + _droppedEventCounter = counter; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + // TODO(ncooke3): Implement +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + // TODO(ncooke3): Implement + return nil; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index 53ee320e..80d9beb4 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -93,7 +93,7 @@ - (void)registerStorage:(id)storage target:(GDTCORTarget) }); } -// TODO(ncooke): Add `GDTCORRegistar` test for this API. +// TODO(ncooke3): Add `GDTCORRegistar` test for this API. - (void)registerMetricsController:(id)metricsController target:(GDTCORTarget)target { __weak GDTCORRegistrar *weakSelf = self; diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index f674ffad..713c05aa 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -25,24 +25,35 @@ NS_ASSUME_NONNULL_BEGIN -// TODO(ncooke3): Document. +/// A storage delegate that can perform metrics related tasks. @protocol GDTCORMetricsControllerProtocol -// TODO(ncooke3): Document. +/// Updates the metric's event counter for the given events dropped for a given reason. +/// @param reason The reason why the events are being dropped. +/// @param events The events that being dropped. - (FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason events:(NSSet *)events; -// TODO(ncooke3): Document. -- (FBLPromise *)fetchMetrics; +/// Gets and resets the currently stored metrics. +/// @return A promise resolving with the metrics retrieved before the reset. +- (FBLPromise *)getAndResetMetrics; -// TODO(ncooke3): Document. -- (FBLPromise *)confirmMetrics:(GDTCORMetrics *)metrics wereUploaded:(BOOL)uploaded; +/// Offers metrics for re-storing in storage. +/// @note If the metrics are determined to be from the future, they will be ignored. +/// @param metrics The metrics to offer for storage. +- (FBLPromise *)offerMetrics:(GDTCORMetrics *)metrics; -// TODO(ncooke3): Document. +/// Returns whether or not a given target supports metrics collection. +/// @param target A target that may or may not support metrics collection. +/// @return `YES` if the given target supports metrics collection; otherwise, `NO`. - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target; @end +/// Returns a metrics controller instance for the given target. +/// @param target The target to retireve a corresponding metrics controller from. +/// @return The given target's corresponding metrics controller instance, or `nil` if it does not +/// have one. FOUNDATION_EXPORT id _Nullable GDTCORMetricsControllerInstanceForTarget( GDTCORTarget target); diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index c09e07a5..70ef9841 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -139,6 +139,19 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, - (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target deleteEvents:(BOOL)deleteEvents; +// TODO(ncooke3): Document. +typedef id GDTCORLibraryData; + +// TODO(ncooke3): Document. +typedef GDTCORLibraryData _Nullable (^GDTCORStorageLibraryDataReadWriteBlock)( + GDTCORLibraryData _Nullable fetchedValue, NSError *_Nullable fetchError); + +// TODO(ncooke3): Document. +- (FBLPromise *) + fetchAndUpdateLibraryDataForKey:(NSString *)key + klass:(Class)klass + readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock; + /** See `hasEventsForTarget:onComplete:`. * @return A promise object that is resolved with @YES if there are events for the specified target * and @NO otherwise. diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h new file mode 100644 index 00000000..455af55c --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +// TODO(ncooke3): Document. +// TODO(ncooke3): Consider renaming to `GDTCORMetricsDroppedEventCounter`. +@interface GDTCOREventMetricsCounter : NSObject + +// TODO(ncooke3): Document. ++ (instancetype)counterWithEvents:(NSArray *)events + droppedForReason:(GDTCOREventDropReason)reason; + +- (instancetype)init NS_UNAVAILABLE; + +// TODO(ncooke3): Document. +- (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index b10c7bb7..95337211 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -18,9 +18,19 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" +@class GDTCOREventMetricsCounter; + NS_ASSUME_NONNULL_BEGIN +// TODO(ncooke3): Document. @interface GDTCORMetrics : NSObject + +// TODO(ncooke3): Document. +@property(nonatomic, readonly) NSDate *collectionStartDate; + +// TODO(ncooke3): Document. +@property(nonatomic, readonly) GDTCOREventMetricsCounter *droppedEventCounter; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h new file mode 100644 index 00000000..6117f3eb --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" + +@class GDTCOREventMetricsCounter; + +NS_ASSUME_NONNULL_BEGIN + +// TODO(ncooke3): Document. +@interface GDTCORMetricsMetadata : NSObject + +// TODO(ncooke3): Document. +@property(nonatomic, copy, readonly) NSDate *collectionStartDate; + +// TODO(ncooke3): Document. +@property(nonatomic, copy, readonly, nullable) GDTCOREventMetricsCounter *droppedEventCounter; + +// TODO(ncooke3): Document. ++ (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate + eventMetricsCounter: + (nullable GDTCOREventMetricsCounter *)eventMetricsCounter; + +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h index c06870b3..55db8d36 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h @@ -31,10 +31,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, nullable) void (^onLogEventsDroppedHandler) (GDTCOREventDropReason reason, NSSet *events); -@property(nonatomic, copy, nullable) FBLPromise * (^onFetchMetricsHandler)(void); +@property(nonatomic, copy, nullable) FBLPromise * (^onGetAndResetMetricsHandler) + (void); -@property(nonatomic, copy, nullable) void (^onConfirmMetricsHandler) - (GDTCORMetrics *metrics, BOOL uploaded); +@property(nonatomic, copy, nullable) void (^onConfirmMetricsHandler)(GDTCORMetrics *metrics); @property(nonatomic, copy, nullable) BOOL (^onTargetSupportsMetricsCollectionHandler)(GDTCORTarget); diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m index 0735e45b..246eb09c 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m @@ -34,18 +34,18 @@ @implementation GDTCORMetricsControllerFake return [FBLPromise resolvedWith:nil]; } -- (FBLPromise *)fetchMetrics { - if (self.onFetchMetricsHandler) { - return self.onFetchMetricsHandler(); +- (nonnull FBLPromise *)getAndResetMetrics { + if (self.onGetAndResetMetricsHandler) { + return self.onGetAndResetMetricsHandler(); } else { [self doesNotRecognizeSelector:_cmd]; return [FBLPromise resolvedWith:nil]; } } -- (FBLPromise *)confirmMetrics:(GDTCORMetrics *)metrics wereUploaded:(BOOL)uploaded { +- (FBLPromise *)offerMetrics:(GDTCORMetrics *)metrics { if (self.onConfirmMetricsHandler) { - self.onConfirmMetricsHandler(metrics, uploaded); + self.onConfirmMetricsHandler(metrics); } else { [self doesNotRecognizeSelector:_cmd]; } @@ -63,11 +63,11 @@ - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { - (void)storage:(id)storage didRemoveExpiredEvent:(GDTCOREvent *)event { - // TODO(ncooke): Implement. + // TODO(ncooke3): Implement. } - (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { - // TODO(ncooke): Implement. + // TODO(ncooke3): Implement. } @end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m new file mode 100644 index 00000000..18ee6461 --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -0,0 +1,161 @@ +/* + * Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" + +#import "FBLPromise+Await.h" +#import "FBLPromise+Testing.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" + +@interface GDTCORMetricsControllerTest : GDTCORTestCase +@end + +@implementation GDTCORMetricsControllerTest + +- (void)SKIP_testLoggedEventsAreFetchedAsMetrics { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + + // When + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + + // Then + // GDTCORMetrics *metrics = FBLPromiseAwait([metricsController getAndResetMetrics], nil); + // TODO(ncooke3): Assert that the metrics contain the previously logged events. +} + +- (void)SKIP_testLogEvents_WhenNoEvents_DoesNothing { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + + // When + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + + // Then + // TODO(ncooke3): Assert that the metrics are empty with expected time window. +} + +- (void)SKIP_testFetchMetrics_WhenNoMetricsAreStored_ReturnsEmptyMetrics { + // Given + // GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + + // Then + // TODO(ncooke3): Assert that fetched metrics are empty with expected time window. +} + +- (void) + SKIP_testFetchMetrics_WhenSomeMetricsNeedReupload_ReturnsLastConfirmedMetricsThatFailedToUpload { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + // - Create pending metrics. + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + + // When + GDTCORMetrics *metrics1 = [[GDTCORMetrics alloc] init]; + [metricsController offerMetrics:metrics1]; + + GDTCORMetrics *metrics2 = [[GDTCORMetrics alloc] init]; + [metricsController offerMetrics:metrics2]; + + // Then + // - TODO(ncooke3): Add comment. + GDTCORMetrics *fetchedMetrics1 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); + XCTAssertEqualObjects(fetchedMetrics1, metrics2); + + // - TODO(ncooke3): Add comment. + GDTCORMetrics *fetchedMetrics2 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); + XCTAssertEqualObjects(fetchedMetrics2, metrics1); + + // - TODO(ncooke3): Add comment. + // GDTCORMetrics *fetchedMetrics3 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); + // TODO(ncooke3): Assert that `metricsPromise3`'s metrics contain logged events. + + // TODO(ncooke3): Assert that the metrics are empty with the expected test window. +} + +- (void)SKIP_testFetchMetrics_WhenNoMetricsNeedReuploading_ReturnsNewMetricsFromPendingMetricsData { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + // - Create and fetch pending metrics data. + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + // GDTCORMetrics *metrics = FBLPromiseAwait([metricsController getAndResetMetrics], nil); + + // - * Client begins uploading fetched metrics... * + + // When + // - Create new pending metrics data. + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + + // Then + // GDTCORMetrics *newPendingMetrics = FBLPromiseAwait([metricsController getAndResetMetrics], + // nil); + // TODO(ncooke3): Assert that the metrics contain the new pending metrics data. + + // TODO(ncooke3): Assert that the metrics are empty with expected time window. +} + +- (void)SKIP_testAppendingMetrics_SavesMetricsForRefetchingLater { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + // - TODO(ncooke3): Add context. + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + GDTCORMetrics *metricsThatFailedToUpload = + FBLPromiseAwait([metricsController getAndResetMetrics], nil); + + // When + // - * Uploading metrics fails * + // - Alert the metrics controller about the upload and save metrics for later. + [metricsController offerMetrics:metricsThatFailedToUpload]; + + // Then + GDTCORMetrics *metricsToRetryUploading = + FBLPromiseAwait([metricsController getAndResetMetrics], nil); + XCTAssertEqualObjects(metricsToRetryUploading, metricsThatFailedToUpload); + + // TODO(ncooke3): Assert that pending metrics are empty with expected time window. +} + +- (void)testIsMetricsCollectionSupportedForTarget { + // Given + GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + NSDictionary *targetSupportMatrix = @{ + @(kGDTCORTargetTest) : @YES, + @(kGDTCORTargetFLL) : @YES, + @(kGDTCORTargetCSH) : @YES, + @(kGDTCORTargetCCT) : @NO, + @(kGDTCORTargetINT) : @NO + }; + + for (NSNumber *target in targetSupportMatrix) { + // When + BOOL isSupported = [metricsController isMetricsCollectionSupportedForTarget:target.intValue]; + // Then + BOOL expectedIsSupported = [(NSNumber *)targetSupportMatrix[target] boolValue]; + XCTAssertEqual(isSupported, expectedIsSupported, @"Failed for GDTCORTarget: %@", target); + } +} + +- (void)DISABLED_testGDTCORMetricsControllerInstanceForTarget { + // Metrics controller registration is done at `GDTCORMetricsController +load` time. + // Assert that it is registered for the FLL and CSH targets, but not for CCT. + XCTAssertNotNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetFLL)); + XCTAssertNotNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetCSH)); + XCTAssertNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetCCT)); +} + +@end From 20c835729e3656d940c83651550788d3656360e9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 20 May 2022 19:11:47 -0400 Subject: [PATCH 042/131] [Metrics] Import and generate protos (#61) * Add client_metrics.proto and corresponding options * Add generated protos * [skip ci] Copyright 2019 -> 2022 * [skip ci] Copyright 2019 -> 2022 * [skip ci] dont forget LLC * [skip ci] don't forget LLC (2) * [skip ci] Regenerate with nanopb-0.3.9.9 * [skip ci] Update template copyright --- .../Protogen/nanopb/client_metrics.nanopb.c | 92 ++++++++++++ .../Protogen/nanopb/client_metrics.nanopb.h | 141 ++++++++++++++++++ .../Protos/client_metrics.options | 21 +++ .../ProtoSupport/Protos/client_metrics.proto | 92 ++++++++++++ .../ProtoSupport/proto_generator.py | 2 +- 5 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.c create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h create mode 100644 GoogleDataTransport/ProtoSupport/Protos/client_metrics.options create mode 100644 GoogleDataTransport/ProtoSupport/Protos/client_metrics.proto diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.c new file mode 100644 index 00000000..1e959545 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.c @@ -0,0 +1,92 @@ +/* + * Copyright 2022 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. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.9 */ + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + +const pb_field_t gdt_client_metrics_ClientMetrics_fields[5] = { + PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, gdt_client_metrics_ClientMetrics, window, window, &gdt_client_metrics_TimeWindow_fields), + PB_FIELD( 2, MESSAGE , REPEATED, POINTER , OTHER, gdt_client_metrics_ClientMetrics, log_source_metrics, window, &gdt_client_metrics_LogSourceMetrics_fields), + PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, gdt_client_metrics_ClientMetrics, global_metrics, log_source_metrics, &gdt_client_metrics_GlobalMetrics_fields), + PB_FIELD( 4, BYTES , SINGULAR, POINTER , OTHER, gdt_client_metrics_ClientMetrics, app_namespace, global_metrics, 0), + PB_LAST_FIELD +}; + +const pb_field_t gdt_client_metrics_TimeWindow_fields[3] = { + PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_TimeWindow, start_ms, start_ms, 0), + PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, gdt_client_metrics_TimeWindow, end_ms, start_ms, 0), + PB_LAST_FIELD +}; + +const pb_field_t gdt_client_metrics_GlobalMetrics_fields[2] = { + PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, gdt_client_metrics_GlobalMetrics, storage_metrics, storage_metrics, &gdt_client_metrics_StorageMetrics_fields), + PB_LAST_FIELD +}; + +const pb_field_t gdt_client_metrics_StorageMetrics_fields[3] = { + PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_StorageMetrics, current_cache_size_bytes, current_cache_size_bytes, 0), + PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, gdt_client_metrics_StorageMetrics, max_cache_size_bytes, current_cache_size_bytes, 0), + PB_LAST_FIELD +}; + +const pb_field_t gdt_client_metrics_LogSourceMetrics_fields[3] = { + PB_FIELD( 1, BYTES , SINGULAR, POINTER , FIRST, gdt_client_metrics_LogSourceMetrics, log_source, log_source, 0), + PB_FIELD( 2, MESSAGE , REPEATED, POINTER , OTHER, gdt_client_metrics_LogSourceMetrics, log_event_dropped, log_source, &gdt_client_metrics_LogEventDropped_fields), + PB_LAST_FIELD +}; + +const pb_field_t gdt_client_metrics_LogEventDropped_fields[3] = { + PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_LogEventDropped, events_dropped_count, events_dropped_count, 0), + PB_FIELD( 3, UENUM , SINGULAR, STATIC , OTHER, gdt_client_metrics_LogEventDropped, reason, events_dropped_count, 0), + PB_LAST_FIELD +}; + + + +/* Check that field information fits in pb_field_t */ +#if !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_32BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in 8 or 16 bit + * field descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_client_metrics_ClientMetrics, window) < 65536 && pb_membersize(gdt_client_metrics_ClientMetrics, global_metrics) < 65536 && pb_membersize(gdt_client_metrics_GlobalMetrics, storage_metrics) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_client_metrics_ClientMetrics_gdt_client_metrics_TimeWindow_gdt_client_metrics_GlobalMetrics_gdt_client_metrics_StorageMetrics_gdt_client_metrics_LogSourceMetrics_gdt_client_metrics_LogEventDropped) +#endif + +#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_16BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in the default + * 8 bit descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_client_metrics_ClientMetrics, window) < 256 && pb_membersize(gdt_client_metrics_ClientMetrics, global_metrics) < 256 && pb_membersize(gdt_client_metrics_GlobalMetrics, storage_metrics) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_client_metrics_ClientMetrics_gdt_client_metrics_TimeWindow_gdt_client_metrics_GlobalMetrics_gdt_client_metrics_StorageMetrics_gdt_client_metrics_LogSourceMetrics_gdt_client_metrics_LogEventDropped) +#endif + + +/* @@protoc_insertion_point(eof) */ diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h new file mode 100644 index 00000000..c4d76862 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h @@ -0,0 +1,141 @@ +/* + * Copyright 2022 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. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.9 */ + +#ifndef PB_GDT_CLIENT_METRICS_CLIENT_METRICS_NANOPB_H_INCLUDED +#define PB_GDT_CLIENT_METRICS_CLIENT_METRICS_NANOPB_H_INCLUDED +#include + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Enum definitions */ +typedef enum _gdt_client_metrics_LogEventDropped_Reason { + gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN = 0, + gdt_client_metrics_LogEventDropped_Reason_MESSAGE_TOO_OLD = 1, + gdt_client_metrics_LogEventDropped_Reason_CACHE_FULL = 2, + gdt_client_metrics_LogEventDropped_Reason_PAYLOAD_TOO_BIG = 3, + gdt_client_metrics_LogEventDropped_Reason_MAX_RETRIES_REACHED = 4, + gdt_client_metrics_LogEventDropped_Reason_INVALID_PAYLOD = 5, + gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR = 6 +} gdt_client_metrics_LogEventDropped_Reason; +#define _gdt_client_metrics_LogEventDropped_Reason_MIN gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN +#define _gdt_client_metrics_LogEventDropped_Reason_MAX gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR +#define _gdt_client_metrics_LogEventDropped_Reason_ARRAYSIZE ((gdt_client_metrics_LogEventDropped_Reason)(gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR+1)) + +/* Struct definitions */ +typedef struct _gdt_client_metrics_LogSourceMetrics { + pb_bytes_array_t *log_source; + pb_size_t log_event_dropped_count; + struct _gdt_client_metrics_LogEventDropped *log_event_dropped; +/* @@protoc_insertion_point(struct:gdt_client_metrics_LogSourceMetrics) */ +} gdt_client_metrics_LogSourceMetrics; + +typedef struct _gdt_client_metrics_LogEventDropped { + int64_t events_dropped_count; + gdt_client_metrics_LogEventDropped_Reason reason; +/* @@protoc_insertion_point(struct:gdt_client_metrics_LogEventDropped) */ +} gdt_client_metrics_LogEventDropped; + +typedef struct _gdt_client_metrics_StorageMetrics { + int64_t current_cache_size_bytes; + int64_t max_cache_size_bytes; +/* @@protoc_insertion_point(struct:gdt_client_metrics_StorageMetrics) */ +} gdt_client_metrics_StorageMetrics; + +typedef struct _gdt_client_metrics_TimeWindow { + int64_t start_ms; + int64_t end_ms; +/* @@protoc_insertion_point(struct:gdt_client_metrics_TimeWindow) */ +} gdt_client_metrics_TimeWindow; + +typedef struct _gdt_client_metrics_GlobalMetrics { + gdt_client_metrics_StorageMetrics storage_metrics; +/* @@protoc_insertion_point(struct:gdt_client_metrics_GlobalMetrics) */ +} gdt_client_metrics_GlobalMetrics; + +typedef struct _gdt_client_metrics_ClientMetrics { + gdt_client_metrics_TimeWindow window; + pb_size_t log_source_metrics_count; + struct _gdt_client_metrics_LogSourceMetrics *log_source_metrics; + gdt_client_metrics_GlobalMetrics global_metrics; + pb_bytes_array_t *app_namespace; +/* @@protoc_insertion_point(struct:gdt_client_metrics_ClientMetrics) */ +} gdt_client_metrics_ClientMetrics; + +/* Default values for struct fields */ + +/* Initializer values for message structs */ +#define gdt_client_metrics_ClientMetrics_init_default {gdt_client_metrics_TimeWindow_init_default, 0, NULL, gdt_client_metrics_GlobalMetrics_init_default, NULL} +#define gdt_client_metrics_TimeWindow_init_default {0, 0} +#define gdt_client_metrics_GlobalMetrics_init_default {gdt_client_metrics_StorageMetrics_init_default} +#define gdt_client_metrics_StorageMetrics_init_default {0, 0} +#define gdt_client_metrics_LogSourceMetrics_init_default {NULL, 0, NULL} +#define gdt_client_metrics_LogEventDropped_init_default {0, _gdt_client_metrics_LogEventDropped_Reason_MIN} +#define gdt_client_metrics_ClientMetrics_init_zero {gdt_client_metrics_TimeWindow_init_zero, 0, NULL, gdt_client_metrics_GlobalMetrics_init_zero, NULL} +#define gdt_client_metrics_TimeWindow_init_zero {0, 0} +#define gdt_client_metrics_GlobalMetrics_init_zero {gdt_client_metrics_StorageMetrics_init_zero} +#define gdt_client_metrics_StorageMetrics_init_zero {0, 0} +#define gdt_client_metrics_LogSourceMetrics_init_zero {NULL, 0, NULL} +#define gdt_client_metrics_LogEventDropped_init_zero {0, _gdt_client_metrics_LogEventDropped_Reason_MIN} + +/* Field tags (for use in manual encoding/decoding) */ +#define gdt_client_metrics_LogSourceMetrics_log_source_tag 1 +#define gdt_client_metrics_LogSourceMetrics_log_event_dropped_tag 2 +#define gdt_client_metrics_LogEventDropped_events_dropped_count_tag 1 +#define gdt_client_metrics_LogEventDropped_reason_tag 3 +#define gdt_client_metrics_StorageMetrics_current_cache_size_bytes_tag 1 +#define gdt_client_metrics_StorageMetrics_max_cache_size_bytes_tag 2 +#define gdt_client_metrics_TimeWindow_start_ms_tag 1 +#define gdt_client_metrics_TimeWindow_end_ms_tag 2 +#define gdt_client_metrics_GlobalMetrics_storage_metrics_tag 1 +#define gdt_client_metrics_ClientMetrics_window_tag 1 +#define gdt_client_metrics_ClientMetrics_log_source_metrics_tag 2 +#define gdt_client_metrics_ClientMetrics_global_metrics_tag 3 +#define gdt_client_metrics_ClientMetrics_app_namespace_tag 4 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t gdt_client_metrics_ClientMetrics_fields[5]; +extern const pb_field_t gdt_client_metrics_TimeWindow_fields[3]; +extern const pb_field_t gdt_client_metrics_GlobalMetrics_fields[2]; +extern const pb_field_t gdt_client_metrics_StorageMetrics_fields[3]; +extern const pb_field_t gdt_client_metrics_LogSourceMetrics_fields[3]; +extern const pb_field_t gdt_client_metrics_LogEventDropped_fields[3]; + +/* Maximum encoded size of messages (where known) */ +/* gdt_client_metrics_ClientMetrics_size depends on runtime parameters */ +#define gdt_client_metrics_TimeWindow_size 22 +#define gdt_client_metrics_GlobalMetrics_size 24 +#define gdt_client_metrics_StorageMetrics_size 22 +/* gdt_client_metrics_LogSourceMetrics_size depends on runtime parameters */ +#define gdt_client_metrics_LogEventDropped_size 13 + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define CLIENT_METRICS_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/GoogleDataTransport/ProtoSupport/Protos/client_metrics.options b/GoogleDataTransport/ProtoSupport/Protos/client_metrics.options new file mode 100644 index 00000000..0e104680 --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/client_metrics.options @@ -0,0 +1,21 @@ +# Copyright 2022 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. + +# Options for client_metrics.proto + +gdt_client_metrics.ClientMetrics.log_source_metrics type:FT_POINTER +gdt_client_metrics.ClientMetrics.app_namespace type:FT_POINTER + +gdt_client_metrics.LogSourceMetrics.log_source type:FT_POINTER +gdt_client_metrics.LogSourceMetrics.log_event_dropped type:FT_POINTER \ No newline at end of file diff --git a/GoogleDataTransport/ProtoSupport/Protos/client_metrics.proto b/GoogleDataTransport/ProtoSupport/Protos/client_metrics.proto new file mode 100644 index 00000000..a859f7cc --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/client_metrics.proto @@ -0,0 +1,92 @@ +// Copyright 2022 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. + +syntax = "proto3"; + +package gdt_client_metrics; + +// Top level metrics for all client analytics metrics. +// These metrics should be sent as a part of every request that is uploaded to +// FireLog server. In more detail, an additional LogRequest should be added to +// the BatchedLogRequest, where the LogSource of the LogRequest should be +// GDT_CLIENT_METRICS and the LogRequest should have a single LogEvent whose +// payload is a GoogleDataTransportClientMetrics message. +// +// See go/firelog-client-analytics for more details. +message ClientMetrics { + // The window of time over which the metrics are evaluated. + TimeWindow window = 1; + + repeated LogSourceMetrics log_source_metrics = 2; + + GlobalMetrics global_metrics = 3; + + // The bundle ID on Apple platforms (e.g., iOS) or the package name on Android + string app_namespace = 4; +} + +// Represents an arbitrary window of time. +message TimeWindow { + // The time that the window first starts. + // start_ms is the number of milliseconds since the UNIX epoch + // (January 1, 1970 00:00:00 UTC) + int64 start_ms = 1; + + // The time that the window ends. + // end_ms is the number of milliseconds since the UNIX epoch + // (January 1, 1970 00:00:00 UTC) + int64 end_ms = 2; +} + +// Metrics per app, not per log source +message GlobalMetrics { + StorageMetrics storage_metrics = 1; +} + +message StorageMetrics { + // The number of bytes of storage the event cache was consuming on the client + // at the time the request was sent. + int64 current_cache_size_bytes = 1; + + // The maximum number of bytes to which the event cache is allowed to grow. + int64 max_cache_size_bytes = 2; +} + +// Metrics per log source. +message LogSourceMetrics { + // A LogSource uniquely identifies a logging configuration. log_source should + // contains a string value of the LogSource from + // google3/wireless/android/play/playlog/proto/clientanalytics.proto + string log_source = 1; + + repeated LogEventDropped log_event_dropped = 2; +} + +message LogEventDropped { + // A count of how many log event have been dropped on the client. + int64 events_dropped_count = 1; + + // The reason why log events have been dropped on the client. + enum Reason { + REASON_UNKNOWN = 0; + MESSAGE_TOO_OLD = 1; + CACHE_FULL = 2; + PAYLOAD_TOO_BIG = 3; + MAX_RETRIES_REACHED = 4; + INVALID_PAYLOD = 5; + SERVER_ERROR = 6; + } + + Reason reason = 3; +} diff --git a/GoogleDataTransport/ProtoSupport/proto_generator.py b/GoogleDataTransport/ProtoSupport/proto_generator.py index 93371613..fab4b22b 100755 --- a/GoogleDataTransport/ProtoSupport/proto_generator.py +++ b/GoogleDataTransport/ProtoSupport/proto_generator.py @@ -39,7 +39,7 @@ COPYRIGHT_NOTICE = ''' /* - * Copyright 2019 Google + * Copyright 2022 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From e11226c1ec05eedafd7a655855cc3432bdc0556e Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 26 May 2022 13:10:30 -0400 Subject: [PATCH 043/131] [Metrics] Continue implementation of metrics controller (#63) * Continue implementation of metrics controller * Add documentation * [skip ci] Review --- .../Common/TestStorage/GDTCCTTestStorage.m | 12 ++- .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 3 +- .../GDTCORFlatFileStorage+Promises.m | 12 ++- .../GDTCORLibrary/GDTCORMetrics.m | 79 +++++++++++++++---- .../GDTCORLibrary/GDTCORMetricsController.m | 77 ++++++++---------- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 28 +++---- .../Internal/GDTCORStorageProtocol.h | 26 +++--- .../Internal/GDTCORStorageSizeBytes.h | 16 ++++ .../Private/GDTCOREventMetricsCounter.h | 39 ++++----- .../GDTCORLibrary/Private/GDTCORMetrics.h | 14 +++- .../Private/GDTCORMetricsController.h | 28 +++---- .../Private/GDTCORMetricsMetadata.h | 40 +++++----- .../Private/GDTCORStorageMetadata.h | 32 ++++++++ .../Unit/GDTCORMetricsControllerTest.m | 28 +++---- 14 files changed, 263 insertions(+), 171 deletions(-) create mode 100644 GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h diff --git a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m index 33e49ed8..6f825525 100644 --- a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m +++ b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m @@ -207,10 +207,14 @@ - (void)defaultBatchWithEventSelector:(nonnull GDTCORStorageEventSelector *)even }]; } -- (FBLPromise *) - fetchAndUpdateLibraryDataForKey:(NSString *)key - klass:(Class)klass - readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock { +- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: + (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler { + // TODO(ncooke3): Implement. + return [FBLPromise resolvedWith:nil]; +} + +- (FBLPromise *)fetchStorageMetadata { // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 440da3a2..5196fb02 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -24,7 +24,8 @@ @interface GDTCOREventMetricsCounter () -// TODO(ncooke3): Document. +/// A dictionary of log sources that map to counters that reflect the number of events dropped for a given set +/// of reasons (``GDTCOREventDropReason``). @property(nonatomic) NSDictionary *droppedEventCounterByMappingID; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m index 48a32ffb..031602a1 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m @@ -94,10 +94,14 @@ @implementation GDTCORFlatFileStorage (Promises) }]; } -- (FBLPromise *) - fetchAndUpdateLibraryDataForKey:(NSString *)key - klass:(Class)klass - readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock { +- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: + (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler { + // TODO(ncooke3): Implement. + return [FBLPromise resolvedWith:nil]; +} + +- (FBLPromise *)fetchStorageMetadata { // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m index b38b7b67..5c96c9a8 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -1,23 +1,72 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" + +@interface GDTCORMetrics () + +/// The end of the time window over which the metrics were collected. +@property(nonatomic, readonly) NSDate *collectionEndDate; +/// The number of bytes the event cache was consuming in storage. +@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize; +/// The maximum number of bytes that the event cache is allowed to grow. +@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize; +/// The bundle ID associated with the metrics being collected. +@property(nonatomic, readonly) NSString *bundleID; + +@end + @implementation GDTCORMetrics +- (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate + collectionEndDate:(NSDate *)collectionEndDate + droppedEventCounter:(GDTCOREventMetricsCounter *)droppedEventCounter + currentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize + maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize + bundleID:(NSString *)bundleID { + self = [super init]; + if (self) { + _collectionStartDate = collectionStartDate; + _collectionEndDate = collectionEndDate; + _droppedEventCounter = droppedEventCounter; + _currentCacheSize = currentCacheSize; + _maxCacheSize = maxCacheSize; + _bundleID = bundleID; + } + return self; +} + ++ (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetadata + storageMetadata:(GDTCORStorageMetadata *)storageMetadata { + // The window of collection ends at the time of creating the metrics object. + NSDate *collectionEndDate = [NSDate date]; + // The main bundle ID is associated with the created metrics. + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @""; + + return [[GDTCORMetrics alloc] initWithCollectionStartDate:metricsMetadata.collectionStartDate + collectionEndDate:collectionEndDate + droppedEventCounter:metricsMetadata.droppedEventCounter + currentCacheSize:storageMetadata.currentCacheSize + maxCacheSize:storageMetadata.maxCacheSize + bundleID:bundleID]; +} + - (nonnull NSData *)transportBytes { // TODO(ncooke3): Implement. return [NSData data]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index ecea6b3b..91b39b77 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 @@ -35,11 +33,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" -// TODO(ncooke3): Document. -static NSString *const kMetricsLibraryDataKey = @"metrics-library-data"; - @interface GDTCORMetricsController () -// TODO(ncooke3): Document. +/// The underlying storage object where metrics are stored. @property(nonatomic) id storage; @end @@ -72,8 +67,8 @@ - (instancetype)initWithStorage:(id)storage { - (nonnull FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason events:(nonnull NSSet *)events { - GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( - GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, + NSError *_Nullable fetchError) { GDTCOREventMetricsCounter *metricsCounter = [GDTCOREventMetricsCounter counterWithEvents:[events allObjects] droppedForReason:reason]; @@ -92,19 +87,14 @@ - (instancetype)initWithStorage:(id)storage { } }; - return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey - klass:[GDTCORMetricsMetadata class] - readWriteBlock:readWriteblock] - .then(^id _Nullable(GDTCORLibraryData _Nullable value) { - return nil; - }); + return [_storage fetchAndUpdateClientMetricsWithHandler:handler]; } - (nonnull FBLPromise *)getAndResetMetrics { __block GDTCORMetricsMetadata *snapshottedMetricsMetadata = nil; - GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( - GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, + NSError *_Nullable fetchError) { if (metricsMetadata) { snapshottedMetricsMetadata = metricsMetadata; } else { @@ -114,22 +104,24 @@ - (instancetype)initWithStorage:(id)storage { eventMetricsCounter:nil]; }; - return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey - klass:[GDTCORMetricsMetadata class] - readWriteBlock:readWriteblock] - .then(^id _Nullable(GDTCORLibraryData _Nullable value) { - // TODO(ncooke3): Create metrics object using snapshottedMetadata. - return nil; + return FBLPromise + .all(@[ + [_storage fetchAndUpdateClientMetricsWithHandler:handler], [_storage fetchStorageMetadata] + ]) + .then(^GDTCORMetrics *(NSArray *metricsMetadataAndStorageMetadata) { + return + [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadataAndStorageMetadata.firstObject + storageMetadata:metricsMetadataAndStorageMetadata.lastObject]; }); } - (nonnull FBLPromise *)offerMetrics:(nonnull GDTCORMetrics *)metrics { - GDTCORStorageLibraryDataReadWriteBlock readWriteblock = ^GDTCORMetricsMetadata *( - GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { + __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, + NSError *_Nullable fetchError) { if (metricsMetadata) { if (metrics.collectionStartDate <= metricsMetadata.collectionStartDate) { - // If the metrics to append are older than the metrics represented by the - // currently stored metrics, then return a new metadata object that + // If the metrics to append are older than the metrics represented by + // the currently stored metrics, then return a new metadata object that // incorporates the data from the given metrics. return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[metricsMetadata collectionStartDate] @@ -164,12 +156,7 @@ - (instancetype)initWithStorage:(id)storage { } }; - return [_storage fetchAndUpdateLibraryDataForKey:kMetricsLibraryDataKey - klass:[GDTCORMetricsMetadata class] - readWriteBlock:readWriteblock] - .then(^id _Nullable(GDTCORLibraryData _Nullable value) { - return nil; - }); + return [_storage fetchAndUpdateClientMetricsWithHandler:handler]; } - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index 1d29b032..294baefd 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index 70ef9841..2d00e97c 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -18,6 +18,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" @class GDTCOREvent; @@ -28,9 +29,6 @@ NS_ASSUME_NONNULL_BEGIN -/** The data type to represent storage size. */ -typedef uint64_t GDTCORStorageSizeBytes; - typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, NSSet *_Nullable batchEvents); @@ -123,7 +121,10 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, @end -// TODO: Consider complete replacing block based API by promise API. +// TODO(ncooke3): Consider complete replacing block based API by promise API. + +@class GDTCORMetricsMetadata; +@class GDTCORStorageMetadata; /** Promise based version of API defined in GDTCORStorageProtocol. See API docs for corresponding * methods in GDTCORStorageProtocol. */ @@ -139,18 +140,11 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, - (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target deleteEvents:(BOOL)deleteEvents; -// TODO(ncooke3): Document. -typedef id GDTCORLibraryData; - -// TODO(ncooke3): Document. -typedef GDTCORLibraryData _Nullable (^GDTCORStorageLibraryDataReadWriteBlock)( - GDTCORLibraryData _Nullable fetchedValue, NSError *_Nullable fetchError); +- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: + (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler; -// TODO(ncooke3): Document. -- (FBLPromise *) - fetchAndUpdateLibraryDataForKey:(NSString *)key - klass:(Class)klass - readWriteBlock:(GDTCORStorageLibraryDataReadWriteBlock)readWriteBlock; +- (FBLPromise *)fetchStorageMetadata; /** See `hasEventsForTarget:onComplete:`. * @return A promise object that is resolved with @YES if there are events for the specified target @@ -175,8 +169,6 @@ typedef GDTCORLibraryData _Nullable (^GDTCORStorageLibraryDataReadWriteBlock)( FOUNDATION_EXPORT id _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target); -// TODO: Ideally we should remove completion-based API and use promise-based one. Need to double -// check if it's ok. FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target); diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h new file mode 100644 index 00000000..ce4ce066 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h @@ -0,0 +1,16 @@ +// Copyright 2022 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. + +/// The data type to represent storage size. +typedef uint64_t GDTCORStorageSizeBytes; diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h index 455af55c..f23bbe72 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 @@ -22,17 +20,22 @@ NS_ASSUME_NONNULL_BEGIN -// TODO(ncooke3): Document. // TODO(ncooke3): Consider renaming to `GDTCORMetricsDroppedEventCounter`. +/// A counter object that tracks, per log source, the number of events dropped for a variety of +/// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK. @interface GDTCOREventMetricsCounter : NSObject -// TODO(ncooke3): Document. +/// Creates a dropped event counter for a collection of events that were dropped for a given reason. +/// @param events The collection of events that were dropped. +/// @param reason The reason for which given events were dropped. + (instancetype)counterWithEvents:(NSArray *)events droppedForReason:(GDTCOREventDropReason)reason; +/// This API is unavailable. - (instancetype)init NS_UNAVAILABLE; -// TODO(ncooke3): Document. +/// Returns a counter created by merging this counter with the given counter. +/// @param counter The given counter to merge with. - (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter; @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index 95337211..1f9006ac 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -19,18 +19,26 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" @class GDTCOREventMetricsCounter; +@class GDTCORMetricsMetadata; +@class GDTCORStorageMetadata; NS_ASSUME_NONNULL_BEGIN -// TODO(ncooke3): Document. +/// An object representing metrics that represent a snapshot of the SDK's state and performance. @interface GDTCORMetrics : NSObject -// TODO(ncooke3): Document. +/// The start of the time window over which the metrics were collected. @property(nonatomic, readonly) NSDate *collectionStartDate; -// TODO(ncooke3): Document. +/// The dropped event counter associated with the metrics. @property(nonatomic, readonly) GDTCOREventMetricsCounter *droppedEventCounter; +/// Creates a metrics instance with the provided metadata. +/// @param metricsMetadata The provided metrics metadata. +/// @param storageMetadata The provided storage metadata. ++ (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetadata + storageMetadata:(GDTCORStorageMetadata *)storageMetadata; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h index be89e04b..31e11184 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h index 6117f3eb..206c880d 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 @@ -22,20 +20,24 @@ NS_ASSUME_NONNULL_BEGIN -// TODO(ncooke3): Document. +/// An encodable model object that contains metadata that is persisted in storage until ready to be +/// used to create a ``GDTCORMetrics`` instance. @interface GDTCORMetricsMetadata : NSObject -// TODO(ncooke3): Document. +/// The start of the time window over which the metrics were collected. @property(nonatomic, copy, readonly) NSDate *collectionStartDate; -// TODO(ncooke3): Document. +/// The dropped event counter associated with the metrics. @property(nonatomic, copy, readonly, nullable) GDTCOREventMetricsCounter *droppedEventCounter; -// TODO(ncooke3): Document. +/// Creates a metrics metadata object with the provided information. +/// @param collectedSinceDate The start of the time window over which the metrics were collected. +/// @param eventMetricsCounter The dropped event counter associated with the metrics. + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate eventMetricsCounter: (nullable GDTCOREventMetricsCounter *)eventMetricsCounter; +/// This API is unavailable. - (instancetype)init NS_UNAVAILABLE; @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h new file mode 100644 index 00000000..661ec4b3 --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h @@ -0,0 +1,32 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A model object that contains metadata about the current state of the SDK's storage container. +@interface GDTCORStorageMetadata : NSObject + +/// The number of bytes the event cache is consuming in storage. +@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize; + +/// The maximum number of bytes that the event cache may consume in storage. +@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m index 18ee6461..3bfb618d 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" From 29cfb7898671faa4153bd1631819d438199b22c0 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 26 May 2022 15:13:52 -0400 Subject: [PATCH 044/131] [Metrics] Add metrics controller unit tests (1) (#64) * Add metrics controller unit tests (1) * Review and fix test --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 46 ++++---- .../GDTCCTTests/Unit/GDTCCTUploaderTest.m | 67 ++++++++++++ .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 63 ++++++++--- .../GDTCORLibrary/GDTCORMetricsController.m | 39 ++++--- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 5 +- .../GDTCORLibrary/GDTCORStorageMetadata.m | 36 +++++++ .../Private/GDTCOREventMetricsCounter.h | 9 ++ .../Private/GDTCORMetricsController.h | 9 ++ .../Private/GDTCORMetricsMetadata.h | 3 +- .../Private/GDTCORStorageMetadata.h | 9 ++ .../Common/Fakes/GDTCORStorageFake.h | 5 +- .../Common/Fakes/GDTCORStorageFake.m | 65 ++++++++++- .../Unit/GDTCORMetricsControllerTest.m | 101 +++++++++++++++--- 13 files changed, 386 insertions(+), 71 deletions(-) create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORStorageMetadata.m diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index ba6b7d36..8629badc 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -148,7 +148,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions // 1. Check if the conditions for the target are suitable. [self isReadyToUploadTarget:target conditions:conditions] .validateOn(self.uploaderQueue, - ^BOOL(NSNull *result) { + ^BOOL(NSNull *__unused _) { // 2. Stop the operation if it has been cancelled. return !self.isCancelled; }) @@ -158,7 +158,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions return [storage removeAllBatchesForTarget:target deleteEvents:NO]; }) .thenOn(self.uploaderQueue, - ^FBLPromise *(NSNull *result) { + ^FBLPromise *(NSNull *__unused _) { // There may be a big amount of events stored, so creating a batch may be an // expensive operation. @@ -172,7 +172,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions return hasEvents.boolValue; }) .thenOn(self.uploaderQueue, - ^FBLPromise *(NSNumber *result) { + ^FBLPromise *(NSNumber *__unused _) { // 6. Fetch events to upload. GDTCORStorageEventSelector *eventSelector = [self eventSelectorTarget:target withConditions:conditions]; @@ -189,7 +189,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions return [self batchByAddingMetricsEventToBatch:batch forTarget:target]; }) .validateOn(self.uploaderQueue, - ^BOOL(GDTCORUploadBatch *result) { + ^BOOL(GDTCORUploadBatch *__unused _) { // 8. Stop the operation if it has been cancelled. return !self.isCancelled; }) @@ -214,7 +214,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions #pragma mark - Upload implementation details -/** TODO(ncooke3): Document. */ +/** Uploads a given batch from storage to a target. */ - (FBLPromise *)uploadBatch:(GDTCORUploadBatch *)batch toTarget:(GDTCORTarget)target storage:(id)storage { @@ -234,11 +234,11 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions }); } -/** TODO(ncooke3): Document. */ +/** Processes a URL session response for a given batch from storage. */ - (FBLPromise *)processResponse:(GULURLSessionDataResponse *)response forBatch:(GDTCORUploadBatch *)batch storage:(id)storage { - // 1. Cleanup batch based on the response's status code. + // Cleanup batch based on the response's status code. NSInteger statusCode = response.HTTPResponse.statusCode; BOOL isSuccess = statusCode >= 200 && statusCode < 300; // Transient errors include "too many requests" (429) and server errors (5xx). @@ -247,7 +247,8 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions BOOL shouldDeleteEvents = isSuccess || !isTransientError; - // Place metrics back in storage if upload failed. + // If the batch included metrics and the upload failed, place metrics back + // in storage. GDTCORMetrics *uploadedMetrics = [self currentMetrics]; if (uploadedMetrics && !isSuccess) { [self.metricsController offerMetrics:uploadedMetrics]; @@ -520,22 +521,27 @@ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target - (FBLPromise *)batchByAddingMetricsEventToBatch:(GDTCORUploadBatch *)batch forTarget:(GDTCORTarget)target { - return [self.metricsController getAndResetMetrics].thenOn( - self.uploaderQueue, ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { - // TODO(ncooke3): Define kMetricEventMappingID. - GDTCOREvent *metricsEvent = [[GDTCOREvent alloc] initWithMappingID:@"kMetricEventMappingID" - target:target]; + return [self.metricsController getAndResetMetrics] + .thenOn(self.uploaderQueue, + ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { + // TODO(ncooke3): Define kMetricEventMappingID. + GDTCOREvent *metricsEvent = + [[GDTCOREvent alloc] initWithMappingID:@"kMetricEventMappingID" target:target]; - [metricsEvent setDataObject:metrics]; + [metricsEvent setDataObject:metrics]; - // Save the metrics so they can be reset later upon successful upload. - [self setCurrentMetrics:metrics]; + // Save the metrics so they can be reset later upon successful upload. + [self setCurrentMetrics:metrics]; - GDTCORUploadBatch *batchWithMetricEvent = [[GDTCORUploadBatch alloc] - initWithBatchID:batch.batchID - events:[batch.events setByAddingObject:metricsEvent]]; + GDTCORUploadBatch *batchWithMetricEvent = [[GDTCORUploadBatch alloc] + initWithBatchID:batch.batchID + events:[batch.events setByAddingObject:metricsEvent]]; - return batchWithMetricEvent; + return batchWithMetricEvent; + }) + .recoverOn(self.uploaderQueue, ^GDTCORUploadBatch *(NSError *error) { + // Return given batch if an error occurs (i.e. no metrics were fetched). + return batch; }); } diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m index cca1d7dc..cf3bd712 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m @@ -529,6 +529,73 @@ - (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetrics [self waitForUploadOperationsToFinish:self.uploader]; } +- (void)testUploadTarget_WhenFailureToAddMetricsToBatch_DoesNotImpedeBatchUpload { + // Given + // - Generate and batch a test event. + [self.generator generateEvent:GDTCOREventQoSFast]; + [self batchEvents]; + + // - Set storage expectations. + [self setUpStorageExpectations]; + + XCTestExpectation *hasEventsExpectation = [self expectStorageHasEventsForTarget:kGDTCORTargetTest + result:YES]; + + // - Set metrics controller expectations. + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake + target:kGDTCORTargetTest]; + + XCTestExpectation *targetSupportsMetricsCollectionExpectation = + [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; + metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { + [targetSupportsMetricsCollectionExpectation fulfill]; + return YES; + }; + + NSError *error = [NSError errorWithDomain:@"metrics controller error" code:1 userInfo:nil]; + XCTestExpectation *fetchMetricsExpectation = + [self expectationWithDescription:@"fetchMetricsExpectation"]; + metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { + [fetchMetricsExpectation fulfill]; + + return [FBLPromise resolvedWith:error]; + }; + + XCTestExpectation *metricsConfirmationExpectation = + [self expectationWithDescription:@"metricsConfirmationExpectation"]; + metricsConfirmationExpectation.inverted = YES; + metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { + [metricsConfirmationExpectation fulfill]; + }; + + XCTestExpectation *droppedEventsAreLoggedExpectation = + [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; + droppedEventsAreLoggedExpectation.inverted = YES; + metricsControllerFake.onLogEventsDroppedHandler = + ^(GDTCOREventDropReason _, NSSet *__) { + [droppedEventsAreLoggedExpectation fulfill]; + }; + + // - Set response expectations. + XCTestExpectation *responseSentExpectation = [self expectationTestServerSuccessRequestResponse]; + + // When + [self.uploader uploadTarget:kGDTCORTargetTest withConditions:GDTCORUploadConditionWifiData]; + + // Then + [self waitForExpectations:@[ + self.testStorage.batchIDsForTargetExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, + fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, + droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + ] + timeout:1 + enforceOrder:YES]; + [self waitForUploadOperationsToFinish:self.uploader]; +} + - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmedWithSuccess { // Given // - Generate and batch a test event. diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 5196fb02..600498f7 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 @@ -24,8 +22,8 @@ @interface GDTCOREventMetricsCounter () -/// A dictionary of log sources that map to counters that reflect the number of events dropped for a given set -/// of reasons (``GDTCOREventDropReason``). +/// A dictionary of log sources that map to counters that reflect the number of events dropped for a +/// given set of reasons (``GDTCOREventDropReason``). @property(nonatomic) NSDictionary *droppedEventCounterByMappingID; @@ -33,6 +31,10 @@ @interface GDTCOREventMetricsCounter () @implementation GDTCOREventMetricsCounter ++ (instancetype)counter { + return [[self alloc] initWithDroppedEventCounterByMappingID:@{}]; +} + + (instancetype)counterWithEvents:(NSArray *)events droppedForReason:(GDTCOREventDropReason)reason { NSMutableDictionary *eventCounterByMappingID = @@ -119,6 +121,33 @@ + (NSDictionary *)dictionaryByMergingDictionary:(NSDictionary *)dictionary return [mergedDictionary copy]; } +#pragma mark - Equality + +- (BOOL)isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)otherDroppedEventCounter { + return [_droppedEventCounterByMappingID + isEqualToDictionary:otherDroppedEventCounter.droppedEventCounterByMappingID]; +} + +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } + + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[self class]]) { + return NO; + } + + return [self isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)object]; +} + +- (NSUInteger)hash { + return [_droppedEventCounterByMappingID hash]; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index 91b39b77..abfe26bd 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -67,6 +67,11 @@ - (instancetype)initWithStorage:(id)storage { - (nonnull FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason events:(nonnull NSSet *)events { + // No-op if there are no events to log. + if ([events count] == 0) { + return [FBLPromise resolvedWith:nil]; + } + __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { GDTCOREventMetricsCounter *metricsCounter = @@ -91,7 +96,7 @@ - (instancetype)initWithStorage:(id)storage { } - (nonnull FBLPromise *)getAndResetMetrics { - __block GDTCORMetricsMetadata *snapshottedMetricsMetadata = nil; + __block GDTCORMetricsMetadata *_Nullable snapshottedMetricsMetadata = nil; __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { @@ -100,18 +105,26 @@ - (instancetype)initWithStorage:(id)storage { } else { GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); } - return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:nil]; + return + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; }; - return FBLPromise - .all(@[ - [_storage fetchAndUpdateClientMetricsWithHandler:handler], [_storage fetchStorageMetadata] - ]) - .then(^GDTCORMetrics *(NSArray *metricsMetadataAndStorageMetadata) { - return - [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadataAndStorageMetadata.firstObject - storageMetadata:metricsMetadataAndStorageMetadata.lastObject]; + return [_storage fetchAndUpdateClientMetricsWithHandler:handler] + .validate(^BOOL(NSNull *__unused _) { + // Break and reject the promise chain when storage contains no metrics + // metadata. + return snapshottedMetricsMetadata != nil; + }) + .then(^FBLPromise *(NSNull *__unused _) { + // Fetch and return storage metadata (needed for metrics). + return [self.storage fetchStorageMetadata]; + }) + .then(^GDTCORMetrics *(GDTCORStorageMetadata *storageMetadata) { + // Use the fetched metrics & storage metadata to create and return a + // complete metrics object. + return [GDTCORMetrics metricsWithMetricsMetadata:snapshottedMetricsMetadata + storageMetadata:storageMetadata]; }); } @@ -151,7 +164,9 @@ - (instancetype)initWithStorage:(id)storage { // This catches an edge case where the given metrics are from the // future. If this occurs, ignore them and store an empty metadata // object intended to track metrics metadata from this time forward. - return [GDTCORMetricsMetadata metadataWithCollectionStartDate:now eventMetricsCounter:nil]; + return [GDTCORMetricsMetadata + metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; } } }; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index 294baefd..d19f65d8 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -19,13 +19,12 @@ @implementation GDTCORMetricsMetadata + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate - eventMetricsCounter: - (nullable GDTCOREventMetricsCounter *)eventMetricsCounter { + eventMetricsCounter:(GDTCOREventMetricsCounter *)eventMetricsCounter { return [[self alloc] initWithCollectionStartDate:collectedSinceDate counter:eventMetricsCounter]; } - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate - counter:(nullable GDTCOREventMetricsCounter *)counter { + counter:(GDTCOREventMetricsCounter *)counter { self = [super init]; if (self) { _collectionStartDate = collectionStartDate; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORStorageMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORStorageMetadata.m new file mode 100644 index 00000000..de3b466e --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORStorageMetadata.m @@ -0,0 +1,36 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" + +@implementation GDTCORStorageMetadata + +- (instancetype)initWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize + maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize { + self = [super init]; + if (self) { + _currentCacheSize = currentCacheSize; + _maxCacheSize = maxCacheSize; + } + return self; +} + ++ (instancetype)metadataWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize + maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize { + return [[self alloc] initWithCurrentCacheSize:currentCacheSize maxCacheSize:maxCacheSize]; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h index f23bbe72..1c398733 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h @@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN /// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK. @interface GDTCOREventMetricsCounter : NSObject +/// Creates an empty dropped event counter. ++ (instancetype)counter; + /// Creates a dropped event counter for a collection of events that were dropped for a given reason. /// @param events The collection of events that were dropped. /// @param reason The reason for which given events were dropped. @@ -38,6 +41,12 @@ NS_ASSUME_NONNULL_BEGIN /// @param counter The given counter to merge with. - (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter; +/// Returns a Boolean value that indicates whether the receiving dropped event counter is equal to +/// the given dropped event counter. +/// @param otherDroppedEventCounter The dropped event counter with which to compare the +/// receiving dropped event counter. +- (BOOL)isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)otherDroppedEventCounter; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h index 31e11184..7e037515 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h @@ -16,10 +16,19 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h" +@protocol GDTCORStoragePromiseProtocol; + NS_ASSUME_NONNULL_BEGIN @interface GDTCORMetricsController : NSObject +/// Designated initializer. +/// @param storage The storage object to read and write metrics data from. +- (instancetype)initWithStorage:(id)storage NS_DESIGNATED_INITIALIZER; + +/// This API is unavailable. +- (instancetype)init NS_UNAVAILABLE; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h index 206c880d..ca00dcd0 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h @@ -34,8 +34,7 @@ NS_ASSUME_NONNULL_BEGIN /// @param collectedSinceDate The start of the time window over which the metrics were collected. /// @param eventMetricsCounter The dropped event counter associated with the metrics. + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate - eventMetricsCounter: - (nullable GDTCOREventMetricsCounter *)eventMetricsCounter; + eventMetricsCounter:(GDTCOREventMetricsCounter *)eventMetricsCounter; /// This API is unavailable. - (instancetype)init NS_UNAVAILABLE; diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h index 661ec4b3..90b529e4 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h @@ -27,6 +27,15 @@ NS_ASSUME_NONNULL_BEGIN /// The maximum number of bytes that the event cache may consume in storage. @property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize; +/// Creates a storage metadata object with the provided information. +/// @param currentCacheSize The current number of bytes the event cache is consuming. +/// @param maxCacheSize The current maximum capacity (in bytes) that the event cache may consume. ++ (instancetype)metadataWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize + maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize; + +/// This API is unavailable. +- (instancetype)init NS_UNAVAILABLE; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h index 1d4edf56..2a3b771b 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h @@ -19,7 +19,10 @@ NS_ASSUME_NONNULL_BEGIN /** A functionless fake that can be injected into classes that need it. */ -@interface GDTCORStorageFake : NSObject +@interface GDTCORStorageFake : NSObject + +/// Creates an storage fake. ++ (instancetype)storageFake; @end diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m index c9eb6fd0..9ec89405 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m @@ -16,11 +16,25 @@ #import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h" +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" + @implementation GDTCORStorageFake { - /** Store the events in memory. */ + /// Store the events in memory. NSMutableDictionary *_storedEvents; + /// Store the metrics metadata in memory. + GDTCORMetricsMetadata *_Nullable _storedMetricsMetadata; +} + ++ (instancetype)storageFake { + return [[self alloc] init]; } - (void)storeEvent:(GDTCOREvent *)event @@ -90,4 +104,53 @@ - (void)batchIDsForTarget:(GDTCORTarget)target - (void)checkForExpirations { } +- (nonnull FBLPromise *> *)batchIDsForTarget:(GDTCORTarget)target { + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)batchWithEventSelector: + (nonnull GDTCORStorageEventSelector *)eventSelector + batchExpiration:(nonnull NSDate *)expiration { + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)fetchAndUpdateClientMetricsWithHandler: + (nonnull GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable, + NSError *_Nullable))handler { + if (_storedMetricsMetadata != nil) { + _storedMetricsMetadata = handler(_storedMetricsMetadata, nil); + } else { + NSError *emptyStorageError = [NSError errorWithDomain:@"GDTCORStorageFake" code:1 userInfo:nil]; + _storedMetricsMetadata = handler(nil, emptyStorageError); + } + + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)fetchStorageMetadata { + GDTCORStorageMetadata *storageMetadata = + [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB + maxCacheSize:20 * 1000 * 1000]; // 20 MB + return [FBLPromise resolvedWith:storageMetadata]; +} + +- (nonnull FBLPromise *)hasEventsForTarget:(GDTCORTarget)target { + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target + deleteEvents:(BOOL)deleteEvents { + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)removeBatchWithID:(nonnull NSNumber *)batchID + deleteEvents:(BOOL)deleteEvents { + return [FBLPromise resolvedWith:nil]; +} + +- (nonnull FBLPromise *)removeBatchesWithIDs:(nonnull NSSet *)batchIDs + deleteEvents:(BOOL)deleteEvents { + return [FBLPromise resolvedWith:nil]; +} + @end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m index 3bfb618d..e8c3f41f 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -17,49 +17,117 @@ #import "FBLPromise+Await.h" #import "FBLPromise+Testing.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" +#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h" + @interface GDTCORMetricsControllerTest : GDTCORTestCase @end @implementation GDTCORMetricsControllerTest -- (void)SKIP_testLoggedEventsAreFetchedAsMetrics { +- (void)testLoggingEvents_WhenEventSetIsEmpty_ThenNoOp { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; // When [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; // Then - // GDTCORMetrics *metrics = FBLPromiseAwait([metricsController getAndResetMetrics], nil); - // TODO(ncooke3): Assert that the metrics contain the previously logged events. + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert([metricsPromise isRejected]); } -- (void)SKIP_testLogEvents_WhenNoEvents_DoesNothing { +- (void)testLoggingEvents_WhenNoMetricsAreStored_StoresEventsAsMetrics { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; + + NSSet *droppedEvents = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + ]]; + + GDTCOREventMetricsCounter *expectedCounter = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; // When - [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; // Then - // TODO(ncooke3): Assert that the metrics are empty with expected time window. + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + + XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounter); +} + +- (void)testLoggingEvents_WhenMetricsAreStored_StoresEventsAsMetrics { + // Given + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; + + NSSet *droppedEvents1 = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + ]]; + + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents1]; + + // When + NSSet *droppedEvents2 = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_3" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_3" target:kGDTCORTargetTest], + ]]; + + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents2]; + + // Then + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + + GDTCOREventMetricsCounter *expectedCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents1 allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; + + GDTCOREventMetricsCounter *expectedCounter2 = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents2 allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; + + GDTCOREventMetricsCounter *expectedCounterCombined = + [expectedCounter1 counterByMergingWithCounter:expectedCounter2]; + + XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounterCombined); } -- (void)SKIP_testFetchMetrics_WhenNoMetricsAreStored_ReturnsEmptyMetrics { +- (void)testGetAndResetMetrics_WhenNoMetricsAreStored_ReturnsRejectedPromise { // Given - // GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; + + // When + __auto_type metricsPromise = [metricsController getAndResetMetrics]; // Then - // TODO(ncooke3): Assert that fetched metrics are empty with expected time window. + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert([metricsPromise isRejected]); } - (void) SKIP_testFetchMetrics_WhenSomeMetricsNeedReupload_ReturnsLastConfirmedMetricsThatFailedToUpload { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; // - Create pending metrics. [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; @@ -88,7 +156,8 @@ - (void)SKIP_testFetchMetrics_WhenNoMetricsAreStored_ReturnsEmptyMetrics { - (void)SKIP_testFetchMetrics_WhenNoMetricsNeedReuploading_ReturnsNewMetricsFromPendingMetricsData { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; // - Create and fetch pending metrics data. [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; // GDTCORMetrics *metrics = FBLPromiseAwait([metricsController getAndResetMetrics], nil); @@ -109,7 +178,8 @@ - (void)SKIP_testFetchMetrics_WhenNoMetricsNeedReuploading_ReturnsNewMetricsFrom - (void)SKIP_testAppendingMetrics_SavesMetricsForRefetchingLater { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; // - TODO(ncooke3): Add context. [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; GDTCORMetrics *metricsThatFailedToUpload = @@ -130,7 +200,8 @@ - (void)SKIP_testAppendingMetrics_SavesMetricsForRefetchingLater { - (void)testIsMetricsCollectionSupportedForTarget { // Given - GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] init]; + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; NSDictionary *targetSupportMatrix = @{ @(kGDTCORTargetTest) : @YES, @(kGDTCORTargetFLL) : @YES, From 11a19b94f5e4ca74e028d27353c9f946be400024 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 26 May 2022 18:01:02 -0400 Subject: [PATCH 045/131] [Metrics] Add metrics controller unit tests (2) (#65) * Add metrics controller unit tests (2) * [skip ci] Review --- .../GDTCORLibrary/GDTCORMetricsController.m | 15 +- .../Unit/GDTCORMetricsControllerTest.m | 238 ++++++++++++++---- 2 files changed, 206 insertions(+), 47 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index abfe26bd..d7d6fb45 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -32,6 +32,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" @interface GDTCORMetricsController () /// The underlying storage object where metrics are stored. @@ -129,15 +130,21 @@ - (instancetype)initWithStorage:(id)storage { } - (nonnull FBLPromise *)offerMetrics:(nonnull GDTCORMetrics *)metrics { + // No-op if there are no metrics to offer. + if (metrics == nil) { + return [FBLPromise resolvedWith:nil]; + } + __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { if (metricsMetadata) { - if (metrics.collectionStartDate <= metricsMetadata.collectionStartDate) { + if (metrics.collectionStartDate.timeIntervalSince1970 <= + metricsMetadata.collectionStartDate.timeIntervalSince1970) { // If the metrics to append are older than the metrics represented by // the currently stored metrics, then return a new metadata object that // incorporates the data from the given metrics. return [GDTCORMetricsMetadata - metadataWithCollectionStartDate:[metricsMetadata collectionStartDate] + metadataWithCollectionStartDate:[metrics collectionStartDate] eventMetricsCounter: [metricsMetadata.droppedEventCounter counterByMergingWithCounter:metrics.droppedEventCounter]]; @@ -146,7 +153,7 @@ - (instancetype)initWithStorage:(id)storage { // newer than metrics represented by the currently stored metrics // metadata. In this case, return the existing metadata object as the // given metrics are assumed to already be accounted for by the - // currenly metadata. + // currently stored metadata. return metricsMetadata; } } else { @@ -154,7 +161,7 @@ - (instancetype)initWithStorage:(id)storage { GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); NSDate *now = [NSDate date]; - if (metrics.collectionStartDate <= now) { + if (metrics.collectionStartDate.timeIntervalSince1970 <= now.timeIntervalSince1970) { // The given metrics are were recorded up until now. They wouldn't // be offered if they were successfully uploaded so their // corresponding metadata can be safely placed back in storage. diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m index e8c3f41f..1850ba02 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -23,6 +23,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" #import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h" @@ -110,7 +112,7 @@ - (void)testLoggingEvents_WhenMetricsAreStored_StoresEventsAsMetrics { XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounterCombined); } -- (void)testGetAndResetMetrics_WhenNoMetricsAreStored_ReturnsRejectedPromise { +- (void)testGetAndResetMetrics_WhenNoMetricsDataIsStored_ReturnsRejectedPromise { // Given GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; @@ -119,83 +121,233 @@ - (void)testGetAndResetMetrics_WhenNoMetricsAreStored_ReturnsRejectedPromise { __auto_type metricsPromise = [metricsController getAndResetMetrics]; // Then + // - Assert that the retrieved metrics promise was rejected. FBLWaitForPromisesWithTimeout(0.5); - XCTAssert([metricsPromise isRejected]); + XCTAssert(metricsPromise.isRejected); + + // - Assert that metrics metadata was reset to start tracking metrics data + // from this point forward. + __auto_type metricsPromise2 = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + + // Dates should be roughly equal (within one second of each other). + XCTAssertEqualWithAccuracy(metricsPromise2.value.collectionStartDate.timeIntervalSince1970, + NSDate.date.timeIntervalSince1970, 1); + // The dropped event counter should be empty. + XCTAssertEqualObjects([metricsPromise2.value droppedEventCounter], + [GDTCOREventMetricsCounter counter]); } -- (void) - SKIP_testFetchMetrics_WhenSomeMetricsNeedReupload_ReturnsLastConfirmedMetricsThatFailedToUpload { +- (void)testGetAndResetMetrics_WhenMetricsDataIsStored_ReturnsMetrics { // Given GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; - // - Create pending metrics. - [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + + NSSet *droppedEvents = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + ]]; + + GDTCOREventMetricsCounter *expectedCounter = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; // When - GDTCORMetrics *metrics1 = [[GDTCORMetrics alloc] init]; - [metricsController offerMetrics:metrics1]; + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; + + // Then + // - Assert that the retrieved metrics have the expected dropped event counter. + __auto_type metricsPromise1 = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + + XCTAssertEqualObjects([metricsPromise1.value droppedEventCounter], expectedCounter); + + // - Assert that metrics metadata was reset to start tracking metrics data + // from this point forward. + __auto_type metricsPromise2 = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); - GDTCORMetrics *metrics2 = [[GDTCORMetrics alloc] init]; - [metricsController offerMetrics:metrics2]; + // Dates should be roughly equal (within one second of each other). + XCTAssertEqualWithAccuracy(metricsPromise2.value.collectionStartDate.timeIntervalSince1970, + NSDate.date.timeIntervalSince1970, 1); + // The dropped event counter should be empty. + XCTAssertEqualObjects([metricsPromise2.value droppedEventCounter], + [GDTCOREventMetricsCounter counter]); +} + +- (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreValid_ThenAcceptMetrics { + // Given + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; + + // - Create valid metrics to offer. + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + + GDTCORStorageMetadata *storageMetadata = + [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB + maxCacheSize:20 * 1000 * 1000]; // 20 MB + + GDTCORMetrics *metrics = [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadata + storageMetadata:storageMetadata]; + + // When + // - Storage is empty and will pass back an error. See ``GDTCORStorageFake``. + __auto_type offerPromise = [metricsController offerMetrics:metrics]; // Then - // - TODO(ncooke3): Add comment. - GDTCORMetrics *fetchedMetrics1 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); - XCTAssertEqualObjects(fetchedMetrics1, metrics2); + // - Assert promise was resolved. + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(offerPromise.isFulfilled); - // - TODO(ncooke3): Add comment. - GDTCORMetrics *fetchedMetrics2 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); - XCTAssertEqualObjects(fetchedMetrics2, metrics1); + // - Assert that retrieving the metrics contains the metadata from the offered metrics. + __auto_type metricsPromise1 = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + XCTAssertEqualObjects([metricsPromise1.value collectionStartDate], + metricsMetadata.collectionStartDate); + XCTAssertEqualObjects([metricsPromise1.value droppedEventCounter], + metricsMetadata.droppedEventCounter); +} - // - TODO(ncooke3): Add comment. - // GDTCORMetrics *fetchedMetrics3 = FBLPromiseAwait([metricsController getAndResetMetrics], nil); - // TODO(ncooke3): Assert that `metricsPromise3`'s metrics contain logged events. +- (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreInvalid_ThenRejectMetrics { + // Given + GDTCORMetricsController *metricsController = + [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; + + // - Create invalid metrics to offer. + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; - // TODO(ncooke3): Assert that the metrics are empty with the expected test window. + GDTCORStorageMetadata *storageMetadata = + [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB + maxCacheSize:20 * 1000 * 1000]; // 20 MB + + GDTCORMetrics *metrics = [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadata + storageMetadata:storageMetadata]; + + // When + // - Storage is empty and will pass back an error. See ``GDTCORStorageFake``. + __auto_type offerPromise = [metricsController offerMetrics:metrics]; + + // Then + // - Assert promise was resolved. + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(offerPromise.isFulfilled); + + // - Assert that retrieving the metrics contains the metadata from the offered metrics. + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + // Dates should be roughly equal (within one second of each other). + XCTAssertEqualWithAccuracy(metricsPromise.value.collectionStartDate.timeIntervalSince1970, + NSDate.date.timeIntervalSince1970, 1); + // The dropped event counter should be empty. + XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], + [GDTCOREventMetricsCounter counter]); } -- (void)SKIP_testFetchMetrics_WhenNoMetricsNeedReuploading_ReturnsNewMetricsFromPendingMetricsData { +- (void)testOfferMetrics_WhenOfferedMetricsAreInvalid_ThenRejectMetrics { // Given GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; - // - Create and fetch pending metrics data. - [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; - // GDTCORMetrics *metrics = FBLPromiseAwait([metricsController getAndResetMetrics], nil); - // - * Client begins uploading fetched metrics... * + // - Populate storage with metrics metadata from dropped events. + NSSet *droppedEvents = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + ]]; + + GDTCOREventMetricsCounter *expectedCounter = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; + + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; + + // - Create invalid metrics to offer. + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + + GDTCORStorageMetadata *storageMetadata = + [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB + maxCacheSize:20 * 1000 * 1000]; // 20 MB + + GDTCORMetrics *metrics = [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadata + storageMetadata:storageMetadata]; // When - // - Create new pending metrics data. - [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; + __auto_type offerPromise = [metricsController offerMetrics:metrics]; // Then - // GDTCORMetrics *newPendingMetrics = FBLPromiseAwait([metricsController getAndResetMetrics], - // nil); - // TODO(ncooke3): Assert that the metrics contain the new pending metrics data. + // - Assert promise was resolved. + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(offerPromise.isFulfilled); - // TODO(ncooke3): Assert that the metrics are empty with expected time window. + // - Assert that retrieving the metrics contains the metadata from the original metrics. + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounter); } -- (void)SKIP_testAppendingMetrics_SavesMetricsForRefetchingLater { +- (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { // Given GDTCORMetricsController *metricsController = [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; - // - TODO(ncooke3): Add context. - [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:[NSSet set]]; - GDTCORMetrics *metricsThatFailedToUpload = - FBLPromiseAwait([metricsController getAndResetMetrics], nil); + + // - Populate storage with metrics metadata from dropped events. + NSSet *droppedEvents = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + ]]; + + GDTCOREventMetricsCounter *originalCounter = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; + + [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; + + // - Create valid metrics to offer. + NSSet *droppedEventsToOffer = [NSSet setWithArray:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_source_3" target:kGDTCORTargetTest], + ]]; + + GDTCOREventMetricsCounter *counterToOffer = + [GDTCOREventMetricsCounter counterWithEvents:[droppedEventsToOffer allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; + + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] + eventMetricsCounter:counterToOffer]; + + GDTCORStorageMetadata *storageMetadata = + [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB + maxCacheSize:20 * 1000 * 1000]; // 20 MB + + GDTCORMetrics *metrics = [GDTCORMetrics metricsWithMetricsMetadata:metricsMetadata + storageMetadata:storageMetadata]; // When - // - * Uploading metrics fails * - // - Alert the metrics controller about the upload and save metrics for later. - [metricsController offerMetrics:metricsThatFailedToUpload]; + __auto_type offerPromise = [metricsController offerMetrics:metrics]; // Then - GDTCORMetrics *metricsToRetryUploading = - FBLPromiseAwait([metricsController getAndResetMetrics], nil); - XCTAssertEqualObjects(metricsToRetryUploading, metricsThatFailedToUpload); + // - Assert promise was resolved. + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(offerPromise.isFulfilled); - // TODO(ncooke3): Assert that pending metrics are empty with expected time window. + // - Assert that retrieving the metrics contains the combined metadata from + // the original metrics and the offered metrics. + __auto_type metricsPromise = [metricsController getAndResetMetrics]; + FBLWaitForPromisesWithTimeout(0.5); + XCTAssertEqualObjects([metricsPromise.value collectionStartDate], [NSDate distantPast]); + GDTCOREventMetricsCounter *expectedCombinedCounter = + [originalCounter counterByMergingWithCounter:counterToOffer]; + XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCombinedCounter); } - (void)testIsMetricsCollectionSupportedForTarget { From 9f48059f20141123d34ab23be87342d50c9339ab Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 27 May 2022 14:07:03 -0400 Subject: [PATCH 046/131] [Metrics] Integrate the storage delegate (#66) * Update and integrate storage delegate * Set the storage delegate in the registrar * Fully synthesize delegates * Add unit test for dropping event * Add unit test for removing expired events * Review --- .../Common/TestStorage/GDTCCTTestStorage.m | 2 + .../GDTCORLibrary/GDTCORFlatFileStorage.m | 36 ++++++++++-- .../GDTCORLibrary/GDTCORMetricsController.m | 15 +++-- .../GDTCORLibrary/GDTCORRegistrar.m | 7 ++- .../GDTCORLibrary/Internal/GDTCORRegistrar.h | 12 ++-- .../Internal/GDTCORStorageProtocol.h | 30 ++++++++-- .../Fakes/GDTCORMetricsControllerFake.h | 33 ++++++----- .../Fakes/GDTCORMetricsControllerFake.m | 46 ++++++++------- .../Common/Fakes/GDTCORStorageFake.m | 2 + .../Unit/GDTCORFlatFileStorageTest.m | 56 +++++++++++++++++-- .../GDTCORTests/Unit/GDTCORRegistrarTest.m | 56 ++++++++++++++++++- 11 files changed, 234 insertions(+), 61 deletions(-) diff --git a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m index 6f825525..60b8e859 100644 --- a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m +++ b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m @@ -33,6 +33,8 @@ @implementation GDTCCTTestStorage { NSMutableDictionary *> *_batches; } +@synthesize delegate = _delegate; + - (instancetype)init { self = [super init]; if (self) { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m index 6e326d84..0cb8af43 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m @@ -65,6 +65,7 @@ @interface GDTCORFlatFileStorage () @implementation GDTCORFlatFileStorage @synthesize sizeTracker = _sizeTracker; +@synthesize delegate = _delegate; + (void)load { #if !NDEBUG @@ -157,6 +158,11 @@ - (void)storeEvent:(GDTCOREvent *)event userInfo:@{ NSLocalizedFailureReasonErrorKey : @"Storage size limit has been reached." }]; + if (self.delegate != nil) { + GDTCORLogDebug(@"Delegate notified that event with mapping ID %@ was dropped.", + event.mappingID); + [self.delegate storage:self didDropEvent:event]; + } completion(NO, error); return; } @@ -421,6 +427,7 @@ - (void)checkForExpirations { } // Find expired events and remove them from the storage. + NSMutableSet *expiredEvents = [NSMutableSet set]; NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath]; NSString *path; @@ -440,17 +447,38 @@ - (void)checkForExpirations { NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey]; if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) { NSString *pathToDelete = [eventDataPath stringByAppendingPathComponent:path]; - NSError *error; - [fileManager removeItemAtPath:pathToDelete error:&error]; - if (error != nil) { - GDTCORLogDebug(@"There was an error deleting an expired item: %@", error); + + // Decode the expired event from the path to be deleted. + NSError *decodeError; + GDTCOREvent *event = (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], pathToDelete, + nil, &decodeError); + if (event == nil || decodeError) { + GDTCORLogDebug(@"Error deserializing event while checking for expired events: %@", + decodeError); + event = nil; + } + + // Remove the path to be deleted, adding the decoded event to the + // event set if the removal was successful. + NSError *removeError; + [fileManager removeItemAtPath:pathToDelete error:&removeError]; + if (removeError != nil) { + GDTCORLogDebug(@"There was an error deleting an expired item: %@", removeError); } else { GDTCORLogDebug(@"Item deleted because it expired: %@", pathToDelete); + if (event) { + [expiredEvents addObject:event]; + } } } } } + if (self.delegate != nil && [expiredEvents count] > 0) { + GDTCORLogDebug(@"Delegate notified that %@ events were dropped.", @(expiredEvents.count)); + [self.delegate storage:self didRemoveExpiredEvents:[expiredEvents copy]]; + } + [self.sizeTracker resetCachedSize]; }); } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index d7d6fb45..63527a09 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -194,18 +194,21 @@ - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { return NO; } - NSAssert(NO, @"This code path shouldn't be reached."); + GDTCORLogDebug(@"This code path shouldn't be reached." + @"Invalid target %ld does not support metrics collection", + target); + return NO; } #pragma mark - GDTCORStorageDelegate -- (void)storage:(id)storage - didRemoveExpiredEvent:(GDTCOREvent *)event { - [self logEventsDroppedForReason:GDTCOREventDropReasonMessageTooOld - events:[NSSet setWithObject:event]]; +- (void)storage:(id)storage + didRemoveExpiredEvents:(nonnull NSSet *)events { + [self logEventsDroppedForReason:GDTCOREventDropReasonMessageTooOld events:events]; } -- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { +- (void)storage:(nonnull id)storage + didDropEvent:(nonnull GDTCOREvent *)event { [self logEventsDroppedForReason:GDTCOREventDropReasonStorageFull events:[NSSet setWithObject:event]]; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index 80d9beb4..8590d7b5 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -89,11 +89,11 @@ - (void)registerStorage:(id)storage target:(GDTCORTarget) if (strongSelf) { GDTCORLogDebug(@"Registered storage: %@ for target:%ld", storage, (long)target); strongSelf->_targetToStorage[@(target)] = storage; + [self setMetricsControllerAsStorageDelegateForTarget:target]; } }); } -// TODO(ncooke3): Add `GDTCORRegistar` test for this API. - (void)registerMetricsController:(id)metricsController target:(GDTCORTarget)target { __weak GDTCORRegistrar *weakSelf = self; @@ -103,6 +103,7 @@ - (void)registerMetricsController:(id)metricsCo GDTCORLogDebug(@"Registered metrics controller: %@ for target:%ld", metricsController, (long)target); strongSelf->_targetToMetricsController[@(target)] = metricsController; + [self setMetricsControllerAsStorageDelegateForTarget:target]; } }); } @@ -145,6 +146,10 @@ - (void)registerMetricsController:(id)metricsCo return targetToMetricsController; } +- (void)setMetricsControllerAsStorageDelegateForTarget:(GDTCORTarget)target { + _targetToStorage[@(target)].delegate = _targetToMetricsController[@(target)]; +} + #pragma mark - GDTCORLifecycleProtocol - (void)appWillBackground:(nonnull GDTCORApplication *)app { diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h index 5d75eb6e..d5e50bbe 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h @@ -34,19 +34,23 @@ NS_ASSUME_NONNULL_BEGIN /** Registers a backend implementation with the GoogleDataTransport infrastructure. * - * @param backend The backend object to register. + * @param backend The backend object to register with the given target. * @param target The target this backend object will be responsible for. */ - (void)registerUploader:(id)backend target:(GDTCORTarget)target; /** Registers a storage implementation with the GoogleDataTransport infrastructure. * - * @param storage The storage instance to be associated with this uploader and target. - * @param target The target this backend object will be responsible for. + * @param storage The storage object to register with the given target. + * @param target The target this storage object will be responsible for. */ - (void)registerStorage:(id)storage target:(GDTCORTarget)target; -// TODO(ncooke3): Document. +/** Registers a metrics controller implementation with the GoogleDataTransport infrastructure. + * + * @param metricsController The metrics controller object to register with the given target. + * @param target The target this metrics controller object will be responsible for. + */ - (void)registerMetricsController:(id)metricsController target:(GDTCORTarget)target; diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index 2d00e97c..e1fe51a6 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -27,14 +27,21 @@ @class FBLPromise; +@protocol GDTCORStorageDelegate; + NS_ASSUME_NONNULL_BEGIN typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, NSSet *_Nullable batchEvents); +#pragma mark - GDTCORStorageProtocol + /** Defines the interface a storage subsystem is expected to implement. */ @protocol GDTCORStorageProtocol +/// The object that acts as the delegate of the storage instance. +@property(nonatomic, weak, nullable) id delegate; + @required /** Stores an event and calls onComplete with a non-nil error if anything went wrong. @@ -121,6 +128,8 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, @end +#pragma mark - GDTCORStoragePromiseProtocol + // TODO(ncooke3): Consider complete replacing block based API by promise API. @class GDTCORMetricsMetadata; @@ -173,14 +182,23 @@ FOUNDATION_EXPORT id _Nullable GDTCORStoragePromiseInstanceForTarget( GDTCORTarget target); -// TODO(ncooke3): Document. +#pragma mark - GDTCORStorageDelegate + +/// A type that can be delegated actions from a storage instance. @protocol GDTCORStorageDelegate -// TODO(ncooke3): Document. -- (void)storage:(id)storage - didRemoveExpiredEvent:(GDTCOREvent *)event; -// TODO(ncooke3): Document. -- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event; +/// Tells the delegate that the storage instance has removed a set of expired events. +/// @param storage The storage instance informing the delegate of this impending event. +/// @param events A set of events that were removed from storage due to their expiration. +- (void)storage:(id)storage + didRemoveExpiredEvents:(NSSet *)events; + +/// Tells the delegate that the storage instance has dropped an event due to the event cache being +/// full. +/// @param storage The storage instance informing the delegate of this impending event. +/// @param event An event that was dropped due to the event cache being full. +- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h index 55db8d36..22559399 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 @@ -38,6 +36,11 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, nullable) BOOL (^onTargetSupportsMetricsCollectionHandler)(GDTCORTarget); +@property(nonatomic, copy, nullable) void (^onStorageDidDropEvent)(GDTCOREvent *event); + +@property(nonatomic, copy, nullable) void (^onStorageDidRemoveExpiredEvents) + (NSSet *events); + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m index 246eb09c..9375624b 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h" @@ -61,13 +59,21 @@ - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { } } -- (void)storage:(id)storage - didRemoveExpiredEvent:(GDTCOREvent *)event { - // TODO(ncooke3): Implement. +- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { + if (self.onStorageDidDropEvent) { + return self.onStorageDidDropEvent(event); + } else { + [self doesNotRecognizeSelector:_cmd]; + } } -- (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { - // TODO(ncooke3): Implement. +- (void)storage:(nonnull id)storage + didRemoveExpiredEvents:(nonnull NSSet *)events { + if (self.onStorageDidRemoveExpiredEvents) { + return self.onStorageDidRemoveExpiredEvents(events); + } else { + [self doesNotRecognizeSelector:_cmd]; + } } @end diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m index 9ec89405..98f19ae2 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m @@ -33,6 +33,8 @@ @implementation GDTCORStorageFake { GDTCORMetricsMetadata *_Nullable _storedMetricsMetadata; } +@synthesize delegate = _delegate; + + (instancetype)storageFake { return [[self alloc] init]; } diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index f801e059..62b1d5f7 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -28,6 +28,7 @@ #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCOREventGenerator.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORTestUploader.h" +#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h" #import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORUploadCoordinatorFake.h" #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.h" @@ -759,13 +760,23 @@ - (void)testCheckForExpirations_WhenEventsExpire { [self waitForExpectations:@[ expectation ] timeout:10]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]]; [[GDTCORFlatFileStorage sharedInstance] checkForExpirations]; + + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + [GDTCORFlatFileStorage sharedInstance].delegate = metricsController; + XCTestExpectation *metricsControllerExpectation = + [self expectationWithDescription:@"metricsControllerExpectation"]; + metricsController.onStorageDidRemoveExpiredEvents = ^(NSSet *events) { + XCTAssertTrue(events.count > 0); + [metricsControllerExpectation fulfill]; + }; + expectation = [self expectationWithDescription:@"hasEvent completion called"]; [[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest onComplete:^(BOOL hasEvents) { XCTAssertFalse(hasEvents); [expectation fulfill]; }]; - [self waitForExpectations:@[ expectation ] timeout:10]; + [self waitForExpectations:@[ metricsControllerExpectation, expectation ] timeout:10]; } - (void)testCheckForExpirations_WhenBatchWithNotExpiredEventsExpires { @@ -778,6 +789,17 @@ - (void)testCheckForExpirations_WhenBatchWithNotExpiredEventsExpires { // 0.2. Wait for batch expiration. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:batchExpiresIn]]; + // 0.3. Expect that the storage delegate will not be invoked because no + // events will expire in this test. + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + [GDTCORFlatFileStorage sharedInstance].delegate = metricsController; + XCTestExpectation *metricsControllerExpectation = + [self expectationWithDescription:@"metricsControllerExpectation"]; + metricsControllerExpectation.inverted = YES; + metricsController.onStorageDidRemoveExpiredEvents = ^(NSSet *events) { + [metricsControllerExpectation fulfill]; + }; + // 1. Check for expiration. [[GDTCORFlatFileStorage sharedInstance] checkForExpirations]; @@ -808,7 +830,10 @@ - (void)testCheckForExpirations_WhenBatchWithNotExpiredEventsExpires { XCTAssertEqualObjects(batchEventsIDs, generatedEventsIDs); }]; - [self waitForExpectations:@[ getBatchesExpectation, getEventsExpectation ] timeout:0.5]; + [self waitForExpectations:@[ + metricsControllerExpectation, getBatchesExpectation, getEventsExpectation + ] + timeout:0.5]; } - (void)testCheckForExpirations_WhenBatchWithExpiredEventsExpires { @@ -820,6 +845,17 @@ - (void)testCheckForExpirations_WhenBatchWithExpiredEventsExpires { // 0.2. Wait for batch expiration. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:batchExpiresIn]]; + // 0.3. Expect that the storage delegate will be invoked because events will + // expire in this test. + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + [GDTCORFlatFileStorage sharedInstance].delegate = metricsController; + XCTestExpectation *metricsControllerExpectation = + [self expectationWithDescription:@"metricsControllerExpectation"]; + metricsController.onStorageDidRemoveExpiredEvents = ^(NSSet *events) { + XCTAssertTrue(events.count > 0); + [metricsControllerExpectation fulfill]; + }; + // 1. Check for expiration. [[GDTCORFlatFileStorage sharedInstance] checkForExpirations]; @@ -847,7 +883,10 @@ - (void)testCheckForExpirations_WhenBatchWithExpiredEventsExpires { XCTAssertEqual(batchEvents.count, 0); }]; - [self waitForExpectations:@[ getBatchesExpectation, getEventsExpectation ] timeout:0.5]; + [self waitForExpectations:@[ + metricsControllerExpectation, getBatchesExpectation, getEventsExpectation + ] + timeout:0.5]; } #pragma mark - Remove Batch tests @@ -1136,6 +1175,15 @@ - (void)testStoreEvent_WhenSizeLimitReached_ThenNewEventIsSkipped { mappingID:nil]; event.expirationDate = [NSDate dateWithTimeIntervalSinceNow:1000]; + GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; + storage.delegate = metricsControllerFake; + XCTestExpectation *metricsControllerExpectation = + [self expectationWithDescription:@"metricsControllerExpectation"]; + metricsControllerFake.onStorageDidDropEvent = ^(GDTCOREvent *droppedEvent) { + XCTAssertEqual(droppedEvent, event); + [metricsControllerExpectation fulfill]; + }; + XCTestExpectation *storeExpectation1 = [self expectationWithDescription:@"storeExpectation1"]; [storage storeEvent:event onComplete:^(BOOL wasWritten, NSError *_Nullable error) { @@ -1145,7 +1193,7 @@ - (void)testStoreEvent_WhenSizeLimitReached_ThenNewEventIsSkipped { XCTAssertEqual(error.code, GDTCORFlatFileStorageErrorSizeLimitReached); [storeExpectation1 fulfill]; }]; - [self waitForExpectations:@[ storeExpectation1 ] timeout:5]; + [self waitForExpectations:@[ metricsControllerExpectation, storeExpectation1 ] timeout:5]; // 4. Check the storage size didn't change. XCTAssertEqual([self storageSize], storageSize); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORRegistrarTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORRegistrarTest.m index fdb51dd3..464eee48 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORRegistrarTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORRegistrarTest.m @@ -19,6 +19,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" +#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h" +#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORTestUploader.h" @interface GDTCORRegistrarTest : GDTCORTestCase @@ -47,6 +49,58 @@ - (void)testRegisterUpload { XCTAssertEqual(uploader, registrar.targetToUploader[@(_target)]); } -// TODO(mikehaney24): Add test for registering a storage. +/** Test registering a storage. */ +- (void)testRegisterStorage { + GDTCORRegistrar *registrar = [GDTCORRegistrar sharedInstance]; + GDTCORStorageFake *storage = [[GDTCORStorageFake alloc] init]; + XCTAssertNoThrow([registrar registerStorage:storage target:self.target]); + XCTAssertEqual(storage, registrar.targetToStorage[@(_target)]); +} + +/** Test registering a metrics controller. */ +- (void)testRegisterMetricsController { + GDTCORRegistrar *registrar = [GDTCORRegistrar sharedInstance]; + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + XCTAssertNoThrow([registrar registerMetricsController:metricsController target:self.target]); + XCTAssertEqual(metricsController, registrar.targetToMetricsController[@(_target)]); +} + +/** Tests that the metrics controller is set as the storage delegate when the storage object is + * registered first. Since objects are registered at `+ load` time, the order in which the storage + * and metrics controller objects are registered is non-deterministic. + */ +- (void)testMetricsControllerIsSetAsStorageDelegate_WhenStorageIsRegisteredFirst { + // Given + GDTCORRegistrar *registrar = [GDTCORRegistrar sharedInstance]; + // When + GDTCORStorageFake *storage = [[GDTCORStorageFake alloc] init]; + XCTAssertNoThrow([registrar registerStorage:storage target:self.target]); + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + XCTAssertNoThrow([registrar registerMetricsController:metricsController target:self.target]); + // Then + dispatch_sync(registrar.registrarQueue, ^{ + // Drain queue. + }); + XCTAssertEqual(storage.delegate, metricsController); +} + +/** Tests that the metrics controller is set as the storage delegate when the metrics controller + * object is registered first. Since objects are registered at `+ load` time, the order in which the + * storage and metrics controller objects are registered is non-deterministic. + */ +- (void)testMetricsControllerIsSetAsStorageDelegate_WhenMetricsControllerIsRegisteredFirst { + // Given + GDTCORRegistrar *registrar = [GDTCORRegistrar sharedInstance]; + // When + GDTCORMetricsControllerFake *metricsController = [[GDTCORMetricsControllerFake alloc] init]; + XCTAssertNoThrow([registrar registerMetricsController:metricsController target:self.target]); + GDTCORStorageFake *storage = [[GDTCORStorageFake alloc] init]; + XCTAssertNoThrow([registrar registerStorage:storage target:self.target]); + // Then + dispatch_sync(registrar.registrarQueue, ^{ + // Drain queue. + }); + XCTAssertEqual(storage.delegate, metricsController); +} @end From 16a150ba6774f41b716ac7a6b2f86791a3d05aba Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 27 May 2022 20:00:43 -0400 Subject: [PATCH 047/131] [Metrics] Implement `NSSecureCoding` and add unit tests for `GDTCOREventMetricsCounter` (#67) * Add unit tests for metrics counter * Resolve TODO * [skip ci] Resolve comment TODO --- .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 22 +- .../Private/GDTCORRegistrar_Private.h | 2 +- .../Unit/GDTCOREventMetricsCounterTest.m | 294 ++++++++++++++++++ 3 files changed, 309 insertions(+), 9 deletions(-) create mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 600498f7..3c825165 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -18,6 +18,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +static NSString *const kDroppedEventCounterByMappingID = @"droppedEventCounterByMappingID"; + typedef NSDictionary GDTCORDroppedEventCounter; @interface GDTCOREventMetricsCounter () @@ -40,8 +42,7 @@ + (instancetype)counterWithEvents:(NSArray *)events NSMutableDictionary *eventCounterByMappingID = [NSMutableDictionary dictionary]; - for (GDTCOREvent *event in events) { - // TODO(ncooke3): Should I use an autorelease pool? + for (GDTCOREvent *event in [events copy]) { // Dropped events with a `nil` or empty mapping ID (log source) are not recorded. if (event.mappingID.length == 0) { continue; @@ -104,7 +105,6 @@ - (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCo + (NSDictionary *)dictionaryByMergingDictionary:(NSDictionary *)dictionary withOtherDictionary:(NSDictionary *)otherDictionary uniquingKeysWithBlock:(id (^)(id value1, id value2))block { - // TODO(ncooke3): Optimize so larger dictionary is passed in below. NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary]; [otherDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { @@ -154,13 +154,19 @@ + (BOOL)supportsSecureCoding { return YES; } -- (void)encodeWithCoder:(nonnull NSCoder *)coder { - // TODO(ncooke3): Implement +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + self = [super init]; + if (self) { + _droppedEventCounterByMappingID = + [coder decodeObjectOfClasses: + [NSSet setWithArray:@[ NSDictionary.class, NSString.class, NSNumber.class ]] + forKey:kDroppedEventCounterByMappingID]; + } + return self; } -- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - // TODO(ncooke3): Implement - return nil; +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeObject:self.droppedEventCounterByMappingID forKey:kDroppedEventCounterByMappingID]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h index c75e3ed7..9f455487 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h @@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN @property(atomic, readonly) NSMutableDictionary> *targetToStorage; -// TODO(ncooke3): Document. +/** A map of targets to metrics controller instances. */ @property(atomic, readonly) NSMutableDictionary> *targetToMetricsController; diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m new file mode 100644 index 00000000..34b0705b --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m @@ -0,0 +1,294 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +typedef NSDictionary GDTCORDroppedEventCounter; + +@interface GDTCOREventMetricsCounter (Internal) + +/// Designated initializer exposed for testing. +- (instancetype)initWithDroppedEventCounterByMappingID: + (NSDictionary *)droppedEventCounterByMappingID; + +@end + +@interface GDTCOREventMetricsCounterTest : GDTCORTestCase +@end + +@implementation GDTCOREventMetricsCounterTest + +- (void)testCounterFactoryCreatesAndReturnsCounter { + // Given + GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; + GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; + // Then + XCTAssertFalse(eventMetricsCounter1 == eventMetricsCounter2); +} + +- (void)testCounterFactoryReturnsEmptyCounter { + // Given + GDTCOREventMetricsCounter *eventMetricsCounter = [GDTCOREventMetricsCounter counter]; + // Then + GDTCOREventMetricsCounter *expectedEventMetricsCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{}]; + XCTAssertEqualObjects(eventMetricsCounter, expectedEventMetricsCounter); +} + +- (void)testCounterWithEvents_WhenEventsAreEmpty_CreatesEmptyCounter { + // Given + GDTCOREventMetricsCounter *eventMetricsCounter = + [GDTCOREventMetricsCounter counterWithEvents:@[] + droppedForReason:GDTCOREventDropReasonUnknown]; + // Then + XCTAssertEqualObjects(eventMetricsCounter, [GDTCOREventMetricsCounter counter]); +} + +- (void)testCounterWithEvents { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest] + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter2 = + [GDTCOREventMetricsCounter counterWithEvents:events2 + droppedForReason:GDTCOREventDropReasonServerError]; + + // When + GDTCOREventMetricsCounter *mergedEventMetricsCounter = + [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; + // - Assert that merging the above counters in other ways result in same counter. + [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; + + // Then + GDTCOREventMetricsCounter *expectedEventMetricsCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_2" : @{ + @(GDTCOREventDropReasonStorageFull) : @(1), + @(GDTCOREventDropReasonServerError) : @(1), + }, + }]; + XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); +} + +- (void)testMergingCounters_WhenBothCountersAreEmpty_ReturnsEmptyCounter { + // Given + GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; + GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; + // Then + [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; +} + +- (void)testMergingCounters_WhenGivenCounterIsEmpty_ReturnsReceivingCounter { + // Given + GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"log_src" target:kGDTCORTargetFLL]; + GDTCOREventMetricsCounter *eventMetricsCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:@[ event ] + droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; + // When + GDTCOREventMetricsCounter *mergedCounter = + [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; + [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; + // Then + XCTAssertEqualObjects(mergedCounter, eventMetricsCounter1); + XCTAssertFalse(mergedCounter == eventMetricsCounter1); +} + +- (void)testMergingCounters_WhenCounterKeysDontCollide { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_4" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_5" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_6" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter2 = + [GDTCOREventMetricsCounter counterWithEvents:events2 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + GDTCOREventMetricsCounter *mergedEventMetricsCounter = + [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; + // - Assert that merging the above counters in other ways result in same counter. + // [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; + + // Then + // - Expect no collisions for both the mapping IDs and drop reasons. + GDTCOREventMetricsCounter *expectedEventMetricsCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_4" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_5" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_6" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + }]; + XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); +} + +- (void)testMergingCounters_WhenCounterKeysCollide { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter2 = + [GDTCOREventMetricsCounter counterWithEvents:events2 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + GDTCOREventMetricsCounter *mergedEventMetricsCounter = + [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; + // - Assert that merging the above counters in other ways result in same counter. + [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; + + // Then + // - Expect resolved collisions for both the mapping IDs and drop reasons. + GDTCOREventMetricsCounter *expectedEventMetricsCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + }]; + XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); +} + +- (void)testEqualityEdgeCases { + GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; + GDTCOREventMetricsCounter *eventMetricsCounter2 = eventMetricsCounter1; + XCTAssert([eventMetricsCounter1 isEqual:eventMetricsCounter2]); + XCTAssertFalse([eventMetricsCounter1 isEqual:@"some string"]); + XCTAssertFalse([eventMetricsCounter1 isEqual:nil]); +} + +- (void)testEqualObjectsHaveSameHash { + // Given + GDTCOREventMetricsCounter *eventMetricsCounter1 = + [GDTCOREventMetricsCounter counterWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCOREventMetricsCounter *eventMetricsCounter2 = + [GDTCOREventMetricsCounter counterWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCOREventMetricsCounter *eventMetricsCounter3 = + [GDTCOREventMetricsCounter counterWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // Then + XCTAssertEqualObjects(eventMetricsCounter1, eventMetricsCounter2); + XCTAssertEqual(eventMetricsCounter1.hash, eventMetricsCounter2.hash); + XCTAssertNotEqualObjects(eventMetricsCounter2, eventMetricsCounter3); + XCTAssertNotEqual(eventMetricsCounter2.hash, eventMetricsCounter3.hash); +} + +- (void)testSecureCoding { + // Given + NSArray *events = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter = + [GDTCOREventMetricsCounter counterWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + // - Encode the counter. + NSError *encodeError; + NSData *encodedCounter = GDTCOREncodeArchive(eventMetricsCounter, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedCounter); + + // - Write it to disk. + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"counter.dat"]; + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedCounter, filePath, &writeError); + XCTAssertNil(writeError); + XCTAssertTrue(writeResult); + + // Then + // - Decode the counter from disk. + NSError *decodeError; + GDTCOREventMetricsCounter *decodedCounter = (GDTCOREventMetricsCounter *)GDTCORDecodeArchive( + GDTCOREventMetricsCounter.class, filePath, nil, &decodeError); + XCTAssertNil(decodeError); + XCTAssertNotNil(decodedCounter); + XCTAssertEqualObjects(decodedCounter, eventMetricsCounter); +} + +- (void)assertMergedCounterFromCounter1:(GDTCOREventMetricsCounter *)counter1 + counter2:(GDTCOREventMetricsCounter *)counter2 { + // There are three ways to merge the given counters, and all three ways + // should result in the same merged counter. + GDTCOREventMetricsCounter *mergedCounter1 = [counter1 counterByMergingWithCounter:counter2]; + + GDTCOREventMetricsCounter *mergedCounter2 = [counter2 counterByMergingWithCounter:counter1]; + + GDTCOREventMetricsCounter *mergedCounter3 = [[GDTCOREventMetricsCounter.counter + counterByMergingWithCounter:counter1] counterByMergingWithCounter:counter2]; + + XCTAssertEqualObjects(mergedCounter1, mergedCounter2); + XCTAssertEqualObjects(mergedCounter2, mergedCounter3); +} + +@end From ad6f00593b0f011242423c879dcd295d11edea9d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sat, 28 May 2022 21:27:08 -0400 Subject: [PATCH 048/131] Implement NSCoding and add unit tests (#68) --- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 49 +++++++- .../Private/GDTCORMetricsMetadata.h | 8 +- .../Unit/GDTCORMetricsMetadataTest.m | 110 ++++++++++++++++++ 3 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index d19f65d8..1d5678d7 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -16,6 +16,11 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" + +static NSString *const kCollectionStartDate = @"collectionStartDate"; +static NSString *const kDroppedEventCounter = @"droppedEventCounter"; + @implementation GDTCORMetricsMetadata + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate @@ -33,19 +38,53 @@ - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate return self; } +#pragma mark - Equality + +- (BOOL)isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)otherMetricsMetadata { + return [self.collectionStartDate isEqualToDate:otherMetricsMetadata.collectionStartDate] && + [self.droppedEventCounter + isEqualToDroppedEventCounter:otherMetricsMetadata.droppedEventCounter]; +} + +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } + + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[self class]]) { + return NO; + } + + return [self isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)object]; +} + +- (NSUInteger)hash { + return [self.collectionStartDate hash] ^ [self.droppedEventCounter hash]; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } -- (void)encodeWithCoder:(nonnull NSCoder *)coder { - // TODO(ncooke3): Implement +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + self = [super init]; + if (self) { + _collectionStartDate = [coder decodeObjectOfClass:[NSDate class] forKey:kCollectionStartDate]; + _droppedEventCounter = [coder decodeObjectOfClass:[GDTCOREventMetricsCounter class] + forKey:kDroppedEventCounter]; + } + return self; } -- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - // TODO(ncooke3): Implement - return nil; +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeObject:self.collectionStartDate forKey:kCollectionStartDate]; + [coder encodeObject:self.droppedEventCounter forKey:kDroppedEventCounter]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h index ca00dcd0..0a42b2a5 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, readonly) NSDate *collectionStartDate; /// The dropped event counter associated with the metrics. -@property(nonatomic, copy, readonly, nullable) GDTCOREventMetricsCounter *droppedEventCounter; +@property(nonatomic, copy, readonly) GDTCOREventMetricsCounter *droppedEventCounter; /// Creates a metrics metadata object with the provided information. /// @param collectedSinceDate The start of the time window over which the metrics were collected. @@ -39,6 +39,12 @@ NS_ASSUME_NONNULL_BEGIN /// This API is unavailable. - (instancetype)init NS_UNAVAILABLE; +/// Returns a Boolean value that indicates whether the receiving metrics metadata is equal to +/// the given metrics metadata. +/// @param otherMetricsMetadata The metrics metadata with which to compare the +/// receiving metrics metadata. +- (BOOL)isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)otherMetricsMetadata; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m new file mode 100644 index 00000000..00e6bf70 --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m @@ -0,0 +1,110 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +@interface GDTCORMetricsMetadataTest : XCTestCase + +@end + +@implementation GDTCORMetricsMetadataTest + +- (void)testEqualityEdgeCases { + GDTCORMetricsMetadata *metricsMetadata1 = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + GDTCORMetricsMetadata *metricsMetadata2 = metricsMetadata1; + XCTAssert([metricsMetadata1 isEqual:metricsMetadata2]); + XCTAssertFalse([metricsMetadata1 isEqual:@"some string"]); + XCTAssertFalse([metricsMetadata1 isEqual:nil]); +} + +- (void)testEqualObjectsHaveSameHash { + // Given + NSArray *events = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter = + [GDTCOREventMetricsCounter counterWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCORMetricsMetadata *metricsMetadata1 = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] + eventMetricsCounter:eventMetricsCounter]; + + GDTCORMetricsMetadata *metricsMetadata2 = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] + eventMetricsCounter:eventMetricsCounter]; + + GDTCORMetricsMetadata *metricsMetadata3 = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + // Then + XCTAssertEqualObjects(metricsMetadata1, metricsMetadata2); + XCTAssertEqual(metricsMetadata1.hash, metricsMetadata2.hash); + XCTAssertNotEqualObjects(metricsMetadata2, metricsMetadata3); + XCTAssertNotEqual(metricsMetadata2.hash, metricsMetadata3.hash); +} + +- (void)testSecureCoding { + // Given + NSArray *events = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCOREventMetricsCounter *eventMetricsCounter = + [GDTCOREventMetricsCounter counterWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:eventMetricsCounter]; + + // When + // - Encode the metrics metadata. + NSError *encodeError; + NSData *encodedMetricsMetadata = GDTCOREncodeArchive(metricsMetadata, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedMetricsMetadata); + + // - Write it to disk. + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"metadata.dat"]; + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedMetricsMetadata, filePath, &writeError); + XCTAssertNil(writeError); + XCTAssertTrue(writeResult); + + // Then + // - Decode the metrics metadata from disk. + NSError *decodeError; + GDTCORMetricsMetadata *decodedMetricsMetadata = (GDTCORMetricsMetadata *)GDTCORDecodeArchive( + GDTCORMetricsMetadata.class, filePath, nil, &decodeError); + XCTAssertNil(decodeError); + XCTAssertNotNil(decodedMetricsMetadata); + XCTAssertEqualObjects(decodedMetricsMetadata, metricsMetadata); +} + +@end From 6e59d06166f7d3f8a3e4d8e80d32a3cc81f512f3 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 31 May 2022 11:23:07 -0400 Subject: [PATCH 049/131] [Metrics] Implement metrics storage and add unit tests (#69) * Implement metrics storage and add unit tests * Fix warning --- .../Common/TestStorage/GDTCCTTestStorage.m | 6 +- .../GDTCORFlatFileStorage+Promises.m | 55 +++++++-- .../GDTCORLibrary/GDTCORFlatFileStorage.m | 6 +- .../GDTCORLibrary/GDTCORMetricsController.m | 8 +- .../GDTCORLibrary/GDTCORPlatform.m | 32 ++++-- .../GDTCORLibrary/Internal/GDTCORPlatform.h | 27 +++-- .../Internal/GDTCORStorageProtocol.h | 11 +- .../Common/Fakes/GDTCORStorageFake.m | 6 +- .../GDTCORTests/Unit/GDTCORClockTest.m | 2 +- .../Unit/GDTCOREventMetricsCounterTest.m | 5 +- .../GDTCORTests/Unit/GDTCOREventTest.m | 2 +- .../Unit/GDTCORFlatFileStorageTest.m | 104 ++++++++++++++++++ .../Unit/GDTCORMetricsMetadataTest.m | 5 +- 13 files changed, 217 insertions(+), 52 deletions(-) diff --git a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m index 60b8e859..2bf56031 100644 --- a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m +++ b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m @@ -209,9 +209,9 @@ - (void)defaultBatchWithEventSelector:(nonnull GDTCORStorageEventSelector *)even }]; } -- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: - (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, - NSError *_Nullable fetchError))handler { +- (FBLPromise *)fetchAndUpdateMetricsWithHandler: + (GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler { // TODO(ncooke3): Implement. return [FBLPromise resolvedWith:nil]; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m index 031602a1..2f018b2c 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m @@ -22,6 +22,10 @@ #import "FBLPromises.h" #endif +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h" @implementation GDTCORFlatFileStorage (Promises) @@ -94,16 +98,53 @@ @implementation GDTCORFlatFileStorage (Promises) }]; } -- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: - (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, - NSError *_Nullable fetchError))handler { - // TODO(ncooke3): Implement. - return [FBLPromise resolvedWith:nil]; +- (FBLPromise *)fetchAndUpdateMetricsWithHandler: + (GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler { + return FBLPromise.doOn(self.storageQueue, ^id { + // Fetch the stored metrics metadata. + NSError *decodeError; + NSString *metricsMetadataPath = + [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:@"metrics_metadata"]; + GDTCORMetricsMetadata *decodedMetadata = (GDTCORMetricsMetadata *)GDTCORDecodeArchiveAtPath( + GDTCORMetricsMetadata.class, metricsMetadataPath, &decodeError); + + // Update the metadata using the retrieved metadata. + GDTCORMetricsMetadata *updatedMetadata = handler(decodedMetadata, decodeError); + if (updatedMetadata == nil) { + // `nil` metadata is not expected and will be a no-op. + return nil; + } + + if (![updatedMetadata isEqual:decodedMetadata]) { + // The metadata was updated so it needs to be saved. + // - Encode the updated metadata. + NSError *encodeError; + NSData *encodedMetadata = GDTCOREncodeArchive(updatedMetadata, nil, &encodeError); + if (encodeError) { + return encodeError; + } + + // - Write the encoded metadata to disk. + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedMetadata, metricsMetadataPath, &writeError); + if (writeResult == NO || writeError) { + return writeError; + } + } + + return nil; + }); } - (FBLPromise *)fetchStorageMetadata { - // TODO(ncooke3): Implement. - return [FBLPromise resolvedWith:nil]; + return FBLPromise.asyncOn(self.storageQueue, ^(FBLPromiseFulfillBlock _Nonnull fulfill, + FBLPromiseRejectBlock _Nonnull reject) { + [self storageSizeWithCallback:^(GDTCORStorageSizeBytes storageSize) { + fulfill([GDTCORStorageMetadata metadataWithCurrentCacheSize:storageSize + maxCacheSize:kGDTCORFlatFileStorageSizeLimit]); + }]; + }); } // TODO: Move to a separate class/extension when needed in more places. diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m index 0cb8af43..22e84670 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m @@ -206,7 +206,7 @@ - (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelect for (NSString *eventPath in paths) { NSError *error; GDTCOREvent *event = - (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], eventPath, nil, &error); + (GDTCOREvent *)GDTCORDecodeArchiveAtPath([GDTCOREvent class], eventPath, &error); if (event == nil || error) { GDTCORLogDebug(@"Error deserializing event: %@", error); [[NSFileManager defaultManager] removeItemAtPath:eventPath error:nil]; @@ -450,8 +450,8 @@ - (void)checkForExpirations { // Decode the expired event from the path to be deleted. NSError *decodeError; - GDTCOREvent *event = (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], pathToDelete, - nil, &decodeError); + GDTCOREvent *event = (GDTCOREvent *)GDTCORDecodeArchiveAtPath([GDTCOREvent class], + pathToDelete, &decodeError); if (event == nil || decodeError) { GDTCORLogDebug(@"Error deserializing event while checking for expired events: %@", decodeError); diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index 63527a09..d46209f4 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -93,7 +93,7 @@ - (instancetype)initWithStorage:(id)storage { } }; - return [_storage fetchAndUpdateClientMetricsWithHandler:handler]; + return [_storage fetchAndUpdateMetricsWithHandler:handler]; } - (nonnull FBLPromise *)getAndResetMetrics { @@ -111,7 +111,7 @@ - (instancetype)initWithStorage:(id)storage { eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; }; - return [_storage fetchAndUpdateClientMetricsWithHandler:handler] + return [_storage fetchAndUpdateMetricsWithHandler:handler] .validate(^BOOL(NSNull *__unused _) { // Break and reject the promise chain when storage contains no metrics // metadata. @@ -178,7 +178,7 @@ - (instancetype)initWithStorage:(id)storage { } }; - return [_storage fetchAndUpdateClientMetricsWithHandler:handler]; + return [_storage fetchAndUpdateMetricsWithHandler:handler]; } - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { @@ -196,7 +196,7 @@ - (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { GDTCORLogDebug(@"This code path shouldn't be reached." @"Invalid target %ld does not support metrics collection", - target); + (long)target); return NO; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index a6207ec2..a82fd511 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -182,6 +182,8 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() { NSError *_Nullable *error) { BOOL result = NO; if (filePath.length > 0) { + // TODO(ncooke3): For future cleanup– this API shouldn't touch the file + // system unless it successfully encoded the given object. result = [[NSFileManager defaultManager] createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] withIntermediateDirectories:YES @@ -238,25 +240,31 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() { return resultData; } +id _Nullable GDTCORDecodeArchiveAtPath(Class archiveClass, + NSString *_Nonnull archivePath, + NSError **_Nonnull error) { + NSData *data = [NSData dataWithContentsOfFile:archivePath options:0 error:error]; + if (data == nil) { + // Reading the file failed and `error` will be populated. + return nil; + } + + return GDTCORDecodeArchive(archiveClass, data, error); +} + id _Nullable GDTCORDecodeArchive(Class archiveClass, - NSString *_Nullable archivePath, - NSData *_Nullable archiveData, - NSError *_Nullable *error) { + NSData *_Nonnull archiveData, + NSError **_Nonnull error) { id unarchivedObject = nil; if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) { - NSData *data = archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath]; - if (data) { - unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass - fromData:data - error:error]; - } + unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass + fromData:archiveData + error:error]; } else { @try { - NSData *archivedData = - archiveData ? archiveData : [NSData dataWithContentsOfFile:archivePath]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData]; + unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; #pragma clang diagnostic pop } @catch (NSException *exception) { NSString *errorString = diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h index 44252ab6..d7ee32ba 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h @@ -143,18 +143,23 @@ NSData *_Nullable GDTCOREncodeArchive(id obj, NSString *_Nullable filePath, NSError *_Nullable *error); -/** Decodes an object of the given class from the given archive path or data and populates the given - * error if it fails. - * - * @param archiveClass The class of the archive's root object. - * @param archivePath The path to the archived data. Don't use with the archiveData param. - * @param archiveData The data to decode. Don't use with the archivePath param. - * @param error The error to populate if something goes wrong. - */ +/// Decodes an object of the given class from the given archive path and populates the given error +/// if it fails. +/// @param archiveClass The class of the archive's root object. +/// @param archivePath The path to the archived data. +/// @param error The error to populate if something goes wrong. +id _Nullable GDTCORDecodeArchiveAtPath(Class archiveClass, + NSString *_Nonnull archivePath, + NSError **_Nonnull error); + +/// Decodes an object of the given class from the given data and populates the given error if it +/// fails. +/// @param archiveClass The class of the archive's root object. +/// @param archiveData The data to decode. +/// @param error The error to populate if something goes wrong. id _Nullable GDTCORDecodeArchive(Class archiveClass, - NSString *_Nullable archivePath, - NSData *_Nullable archiveData, - NSError *_Nullable *error); + NSData *_Nonnull archiveData, + NSError **_Nonnull error); /** Writes the provided data to a file at the provided path. Intermediate directories will be * created as needed. diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index e1fe51a6..132c4d4d 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -149,9 +149,14 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, - (FBLPromise *)removeAllBatchesForTarget:(GDTCORTarget)target deleteEvents:(BOOL)deleteEvents; -- (FBLPromise *)fetchAndUpdateClientMetricsWithHandler: - (GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, - NSError *_Nullable fetchError))handler; +/// Fetches metrics metadata from storage, passes them to the given handler, and writes the +/// resulting metrics metadata from the given handler to storage. +/// @note This API is thread-safe. +/// @param handler A handler to process the fetch result and return an updated value to store. +/// @return A promise that is fulfilled if the update is successful, and rejected otherwise. +- (FBLPromise *)fetchAndUpdateMetricsWithHandler: + (GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, + NSError *_Nullable fetchError))handler; - (FBLPromise *)fetchStorageMetadata; diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m index 98f19ae2..23567c78 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m @@ -116,9 +116,9 @@ - (void)checkForExpirations { return [FBLPromise resolvedWith:nil]; } -- (nonnull FBLPromise *)fetchAndUpdateClientMetricsWithHandler: - (nonnull GDTCORMetricsMetadata *_Nullable (^)(GDTCORMetricsMetadata *_Nullable, - NSError *_Nullable))handler { +- (nonnull FBLPromise *)fetchAndUpdateMetricsWithHandler: + (nonnull GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable, + NSError *_Nullable))handler { if (_storedMetricsMetadata != nil) { _storedMetricsMetadata = handler(_storedMetricsMetadata, nil); } else { diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORClockTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORClockTest.m index 7cdd4ce2..26eb8a67 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORClockTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORClockTest.m @@ -59,7 +59,7 @@ - (void)testEncoding { error = nil; GDTCORClock *unarchivedClock = - (GDTCORClock *)GDTCORDecodeArchive([GDTCORClock class], nil, clockData, &error); + (GDTCORClock *)GDTCORDecodeArchive([GDTCORClock class], clockData, &error); XCTAssertNil(error); XCTAssertNotNil(unarchivedClock); XCTAssertEqual([clock hash], [unarchivedClock hash]); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m index 34b0705b..76e1c3d9 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m @@ -269,8 +269,9 @@ - (void)testSecureCoding { // Then // - Decode the counter from disk. NSError *decodeError; - GDTCOREventMetricsCounter *decodedCounter = (GDTCOREventMetricsCounter *)GDTCORDecodeArchive( - GDTCOREventMetricsCounter.class, filePath, nil, &decodeError); + GDTCOREventMetricsCounter *decodedCounter = + (GDTCOREventMetricsCounter *)GDTCORDecodeArchiveAtPath(GDTCOREventMetricsCounter.class, + filePath, &decodeError); XCTAssertNil(decodeError); XCTAssertNotNil(decodedCounter); XCTAssertEqualObjects(decodedCounter, eventMetricsCounter); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m index f01b1749..77a39089 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m @@ -61,7 +61,7 @@ - (void)testArchiving { event = nil; error = nil; GDTCOREvent *decodedEvent = - (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], nil, archiveData, &error); + (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], archiveData, &error); XCTAssertNil(error); XCTAssertNotNil(decodedEvent); XCTAssertEqualObjects(decodedEvent.mappingID, @"testID"); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index 62b1d5f7..276c6f71 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -16,7 +16,12 @@ #import "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" +#import "FBLPromise+Testing.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" @@ -293,6 +298,105 @@ - (void)testStoringEventsDuringTerminate { self.continueAfterFailure = originalValueOfContinueAfterFailure; } +- (void)testFetchAndUpdateMetrics { + // Given + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + + __auto_type fetchAndUpdatePromise1 = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNil(fetchedMetadata); + XCTAssertNotNil(fetchError); + return metricsMetadata; + }]; + + // When + __auto_type fetchAndUpdatePromise2 = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNotNil(fetchedMetadata); + XCTAssertNil(fetchError); + XCTAssertEqualObjects(fetchedMetadata, metricsMetadata); + }]; + + // Then + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(fetchAndUpdatePromise1.isFulfilled); + XCTAssert(fetchAndUpdatePromise2.isFulfilled); +} + +- (void)testFetchAndUpdateMetrics_WhenDecodeError { + // When + __auto_type fetchAndUpdatePromise = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNil(fetchedMetadata); + XCTAssertNotNil(fetchError); + XCTAssertEqualObjects(fetchError.localizedFailureReason, @"The file doesn’t exist."); + }]; + + // Then + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(fetchAndUpdatePromise.isFulfilled); +} + +- (void)testFetchAndUpdateMetrics_WhenUpdatedMetadataIsUnexpected_DoesNothing { + // Given + GDTCORMetricsMetadata *metricsMetadata = + [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + + __auto_type fetchAndUpdatePromise1 = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNil(fetchedMetadata); + XCTAssertNotNil(fetchError); + return metricsMetadata; + }]; + + // When + __auto_type fetchAndUpdatePromise2 = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNotNil(fetchedMetadata); + XCTAssertNil(fetchError); + return nil; + }]; + + // Then + __auto_type fetchAndUpdatePromise3 = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNotNil(fetchedMetadata); + XCTAssertNil(fetchError); + XCTAssertEqualObjects(fetchedMetadata, metricsMetadata); + }]; + + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(fetchAndUpdatePromise1.isFulfilled); + XCTAssert(fetchAndUpdatePromise2.isFulfilled); + XCTAssert(fetchAndUpdatePromise3.isFulfilled); +} + +- (void)testFetchAndUpdateMetrics_WhenUpdateFails_RejectsPromise { + // When + __auto_type fetchAndUpdatePromise = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNil(fetchedMetadata); + XCTAssertNotNil(fetchError); + // Return an object that doesn't conform to `NSCoding` so the update + // will fail because the "GDTCORMetricsMetadata" cannot be encoded. + return (GDTCORMetricsMetadata *)[[NSObject alloc] init]; + }]; + + // Then + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(fetchAndUpdatePromise.isRejected); +} + - (void)testSaveAndLoadLibraryData { __weak NSData *weakData; NSString *dataKey = NSStringFromSelector(_cmd); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m index 00e6bf70..d5a2dc41 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m @@ -100,8 +100,9 @@ - (void)testSecureCoding { // Then // - Decode the metrics metadata from disk. NSError *decodeError; - GDTCORMetricsMetadata *decodedMetricsMetadata = (GDTCORMetricsMetadata *)GDTCORDecodeArchive( - GDTCORMetricsMetadata.class, filePath, nil, &decodeError); + GDTCORMetricsMetadata *decodedMetricsMetadata = + (GDTCORMetricsMetadata *)GDTCORDecodeArchiveAtPath(GDTCORMetricsMetadata.class, filePath, + &decodeError); XCTAssertNil(decodeError); XCTAssertNotNil(decodedMetricsMetadata); XCTAssertEqualObjects(decodedMetricsMetadata, metricsMetadata); From 853918eeeb0de4217d7e8a1361905d8889a8b8d3 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 31 May 2022 17:23:50 -0400 Subject: [PATCH 050/131] [Metrics] Implement `GDTCOREventDataObject` conformance for `GDTCORMetrics`. (#70) * [Metrics] Implement GDTCOREventDataObject conformance for GDTCORMetrics. * Fix warning * Fix regression from previous PR * Fix proto conversion * [skip ci] Review --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 1 + .../GDTCORMetrics+GDTCCTSupport.m | 194 ++++++++++++++++++ .../Private/GDTCORMetrics+GDTCCTSupport.h | 25 +++ .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 11 - .../GDTCORLibrary/GDTCORMetrics.m | 19 -- .../GDTCORLibrary/GDTCORRegistrar.m | 2 +- .../Internal/GDTCOREventDropReason.h | 31 ++- .../Private/GDTCOREventMetricsCounter.h | 7 + .../GDTCORLibrary/Private/GDTCORMetrics.h | 16 +- .../Unit/GDTCORFlatFileStorageTest.m | 5 +- 10 files changed, 262 insertions(+), 49 deletions(-) create mode 100644 GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m create mode 100644 GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 8629badc..0b561dde 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -38,6 +38,7 @@ #import #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m new file mode 100644 index 00000000..5c67bcd5 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m @@ -0,0 +1,194 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" + +#import +#import +#import + +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" + +@implementation GDTCORMetrics (GDTCCTSupport) + +- (NSData *)transportBytes { + // Create and populate proto. + gdt_client_metrics_ClientMetrics clientMetricsProto = + gdt_client_metrics_ClientMetrics_init_default; + + clientMetricsProto.window = + GDTCCTConstructTimeWindow(self.collectionStartDate, self.collectionEndDate); + + clientMetricsProto.log_source_metrics = GDTCCTConstructLogSourceMetrics(self.droppedEventCounter); + clientMetricsProto.log_source_metrics_count = + GDTCCTGetLogSourceMetricsCount(self.droppedEventCounter); + + clientMetricsProto.global_metrics = + GDTCCTConstructGlobalMetrics(self.currentCacheSize, self.maxCacheSize); + + clientMetricsProto.app_namespace = GDTCCTEncodeString(self.bundleID); + + // Encode proto into a data buffer. + pb_ostream_t sizeStream = PB_OSTREAM_SIZING; + + // - Encode 1 time to determine the expected size of the buffer. + if (!pb_encode(&sizeStream, gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto)) { + GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s", + PB_GET_ERROR(&sizeStream)); + } + + // - Encode a 2nd time to actually copy the proto's bytes into the buffer. + size_t bufferSize = sizeStream.bytes_written; + CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize); + CFDataSetLength(dataRef, bufferSize); + pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize); + if (!pb_encode(&ostream, gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto)) { + GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s", + PB_GET_ERROR(&ostream)); + } + CFDataSetLength(dataRef, ostream.bytes_written); + + // Release the allocated proto. + pb_release(gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto); + + return CFBridgingRelease(dataRef); +} + +/// Constructs and returns a ``gdt_client_metrics_LogSourceMetrics`` from the given dropped event +/// counter. +/// @param droppedEventCounter The given dropped event counter. +gdt_client_metrics_LogSourceMetrics *GDTCCTConstructLogSourceMetrics( + GDTCOREventMetricsCounter *droppedEventCounter) { + // The metrics proto is a repeating field where each element represents the + // dropped event data for a log source (mapping ID). + NSUInteger logMetricsCount = [droppedEventCounter.droppedEventCounterByMappingID count]; + gdt_client_metrics_LogSourceMetrics *repeatedLogSourceMetrics = + calloc(logMetricsCount, sizeof(gdt_client_metrics_LogSourceMetrics)); + + // Each log source (mapping ID) has a corresponding dropped event counter. + // Enumerate over the dictionary of mapping IDs and, for each mappingID, + // create a proto representation of the number of events dropped for each + // given reason. + __block NSUInteger logSourceIndex = 0; + [droppedEventCounter.droppedEventCounterByMappingID + enumerateKeysAndObjectsUsingBlock:^(NSString *mappingID, + GDTCORDroppedEventCounter *eventCounterForMappingID, + BOOL *__unused _) { + // Create the log source proto for the given mapping ID. It contains a + // repeating field to encapuslate the number of events dropped for each + // given drop reason. + __block gdt_client_metrics_LogSourceMetrics logSourceMetrics = + gdt_client_metrics_LogSourceMetrics_init_zero; + logSourceMetrics.log_source = GDTCCTEncodeString(mappingID); + logSourceMetrics.log_event_dropped_count = (pb_size_t)eventCounterForMappingID.count; + logSourceMetrics.log_event_dropped = + calloc(eventCounterForMappingID.count, sizeof(gdt_client_metrics_LogEventDropped)); + + // Each dropped event counter counts the number of events dropped for + // each drop reason. Enumerate over all of these counters to populate + // the log source proto's repeating field of event drop data. + __block NSUInteger eventCounterIndex = 0; + [eventCounterForMappingID + enumerateKeysAndObjectsUsingBlock:^(NSNumber *eventDropReason, + NSNumber *droppedEventCount, BOOL *__unused _) { + gdt_client_metrics_LogEventDropped droppedEvents = + gdt_client_metrics_LogEventDropped_init_zero; + droppedEvents.events_dropped_count = droppedEventCount.integerValue; + droppedEvents.reason = + GDTCCTConvertEventDropReasonToProtoReason(eventDropReason.integerValue); + + // Append the dropped events proto to the repeated field and + // increment the index used for appending. + logSourceMetrics.log_event_dropped[eventCounterIndex] = droppedEvents; + eventCounterIndex += 1; + }]; + + // Append the metrics for the given log source (mappingID) to the + // repeated field and increment the index used for appending. + repeatedLogSourceMetrics[logSourceIndex] = logSourceMetrics; + logSourceIndex += 1; + }]; + + return repeatedLogSourceMetrics; +} + +/// Returns the count of log sources that have event drop metrics. +/// @param droppedEventCounter The given dropped event counter. +pb_size_t GDTCCTGetLogSourceMetricsCount(GDTCOREventMetricsCounter *droppedEventCounter) { + return (pb_size_t)droppedEventCounter.droppedEventCounterByMappingID.count; +} + +/// Constructs and returns a ``gdt_client_metrics_TimeWindow`` proto from the given parameters. +/// @param collectionStartDate The start of the time window. +/// @param collectionEndDate The end of the time window. +gdt_client_metrics_TimeWindow GDTCCTConstructTimeWindow(NSDate *collectionStartDate, + NSDate *collectionEndDate) { + gdt_client_metrics_TimeWindow timeWindow = gdt_client_metrics_TimeWindow_init_zero; + // `- [NSDate timeIntervalSince1970]` returns a time interval in seconds so + // multiply by 1000 to convert to milliseconds. + timeWindow.start_ms = (int64_t)collectionStartDate.timeIntervalSince1970 * 1000; + timeWindow.end_ms = (int64_t)collectionEndDate.timeIntervalSince1970 * 1000; + return timeWindow; +} + +/// Constructs and returns a ``gdt_client_metrics_GlobalMetrics`` proto from the given parameters. +/// @param currentCacheSize The current cache size. +/// @param maxCacheSize The max cache size. +gdt_client_metrics_GlobalMetrics GDTCCTConstructGlobalMetrics(uint64_t currentCacheSize, + uint64_t maxCacheSize) { + gdt_client_metrics_StorageMetrics storageMetrics = gdt_client_metrics_StorageMetrics_init_zero; + storageMetrics.current_cache_size_bytes = currentCacheSize; + storageMetrics.max_cache_size_bytes = maxCacheSize; + + gdt_client_metrics_GlobalMetrics globalMetrics = gdt_client_metrics_GlobalMetrics_init_zero; + globalMetrics.storage_metrics = storageMetrics; + + return globalMetrics; +} + +/// Returns the corresponding ``gdt_client_metrics_LogEventDropped_Reason`` for the given +/// ``GDTCOREventDropReason``. +/// +/// To represent ``GDTCOREventDropReason`` in a proto, the reason must be mapped to a +/// ``gdt_client_metrics_LogEventDropped_Reason``. +/// +/// @param reason The ``GDTCOREventDropReason`` to represent in a proto. +gdt_client_metrics_LogEventDropped_Reason GDTCCTConvertEventDropReasonToProtoReason( + GDTCOREventDropReason reason) { + switch (reason) { + case GDTCOREventDropReasonUnknown: + return gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN; + case GDTCOREventDropReasonMessageTooOld: + return gdt_client_metrics_LogEventDropped_Reason_MESSAGE_TOO_OLD; + case GDTCOREventDropReasonStorageFull: + return gdt_client_metrics_LogEventDropped_Reason_CACHE_FULL; + case GDTCOREventDropReasonPayloadTooBig: + return gdt_client_metrics_LogEventDropped_Reason_PAYLOAD_TOO_BIG; + case GDTCOREventDropReasonMaxRetriesReached: + return gdt_client_metrics_LogEventDropped_Reason_MAX_RETRIES_REACHED; + case GDTCOREventDropReasonInvalidPayload: + // The below typo (`PAYLOD`) is currently checked in to g3. + return gdt_client_metrics_LogEventDropped_Reason_INVALID_PAYLOD; + case GDTCOREventDropReasonServerError: + return gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR; + } +} + +@end diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h new file mode 100644 index 00000000..8d1e6968 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h @@ -0,0 +1,25 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" + +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCORMetrics (GDTCCTSupport) + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 3c825165..adab23c6 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -20,17 +20,6 @@ static NSString *const kDroppedEventCounterByMappingID = @"droppedEventCounterByMappingID"; -typedef NSDictionary GDTCORDroppedEventCounter; - -@interface GDTCOREventMetricsCounter () - -/// A dictionary of log sources that map to counters that reflect the number of events dropped for a -/// given set of reasons (``GDTCOREventDropReason``). -@property(nonatomic) - NSDictionary *droppedEventCounterByMappingID; - -@end - @implementation GDTCOREventMetricsCounter + (instancetype)counter { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m index 5c96c9a8..ac3da425 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -14,24 +14,10 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" -#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" -@interface GDTCORMetrics () - -/// The end of the time window over which the metrics were collected. -@property(nonatomic, readonly) NSDate *collectionEndDate; -/// The number of bytes the event cache was consuming in storage. -@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize; -/// The maximum number of bytes that the event cache is allowed to grow. -@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize; -/// The bundle ID associated with the metrics being collected. -@property(nonatomic, readonly) NSString *bundleID; - -@end - @implementation GDTCORMetrics - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate @@ -67,9 +53,4 @@ + (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetad bundleID:bundleID]; } -- (nonnull NSData *)transportBytes { - // TODO(ncooke3): Implement. - return [NSData data]; -} - @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index 8590d7b5..cc2e31ef 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -47,7 +47,7 @@ @implementation GDTCORRegistrar { /** Backing ivar for targetToStorage property. */ NSMutableDictionary> *_targetToStorage; - // TODO(ncooke3): Document. + /** Backing ivar for targetToMetricsController property. */ NSMutableDictionary> *_targetToMetricsController; } diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h index 3bdf81a3..791678b4 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h @@ -1,22 +1,21 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 -// TODO(ncooke3): Document. +/// The reason the event was "dropped". An event is considered "dropped" when it is no longer +/// tracked by the SDK (i.e. deleted). typedef NS_ENUM(NSInteger, GDTCOREventDropReason) { GDTCOREventDropReasonUnknown = 0, GDTCOREventDropReasonMessageTooOld, diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h index 1c398733..cc3ed171 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h @@ -18,6 +18,8 @@ @class GDTCOREvent; +typedef NSDictionary GDTCORDroppedEventCounter; + NS_ASSUME_NONNULL_BEGIN // TODO(ncooke3): Consider renaming to `GDTCORMetricsDroppedEventCounter`. @@ -25,6 +27,11 @@ NS_ASSUME_NONNULL_BEGIN /// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK. @interface GDTCOREventMetricsCounter : NSObject +/// A dictionary of log sources that map to counters that reflect the number of events dropped for a +/// given set of reasons (``GDTCOREventDropReason``). +@property(nonatomic, readonly) + NSDictionary *droppedEventCounterByMappingID; + /// Creates an empty dropped event counter. + (instancetype)counter; diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index 1f9006ac..ed10c11e 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -18,6 +18,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" + @class GDTCOREventMetricsCounter; @class GDTCORMetricsMetadata; @class GDTCORStorageMetadata; @@ -25,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN /// An object representing metrics that represent a snapshot of the SDK's state and performance. -@interface GDTCORMetrics : NSObject +@interface GDTCORMetrics : NSObject /// The start of the time window over which the metrics were collected. @property(nonatomic, readonly) NSDate *collectionStartDate; @@ -33,6 +35,18 @@ NS_ASSUME_NONNULL_BEGIN /// The dropped event counter associated with the metrics. @property(nonatomic, readonly) GDTCOREventMetricsCounter *droppedEventCounter; +/// The end of the time window over which the metrics were collected. +@property(nonatomic, readonly) NSDate *collectionEndDate; + +/// The number of bytes the event cache was consuming in storage. +@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize; + +/// The maximum number of bytes that the event cache is allowed to grow. +@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize; + +/// The bundle ID associated with the metrics being collected. +@property(nonatomic, readonly) NSString *bundleID; + /// Creates a metrics instance with the provided metadata. /// @param metricsMetadata The provided metrics metadata. /// @param storageMetadata The provided storage metadata. diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index 276c6f71..fbd4d7c9 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -319,6 +319,7 @@ - (void)testFetchAndUpdateMetrics { XCTAssertNotNil(fetchedMetadata); XCTAssertNil(fetchError); XCTAssertEqualObjects(fetchedMetadata, metricsMetadata); + return metricsMetadata; }]; // Then @@ -335,6 +336,7 @@ - (void)testFetchAndUpdateMetrics_WhenDecodeError { XCTAssertNil(fetchedMetadata); XCTAssertNotNil(fetchError); XCTAssertEqualObjects(fetchError.localizedFailureReason, @"The file doesn’t exist."); + return fetchedMetadata; }]; // Then @@ -362,7 +364,7 @@ - (void)testFetchAndUpdateMetrics_WhenUpdatedMetadataIsUnexpected_DoesNothing { GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { XCTAssertNotNil(fetchedMetadata); XCTAssertNil(fetchError); - return nil; + return fetchedMetadata; }]; // Then @@ -372,6 +374,7 @@ - (void)testFetchAndUpdateMetrics_WhenUpdatedMetadataIsUnexpected_DoesNothing { XCTAssertNotNil(fetchedMetadata); XCTAssertNil(fetchError); XCTAssertEqualObjects(fetchedMetadata, metricsMetadata); + return fetchedMetadata; }]; FBLWaitForPromisesWithTimeout(0.5); From 34f8953036d582a3cd4e103441c81721270463a2 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:59:21 -0400 Subject: [PATCH 051/131] [Metrics] Add tests for `GDTCORMetric`'s `GDTCOREventDataObject` conformance (#71) * [Metrics] Add tests for GDTCORMetric's GDTCOREventDataObject conformance * Simplify doc comments * Review --- GoogleDataTransport.podspec | 5 +- .../Integration/GDTCCTIntegrationTest.m | 61 +----- .../Unit/GDTCORMetrics+GDTCCTSupportTests.m | 63 +++++++ .../Unit/Helpers/GDTCCTTestRequestParser.h | 49 +++++ .../Unit/Helpers/GDTCCTTestRequestParser.m | 78 ++++++++ .../Unit/Helpers/GDTCORMetricsTestHelpers.h | 43 +++++ .../Unit/Helpers/GDTCORMetricsTestHelpers.m | 176 ++++++++++++++++++ .../Helpers/NSData+GDTCOREventDataObject.h | 25 +++ .../Helpers/NSData+GDTCOREventDataObject.m | 23 +++ .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 7 + .../GDTCORLibrary/GDTCORMetrics.m | 44 +++++ .../GDTCORLibrary/Private/GDTCORMetrics.h | 5 + .../Unit/GDTCOREventMetricsCounterTest.m | 4 +- 13 files changed, 524 insertions(+), 59 deletions(-) create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/GDTCORMetrics+GDTCCTSupportTests.m create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.m create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h create mode 100644 GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.m diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index a9c812ae..bb2dbbf4 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -58,7 +58,10 @@ Shared library for iOS SDK data transport needs. }.merge(header_search_paths) common_test_sources = ['GoogleDataTransport/GDTCORTests/Common/**/*.{h,m}'] - common_cct_test_sources = ['GoogleDataTransport/GDTCCTTests/Common/**/*.{h,m}'] + common_cct_test_sources = [ + 'GoogleDataTransport/GDTCCTTests/Common/**/*.{h,m}', + 'GoogleDataTransport/GDTCCTTests/Unit/Helpers/**/*.{h,m}' + ] # Test app specs if ENV['GDT_DEV'] && ENV['GDT_DEV'] == '1' then diff --git a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m index 8fecb328..530ce499 100644 --- a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m +++ b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m @@ -27,21 +27,10 @@ #import "GoogleDataTransport/GDTCCTTests/Unit/TestServer/GDTCCTTestServer.h" #import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.h" -#import -#import -#import -#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" - -@interface NSData (GDTCOREventDataObject) -@end - -@implementation NSData (GDTCOREventDataObject) - -- (NSData *)transportBytes { - return [self copy]; -} +#import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h" +#import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h" -@end +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" @interface GDTCCTIntegrationTest : XCTestCase /** If YES, allow the recursive generating of events. */ @@ -200,10 +189,10 @@ - (XCTestExpectation *)expectationForEventsToUpload { // Decode events. GCDWebServerDataRequest *dataRequest = (GCDWebServerDataRequest *)request; NSError *decodeError; - gdt_cct_BatchedLogRequest decodedRequest = [weakSelf requestWithData:dataRequest.data - error:&decodeError]; - __auto_type events = [weakSelf eventsWithBatchRequest:decodedRequest error:&decodeError]; + gdt_cct_BatchedLogRequest decodedRequest = + [GDTCCTTestRequestParser requestWithData:dataRequest.data error:&decodeError]; XCTAssertNil(decodeError); + __auto_type events = [GDTCCTTestRequestParser eventsWithBatchRequest:decodedRequest]; [weakSelf.serverReceivedEvents addObjectsFromArray:events]; // Send response. @@ -218,44 +207,6 @@ - (XCTestExpectation *)expectationForEventsToUpload { return responseSentExpectation; } -- (gdt_cct_BatchedLogRequest)requestWithData:(NSData *)data error:(NSError **)outError { - gdt_cct_BatchedLogRequest request = gdt_cct_BatchedLogRequest_init_default; - pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]); - if (!pb_decode(&istream, gdt_cct_BatchedLogRequest_fields, &request)) { - NSString *nanopb_error = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)]; - NSDictionary *userInfo = @{@"nanopb error:" : nanopb_error}; - if (outError != NULL) { - *outError = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:userInfo]; - } - request = (gdt_cct_BatchedLogRequest)gdt_cct_BatchedLogRequest_init_default; - } - return request; -} - -- (NSArray *)eventsWithBatchRequest:(gdt_cct_BatchedLogRequest)batchRequest - error:(NSError **)outError { - NSMutableArray *events = [NSMutableArray array]; - - for (NSUInteger reqIdx = 0; reqIdx < batchRequest.log_request_count; reqIdx++) { - gdt_cct_LogRequest request = batchRequest.log_request[reqIdx]; - - NSString *mappingID = @(request.log_source).stringValue; - - for (NSUInteger eventIdx = 0; eventIdx < request.log_event_count; eventIdx++) { - gdt_cct_LogEvent event = request.log_event[eventIdx]; - - GDTCOREvent *decodedEvent = [[GDTCOREvent alloc] initWithMappingID:mappingID - target:kGDTCORTargetTest]; - decodedEvent.dataObject = [NSData dataWithBytes:event.source_extension->bytes - length:event.source_extension->size]; - - [events addObject:decodedEvent]; - } - } - - return [events copy]; -} - - (void)assertAllScheduledEventsWereReceived { // Assume unique payload. __auto_type scheduledEventsByPayload = diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCORMetrics+GDTCCTSupportTests.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCORMetrics+GDTCCTSupportTests.m new file mode 100644 index 00000000..db694c74 --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCORMetrics+GDTCCTSupportTests.m @@ -0,0 +1,63 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" + +#import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h" +#import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h" + +@interface GDTCORMetrics_GDTCCTSupportTests : XCTestCase +@end + +@implementation GDTCORMetrics_GDTCCTSupportTests + +- (void)testTransportBytes { + // Given + GDTCORMetrics *metrics = [GDTCORMetricsTestHelpers metricsWithGeneratedDroppedEventCounters]; + + // When + NSData *metricsProtoData = [metrics transportBytes]; + XCTAssertNotNil(metricsProtoData); + XCTAssertGreaterThan(metricsProtoData.length, 0); + + // Then + NSError *decodeError = nil; + gdt_client_metrics_ClientMetrics metricsProto = + [GDTCCTTestRequestParser metricsProtoWithData:metricsProtoData error:&decodeError]; + XCTAssertNil(decodeError); + + [GDTCORMetricsTestHelpers assertMetrics:metrics correspondToProto:metricsProto]; +} + +- (void)testTransportBytes_WhenMetricsContainNoDroppedEventData { + // Given + GDTCORMetrics *metrics = [GDTCORMetricsTestHelpers metricsWithEmptyDroppedEventCounters]; + + // When + NSData *metricsProtoData = [metrics transportBytes]; + XCTAssertNotNil(metricsProtoData); + XCTAssertGreaterThan(metricsProtoData.length, 0); + + // Then + NSError *decodeError = nil; + gdt_client_metrics_ClientMetrics metricsProto = + [GDTCCTTestRequestParser metricsProtoWithData:metricsProtoData error:&decodeError]; + XCTAssertNil(decodeError); + + [GDTCORMetricsTestHelpers assertMetrics:metrics correspondToProto:metricsProto]; +} + +@end diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h new file mode 100644 index 00000000..3b04f295 --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h @@ -0,0 +1,49 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A test utility class for parsing data or protos to their corresponding type. +@interface GDTCCTTestRequestParser : NSObject + +/// Parses the batched log request proto from the provided data. +/// @param data The given data to parse. +/// @param outError If the return value is `nil`, an ``NSError`` indicating why the parsing +/// operation failed. +/// @return An instance of ``gdt_cct_BatchedLogRequest``. ++ (gdt_cct_BatchedLogRequest)requestWithData:(NSData *)data error:(NSError **)outError; + +/// Parses the client metrics proto from the provided data. +/// @param data The given data to parse. +/// @param outError If the return value is `nil`, an ``NSError`` indicating why the parsing +/// operation failed. +/// @return An instance of ``gdt_client_metrics_ClientMetrics``. The instance will have default +/// values in the case of an error (check `outError`). ++ (gdt_client_metrics_ClientMetrics)metricsProtoWithData:(NSData *)data error:(NSError **)outError; + +/// Parses the ``GDTCOREvent``s used in the given batch log request proto. +/// @param batchRequest The given proto to parse. +/// @return An array of ``GDTCOREvent`` objects from the request. ++ (NSArray *)eventsWithBatchRequest:(gdt_cct_BatchedLogRequest)batchRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.m new file mode 100644 index 00000000..0e641b1d --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.m @@ -0,0 +1,78 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h" + +#import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h" + +#import +#import +#import + +@implementation GDTCCTTestRequestParser + ++ (gdt_cct_BatchedLogRequest)requestWithData:(NSData *)data error:(NSError **)outError { + gdt_cct_BatchedLogRequest request = gdt_cct_BatchedLogRequest_init_default; + pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]); + if (!pb_decode(&istream, gdt_cct_BatchedLogRequest_fields, &request)) { + if (outError != NULL) { + NSString *nanopbError = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)]; + *outError = [NSError errorWithDomain:NSStringFromClass(self) + code:-1 + userInfo:@{@"nanopb error" : nanopbError}]; + } + } + return request; +} + ++ (NSArray *)eventsWithBatchRequest:(gdt_cct_BatchedLogRequest)batchRequest { + NSMutableArray *events = [NSMutableArray array]; + + for (NSUInteger reqIdx = 0; reqIdx < batchRequest.log_request_count; reqIdx++) { + gdt_cct_LogRequest request = batchRequest.log_request[reqIdx]; + + NSString *mappingID = @(request.log_source).stringValue; + + for (NSUInteger eventIdx = 0; eventIdx < request.log_event_count; eventIdx++) { + gdt_cct_LogEvent event = request.log_event[eventIdx]; + + GDTCOREvent *decodedEvent = [[GDTCOREvent alloc] initWithMappingID:mappingID + target:kGDTCORTargetTest]; + decodedEvent.dataObject = [NSData dataWithBytes:event.source_extension->bytes + length:event.source_extension->size]; + + [events addObject:decodedEvent]; + } + } + + return [events copy]; +} + ++ (gdt_client_metrics_ClientMetrics)metricsProtoWithData:(NSData *)data error:(NSError **)outError { + gdt_client_metrics_ClientMetrics clientMetrics = gdt_client_metrics_ClientMetrics_init_default; + + pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]); + if (!pb_decode(&istream, gdt_client_metrics_ClientMetrics_fields, &clientMetrics)) { + if (outError != NULL) { + NSString *nanopbError = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)]; + *outError = [NSError errorWithDomain:NSStringFromClass(self) + code:-1 + userInfo:@{@"nanopb error" : nanopbError}]; + } + } + + return clientMetrics; +} + +@end diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h new file mode 100644 index 00000000..6f8a72b1 --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h @@ -0,0 +1,43 @@ +// Copyright 2022 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 + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" + +@class GDTCORMetrics; + +NS_ASSUME_NONNULL_BEGIN + +/// A test utility class that provides helpers when working with ``GDTCORMetrics`` instances. +@interface GDTCORMetricsTestHelpers : NSObject + +/// Creates and returns ``GDTCORMetrics`` instance with randomly generated dropped event counters +/// and storage metadata. ++ (GDTCORMetrics *)metricsWithGeneratedDroppedEventCounters; + +/// Creates and returns ``GDTCORMetrics`` instance with empty dropped event counters. ++ (GDTCORMetrics *)metricsWithEmptyDroppedEventCounters; + +/// Converts the given proto to a metrics object and then uses ``XCTAssertEqualObjects`` to assert +/// that the reconstructed metrics object equals the given metrics object. +/// @param metrics A given metrics object to compare. +/// @param metricsProto The given proto to reconstruct a metrics object with for comparison. ++ (void)assertMetrics:(GDTCORMetrics *)metrics + correspondToProto:(gdt_client_metrics_ClientMetrics)metricsProto; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m new file mode 100644 index 00000000..24acc7ca --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m @@ -0,0 +1,176 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" + +#pragma mark - GDTCOREventMetricsCounter + Internal + +@interface GDTCOREventMetricsCounter (Internal) + +/// Initializer exposed for testing. +- (instancetype)initWithDroppedEventCounterByMappingID: + (NSDictionary *)droppedEventCounterByMappingID; + +@end + +#pragma mark - GDTCORMetrics + Internal + +@interface GDTCORMetrics (Internal) + +/// Initializer exposed for testing. +- (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate + collectionEndDate:(NSDate *)collectionEndDate + droppedEventCounter:(GDTCOREventMetricsCounter *)droppedEventCounter + currentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize + maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize + bundleID:(NSString *)bundleID; +@end + +#pragma mark - GDTCORMetricsTestHelpers + +@implementation GDTCORMetricsTestHelpers + ++ (GDTCORMetrics *)metricsWithGeneratedDroppedEventCounters { + // The below counter's structure was arbitrarily defined. + GDTCOREventMetricsCounter *droppedEventCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonUnknown) : @(arc4random_uniform(5))}, + @"log_src_2" : @{ + @(GDTCOREventDropReasonStorageFull) : @(arc4random_uniform(100)), + @(GDTCOREventDropReasonServerError) : @(arc4random_uniform(100)), + }, + @"log_src_3" : @{ + @(GDTCOREventDropReasonUnknown) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonStorageFull) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonServerError) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonMessageTooOld) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonPayloadTooBig) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonInvalidPayload) : @(arc4random_uniform(10)), + @(GDTCOREventDropReasonMaxRetriesReached) : @(arc4random_uniform(10)), + }, + }]; + + return [[GDTCORMetrics alloc] initWithCollectionStartDate:[NSDate distantPast] + collectionEndDate:[NSDate distantFuture] + droppedEventCounter:droppedEventCounter + currentCacheSize:arc4random_uniform(20 * 1000 * 1000) + maxCacheSize:arc4random_uniform(20 * 1000 * 1000) + bundleID:@"com.test.bundle"]; +} + ++ (GDTCORMetrics *)metricsWithEmptyDroppedEventCounters { + return [[GDTCORMetrics alloc] initWithCollectionStartDate:[NSDate distantPast] + collectionEndDate:[NSDate distantFuture] + droppedEventCounter:[GDTCOREventMetricsCounter counter] + currentCacheSize:arc4random_uniform(20 * 1000 * 1000) + maxCacheSize:arc4random_uniform(20 * 1000 * 1000) + bundleID:@"com.test.bundle"]; +} + ++ (void)assertMetrics:(GDTCORMetrics *)metrics + correspondToProto:(gdt_client_metrics_ClientMetrics)metricsProto { + GDTCORMetrics *metricsFromProto = GDTCCTConvertMetricsProtoToGDTCORMetrics(metricsProto); + XCTAssertEqualObjects(metrics, metricsFromProto); +} + +GDTCORMetrics *GDTCCTConvertMetricsProtoToGDTCORMetrics( + gdt_client_metrics_ClientMetrics metricsProto) { + // Reconstruct dates. + NSDate *collectionStartDate = + [[NSDate alloc] initWithTimeIntervalSince1970:metricsProto.window.start_ms / 1000]; + + NSDate *collectionEndDate = + [[NSDate alloc] initWithTimeIntervalSince1970:metricsProto.window.end_ms / 1000]; + + // Reconstruct dropped event counter. + GDTCOREventMetricsCounter *droppedEventCounter = [GDTCOREventMetricsCounter counter]; + + for (int logSourceIndex = 0; logSourceIndex < metricsProto.log_source_metrics_count; + logSourceIndex++) { + gdt_client_metrics_LogSourceMetrics logSourceMetrics = + metricsProto.log_source_metrics[logSourceIndex]; + + NSString *mappingID = [[NSString alloc] initWithBytes:logSourceMetrics.log_source->bytes + length:logSourceMetrics.log_source->size + encoding:NSUTF8StringEncoding]; + + for (int logEventIndex = 0; logEventIndex < logSourceMetrics.log_event_dropped_count; + logEventIndex++) { + gdt_client_metrics_LogEventDropped logEventDropped = + logSourceMetrics.log_event_dropped[logEventIndex]; + + GDTCOREventMetricsCounter *logSourceDroppedEventCounter = + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + mappingID : @{ + @(GDTCCTConvertProtoReasonToEventDropReason(logEventDropped.reason)) : + @(logEventDropped.events_dropped_count), + }, + }]; + + droppedEventCounter = + [droppedEventCounter counterByMergingWithCounter:logSourceDroppedEventCounter]; + } + } + + // Reconstruct storage metadata. + GDTCORStorageSizeBytes currentCacheSize = + metricsProto.global_metrics.storage_metrics.current_cache_size_bytes; + + GDTCORStorageSizeBytes maxCacheSize = + metricsProto.global_metrics.storage_metrics.max_cache_size_bytes; + + // Reconstruct bundle ID. + NSString *bundleID = [[NSString alloc] initWithBytes:metricsProto.app_namespace->bytes + length:metricsProto.app_namespace->size + encoding:NSUTF8StringEncoding]; + + return [[GDTCORMetrics alloc] initWithCollectionStartDate:collectionStartDate + collectionEndDate:collectionEndDate + droppedEventCounter:droppedEventCounter + currentCacheSize:currentCacheSize + maxCacheSize:maxCacheSize + bundleID:bundleID]; +} + +/// Returns the corresponding ``GDTCOREventDropReason`` for the given +/// ``gdt_client_metrics_LogEventDropped_Reason``. +/// @param protoReason The ``gdt_client_metrics_LogEventDropped_Reason`` to represent as +/// a ``GDTCOREventDropReason``. +GDTCOREventDropReason GDTCCTConvertProtoReasonToEventDropReason( + gdt_client_metrics_LogEventDropped_Reason protoReason) { + switch (protoReason) { + case gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN: + return GDTCOREventDropReasonUnknown; + case gdt_client_metrics_LogEventDropped_Reason_MESSAGE_TOO_OLD: + return GDTCOREventDropReasonMessageTooOld; + case gdt_client_metrics_LogEventDropped_Reason_CACHE_FULL: + return GDTCOREventDropReasonStorageFull; + case gdt_client_metrics_LogEventDropped_Reason_PAYLOAD_TOO_BIG: + return GDTCOREventDropReasonPayloadTooBig; + case gdt_client_metrics_LogEventDropped_Reason_MAX_RETRIES_REACHED: + return GDTCOREventDropReasonMaxRetriesReached; + // The below typo (`PAYLOD`) is currently checked in to g3. + case gdt_client_metrics_LogEventDropped_Reason_INVALID_PAYLOD: + return GDTCOREventDropReasonInvalidPayload; + case gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR: + return GDTCOREventDropReasonServerError; + } +} + +@end diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h new file mode 100644 index 00000000..7dac4475 --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h @@ -0,0 +1,25 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (GDTCOREventDataObject) + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.m new file mode 100644 index 00000000..31d81fff --- /dev/null +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.m @@ -0,0 +1,23 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h" + +@implementation NSData (GDTCOREventDataObject) + +- (NSData *)transportBytes { + return [self copy]; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index adab23c6..5a0779d1 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -158,4 +158,11 @@ - (void)encodeWithCoder:(nonnull NSCoder *)coder { [coder encodeObject:self.droppedEventCounterByMappingID forKey:kDroppedEventCounterByMappingID]; } +#pragma mark - Description + +- (NSString *)description { + return [NSString + stringWithFormat:@"%@ %@", [super description], self.droppedEventCounterByMappingID]; +} + @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m index ac3da425..b6eb74fc 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -53,4 +53,48 @@ + (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetad bundleID:bundleID]; } +#pragma mark - Equality + +- (BOOL)isEqualToMetrics:(GDTCORMetrics *)otherMetrics { + return [self.collectionStartDate isEqualToDate:otherMetrics.collectionStartDate] && + [self.collectionEndDate isEqualToDate:otherMetrics.collectionEndDate] && + [self.droppedEventCounter isEqualToDroppedEventCounter:otherMetrics.droppedEventCounter] && + [self.bundleID isEqualToString:otherMetrics.bundleID] && + self.currentCacheSize == otherMetrics.currentCacheSize && + self.maxCacheSize == otherMetrics.maxCacheSize; +} + +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } + + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[self class]]) { + return NO; + } + + return [self isEqualToMetrics:(GDTCORMetrics *)object]; +} + +- (NSUInteger)hash { + return [self.collectionStartDate hash] ^ [self.collectionEndDate hash] ^ + [self.droppedEventCounter hash] ^ [self.bundleID hash] ^ [@(self.currentCacheSize) hash] ^ + [@(self.maxCacheSize) hash]; +} + +#pragma mark - Description + +- (NSString *)description { + return [NSString + stringWithFormat: + @"%@ {\n\tcollectionStartDate: %@,\n\tcollectionEndDate: %@,\n\tcurrentCacheSize: " + @"%llu,\n\tmaxCacheSize: %llu,\n\tbundleID: %@,\n\tdroppedEventCounter: %@}\n", + [super description], self.collectionStartDate, self.collectionEndDate, + self.currentCacheSize, self.maxCacheSize, self.bundleID, self.droppedEventCounter]; +} + @end diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index ed10c11e..2163fedd 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -53,6 +53,11 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetadata storageMetadata:(GDTCORStorageMetadata *)storageMetadata; +/// Returns a Boolean value that indicates whether the receiving metrics is equal to the given +/// metrics. +/// @param otherMetrics The metrics with which to compare the receiving metrics. +- (BOOL)isEqualToMetrics:(GDTCORMetrics *)otherMetrics; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m index 76e1c3d9..ed59a251 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m @@ -20,11 +20,9 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" -typedef NSDictionary GDTCORDroppedEventCounter; - @interface GDTCOREventMetricsCounter (Internal) -/// Designated initializer exposed for testing. +/// Initializer exposed for testing. - (instancetype)initWithDroppedEventCounterByMappingID: (NSDictionary *)droppedEventCounterByMappingID; From 518349d69280dabf7200d6fd4d67017a5a87d102 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:11:59 -0400 Subject: [PATCH 052/131] [Metrics] Handle potential data corruption when decoding encodable types (#72) --- .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 18 +++++++---- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 18 +++++++---- .../Unit/GDTCOREventMetricsCounterTest.m | 31 ++++++++++++++++++ .../Unit/GDTCORMetricsMetadataTest.m | 32 +++++++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 5a0779d1..92e56d0e 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -144,14 +144,18 @@ + (BOOL)supportsSecureCoding { } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - self = [super init]; - if (self) { - _droppedEventCounterByMappingID = - [coder decodeObjectOfClasses: - [NSSet setWithArray:@[ NSDictionary.class, NSString.class, NSNumber.class ]] - forKey:kDroppedEventCounterByMappingID]; + NSDictionary *droppedEventCounterByMappingID = + [coder decodeObjectOfClasses: + [NSSet setWithArray:@[ NSDictionary.class, NSString.class, NSNumber.class ]] + forKey:kDroppedEventCounterByMappingID]; + + if (!droppedEventCounterByMappingID || + ![droppedEventCounterByMappingID isKindOfClass:[NSDictionary class]]) { + // If any of the fields are corrupted, the initializer should fail. + return nil; } - return self; + + return [self initWithDroppedEventCounterByMappingID:droppedEventCounterByMappingID]; } - (void)encodeWithCoder:(nonnull NSCoder *)coder { diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index 1d5678d7..1f39d08e 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -73,13 +73,19 @@ + (BOOL)supportsSecureCoding { } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - self = [super init]; - if (self) { - _collectionStartDate = [coder decodeObjectOfClass:[NSDate class] forKey:kCollectionStartDate]; - _droppedEventCounter = [coder decodeObjectOfClass:[GDTCOREventMetricsCounter class] - forKey:kDroppedEventCounter]; + NSDate *collectionStartDate = [coder decodeObjectOfClass:[NSDate class] + forKey:kCollectionStartDate]; + GDTCOREventMetricsCounter *droppedEventCounter = + [coder decodeObjectOfClass:[GDTCOREventMetricsCounter class] forKey:kDroppedEventCounter]; + + if (!collectionStartDate || !droppedEventCounter || + ![collectionStartDate isKindOfClass:[NSDate class]] || + ![droppedEventCounter isKindOfClass:[GDTCOREventMetricsCounter class]]) { + // If any of the fields are corrupted, the initializer should fail. + return nil; } - return self; + + return [self initWithCollectionStartDate:collectionStartDate counter:droppedEventCounter]; } - (void)encodeWithCoder:(nonnull NSCoder *)coder { diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m index ed59a251..37c7908d 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m @@ -275,6 +275,37 @@ - (void)testSecureCoding { XCTAssertEqualObjects(decodedCounter, eventMetricsCounter); } +- (void)testSecureCoding_WhenEncodingIsCorrupted { + // Given + // - Create an invalid instance and write its encoding to a file. When + // decoding, the invalid encoding should be treated as a corrupt encoding. + GDTCOREventMetricsCounter *corruptedMetricsCounter = +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@"corrupted"]; +#pragma clang diagnostic pop + + NSError *encodeError; + NSData *encodedMetricsCounter = GDTCOREncodeArchive(corruptedMetricsCounter, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedMetricsCounter); + + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"metadata.dat"]; + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedMetricsCounter, filePath, &writeError); + XCTAssertNil(writeError); + XCTAssertTrue(writeResult); + + // When + NSError *decodeError; + GDTCOREventMetricsCounter *decodedMetricsCounter = + (GDTCOREventMetricsCounter *)GDTCORDecodeArchiveAtPath(GDTCOREventMetricsCounter.class, + filePath, &decodeError); + // Then + XCTAssertNotNil(decodeError); + XCTAssertNil(decodedMetricsCounter); +} + - (void)assertMergedCounterFromCounter1:(GDTCOREventMetricsCounter *)counter1 counter2:(GDTCOREventMetricsCounter *)counter2 { // There are three ways to merge the given counters, and all three ways diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m index d5a2dc41..0156870d 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m @@ -108,4 +108,36 @@ - (void)testSecureCoding { XCTAssertEqualObjects(decodedMetricsMetadata, metricsMetadata); } +- (void)testSecureCoding_WhenEncodingIsCorrupted { + // Given + // - Create an invalid instance and write its encoding to a file. When + // decoding, the invalid encoding should be treated as a corrupt encoding. + GDTCORMetricsMetadata *corruptedMetadata = +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + [GDTCORMetricsMetadata metadataWithCollectionStartDate:@"date" + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; +#pragma clang diagnostic pop + + NSError *encodeError; + NSData *encodedMetricsMetadata = GDTCOREncodeArchive(corruptedMetadata, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedMetricsMetadata); + + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"metadata.dat"]; + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedMetricsMetadata, filePath, &writeError); + XCTAssertNil(writeError); + XCTAssertTrue(writeResult); + + // When + NSError *decodeError; + GDTCORMetricsMetadata *decodedMetricsMetadata = + (GDTCORMetricsMetadata *)GDTCORDecodeArchiveAtPath(GDTCORMetricsMetadata.class, filePath, + &decodeError); + // Then + XCTAssertNotNil(decodeError); + XCTAssertNil(decodedMetricsMetadata); +} + @end From 1efe3339c1523991b544a5ff5a0391f6fe0d4f34 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 3 Jun 2022 11:12:21 -0400 Subject: [PATCH 053/131] [Metrics] Add integration tests for metrics component (#73) * [Metrics] Add integration tests for metrics component * Update duplicate comment * Review * Review 2 * Review 3 --- .github/workflows/datatransport.yml | 4 + .github/workflows/test_coverage.yml | 2 + GoogleDataTransport.podspec | 17 ++- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 9 +- .../GDTCCTLibrary/GDTCCTUploader.m | 13 +- .../GDTCOREvent+GDTMetricsSupport.m | 33 +++++ .../GDTCORMetrics+GDTCCTSupport.m | 8 +- .../GDTCCTLibrary/Private/GDTCCTUploader.h | 4 +- .../Private/GDTCOREvent+GDTMetricsSupport.h | 34 +++++ .../Integration/GDTCCTIntegrationTest.m | 129 ++++++++++++------ .../GDTCORLibrary/GDTCORMetricsController.m | 4 + .../Private/GDTCORMetricsController.h | 3 + 12 files changed, 199 insertions(+), 61 deletions(-) create mode 100644 GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m create mode 100644 GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index e406bbf9..dba5fef1 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -6,6 +6,10 @@ on: # Run every day at 11pm (PST) - cron uses UTC times - cron: '0 7 * * *' +env: + # Used to compile test-specific code in the SDK. + GDT_TEST: 1 + jobs: pod_lib_lint: runs-on: macos-latest diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 37306e5f..daafa796 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -9,6 +9,8 @@ on: env: METRICS_SERVICE_SECRET: ${{ secrets.GHASecretsGPGPassphrase1 }} + # Used to compile test-specific code in the SDK. + GDT_TEST: 1 jobs: pod_lib_lint: diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index bb2dbbf4..bc66401d 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -47,14 +47,21 @@ Shared library for iOS SDK data transport needs. 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/"' } + # The nanopb pod sets these defs, so we must too. (We *do* require 16bit + # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT + # anyways.) + preprocessor_definitions = + 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1'\ + 'GDTCOR_VERSION=' + s.version.to_s + + if ENV['GDT_TEST'] && ENV['GDT_TEST'] == '1' then + preprocessor_definitions += ' GDT_TEST=1' + end + s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY' => 'YES', - 'GCC_PREPROCESSOR_DEFINITIONS' => - # The nanopb pod sets these defs, so we must too. (We *do* require 16bit - # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT - # anyways.) - 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 GDTCOR_VERSION=' + s.version.to_s, + 'GCC_PREPROCESSOR_DEFINITIONS' => preprocessor_definitions }.merge(header_search_paths) common_test_sources = ['GoogleDataTransport/GDTCORTests/Common/**/*.{h,m}'] diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 0b561dde..67dca01f 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -38,7 +38,7 @@ #import #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" -#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" @@ -525,15 +525,10 @@ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target return [self.metricsController getAndResetMetrics] .thenOn(self.uploaderQueue, ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { - // TODO(ncooke3): Define kMetricEventMappingID. - GDTCOREvent *metricsEvent = - [[GDTCOREvent alloc] initWithMappingID:@"kMetricEventMappingID" target:target]; - - [metricsEvent setDataObject:metrics]; - // Save the metrics so they can be reset later upon successful upload. [self setCurrentMetrics:metrics]; + GDTCOREvent *metricsEvent = [GDTCOREvent eventWithMetrics:metrics forTarget:target]; GDTCORUploadBatch *batchWithMetricEvent = [[GDTCORUploadBatch alloc] initWithBatchID:batch.batchID events:[batch.events setByAddingObject:metricsEvent]]; diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m index d94d4146..c436ed31 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploader.m @@ -42,6 +42,9 @@ @implementation GDTCCTUploader + (void)load { GDTCCTUploader *uploader = [GDTCCTUploader sharedInstance]; +#if GDT_TEST + [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetTest]; +#endif // GDT_TEST [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCCT]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetFLL]; [[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCSH]; @@ -138,11 +141,11 @@ + (NSURL *_Nullable)testServerURL { } + (nullable NSURL *)serverURLForTarget:(GDTCORTarget)target { -#if !NDEBUG +#if GDT_TEST if (_testServerURL) { return _testServerURL; } -#endif // !NDEBUG +#endif // GDT_TEST return [GDTCOREndpoints uploadURLForTarget:target]; } @@ -190,8 +193,7 @@ - (nullable NSString *)APIKeyForTarget:(GDTCORTarget)target { return nil; } -#if !NDEBUG - +#if GDT_TEST - (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout { NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeout]; while ([expirationDate compare:[NSDate date]] == NSOrderedDescending) { @@ -206,8 +208,7 @@ - (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout { self.uploadOperationQueue.operations); return NO; } - -#endif // !NDEBUG +#endif // GDT_TEST @end diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m new file mode 100644 index 00000000..ef236eae --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m @@ -0,0 +1,33 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h" + +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" + +/// The mapping ID that represents the `LogSource` for Firelog Client Analytics. +static NSString *const kMetricEventMappingID = @"1710"; + +@implementation GDTCOREvent (GDTMetricsSupport) + ++ (GDTCOREvent *)eventWithMetrics:(GDTCORMetrics *)metrics forTarget:(GDTCORTarget)target { + GDTCOREvent *metricsEvent = [[GDTCOREvent alloc] initWithMappingID:kMetricEventMappingID + target:target]; + metricsEvent.dataObject = metrics; + + return metricsEvent; +} + +@end diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m index 5c67bcd5..f67d9f55 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m @@ -18,13 +18,15 @@ #import #import -#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" -#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" + +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" -#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" + +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" @implementation GDTCORMetrics (GDTCCTSupport) diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h index 876fbe10..36934c68 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedInstance; -#if !NDEBUG +#if GDT_TEST /** An upload URL used across all targets. For testing only. */ @property(class, nullable, nonatomic) NSURL *testServerURL; @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout; -#endif // !NDEBUG +#endif // GDT_TEST @end diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h new file mode 100644 index 00000000..84d36ae3 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h @@ -0,0 +1,34 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" + +@class GDTCORMetrics; + +NS_ASSUME_NONNULL_BEGIN + +@interface GDTCOREvent (GDTMetricsSupport) + +/// Creates and returns an event for the given target with the given metrics. +/// @param metrics The metrics to set at the event's data. +/// @param target The backend target that the event corresponds to. ++ (GDTCOREvent *)eventWithMetrics:(GDTCORMetrics *)metrics forTarget:(GDTCORTarget)target; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m index 530ce499..3880f14a 100644 --- a/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m +++ b/GoogleDataTransport/GDTCCTTests/Integration/GDTCCTIntegrationTest.m @@ -24,13 +24,21 @@ #import #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h" -#import "GoogleDataTransport/GDTCCTTests/Unit/TestServer/GDTCCTTestServer.h" -#import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.h" +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" #import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTTestRequestParser.h" #import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/NSData+GDTCOREventDataObject.h" +#import "GoogleDataTransport/GDTCCTTests/Unit/TestServer/GDTCCTTestServer.h" -#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" +#import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.h" + +/// The mapping ID that represents the `LogSource` for GDT testing. +static NSString *const kTestEventMappingID = @"1018"; + +/// The mapping ID that represents the `LogSource` for GDT metrics. +static NSString *const kMetricEventMappingID = @"1710"; @interface GDTCCTIntegrationTest : XCTestCase /** If YES, allow the recursive generating of events. */ @@ -75,10 +83,6 @@ - (void)setUp { GDTCCTUploader.testServerURL = [self.testServer.serverURL URLByAppendingPathComponent:@"logBatch"]; - - self.transport = [[GDTCORTransport alloc] initWithMappingID:@"1018" - transformers:nil - target:kGDTCORTargetCCT]; } - (void)tearDown { @@ -88,6 +92,64 @@ - (void)tearDown { /** Tests sending data to CCT with a high priority event if network conditions are good. */ - (void)testSendingDataToCCT { + [self assertUploadToTarget:kGDTCORTargetCCT expectingMetrics:NO]; +} + +- (void)testRunsWithoutCrashingToCCT { + self.transport = [[GDTCORTransport alloc] initWithMappingID:kTestEventMappingID + transformers:nil + target:kGDTCORTargetCCT]; + // Just run for a minute whilst generating events. + NSInteger secondsToRun = 65; + self.generateEvents = YES; + + XCTestExpectation *eventsUploaded = [self expectationForEventsToUpload]; + eventsUploaded.expectedFulfillmentCount = 2; + + [self recursivelyGenerateEvent]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(secondsToRun * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + self.generateEvents = NO; + + // Send a high priority event to flush other events. + [self generateEventWithQoSTier:GDTCOREventQoSFast]; + }); + + // Wait for events to be uploaded. + [self waitForExpectations:@[ eventsUploaded ] timeout:secondsToRun + 5]; + + // Validate sent events content. + [self assertAllScheduledEventsWereReceivedWhenExpectingMetrics:NO]; +} + +- (void)testSendingData_WhenTargetSupportsMetrics_AndThereAreMetricsToUpload { + [self assertUploadToTarget:kGDTCORTargetTest expectingMetrics:YES]; +} + +- (void)testSendingData_WhenTargetSupportsMetrics_AndThereAreNoMetricsToUpload { + [self assertUploadToTarget:kGDTCORTargetTest expectingMetrics:NO]; +} + +#pragma mark - Helpers + +/// Asserts that uploading a batch of events to the given target performs as expected. +/// @param target The target to upload the events to. +/// @param expectingMetrics Whether or not a metrics event should be uploaded to the target. +- (void)assertUploadToTarget:(GDTCORTarget)target expectingMetrics:(BOOL)expectingMetrics { + self.transport = [[GDTCORTransport alloc] initWithMappingID:kTestEventMappingID + transformers:nil + target:target]; + + if (expectingMetrics) { + [[GDTCORMetricsController sharedInstance] + logEventsDroppedForReason:GDTCOREventDropReasonStorageFull + events:[NSSet setWithArray:@[ + [self.transport eventForTransport], [self.transport eventForTransport], + [self.transport eventForTransport] + ]]]; + } + // Send a number of events across multiple queues in order to ensure the threading is working as // expected. dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); @@ -116,37 +178,10 @@ - (void)testSendingDataToCCT { [self waitForExpectations:@[ eventsUploaded ] timeout:60.0]; // Validate sent events content. - [self assertAllScheduledEventsWereReceived]; -} - -- (void)testRunsWithoutCrashing { - // Just run for a minute whilst generating events. - NSInteger secondsToRun = 65; - self.generateEvents = YES; - - XCTestExpectation *eventsUploaded = [self expectationForEventsToUpload]; - eventsUploaded.expectedFulfillmentCount = 2; - - [self recursivelyGenerateEvent]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(secondsToRun * NSEC_PER_SEC)), - dispatch_get_main_queue(), ^{ - self.generateEvents = NO; - - // Send a high priority event to flush other events. - [self generateEventWithQoSTier:GDTCOREventQoSFast]; - }); - - // Wait for events to be uploaded. - [self waitForExpectations:@[ eventsUploaded ] timeout:secondsToRun + 5]; - - // Validate sent events content. - [self assertAllScheduledEventsWereReceived]; + [self assertAllScheduledEventsWereReceivedWhenExpectingMetrics:expectingMetrics]; } -#pragma mark - Helpers - -/** Generates an event and sends it through the transport infrastructure. */ +/// Generates an event and sends it through the transport infrastructure. - (void)generateEventWithQoSTier:(GDTCOREventQoS)qosTier { GDTCOREvent *event = [self.transport eventForTransport]; event.dataObject = [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding]; @@ -161,7 +196,7 @@ - (void)generateEventWithQoSTier:(GDTCOREventQoS)qosTier { }); } -/** Generates events recursively at random intervals between 0 and 5 seconds. */ +/// Generates events recursively at random intervals between 0 and 5 seconds. - (void)recursivelyGenerateEvent { if (self.generateEvents) { [self generateEventWithQoSTier:GDTCOREventQosDefault]; @@ -207,7 +242,25 @@ - (XCTestExpectation *)expectationForEventsToUpload { return responseSentExpectation; } -- (void)assertAllScheduledEventsWereReceived { +- (void)assertAllScheduledEventsWereReceivedWhenExpectingMetrics:(BOOL)expectingMetrics { + // Assert that a metrics event was sent only if it was expected. + NSInteger metricsEventCount = 0; + for (GDTCOREvent *receivedEvent in [self.serverReceivedEvents copy]) { + if ([receivedEvent.mappingID isEqualToString:kMetricEventMappingID]) { + // The event is a metrics event. + metricsEventCount += 1; + NSError *decodeError = nil; + gdt_client_metrics_ClientMetrics __unused decodedMetrics = + [GDTCCTTestRequestParser metricsProtoWithData:receivedEvent.serializedDataObjectBytes + error:&decodeError]; + XCTAssertNil(decodeError); + if (expectingMetrics) { + [self.scheduledEvents addObject:receivedEvent]; + } + } + } + XCTAssertEqual(metricsEventCount, @(expectingMetrics).integerValue); + // Assume unique payload. __auto_type scheduledEventsByPayload = [self eventsByPayloadWithEvents:[self.scheduledEvents copy]]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index d46209f4..00f01b13 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -43,6 +43,10 @@ @interface GDTCORMetricsController () @implementation GDTCORMetricsController + (void)load { +#if GDT_TEST + [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] + target:kGDTCORTargetTest]; +#endif // GDT_TEST [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] target:kGDTCORTargetCSH]; [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h index 7e037515..a47da529 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h @@ -22,6 +22,9 @@ NS_ASSUME_NONNULL_BEGIN @interface GDTCORMetricsController : NSObject +/// Returns the event metrics controller singleton. ++ (instancetype)sharedInstance; + /// Designated initializer. /// @param storage The storage object to read and write metrics data from. - (instancetype)initWithStorage:(id)storage NS_DESIGNATED_INITIALIZER; From 743b7ef6163eb1562d85153d7a23d299a64827e9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 7 Jun 2022 15:02:45 -0400 Subject: [PATCH 054/131] [Metrics] Misc. cleanup and resolving TODOs (#75) * [Metrics] Misc. cleanup and resolving TODOs * Add regression test for metrics storage * Fix CI * Fix CI (2) --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 14 +- .../GDTCOREvent+GDTMetricsSupport.m | 2 +- .../GDTCORMetrics+GDTCCTSupport.m | 2 +- .../Private/GDTCCTUploadOperation.h | 22 +- .../Common/TestStorage/GDTCCTTestStorage.m | 4 +- .../GDTCCTTests/Unit/GDTCCTUploaderTest.m | 210 ++++++++---------- .../GDTCORLibrary/GDTCOREventMetricsCounter.m | 2 +- .../GDTCORLibrary/GDTCORMetrics.m | 6 +- .../GDTCORLibrary/GDTCORMetricsController.m | 20 +- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 2 +- .../GDTCORMetricsControllerProtocol.h | 10 +- .../Internal/GDTCORStorageProtocol.h | 1 + .../GDTCORLibrary/Private/GDTCORMetrics.h | 28 ++- .../Fakes/GDTCORMetricsControllerFake.h | 4 +- .../Fakes/GDTCORMetricsControllerFake.m | 13 +- .../Common/Fakes/GDTCORStorageFake.m | 1 + .../Unit/GDTCORFlatFileStorageTest.m | 32 +++ .../Unit/GDTCORMetricsControllerTest.m | 29 --- 18 files changed, 175 insertions(+), 227 deletions(-) diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 67dca01f..04fede58 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -78,7 +78,7 @@ @interface GDTCCTUploadOperation () /// The metrics being uploaded by the operation. These metrics are fetched and included as an event /// in the upload batch as part of the upload process. /// -/// Metrics being uploaded are retained so they can be reset when upload is successful. +/// Metrics being uploaded are retained so they can be re-stored if upload is not successful. @property(nonatomic, nullable) GDTCORMetrics *currentMetrics; /// NSOperation state properties implementation. @@ -182,8 +182,9 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions }) .thenOn(self.uploaderQueue, ^FBLPromise *(GDTCORUploadBatch *batch) { - // 7. Add metrics to batch if metrics collection is supported. - if (![self.metricsController isMetricsCollectionSupportedForTarget:target]) { + // 7. Add metrics to the batch if the target has a + // corresponding metrics controller. + if (!self.metricsController) { return [FBLPromise resolvedWith:batch]; } @@ -231,6 +232,11 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions .recoverOn(self.uploaderQueue, ^id(NSError *error) { // If a network error occurred, move the events back to the main // storage so they can attempt to be uploaded in the next attempt. + // Additionally, if metrics were added to the batch, place them back + // in storage. + if (self.currentMetrics) { + [self.metricsController offerMetrics:self.currentMetrics]; + } return [storage removeBatchWithID:batch.batchID deleteEvents:NO]; }); } @@ -525,7 +531,7 @@ - (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target return [self.metricsController getAndResetMetrics] .thenOn(self.uploaderQueue, ^GDTCORUploadBatch *(GDTCORMetrics *metrics) { - // Save the metrics so they can be reset later upon successful upload. + // Save the metrics so they can be re-stored if upload fails. [self setCurrentMetrics:metrics]; GDTCOREvent *metricsEvent = [GDTCOREvent eventWithMetrics:metrics forTarget:target]; diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m index ef236eae..aa5dabe0 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m @@ -17,7 +17,7 @@ #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" -/// The mapping ID that represents the `LogSource` for Firelog Client Analytics. +/// The mapping ID that represents the `LogSource` for GDT metrics. static NSString *const kMetricEventMappingID = @"1710"; @implementation GDTCOREvent (GDTMetricsSupport) diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m index f67d9f55..94526f05 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m @@ -94,7 +94,7 @@ - (NSData *)transportBytes { GDTCORDroppedEventCounter *eventCounterForMappingID, BOOL *__unused _) { // Create the log source proto for the given mapping ID. It contains a - // repeating field to encapuslate the number of events dropped for each + // repeating field to encapsulate the number of events dropped for each // given drop reason. __block gdt_client_metrics_LogSourceMetrics logSourceMetrics = gdt_client_metrics_LogSourceMetrics_init_zero; diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h index 1061c76c..cfc2c485 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h @@ -43,17 +43,17 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)init NS_UNAVAILABLE; -/** The designated initializer. - * @param target The events target to upload. - * @param conditions A set of upload conditions. The conditions affect the set of events to be - * uploaded, e.g. events with some QoS are not uploaded on a cellular network, etc. - * @param uploadURL The backend URL to upload the events. - * @param queue A queue to dispatch async upload steps. - * @param storage A storage object to fetch events for upload. - * @param metadataProvider An object to retrieve/update data shared between different upload - * operations. - * @return An instance of GDTCCTUploadOperation ready to be added to an NSOperationQueue. - */ +/// Designated initializer. +/// @param target The events target to upload. +/// @param conditions A set of upload conditions. The conditions affect the set of events to be +/// uploaded, e.g. events with some QoS are not uploaded on a cellular network, etc. +/// @param uploadURL The backend URL to upload the events. +/// @param queue A queue to dispatch async upload steps. +/// @param storage A storage object to fetch events for upload. +/// @param metadataProvider An object to retrieve/update data shared between upload operations. +/// @param metricsController The metrics controller corresponding to the given target. If the given +/// target does not support metrics controller, `nil` should be passed. +/// @return An individual operation that can be added to an operation queue. - (instancetype)initWithTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions uploadURL:(NSURL *)uploadURL diff --git a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m index 2bf56031..066fdeb3 100644 --- a/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m +++ b/GoogleDataTransport/GDTCCTTests/Common/TestStorage/GDTCCTTestStorage.m @@ -212,12 +212,12 @@ - (void)defaultBatchWithEventSelector:(nonnull GDTCORStorageEventSelector *)even - (FBLPromise *)fetchAndUpdateMetricsWithHandler: (GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError))handler { - // TODO(ncooke3): Implement. + NSAssert(NO, @"This API should be implemented if this fake is used in tests."); return [FBLPromise resolvedWith:nil]; } - (FBLPromise *)fetchStorageMetadata { - // TODO(ncooke3): Implement. + NSAssert(NO, @"This API should be implemented if this fake is used in tests."); return [FBLPromise resolvedWith:nil]; } diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m index cf3bd712..d4ac0ab3 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m @@ -414,33 +414,29 @@ - (void)testUploadTarget_WhenTargetSupportsMetricsCollection_ThenMetricsAreAdded [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake target:kGDTCORTargetTest]; - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return YES; - }; - GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; + XCTestExpectation *getAndResetMetricsExpectation = + [self expectationWithDescription:@"getAndResetMetricsExpectation"]; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; + // This should not be fulfilled. + [getAndResetMetricsExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsConfirmationExpectation.inverted = YES; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + offerMetricsExpectation.inverted = YES; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *__unused _) { + // This should not be fulfilled. + [offerMetricsExpectation fulfill]; }; XCTestExpectation *droppedEventsAreLoggedExpectation = [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; droppedEventsAreLoggedExpectation.inverted = YES; metricsControllerFake.onLogEventsDroppedHandler = - ^(GDTCOREventDropReason _, NSSet *__) { + ^(GDTCOREventDropReason __unused _, NSSet *__unused __) { + // This should not be fulfilled. [droppedEventsAreLoggedExpectation fulfill]; }; @@ -454,9 +450,9 @@ - (void)testUploadTarget_WhenTargetSupportsMetricsCollection_ThenMetricsAreAdded [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + self.testStorage.batchWithEventSelectorExpectation, getAndResetMetricsExpectation, + responseSentExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 enforceOrder:YES]; @@ -477,36 +473,36 @@ - (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetrics // - Set metrics controller expectations. GDTCORMetricsControllerFake *metricsControllerFake = [[GDTCORMetricsControllerFake alloc] init]; - [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake - target:kGDTCORTargetTest]; - - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return NO; - }; - - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; - fetchMetricsExpectation.inverted = YES; + // The `kGDTCORTargetTest` target is registered at +load time. De-registering + // it here will simulate the scenario where an upload target does not have a + // corresponding metrics controller (and therefore does not support metrics + // collection). + [[[GDTCORRegistrar sharedInstance] targetToMetricsController] + removeObjectForKey:@(kGDTCORTargetTest)]; + + XCTestExpectation *getAndResetExpectation = + [self expectationWithDescription:@"getAndResetExpectation"]; + getAndResetExpectation.inverted = YES; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; + // This should not be fulfilled. + [getAndResetExpectation fulfill]; return [FBLPromise resolvedWith:nil]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsConfirmationExpectation.inverted = YES; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *_) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + offerMetricsExpectation.inverted = YES; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *__unused _) { + // This should not be fulfilled. + [offerMetricsExpectation fulfill]; }; XCTestExpectation *droppedEventsAreLoggedExpectation = [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; droppedEventsAreLoggedExpectation.inverted = YES; metricsControllerFake.onLogEventsDroppedHandler = - ^(GDTCOREventDropReason _, NSSet *__) { + ^(GDTCOREventDropReason __unused _, NSSet *__unused __) { + // This should not be fulfilled. [droppedEventsAreLoggedExpectation fulfill]; }; @@ -520,9 +516,9 @@ - (void)testUploadTarget_WhenTargetDoesNotSupportMetricsCollection_ThenNoMetrics [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + self.testStorage.batchWithEventSelectorExpectation, getAndResetExpectation, + responseSentExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 enforceOrder:YES]; @@ -546,34 +542,28 @@ - (void)testUploadTarget_WhenFailureToAddMetricsToBatch_DoesNotImpedeBatchUpload [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake target:kGDTCORTargetTest]; - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return YES; - }; - NSError *error = [NSError errorWithDomain:@"metrics controller error" code:1 userInfo:nil]; - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; + XCTestExpectation *getAndResetExpectation = + [self expectationWithDescription:@"getAndResetExpectation"]; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; - + [getAndResetExpectation fulfill]; return [FBLPromise resolvedWith:error]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsConfirmationExpectation.inverted = YES; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + offerMetricsExpectation.inverted = YES; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *__unused _) { + // This should not be fulfilled. + [offerMetricsExpectation fulfill]; }; XCTestExpectation *droppedEventsAreLoggedExpectation = [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; droppedEventsAreLoggedExpectation.inverted = YES; metricsControllerFake.onLogEventsDroppedHandler = - ^(GDTCOREventDropReason _, NSSet *__) { + ^(GDTCOREventDropReason __unused _, NSSet *__unused __) { + // This should not be fulfilled. [droppedEventsAreLoggedExpectation fulfill]; }; @@ -587,16 +577,16 @@ - (void)testUploadTarget_WhenFailureToAddMetricsToBatch_DoesNotImpedeBatchUpload [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + self.testStorage.batchWithEventSelectorExpectation, getAndResetExpectation, + responseSentExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 enforceOrder:YES]; [self waitForUploadOperationsToFinish:self.uploader]; } -- (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmedWithSuccess { +- (void)testUploadTarget_WhenBatchWithMetricsAreUploaded { // Given // - Generate and batch a test event. [self.generator generateEvent:GDTCOREventQoSFast]; @@ -613,33 +603,28 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake target:kGDTCORTargetTest]; - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return YES; - }; - GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; + XCTestExpectation *getAndResetExpectation = + [self expectationWithDescription:@"getAndResetExpectation"]; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; + [getAndResetExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsConfirmationExpectation.inverted = YES; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + offerMetricsExpectation.inverted = YES; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *__unused _) { + // This should not be fulfilled. + [offerMetricsExpectation fulfill]; }; XCTestExpectation *droppedEventsAreLoggedExpectation = [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; droppedEventsAreLoggedExpectation.inverted = YES; metricsControllerFake.onLogEventsDroppedHandler = - ^(GDTCOREventDropReason _, NSSet *__) { + ^(GDTCOREventDropReason __unused _, NSSet *__unused __) { + // This should not be fulfilled. [droppedEventsAreLoggedExpectation fulfill]; }; @@ -653,9 +638,9 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseSentExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation + self.testStorage.batchWithEventSelectorExpectation, getAndResetExpectation, + responseSentExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 enforceOrder:YES]; @@ -663,7 +648,7 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed } - (void) - testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToTransientError_ThenMetricsAreConfirmedWithFailure { + testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToTransientError_ThenMetricsAreReStored { // Given // - Generate a test event. [self.generator generateEvent:GDTCOREventQoSFast]; @@ -682,25 +667,18 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake target:kGDTCORTargetTest]; - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return YES; - }; - GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; + XCTestExpectation *getAndResetExpectation = + [self expectationWithDescription:@"getAndResetExpectation"]; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; + [getAndResetExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *metrics) { + [offerMetricsExpectation fulfill]; XCTAssertEqualObjects(metrics, dummyMetrics); }; @@ -708,7 +686,8 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed [self expectationWithDescription:@"droppedEventsAreLoggedExpectation"]; droppedEventsAreLoggedExpectation.inverted = YES; metricsControllerFake.onLogEventsDroppedHandler = - ^(GDTCOREventDropReason _, NSSet *__) { + ^(GDTCOREventDropReason __unused _, NSSet *__unused __) { + // This should not be fulfilled. [droppedEventsAreLoggedExpectation fulfill]; }; @@ -722,9 +701,9 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed // Then [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseFailedExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, getAndResetExpectation, + responseFailedExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 @@ -733,7 +712,7 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed } - (void) - testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToNonTransientError_ThenMetricsAreConfirmedWithFailureAndDroppedBatchEventAreLogged { + testUploadTarget_WhenBatchWithMetricsAreNotUploadedDueToNonTransientError_ThenMetricsAreReStoredAndDroppedBatchEventAreLogged { // Given // - Generate a test event. [self.generator generateEvent:GDTCOREventQoSFast]; @@ -752,25 +731,18 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed [[GDTCORRegistrar sharedInstance] registerMetricsController:metricsControllerFake target:kGDTCORTargetTest]; - XCTestExpectation *targetSupportsMetricsCollectionExpectation = - [self expectationWithDescription:@"targetSupportsMetricsCollectionExpectation"]; - metricsControllerFake.onTargetSupportsMetricsCollectionHandler = ^BOOL(GDTCORTarget _) { - [targetSupportsMetricsCollectionExpectation fulfill]; - return YES; - }; - GDTCORMetrics *dummyMetrics = [[GDTCORMetrics alloc] init]; - XCTestExpectation *fetchMetricsExpectation = - [self expectationWithDescription:@"fetchMetricsExpectation"]; + XCTestExpectation *getAndResetExpectation = + [self expectationWithDescription:@"getAndResetExpectation"]; metricsControllerFake.onGetAndResetMetricsHandler = ^FBLPromise * { - [fetchMetricsExpectation fulfill]; + [getAndResetExpectation fulfill]; return [FBLPromise resolvedWith:dummyMetrics]; }; - XCTestExpectation *metricsConfirmationExpectation = - [self expectationWithDescription:@"metricsConfirmationExpectation"]; - metricsControllerFake.onConfirmMetricsHandler = ^(GDTCORMetrics *metrics) { - [metricsConfirmationExpectation fulfill]; + XCTestExpectation *offerMetricsExpectation = + [self expectationWithDescription:@"offerMetricsExpectation"]; + metricsControllerFake.onOfferMetricsHandler = ^(GDTCORMetrics *metrics) { + [offerMetricsExpectation fulfill]; XCTAssertEqualObjects(metrics, dummyMetrics); }; @@ -793,9 +765,9 @@ - (void)testUploadTarget_WhenBatchWithMetricsAreUploaded_ThenMetricsAreConfirmed // Then [self waitForExpectations:@[ self.testStorage.batchIDsForTargetExpectation, hasEventsExpectation, - self.testStorage.batchWithEventSelectorExpectation, targetSupportsMetricsCollectionExpectation, - fetchMetricsExpectation, responseFailedExpectation, metricsConfirmationExpectation, - droppedEventsAreLoggedExpectation, self.testStorage.removeBatchWithoutDeletingEventsExpectation, + self.testStorage.batchWithEventSelectorExpectation, getAndResetExpectation, + responseFailedExpectation, offerMetricsExpectation, droppedEventsAreLoggedExpectation, + self.testStorage.removeBatchWithoutDeletingEventsExpectation, self.testStorage.removeBatchAndDeleteEventsExpectation ] timeout:1 diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m index 92e56d0e..e57e60d7 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m @@ -61,7 +61,7 @@ - (instancetype)initWithDroppedEventCounterByMappingID: (NSDictionary *)droppedEventCounterByMappingID { self = [super init]; if (self) { - _droppedEventCounterByMappingID = droppedEventCounterByMappingID; + _droppedEventCounterByMappingID = [droppedEventCounterByMappingID copy]; } return self; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m index b6eb74fc..c9fafa44 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -28,12 +28,12 @@ - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate bundleID:(NSString *)bundleID { self = [super init]; if (self) { - _collectionStartDate = collectionStartDate; - _collectionEndDate = collectionEndDate; + _collectionStartDate = [collectionStartDate copy]; + _collectionEndDate = [collectionEndDate copy]; _droppedEventCounter = droppedEventCounter; _currentCacheSize = currentCacheSize; _maxCacheSize = maxCacheSize; - _bundleID = bundleID; + _bundleID = [bundleID copy]; } return self; } diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index 00f01b13..20f696d2 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -47,6 +47,7 @@ + (void)load { [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] target:kGDTCORTargetTest]; #endif // GDT_TEST + // Only the Firelog backend supports metrics collection. [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] target:kGDTCORTargetCSH]; [[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance] @@ -185,25 +186,6 @@ - (instancetype)initWithStorage:(id)storage { return [_storage fetchAndUpdateMetricsWithHandler:handler]; } -- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { - switch (target) { - // Only the Firelog backend supports metrics collection. - case kGDTCORTargetFLL: - case kGDTCORTargetCSH: - case kGDTCORTargetTest: - return YES; - - case kGDTCORTargetCCT: - case kGDTCORTargetINT: - return NO; - } - - GDTCORLogDebug(@"This code path shouldn't be reached." - @"Invalid target %ld does not support metrics collection", - (long)target); - return NO; -} - #pragma mark - GDTCORStorageDelegate - (void)storage:(id)storage diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index 1f39d08e..cd94b285 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -32,7 +32,7 @@ - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate counter:(GDTCOREventMetricsCounter *)counter { self = [super init]; if (self) { - _collectionStartDate = collectionStartDate; + _collectionStartDate = [collectionStartDate copy]; _droppedEventCounter = counter; } return self; diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index 713c05aa..9e02851d 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -28,7 +28,8 @@ NS_ASSUME_NONNULL_BEGIN /// A storage delegate that can perform metrics related tasks. @protocol GDTCORMetricsControllerProtocol -/// Updates the metric's event counter for the given events dropped for a given reason. +/// Updates the corresponding dropped event counters for the given events dropped for a given +/// reason. /// @param reason The reason why the events are being dropped. /// @param events The events that being dropped. - (FBLPromise *)logEventsDroppedForReason:(GDTCOREventDropReason)reason @@ -43,15 +44,10 @@ NS_ASSUME_NONNULL_BEGIN /// @param metrics The metrics to offer for storage. - (FBLPromise *)offerMetrics:(GDTCORMetrics *)metrics; -/// Returns whether or not a given target supports metrics collection. -/// @param target A target that may or may not support metrics collection. -/// @return `YES` if the given target supports metrics collection; otherwise, `NO`. -- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target; - @end /// Returns a metrics controller instance for the given target. -/// @param target The target to retireve a corresponding metrics controller from. +/// @param target The target to retrieve a corresponding metrics controller from. /// @return The given target's corresponding metrics controller instance, or `nil` if it does not /// have one. FOUNDATION_EXPORT diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h index 132c4d4d..2a3ae0e9 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h @@ -158,6 +158,7 @@ typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID, (GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError))handler; +/// Fetches and returns storage metadata. - (FBLPromise *)fetchStorageMetadata; /** See `hasEventsForTarget:onComplete:`. diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index 2163fedd..bb22286f 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -1,18 +1,16 @@ -/* - * Copyright 2022 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. - */ +// Copyright 2022 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 diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h index 22559399..88dbaea8 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.h @@ -32,9 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, copy, nullable) FBLPromise * (^onGetAndResetMetricsHandler) (void); -@property(nonatomic, copy, nullable) void (^onConfirmMetricsHandler)(GDTCORMetrics *metrics); - -@property(nonatomic, copy, nullable) BOOL (^onTargetSupportsMetricsCollectionHandler)(GDTCORTarget); +@property(nonatomic, copy, nullable) void (^onOfferMetricsHandler)(GDTCORMetrics *metrics); @property(nonatomic, copy, nullable) void (^onStorageDidDropEvent)(GDTCOREvent *event); diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m index 9375624b..b221d639 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORMetricsControllerFake.m @@ -42,23 +42,14 @@ @implementation GDTCORMetricsControllerFake } - (FBLPromise *)offerMetrics:(GDTCORMetrics *)metrics { - if (self.onConfirmMetricsHandler) { - self.onConfirmMetricsHandler(metrics); + if (self.onOfferMetricsHandler) { + self.onOfferMetricsHandler(metrics); } else { [self doesNotRecognizeSelector:_cmd]; } return [FBLPromise resolvedWith:nil]; } -- (BOOL)isMetricsCollectionSupportedForTarget:(GDTCORTarget)target { - if (self.onTargetSupportsMetricsCollectionHandler) { - return self.onTargetSupportsMetricsCollectionHandler(target); - } else { - [self doesNotRecognizeSelector:_cmd]; - return NO; - } -} - - (void)storage:(id)storage didDropEvent:(GDTCOREvent *)event { if (self.onStorageDidDropEvent) { return self.onStorageDidDropEvent(event); diff --git a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m index 23567c78..cb596e60 100644 --- a/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m +++ b/GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m @@ -107,6 +107,7 @@ - (void)checkForExpirations { } - (nonnull FBLPromise *> *)batchIDsForTarget:(GDTCORTarget)target { + NSAssert(NO, @"This API should be implemented if this fake is used in tests."); return [FBLPromise resolvedWith:nil]; } diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index fbd4d7c9..fc107708 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -400,6 +400,38 @@ - (void)testFetchAndUpdateMetrics_WhenUpdateFails_RejectsPromise { XCTAssert(fetchAndUpdatePromise.isRejected); } +- (void)testMetricsStorageLocationRegressions { + // Given + // - Initially, there should be no library data stored. + NSString *metricsMetadataPath = [GDTCORFlatFileStorage.libraryDataStoragePath + stringByAppendingPathComponent:@"metrics_metadata"]; + NSError *error; + XCTAssertNil([[NSFileManager defaultManager] contentsOfDirectoryAtPath:metricsMetadataPath + error:&error]); + XCTAssertNotNil(error); + + // When + __auto_type fetchAndUpdatePromise = [GDTCORFlatFileStorage.sharedInstance + fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( + GDTCORMetricsMetadata *_Nullable fetchedMetadata, NSError *_Nullable fetchError) { + XCTAssertNil(fetchedMetadata); + XCTAssertNotNil(fetchError); + return [GDTCORMetricsMetadata + metadataWithCollectionStartDate:[NSDate date] + eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + }]; + + // Then + FBLWaitForPromisesWithTimeout(0.5); + XCTAssert(fetchAndUpdatePromise.isFulfilled); + // - Finally, there should be only one new file. + NSArray *contentsPaths = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:[GDTCORFlatFileStorage libraryDataStoragePath] + error:&error]; + XCTAssertEqual(contentsPaths.count, 1); + XCTAssertTrue([NSFileManager.defaultManager fileExistsAtPath:metricsMetadataPath]); +} + - (void)testSaveAndLoadLibraryData { __weak NSData *weakData; NSString *dataKey = NSStringFromSelector(_cmd); diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m index 1850ba02..eeca15f6 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -350,33 +350,4 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCombinedCounter); } -- (void)testIsMetricsCollectionSupportedForTarget { - // Given - GDTCORMetricsController *metricsController = - [[GDTCORMetricsController alloc] initWithStorage:[GDTCORStorageFake storageFake]]; - NSDictionary *targetSupportMatrix = @{ - @(kGDTCORTargetTest) : @YES, - @(kGDTCORTargetFLL) : @YES, - @(kGDTCORTargetCSH) : @YES, - @(kGDTCORTargetCCT) : @NO, - @(kGDTCORTargetINT) : @NO - }; - - for (NSNumber *target in targetSupportMatrix) { - // When - BOOL isSupported = [metricsController isMetricsCollectionSupportedForTarget:target.intValue]; - // Then - BOOL expectedIsSupported = [(NSNumber *)targetSupportMatrix[target] boolValue]; - XCTAssertEqual(isSupported, expectedIsSupported, @"Failed for GDTCORTarget: %@", target); - } -} - -- (void)DISABLED_testGDTCORMetricsControllerInstanceForTarget { - // Metrics controller registration is done at `GDTCORMetricsController +load` time. - // Assert that it is registered for the FLL and CSH targets, but not for CCT. - XCTAssertNotNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetFLL)); - XCTAssertNotNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetCSH)); - XCTAssertNil(GDTCORMetricsControllerInstanceForTarget(kGDTCORTargetCCT)); -} - @end From 51eb4b5943f27e6ca443f762bf6b493fb18b00e8 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:40:10 -0400 Subject: [PATCH 055/131] [Metrics] Rename `GDTCOREventMetricsCounter` to `GDTCORLogSourceMetrics` (#76) * [Metrics] Rename GDTCOREventMetricsCounter to GDTCORLogSourceMetrics * Fix CI * [skip ci] Review --- .../GDTCORMetrics+GDTCCTSupport.m | 53 +-- .../Unit/Helpers/GDTCORMetricsTestHelpers.m | 48 +-- ...ricsCounter.m => GDTCORLogSourceMetrics.m} | 76 ++-- .../GDTCORLibrary/GDTCORMetrics.m | 16 +- .../GDTCORLibrary/GDTCORMetricsController.m | 29 +- .../GDTCORLibrary/GDTCORMetricsMetadata.m | 30 +- .../GDTCORMetricsControllerProtocol.h | 2 +- .../Private/GDTCOREventMetricsCounter.h | 59 ---- .../Private/GDTCORLogSourceMetrics.h | 53 +++ .../GDTCORLibrary/Private/GDTCORMetrics.h | 6 +- .../Private/GDTCORMetricsMetadata.h | 10 +- .../Unit/GDTCOREventMetricsCounterTest.m | 324 ----------------- .../Unit/GDTCORFlatFileStorageTest.m | 8 +- .../Unit/GDTCORLogSourceMetricsTest.m | 330 ++++++++++++++++++ .../Unit/GDTCORMetricsControllerTest.m | 90 +++-- .../Unit/GDTCORMetricsMetadataTest.m | 26 +- 16 files changed, 590 insertions(+), 570 deletions(-) rename GoogleDataTransport/GDTCORLibrary/{GDTCOREventMetricsCounter.m => GDTCORLogSourceMetrics.m} (66%) delete mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h create mode 100644 GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h delete mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m create mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCORLogSourceMetricsTest.m diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m index 94526f05..f83902ba 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m @@ -24,10 +24,21 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h" +typedef NSDictionary GDTCORDroppedEventCounter; + +@interface GDTCORLogSourceMetrics (Internal) + +/// A dictionary of log sources that map to counters that reflect the number of events dropped for a +/// given set of reasons (``GDTCOREventDropReason``). +@property(nonatomic, readonly) + NSDictionary *droppedEventCounterByLogSource; + +@end + @implementation GDTCORMetrics (GDTCCTSupport) - (NSData *)transportBytes { @@ -38,9 +49,9 @@ - (NSData *)transportBytes { clientMetricsProto.window = GDTCCTConstructTimeWindow(self.collectionStartDate, self.collectionEndDate); - clientMetricsProto.log_source_metrics = GDTCCTConstructLogSourceMetrics(self.droppedEventCounter); + clientMetricsProto.log_source_metrics = GDTCCTConstructLogSourceMetrics(self.logSourceMetrics); clientMetricsProto.log_source_metrics_count = - GDTCCTGetLogSourceMetricsCount(self.droppedEventCounter); + GDTCCTGetLogSourceMetricsCount(self.logSourceMetrics); clientMetricsProto.global_metrics = GDTCCTConstructGlobalMetrics(self.currentCacheSize, self.maxCacheSize); @@ -73,41 +84,41 @@ - (NSData *)transportBytes { return CFBridgingRelease(dataRef); } -/// Constructs and returns a ``gdt_client_metrics_LogSourceMetrics`` from the given dropped event -/// counter. -/// @param droppedEventCounter The given dropped event counter. +/// Constructs and returns a ``gdt_client_metrics_LogSourceMetrics`` from the given log source +/// metrics. +/// @param logSourceMetrics The given log source metrics. gdt_client_metrics_LogSourceMetrics *GDTCCTConstructLogSourceMetrics( - GDTCOREventMetricsCounter *droppedEventCounter) { + GDTCORLogSourceMetrics *logSourceMetrics) { // The metrics proto is a repeating field where each element represents the // dropped event data for a log source (mapping ID). - NSUInteger logMetricsCount = [droppedEventCounter.droppedEventCounterByMappingID count]; + NSUInteger logMetricsCount = logSourceMetrics.droppedEventCounterByLogSource.count; gdt_client_metrics_LogSourceMetrics *repeatedLogSourceMetrics = calloc(logMetricsCount, sizeof(gdt_client_metrics_LogSourceMetrics)); // Each log source (mapping ID) has a corresponding dropped event counter. - // Enumerate over the dictionary of mapping IDs and, for each mappingID, - // create a proto representation of the number of events dropped for each - // given reason. + // Enumerate over the dictionary of log source and, for each log source, + // (mapping ID) create a proto representation of the number of events dropped + // for each given reason. __block NSUInteger logSourceIndex = 0; - [droppedEventCounter.droppedEventCounterByMappingID - enumerateKeysAndObjectsUsingBlock:^(NSString *mappingID, - GDTCORDroppedEventCounter *eventCounterForMappingID, + [logSourceMetrics.droppedEventCounterByLogSource + enumerateKeysAndObjectsUsingBlock:^(NSString *logSource, + GDTCORDroppedEventCounter *eventCounterForLogSource, BOOL *__unused _) { // Create the log source proto for the given mapping ID. It contains a // repeating field to encapsulate the number of events dropped for each // given drop reason. __block gdt_client_metrics_LogSourceMetrics logSourceMetrics = gdt_client_metrics_LogSourceMetrics_init_zero; - logSourceMetrics.log_source = GDTCCTEncodeString(mappingID); - logSourceMetrics.log_event_dropped_count = (pb_size_t)eventCounterForMappingID.count; + logSourceMetrics.log_source = GDTCCTEncodeString(logSource); + logSourceMetrics.log_event_dropped_count = (pb_size_t)eventCounterForLogSource.count; logSourceMetrics.log_event_dropped = - calloc(eventCounterForMappingID.count, sizeof(gdt_client_metrics_LogEventDropped)); + calloc(eventCounterForLogSource.count, sizeof(gdt_client_metrics_LogEventDropped)); // Each dropped event counter counts the number of events dropped for // each drop reason. Enumerate over all of these counters to populate // the log source proto's repeating field of event drop data. __block NSUInteger eventCounterIndex = 0; - [eventCounterForMappingID + [eventCounterForLogSource enumerateKeysAndObjectsUsingBlock:^(NSNumber *eventDropReason, NSNumber *droppedEventCount, BOOL *__unused _) { gdt_client_metrics_LogEventDropped droppedEvents = @@ -132,9 +143,9 @@ - (NSData *)transportBytes { } /// Returns the count of log sources that have event drop metrics. -/// @param droppedEventCounter The given dropped event counter. -pb_size_t GDTCCTGetLogSourceMetricsCount(GDTCOREventMetricsCounter *droppedEventCounter) { - return (pb_size_t)droppedEventCounter.droppedEventCounterByMappingID.count; +/// @param logSourceMetrics The given log source metrics. +pb_size_t GDTCCTGetLogSourceMetricsCount(GDTCORLogSourceMetrics *logSourceMetrics) { + return (pb_size_t)logSourceMetrics.droppedEventCounterByLogSource.count; } /// Constructs and returns a ``gdt_client_metrics_TimeWindow`` proto from the given parameters. diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m index 24acc7ca..619de2c5 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.m @@ -14,18 +14,20 @@ #import "GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCORMetricsTestHelpers.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" -#pragma mark - GDTCOREventMetricsCounter + Internal +#pragma mark - GDTCORLogSourceMetrics + Internal -@interface GDTCOREventMetricsCounter (Internal) +typedef NSDictionary GDTCORDroppedEventCounter; + +@interface GDTCORLogSourceMetrics (Internal) /// Initializer exposed for testing. -- (instancetype)initWithDroppedEventCounterByMappingID: - (NSDictionary *)droppedEventCounterByMappingID; +- (instancetype)initWithDroppedEventCounterByLogSource: + (NSDictionary *)droppedEventCounterByLogSource; @end @@ -36,7 +38,7 @@ @interface GDTCORMetrics (Internal) /// Initializer exposed for testing. - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate collectionEndDate:(NSDate *)collectionEndDate - droppedEventCounter:(GDTCOREventMetricsCounter *)droppedEventCounter + logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics currentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize bundleID:(NSString *)bundleID; @@ -48,8 +50,8 @@ @implementation GDTCORMetricsTestHelpers + (GDTCORMetrics *)metricsWithGeneratedDroppedEventCounters { // The below counter's structure was arbitrarily defined. - GDTCOREventMetricsCounter *droppedEventCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ + GDTCORLogSourceMetrics *logSourceMetrics = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{ @"log_src_1" : @{@(GDTCOREventDropReasonUnknown) : @(arc4random_uniform(5))}, @"log_src_2" : @{ @(GDTCOREventDropReasonStorageFull) : @(arc4random_uniform(100)), @@ -68,7 +70,7 @@ + (GDTCORMetrics *)metricsWithGeneratedDroppedEventCounters { return [[GDTCORMetrics alloc] initWithCollectionStartDate:[NSDate distantPast] collectionEndDate:[NSDate distantFuture] - droppedEventCounter:droppedEventCounter + logSourceMetrics:logSourceMetrics currentCacheSize:arc4random_uniform(20 * 1000 * 1000) maxCacheSize:arc4random_uniform(20 * 1000 * 1000) bundleID:@"com.test.bundle"]; @@ -77,7 +79,7 @@ + (GDTCORMetrics *)metricsWithGeneratedDroppedEventCounters { + (GDTCORMetrics *)metricsWithEmptyDroppedEventCounters { return [[GDTCORMetrics alloc] initWithCollectionStartDate:[NSDate distantPast] collectionEndDate:[NSDate distantFuture] - droppedEventCounter:[GDTCOREventMetricsCounter counter] + logSourceMetrics:[GDTCORLogSourceMetrics metrics] currentCacheSize:arc4random_uniform(20 * 1000 * 1000) maxCacheSize:arc4random_uniform(20 * 1000 * 1000) bundleID:@"com.test.bundle"]; @@ -98,33 +100,33 @@ + (void)assertMetrics:(GDTCORMetrics *)metrics NSDate *collectionEndDate = [[NSDate alloc] initWithTimeIntervalSince1970:metricsProto.window.end_ms / 1000]; - // Reconstruct dropped event counter. - GDTCOREventMetricsCounter *droppedEventCounter = [GDTCOREventMetricsCounter counter]; + // Reconstruct log source metrics. + GDTCORLogSourceMetrics *logSourceMetrics = [GDTCORLogSourceMetrics metrics]; for (int logSourceIndex = 0; logSourceIndex < metricsProto.log_source_metrics_count; logSourceIndex++) { - gdt_client_metrics_LogSourceMetrics logSourceMetrics = + gdt_client_metrics_LogSourceMetrics logSourceMetricsProto = metricsProto.log_source_metrics[logSourceIndex]; - NSString *mappingID = [[NSString alloc] initWithBytes:logSourceMetrics.log_source->bytes - length:logSourceMetrics.log_source->size + NSString *logSource = [[NSString alloc] initWithBytes:logSourceMetricsProto.log_source->bytes + length:logSourceMetricsProto.log_source->size encoding:NSUTF8StringEncoding]; - for (int logEventIndex = 0; logEventIndex < logSourceMetrics.log_event_dropped_count; + for (int logEventIndex = 0; logEventIndex < logSourceMetricsProto.log_event_dropped_count; logEventIndex++) { gdt_client_metrics_LogEventDropped logEventDropped = - logSourceMetrics.log_event_dropped[logEventIndex]; + logSourceMetricsProto.log_event_dropped[logEventIndex]; - GDTCOREventMetricsCounter *logSourceDroppedEventCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ - mappingID : @{ + GDTCORLogSourceMetrics *logSourceDroppedEventCounter = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{ + logSource : @{ @(GDTCCTConvertProtoReasonToEventDropReason(logEventDropped.reason)) : @(logEventDropped.events_dropped_count), }, }]; - droppedEventCounter = - [droppedEventCounter counterByMergingWithCounter:logSourceDroppedEventCounter]; + logSourceMetrics = [logSourceMetrics + logSourceMetricsByMergingWithLogSourceMetrics:logSourceDroppedEventCounter]; } } @@ -142,7 +144,7 @@ + (void)assertMetrics:(GDTCORMetrics *)metrics return [[GDTCORMetrics alloc] initWithCollectionStartDate:collectionStartDate collectionEndDate:collectionEndDate - droppedEventCounter:droppedEventCounter + logSourceMetrics:logSourceMetrics currentCacheSize:currentCacheSize maxCacheSize:maxCacheSize bundleID:bundleID]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m similarity index 66% rename from GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m rename to GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m index e57e60d7..aaabb476 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREventMetricsCounter.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m @@ -14,21 +14,32 @@ #import -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" -static NSString *const kDroppedEventCounterByMappingID = @"droppedEventCounterByMappingID"; +static NSString *const kDroppedEventCounterByLogSource = @"droppedEventCounterByLogSource"; -@implementation GDTCOREventMetricsCounter +typedef NSDictionary GDTCORDroppedEventCounter; -+ (instancetype)counter { - return [[self alloc] initWithDroppedEventCounterByMappingID:@{}]; +@interface GDTCORLogSourceMetrics () + +/// A dictionary of log sources that map to counters that reflect the number of events dropped for a +/// given set of reasons (``GDTCOREventDropReason``). +@property(nonatomic, readonly) + NSDictionary *droppedEventCounterByLogSource; + +@end + +@implementation GDTCORLogSourceMetrics + ++ (instancetype)metrics { + return [[self alloc] initWithDroppedEventCounterByLogSource:@{}]; } -+ (instancetype)counterWithEvents:(NSArray *)events ++ (instancetype)metricsWithEvents:(NSArray *)events droppedForReason:(GDTCOREventDropReason)reason { - NSMutableDictionary *eventCounterByMappingID = + NSMutableDictionary *eventCounterByLogSource = [NSMutableDictionary dictionary]; for (GDTCOREvent *event in [events copy]) { @@ -41,9 +52,9 @@ + (instancetype)counterWithEvents:(NSArray *)events // If the dropped event counter for this event's mapping ID is `nil`, // an empty mutable counter is returned. NSMutableDictionary *eventCounter = - [NSMutableDictionary dictionaryWithDictionary:eventCounterByMappingID[event.mappingID]]; + [NSMutableDictionary dictionaryWithDictionary:eventCounterByLogSource[event.mappingID]]; - // Increment the dropped event counter for the given reason. + // Increment the log source metrics for the given reason. NSInteger currentEventCountForReason = [eventCounter[@(reason)] integerValue]; NSInteger updatedEventCountForReason = currentEventCountForReason + 1; @@ -51,26 +62,27 @@ + (instancetype)counterWithEvents:(NSArray *)events // Update the mapping ID's (log source's) event counter with an immutable // copy of the updated counter. - eventCounterByMappingID[event.mappingID] = [eventCounter copy]; + eventCounterByLogSource[event.mappingID] = [eventCounter copy]; } - return [[self alloc] initWithDroppedEventCounterByMappingID:[eventCounterByMappingID copy]]; + return [[self alloc] initWithDroppedEventCounterByLogSource:[eventCounterByLogSource copy]]; } -- (instancetype)initWithDroppedEventCounterByMappingID: - (NSDictionary *)droppedEventCounterByMappingID { +- (instancetype)initWithDroppedEventCounterByLogSource: + (NSDictionary *)droppedEventCounterByLogSource { self = [super init]; if (self) { - _droppedEventCounterByMappingID = [droppedEventCounterByMappingID copy]; + _droppedEventCounterByLogSource = [droppedEventCounterByLogSource copy]; } return self; } -- (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter { - // Create a new counter by merging the current counter with the given counter. - NSDictionary *mergedEventCounterByMappingID = [[self - class] dictionaryByMergingDictionary:self.droppedEventCounterByMappingID - withOtherDictionary:counter.droppedEventCounterByMappingID +- (GDTCORLogSourceMetrics *)logSourceMetricsByMergingWithLogSourceMetrics: + (GDTCORLogSourceMetrics *)metrics { + // Create new log source metrics by merging the current metrics with the given metrics. + NSDictionary *mergedEventCounterByLogSource = [[self + class] dictionaryByMergingDictionary:self.droppedEventCounterByLogSource + withOtherDictionary:metrics.droppedEventCounterByLogSource uniquingKeysWithBlock:^NSDictionary *(NSDictionary *eventCounter1, NSDictionary *eventCounter2) { return [[self class] @@ -83,7 +95,7 @@ - (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCo }]; return - [[[self class] alloc] initWithDroppedEventCounterByMappingID:mergedEventCounterByMappingID]; + [[[self class] alloc] initWithDroppedEventCounterByLogSource:mergedEventCounterByLogSource]; } /// Creates a new dictionary by merging together two given dictionaries. @@ -112,9 +124,9 @@ + (NSDictionary *)dictionaryByMergingDictionary:(NSDictionary *)dictionary #pragma mark - Equality -- (BOOL)isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)otherDroppedEventCounter { - return [_droppedEventCounterByMappingID - isEqualToDictionary:otherDroppedEventCounter.droppedEventCounterByMappingID]; +- (BOOL)isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)otherMetrics { + return [_droppedEventCounterByLogSource + isEqualToDictionary:otherMetrics.droppedEventCounterByLogSource]; } - (BOOL)isEqual:(nullable id)object { @@ -130,11 +142,11 @@ - (BOOL)isEqual:(nullable id)object { return NO; } - return [self isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)object]; + return [self isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)object]; } - (NSUInteger)hash { - return [_droppedEventCounterByMappingID hash]; + return [_droppedEventCounterByLogSource hash]; } #pragma mark - NSSecureCoding @@ -144,29 +156,29 @@ + (BOOL)supportsSecureCoding { } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { - NSDictionary *droppedEventCounterByMappingID = + NSDictionary *droppedEventCounterByLogSource = [coder decodeObjectOfClasses: [NSSet setWithArray:@[ NSDictionary.class, NSString.class, NSNumber.class ]] - forKey:kDroppedEventCounterByMappingID]; + forKey:kDroppedEventCounterByLogSource]; - if (!droppedEventCounterByMappingID || - ![droppedEventCounterByMappingID isKindOfClass:[NSDictionary class]]) { + if (!droppedEventCounterByLogSource || + ![droppedEventCounterByLogSource isKindOfClass:[NSDictionary class]]) { // If any of the fields are corrupted, the initializer should fail. return nil; } - return [self initWithDroppedEventCounterByMappingID:droppedEventCounterByMappingID]; + return [self initWithDroppedEventCounterByLogSource:droppedEventCounterByLogSource]; } - (void)encodeWithCoder:(nonnull NSCoder *)coder { - [coder encodeObject:self.droppedEventCounterByMappingID forKey:kDroppedEventCounterByMappingID]; + [coder encodeObject:self.droppedEventCounterByLogSource forKey:kDroppedEventCounterByLogSource]; } #pragma mark - Description - (NSString *)description { return [NSString - stringWithFormat:@"%@ %@", [super description], self.droppedEventCounterByMappingID]; + stringWithFormat:@"%@ %@", [super description], self.droppedEventCounterByLogSource]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m index c9fafa44..ffbf0e11 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetrics.m @@ -14,7 +14,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" @@ -22,7 +22,7 @@ @implementation GDTCORMetrics - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate collectionEndDate:(NSDate *)collectionEndDate - droppedEventCounter:(GDTCOREventMetricsCounter *)droppedEventCounter + logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics currentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize bundleID:(NSString *)bundleID { @@ -30,7 +30,7 @@ - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate if (self) { _collectionStartDate = [collectionStartDate copy]; _collectionEndDate = [collectionEndDate copy]; - _droppedEventCounter = droppedEventCounter; + _logSourceMetrics = logSourceMetrics; _currentCacheSize = currentCacheSize; _maxCacheSize = maxCacheSize; _bundleID = [bundleID copy]; @@ -47,7 +47,7 @@ + (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetad return [[GDTCORMetrics alloc] initWithCollectionStartDate:metricsMetadata.collectionStartDate collectionEndDate:collectionEndDate - droppedEventCounter:metricsMetadata.droppedEventCounter + logSourceMetrics:metricsMetadata.logSourceMetrics currentCacheSize:storageMetadata.currentCacheSize maxCacheSize:storageMetadata.maxCacheSize bundleID:bundleID]; @@ -58,7 +58,7 @@ + (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetad - (BOOL)isEqualToMetrics:(GDTCORMetrics *)otherMetrics { return [self.collectionStartDate isEqualToDate:otherMetrics.collectionStartDate] && [self.collectionEndDate isEqualToDate:otherMetrics.collectionEndDate] && - [self.droppedEventCounter isEqualToDroppedEventCounter:otherMetrics.droppedEventCounter] && + [self.logSourceMetrics isEqualToLogSourceMetrics:otherMetrics.logSourceMetrics] && [self.bundleID isEqualToString:otherMetrics.bundleID] && self.currentCacheSize == otherMetrics.currentCacheSize && self.maxCacheSize == otherMetrics.maxCacheSize; @@ -82,7 +82,7 @@ - (BOOL)isEqual:(nullable id)object { - (NSUInteger)hash { return [self.collectionStartDate hash] ^ [self.collectionEndDate hash] ^ - [self.droppedEventCounter hash] ^ [self.bundleID hash] ^ [@(self.currentCacheSize) hash] ^ + [self.logSourceMetrics hash] ^ [self.bundleID hash] ^ [@(self.currentCacheSize) hash] ^ [@(self.maxCacheSize) hash]; } @@ -92,9 +92,9 @@ - (NSString *)description { return [NSString stringWithFormat: @"%@ {\n\tcollectionStartDate: %@,\n\tcollectionEndDate: %@,\n\tcurrentCacheSize: " - @"%llu,\n\tmaxCacheSize: %llu,\n\tbundleID: %@,\n\tdroppedEventCounter: %@}\n", + @"%llu,\n\tmaxCacheSize: %llu,\n\tbundleID: %@,\n\tlogSourceMetrics: %@}\n", [super description], self.collectionStartDate, self.collectionEndDate, - self.currentCacheSize, self.maxCacheSize, self.bundleID, self.droppedEventCounter]; + self.currentCacheSize, self.maxCacheSize, self.bundleID, self.logSourceMetrics]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m index 20f696d2..2978da76 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsController.m @@ -28,8 +28,8 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h" @@ -80,21 +80,21 @@ - (instancetype)initWithStorage:(id)storage { __auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata, NSError *_Nullable fetchError) { - GDTCOREventMetricsCounter *metricsCounter = - [GDTCOREventMetricsCounter counterWithEvents:[events allObjects] droppedForReason:reason]; + GDTCORLogSourceMetrics *logSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:[events allObjects] droppedForReason:reason]; if (metricsMetadata) { - GDTCOREventMetricsCounter *droppedEventCounter = - [metricsMetadata.droppedEventCounter counterByMergingWithCounter:metricsCounter]; + GDTCORLogSourceMetrics *updatedLogSourceMetrics = [metricsMetadata.logSourceMetrics + logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics]; return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[metricsMetadata collectionStartDate] - eventMetricsCounter:droppedEventCounter]; + logSourceMetrics:updatedLogSourceMetrics]; } else { // There was an error (e.g. empty storage); `metricsMetadata` is nil. GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:metricsCounter]; + logSourceMetrics:logSourceMetrics]; } }; @@ -111,9 +111,8 @@ - (instancetype)initWithStorage:(id)storage { } else { GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError); } - return - [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; }; return [_storage fetchAndUpdateMetricsWithHandler:handler] @@ -150,9 +149,9 @@ - (instancetype)initWithStorage:(id)storage { // incorporates the data from the given metrics. return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[metrics collectionStartDate] - eventMetricsCounter: - [metricsMetadata.droppedEventCounter - counterByMergingWithCounter:metrics.droppedEventCounter]]; + logSourceMetrics:[metricsMetadata.logSourceMetrics + logSourceMetricsByMergingWithLogSourceMetrics: + metrics.logSourceMetrics]]; } else { // This catches an edge case where the given metrics to append are // newer than metrics represented by the currently stored metrics @@ -171,14 +170,14 @@ - (instancetype)initWithStorage:(id)storage { // be offered if they were successfully uploaded so their // corresponding metadata can be safely placed back in storage. return [GDTCORMetricsMetadata metadataWithCollectionStartDate:metrics.collectionStartDate - eventMetricsCounter:metrics.droppedEventCounter]; + logSourceMetrics:metrics.logSourceMetrics]; } else { // This catches an edge case where the given metrics are from the // future. If this occurs, ignore them and store an empty metadata // object intended to track metrics metadata from this time forward. return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; } } }; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m index cd94b285..624c8c5c 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORMetricsMetadata.m @@ -16,24 +16,25 @@ #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" static NSString *const kCollectionStartDate = @"collectionStartDate"; -static NSString *const kDroppedEventCounter = @"droppedEventCounter"; +static NSString *const kLogSourceMetrics = @"logSourceMetrics"; @implementation GDTCORMetricsMetadata + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate - eventMetricsCounter:(GDTCOREventMetricsCounter *)eventMetricsCounter { - return [[self alloc] initWithCollectionStartDate:collectedSinceDate counter:eventMetricsCounter]; + logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics { + return [[self alloc] initWithCollectionStartDate:collectedSinceDate + logSourceMetrics:logSourceMetrics]; } - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate - counter:(GDTCOREventMetricsCounter *)counter { + logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics { self = [super init]; if (self) { _collectionStartDate = [collectionStartDate copy]; - _droppedEventCounter = counter; + _logSourceMetrics = logSourceMetrics; } return self; } @@ -42,8 +43,7 @@ - (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate - (BOOL)isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)otherMetricsMetadata { return [self.collectionStartDate isEqualToDate:otherMetricsMetadata.collectionStartDate] && - [self.droppedEventCounter - isEqualToDroppedEventCounter:otherMetricsMetadata.droppedEventCounter]; + [self.logSourceMetrics isEqualToLogSourceMetrics:otherMetricsMetadata.logSourceMetrics]; } - (BOOL)isEqual:(nullable id)object { @@ -63,7 +63,7 @@ - (BOOL)isEqual:(nullable id)object { } - (NSUInteger)hash { - return [self.collectionStartDate hash] ^ [self.droppedEventCounter hash]; + return [self.collectionStartDate hash] ^ [self.logSourceMetrics hash]; } #pragma mark - NSSecureCoding @@ -75,22 +75,22 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { NSDate *collectionStartDate = [coder decodeObjectOfClass:[NSDate class] forKey:kCollectionStartDate]; - GDTCOREventMetricsCounter *droppedEventCounter = - [coder decodeObjectOfClass:[GDTCOREventMetricsCounter class] forKey:kDroppedEventCounter]; + GDTCORLogSourceMetrics *logSourceMetrics = + [coder decodeObjectOfClass:[GDTCORLogSourceMetrics class] forKey:kLogSourceMetrics]; - if (!collectionStartDate || !droppedEventCounter || + if (!collectionStartDate || !logSourceMetrics || ![collectionStartDate isKindOfClass:[NSDate class]] || - ![droppedEventCounter isKindOfClass:[GDTCOREventMetricsCounter class]]) { + ![logSourceMetrics isKindOfClass:[GDTCORLogSourceMetrics class]]) { // If any of the fields are corrupted, the initializer should fail. return nil; } - return [self initWithCollectionStartDate:collectionStartDate counter:droppedEventCounter]; + return [self initWithCollectionStartDate:collectionStartDate logSourceMetrics:logSourceMetrics]; } - (void)encodeWithCoder:(nonnull NSCoder *)coder { [coder encodeObject:self.collectionStartDate forKey:kCollectionStartDate]; - [coder encodeObject:self.droppedEventCounter forKey:kDroppedEventCounter]; + [coder encodeObject:self.logSourceMetrics forKey:kLogSourceMetrics]; } @end diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h index 9e02851d..b4a9f86f 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN /// A storage delegate that can perform metrics related tasks. @protocol GDTCORMetricsControllerProtocol -/// Updates the corresponding dropped event counters for the given events dropped for a given +/// Updates the corresponding log source metricss for the given events dropped for a given /// reason. /// @param reason The reason why the events are being dropped. /// @param events The events that being dropped. diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h deleted file mode 100644 index cc3ed171..00000000 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" - -@class GDTCOREvent; - -typedef NSDictionary GDTCORDroppedEventCounter; - -NS_ASSUME_NONNULL_BEGIN - -// TODO(ncooke3): Consider renaming to `GDTCORMetricsDroppedEventCounter`. -/// A counter object that tracks, per log source, the number of events dropped for a variety of -/// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK. -@interface GDTCOREventMetricsCounter : NSObject - -/// A dictionary of log sources that map to counters that reflect the number of events dropped for a -/// given set of reasons (``GDTCOREventDropReason``). -@property(nonatomic, readonly) - NSDictionary *droppedEventCounterByMappingID; - -/// Creates an empty dropped event counter. -+ (instancetype)counter; - -/// Creates a dropped event counter for a collection of events that were dropped for a given reason. -/// @param events The collection of events that were dropped. -/// @param reason The reason for which given events were dropped. -+ (instancetype)counterWithEvents:(NSArray *)events - droppedForReason:(GDTCOREventDropReason)reason; - -/// This API is unavailable. -- (instancetype)init NS_UNAVAILABLE; - -/// Returns a counter created by merging this counter with the given counter. -/// @param counter The given counter to merge with. -- (GDTCOREventMetricsCounter *)counterByMergingWithCounter:(GDTCOREventMetricsCounter *)counter; - -/// Returns a Boolean value that indicates whether the receiving dropped event counter is equal to -/// the given dropped event counter. -/// @param otherDroppedEventCounter The dropped event counter with which to compare the -/// receiving dropped event counter. -- (BOOL)isEqualToDroppedEventCounter:(GDTCOREventMetricsCounter *)otherDroppedEventCounter; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h new file mode 100644 index 00000000..8722073b --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h @@ -0,0 +1,53 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" + +@class GDTCOREvent; + +NS_ASSUME_NONNULL_BEGIN + +/// A model object that tracks, per log source, the number of events dropped for a variety of +/// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK. +@interface GDTCORLogSourceMetrics : NSObject + +/// Creates an empty log source metrics instance. ++ (instancetype)metrics; + +/// Creates a log source metrics for a collection of events that were dropped for a given reason. +/// @param events The collection of events that were dropped. +/// @param reason The reason for which given events were dropped. ++ (instancetype)metricsWithEvents:(NSArray *)events + droppedForReason:(GDTCOREventDropReason)reason; + +/// This API is unavailable. +- (instancetype)init NS_UNAVAILABLE; + +/// Returns a log source metrics instance created by merging the receiving log +/// source metrics with the given log source metrics. +/// @param logSourceMetrics The given log source metrics to merge with. +- (GDTCORLogSourceMetrics *)logSourceMetricsByMergingWithLogSourceMetrics: + (GDTCORLogSourceMetrics *)logSourceMetrics; + +/// Returns a Boolean value that indicates whether the receiving log source metrics is equal to +/// the given log source metrics. +/// @param otherLogSourceMetrics The log source metrics with which to compare the +/// receiving log source metrics. +- (BOOL)isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)otherLogSourceMetrics; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h index bb22286f..2f3c38fc 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h @@ -18,7 +18,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h" -@class GDTCOREventMetricsCounter; +@class GDTCORLogSourceMetrics; @class GDTCORMetricsMetadata; @class GDTCORStorageMetadata; @@ -30,8 +30,8 @@ NS_ASSUME_NONNULL_BEGIN /// The start of the time window over which the metrics were collected. @property(nonatomic, readonly) NSDate *collectionStartDate; -/// The dropped event counter associated with the metrics. -@property(nonatomic, readonly) GDTCOREventMetricsCounter *droppedEventCounter; +/// The log source metrics associated with the metrics. +@property(nonatomic, readonly) GDTCORLogSourceMetrics *logSourceMetrics; /// The end of the time window over which the metrics were collected. @property(nonatomic, readonly) NSDate *collectionEndDate; diff --git a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h index 0a42b2a5..8214c413 100644 --- a/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h +++ b/GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h @@ -16,7 +16,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" -@class GDTCOREventMetricsCounter; +@class GDTCORLogSourceMetrics; NS_ASSUME_NONNULL_BEGIN @@ -27,14 +27,14 @@ NS_ASSUME_NONNULL_BEGIN /// The start of the time window over which the metrics were collected. @property(nonatomic, copy, readonly) NSDate *collectionStartDate; -/// The dropped event counter associated with the metrics. -@property(nonatomic, copy, readonly) GDTCOREventMetricsCounter *droppedEventCounter; +/// The log source metrics associated with the metrics. +@property(nonatomic, copy, readonly) GDTCORLogSourceMetrics *logSourceMetrics; /// Creates a metrics metadata object with the provided information. /// @param collectedSinceDate The start of the time window over which the metrics were collected. -/// @param eventMetricsCounter The dropped event counter associated with the metrics. +/// @param logSourceMetrics The metrics object that tracks metrics for each log source. + (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate - eventMetricsCounter:(GDTCOREventMetricsCounter *)eventMetricsCounter; + logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics; /// This API is unavailable. - (instancetype)init NS_UNAVAILABLE; diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m deleted file mode 100644 index 37c7908d..00000000 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventMetricsCounterTest.m +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" - -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" - -#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" -#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" -#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" - -@interface GDTCOREventMetricsCounter (Internal) - -/// Initializer exposed for testing. -- (instancetype)initWithDroppedEventCounterByMappingID: - (NSDictionary *)droppedEventCounterByMappingID; - -@end - -@interface GDTCOREventMetricsCounterTest : GDTCORTestCase -@end - -@implementation GDTCOREventMetricsCounterTest - -- (void)testCounterFactoryCreatesAndReturnsCounter { - // Given - GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; - GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; - // Then - XCTAssertFalse(eventMetricsCounter1 == eventMetricsCounter2); -} - -- (void)testCounterFactoryReturnsEmptyCounter { - // Given - GDTCOREventMetricsCounter *eventMetricsCounter = [GDTCOREventMetricsCounter counter]; - // Then - GDTCOREventMetricsCounter *expectedEventMetricsCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{}]; - XCTAssertEqualObjects(eventMetricsCounter, expectedEventMetricsCounter); -} - -- (void)testCounterWithEvents_WhenEventsAreEmpty_CreatesEmptyCounter { - // Given - GDTCOREventMetricsCounter *eventMetricsCounter = - [GDTCOREventMetricsCounter counterWithEvents:@[] - droppedForReason:GDTCOREventDropReasonUnknown]; - // Then - XCTAssertEqualObjects(eventMetricsCounter, [GDTCOREventMetricsCounter counter]); -} - -- (void)testCounterWithEvents { - // Given - NSArray *events1 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest] - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:events1 - droppedForReason:GDTCOREventDropReasonStorageFull]; - - NSArray *events2 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter2 = - [GDTCOREventMetricsCounter counterWithEvents:events2 - droppedForReason:GDTCOREventDropReasonServerError]; - - // When - GDTCOREventMetricsCounter *mergedEventMetricsCounter = - [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; - // - Assert that merging the above counters in other ways result in same counter. - [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; - - // Then - GDTCOREventMetricsCounter *expectedEventMetricsCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ - @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, - @"log_src_2" : @{ - @(GDTCOREventDropReasonStorageFull) : @(1), - @(GDTCOREventDropReasonServerError) : @(1), - }, - }]; - XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); -} - -- (void)testMergingCounters_WhenBothCountersAreEmpty_ReturnsEmptyCounter { - // Given - GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; - GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; - // Then - [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; -} - -- (void)testMergingCounters_WhenGivenCounterIsEmpty_ReturnsReceivingCounter { - // Given - GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"log_src" target:kGDTCORTargetFLL]; - GDTCOREventMetricsCounter *eventMetricsCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:@[ event ] - droppedForReason:GDTCOREventDropReasonUnknown]; - GDTCOREventMetricsCounter *eventMetricsCounter2 = [GDTCOREventMetricsCounter counter]; - // When - GDTCOREventMetricsCounter *mergedCounter = - [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; - [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; - // Then - XCTAssertEqualObjects(mergedCounter, eventMetricsCounter1); - XCTAssertFalse(mergedCounter == eventMetricsCounter1); -} - -- (void)testMergingCounters_WhenCounterKeysDontCollide { - // Given - NSArray *events1 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:events1 - droppedForReason:GDTCOREventDropReasonStorageFull]; - - NSArray *events2 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_4" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_5" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_6" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter2 = - [GDTCOREventMetricsCounter counterWithEvents:events2 - droppedForReason:GDTCOREventDropReasonStorageFull]; - - // When - GDTCOREventMetricsCounter *mergedEventMetricsCounter = - [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; - // - Assert that merging the above counters in other ways result in same counter. - // [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; - - // Then - // - Expect no collisions for both the mapping IDs and drop reasons. - GDTCOREventMetricsCounter *expectedEventMetricsCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ - @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - @"log_src_4" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - @"log_src_5" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - @"log_src_6" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, - }]; - XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); -} - -- (void)testMergingCounters_WhenCounterKeysCollide { - // Given - NSArray *events1 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:events1 - droppedForReason:GDTCOREventDropReasonStorageFull]; - - NSArray *events2 = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter2 = - [GDTCOREventMetricsCounter counterWithEvents:events2 - droppedForReason:GDTCOREventDropReasonStorageFull]; - - // When - GDTCOREventMetricsCounter *mergedEventMetricsCounter = - [eventMetricsCounter1 counterByMergingWithCounter:eventMetricsCounter2]; - // - Assert that merging the above counters in other ways result in same counter. - [self assertMergedCounterFromCounter1:eventMetricsCounter1 counter2:eventMetricsCounter2]; - - // Then - // - Expect resolved collisions for both the mapping IDs and drop reasons. - GDTCOREventMetricsCounter *expectedEventMetricsCounter = - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@{ - @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, - @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, - @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, - }]; - XCTAssertEqualObjects(mergedEventMetricsCounter, expectedEventMetricsCounter); -} - -- (void)testEqualityEdgeCases { - GDTCOREventMetricsCounter *eventMetricsCounter1 = [GDTCOREventMetricsCounter counter]; - GDTCOREventMetricsCounter *eventMetricsCounter2 = eventMetricsCounter1; - XCTAssert([eventMetricsCounter1 isEqual:eventMetricsCounter2]); - XCTAssertFalse([eventMetricsCounter1 isEqual:@"some string"]); - XCTAssertFalse([eventMetricsCounter1 isEqual:nil]); -} - -- (void)testEqualObjectsHaveSameHash { - // Given - GDTCOREventMetricsCounter *eventMetricsCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:@[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - ] - droppedForReason:GDTCOREventDropReasonStorageFull]; - - GDTCOREventMetricsCounter *eventMetricsCounter2 = - [GDTCOREventMetricsCounter counterWithEvents:@[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - ] - droppedForReason:GDTCOREventDropReasonStorageFull]; - - GDTCOREventMetricsCounter *eventMetricsCounter3 = - [GDTCOREventMetricsCounter counterWithEvents:@[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - ] - droppedForReason:GDTCOREventDropReasonStorageFull]; - - // Then - XCTAssertEqualObjects(eventMetricsCounter1, eventMetricsCounter2); - XCTAssertEqual(eventMetricsCounter1.hash, eventMetricsCounter2.hash); - XCTAssertNotEqualObjects(eventMetricsCounter2, eventMetricsCounter3); - XCTAssertNotEqual(eventMetricsCounter2.hash, eventMetricsCounter3.hash); -} - -- (void)testSecureCoding { - // Given - NSArray *events = @[ - [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], - ]; - - GDTCOREventMetricsCounter *eventMetricsCounter = - [GDTCOREventMetricsCounter counterWithEvents:events - droppedForReason:GDTCOREventDropReasonStorageFull]; - - // When - // - Encode the counter. - NSError *encodeError; - NSData *encodedCounter = GDTCOREncodeArchive(eventMetricsCounter, nil, &encodeError); - XCTAssertNil(encodeError); - XCTAssertNotNil(encodedCounter); - - // - Write it to disk. - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"counter.dat"]; - NSError *writeError; - BOOL writeResult = GDTCORWriteDataToFile(encodedCounter, filePath, &writeError); - XCTAssertNil(writeError); - XCTAssertTrue(writeResult); - - // Then - // - Decode the counter from disk. - NSError *decodeError; - GDTCOREventMetricsCounter *decodedCounter = - (GDTCOREventMetricsCounter *)GDTCORDecodeArchiveAtPath(GDTCOREventMetricsCounter.class, - filePath, &decodeError); - XCTAssertNil(decodeError); - XCTAssertNotNil(decodedCounter); - XCTAssertEqualObjects(decodedCounter, eventMetricsCounter); -} - -- (void)testSecureCoding_WhenEncodingIsCorrupted { - // Given - // - Create an invalid instance and write its encoding to a file. When - // decoding, the invalid encoding should be treated as a corrupt encoding. - GDTCOREventMetricsCounter *corruptedMetricsCounter = -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wincompatible-pointer-types" - [[GDTCOREventMetricsCounter alloc] initWithDroppedEventCounterByMappingID:@"corrupted"]; -#pragma clang diagnostic pop - - NSError *encodeError; - NSData *encodedMetricsCounter = GDTCOREncodeArchive(corruptedMetricsCounter, nil, &encodeError); - XCTAssertNil(encodeError); - XCTAssertNotNil(encodedMetricsCounter); - - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"metadata.dat"]; - NSError *writeError; - BOOL writeResult = GDTCORWriteDataToFile(encodedMetricsCounter, filePath, &writeError); - XCTAssertNil(writeError); - XCTAssertTrue(writeResult); - - // When - NSError *decodeError; - GDTCOREventMetricsCounter *decodedMetricsCounter = - (GDTCOREventMetricsCounter *)GDTCORDecodeArchiveAtPath(GDTCOREventMetricsCounter.class, - filePath, &decodeError); - // Then - XCTAssertNotNil(decodeError); - XCTAssertNil(decodedMetricsCounter); -} - -- (void)assertMergedCounterFromCounter1:(GDTCOREventMetricsCounter *)counter1 - counter2:(GDTCOREventMetricsCounter *)counter2 { - // There are three ways to merge the given counters, and all three ways - // should result in the same merged counter. - GDTCOREventMetricsCounter *mergedCounter1 = [counter1 counterByMergingWithCounter:counter2]; - - GDTCOREventMetricsCounter *mergedCounter2 = [counter2 counterByMergingWithCounter:counter1]; - - GDTCOREventMetricsCounter *mergedCounter3 = [[GDTCOREventMetricsCounter.counter - counterByMergingWithCounter:counter1] counterByMergingWithCounter:counter2]; - - XCTAssertEqualObjects(mergedCounter1, mergedCounter2); - XCTAssertEqualObjects(mergedCounter2, mergedCounter3); -} - -@end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index fc107708..3aa492d7 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -18,9 +18,9 @@ #import "FBLPromise+Testing.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h" @@ -302,7 +302,7 @@ - (void)testFetchAndUpdateMetrics { // Given GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; __auto_type fetchAndUpdatePromise1 = [GDTCORFlatFileStorage.sharedInstance fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( @@ -348,7 +348,7 @@ - (void)testFetchAndUpdateMetrics_WhenUpdatedMetadataIsUnexpected_DoesNothing { // Given GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; __auto_type fetchAndUpdatePromise1 = [GDTCORFlatFileStorage.sharedInstance fetchAndUpdateMetricsWithHandler:^GDTCORMetricsMetadata *_Nonnull( @@ -418,7 +418,7 @@ - (void)testMetricsStorageLocationRegressions { XCTAssertNotNil(fetchError); return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; }]; // Then diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORLogSourceMetricsTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORLogSourceMetricsTest.m new file mode 100644 index 00000000..819ad371 --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORLogSourceMetricsTest.m @@ -0,0 +1,330 @@ +// Copyright 2022 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" + +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" + +#pragma mark - GDTCORLogSourceMetrics + Internal + +typedef NSDictionary GDTCORDroppedEventLogSourceMetrics; + +@interface GDTCORLogSourceMetrics (Internal) + +/// Initializer exposed for testing. +- (instancetype)initWithDroppedEventCounterByLogSource: + (NSDictionary *) + droppedEventLogSourceMetricsByMappingID; + +@end + +#pragma mark - GDTCORLogSourceMetricsTest + +@interface GDTCORLogSourceMetricsTest : GDTCORTestCase +@end + +@implementation GDTCORLogSourceMetricsTest + +- (void)testLogSourceMetricsFactoryCreatesAndReturnsLogSourceMetrics { + // Given + GDTCORLogSourceMetrics *logSourceMetrics1 = [GDTCORLogSourceMetrics metrics]; + GDTCORLogSourceMetrics *logSourceMetrics2 = [GDTCORLogSourceMetrics metrics]; + // Then + XCTAssertFalse(logSourceMetrics1 == logSourceMetrics2); +} + +- (void)testLogSourceMetricsFactoryReturnsEmptyLogSourceMetrics { + // Given + GDTCORLogSourceMetrics *logSourceMetrics = [GDTCORLogSourceMetrics metrics]; + // Then + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{}]; + XCTAssertEqualObjects(logSourceMetrics, expectedLogSourceMetrics); +} + +- (void)testLogSourceMetricsWithEvents_WhenEventsAreEmpty_CreatesEmptyLogSourceMetrics { + // Given + GDTCORLogSourceMetrics *logSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:@[] droppedForReason:GDTCOREventDropReasonUnknown]; + // Then + XCTAssertEqualObjects(logSourceMetrics, [GDTCORLogSourceMetrics metrics]); +} + +- (void)testLogSourceMetricsWithEvents { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest] + ]; + + GDTCORLogSourceMetrics *logSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics2 = + [GDTCORLogSourceMetrics metricsWithEvents:events2 + droppedForReason:GDTCOREventDropReasonServerError]; + + // When + GDTCORLogSourceMetrics *mergedLogSourceMetrics = + [logSourceMetrics1 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + // - Assert that merging the above log source metrics in other ways result in same log source + // metrics. + [self assertMergedLogSourceMetricsFromLogSourceMetrics1:logSourceMetrics1 + logSourceMetrics2:logSourceMetrics2]; + + // Then + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_2" : @{ + @(GDTCOREventDropReasonStorageFull) : @(1), + @(GDTCOREventDropReasonServerError) : @(1), + }, + }]; + XCTAssertEqualObjects(mergedLogSourceMetrics, expectedLogSourceMetrics); +} + +- (void)testMergingLogSourceMetrics_WhenBothLogSourceMetricsAreEmpty_ReturnsEmptyLogSourceMetrics { + // Given + GDTCORLogSourceMetrics *logSourceMetrics1 = [GDTCORLogSourceMetrics metrics]; + GDTCORLogSourceMetrics *logSourceMetrics2 = [GDTCORLogSourceMetrics metrics]; + // Then + [self assertMergedLogSourceMetricsFromLogSourceMetrics1:logSourceMetrics1 + logSourceMetrics2:logSourceMetrics2]; +} + +- (void) + testMergingLogSourceMetrics_WhenGivenLogSourceMetricsIsEmpty_ReturnsReceivingLogSourceMetrics { + // Given + GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"log_src" target:kGDTCORTargetFLL]; + GDTCORLogSourceMetrics *logSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:@[ event ] + droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *logSourceMetrics2 = [GDTCORLogSourceMetrics metrics]; + // When + GDTCORLogSourceMetrics *mergedLogSourceMetrics = + [logSourceMetrics1 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + [self assertMergedLogSourceMetricsFromLogSourceMetrics1:logSourceMetrics1 + logSourceMetrics2:logSourceMetrics2]; + // Then + XCTAssertEqualObjects(mergedLogSourceMetrics, logSourceMetrics1); + XCTAssertFalse(mergedLogSourceMetrics == logSourceMetrics1); +} + +- (void)testMergingLogSourceMetrics_WhenLogSourceMetricsKeysDontCollide { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_4" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_5" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_6" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics2 = + [GDTCORLogSourceMetrics metricsWithEvents:events2 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + GDTCORLogSourceMetrics *mergedLogSourceMetrics = + [logSourceMetrics1 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + // - Assert that merging the above log source metrics in other ways result in same log source + // metrics. + [self assertMergedLogSourceMetricsFromLogSourceMetrics1:logSourceMetrics1 + logSourceMetrics2:logSourceMetrics2]; + + // Then + // - Expect no collisions for both the mapping IDs and drop reasons. + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_4" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_5" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + @"log_src_6" : @{@(GDTCOREventDropReasonStorageFull) : @(1)}, + }]; + XCTAssertEqualObjects(mergedLogSourceMetrics, expectedLogSourceMetrics); +} + +- (void)testMergingLogSourceMetrics_WhenLogSourceMetricsKeysCollide { + // Given + NSArray *events1 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:events1 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + NSArray *events2 = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_3" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics2 = + [GDTCORLogSourceMetrics metricsWithEvents:events2 + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + GDTCORLogSourceMetrics *mergedLogSourceMetrics = + [logSourceMetrics1 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + // - Assert that merging the above log source metrics in other ways result in same log source + // metrics. + [self assertMergedLogSourceMetricsFromLogSourceMetrics1:logSourceMetrics1 + logSourceMetrics2:logSourceMetrics2]; + + // Then + // - Expect resolved collisions for both the mapping IDs and drop reasons. + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@{ + @"log_src_1" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_2" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + @"log_src_3" : @{@(GDTCOREventDropReasonStorageFull) : @(2)}, + }]; + XCTAssertEqualObjects(mergedLogSourceMetrics, expectedLogSourceMetrics); +} + +- (void)testEqualityEdgeCases { + GDTCORLogSourceMetrics *logSourceMetrics1 = [GDTCORLogSourceMetrics metrics]; + GDTCORLogSourceMetrics *logSourceMetrics2 = logSourceMetrics1; + XCTAssert([logSourceMetrics1 isEqual:logSourceMetrics2]); + XCTAssertFalse([logSourceMetrics1 isEqual:@"some string"]); + XCTAssertFalse([logSourceMetrics1 isEqual:nil]); +} + +- (void)testEqualObjectsHaveSameHash { + // Given + GDTCORLogSourceMetrics *logSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCORLogSourceMetrics *logSourceMetrics2 = + [GDTCORLogSourceMetrics metricsWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + GDTCORLogSourceMetrics *logSourceMetrics3 = + [GDTCORLogSourceMetrics metricsWithEvents:@[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ] + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // Then + XCTAssertEqualObjects(logSourceMetrics1, logSourceMetrics2); + XCTAssertEqual(logSourceMetrics1.hash, logSourceMetrics2.hash); + XCTAssertNotEqualObjects(logSourceMetrics2, logSourceMetrics3); + XCTAssertNotEqual(logSourceMetrics2.hash, logSourceMetrics3.hash); +} + +- (void)testSecureCoding { + // Given + NSArray *events = @[ + [[GDTCOREvent alloc] initWithMappingID:@"log_src_1" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], + ]; + + GDTCORLogSourceMetrics *logSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; + + // When + // - Encode the log source metrics. + NSError *encodeError; + NSData *encodedLogSourceMetrics = GDTCOREncodeArchive(logSourceMetrics, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedLogSourceMetrics); + + // Then + // - Decode the log source metrics from disk. + NSError *decodeError; + GDTCORLogSourceMetrics *decodedLogSourceMetrics = (GDTCORLogSourceMetrics *)GDTCORDecodeArchive( + GDTCORLogSourceMetrics.class, encodedLogSourceMetrics, &decodeError); + XCTAssertNil(decodeError); + XCTAssertNotNil(decodedLogSourceMetrics); + XCTAssertEqualObjects(decodedLogSourceMetrics, logSourceMetrics); +} + +- (void)testSecureCoding_WhenEncodingIsCorrupted { + // Given + // - Create an invalid instance and write its encoding to a file. When + // decoding, the invalid encoding should be treated as a corrupt encoding. + GDTCORLogSourceMetrics *corruptedMetricsLogSourceMetrics = +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wincompatible-pointer-types" + [[GDTCORLogSourceMetrics alloc] initWithDroppedEventCounterByLogSource:@"corrupted"]; +#pragma clang diagnostic pop + + NSError *encodeError; + NSData *encodedMetricsLogSourceMetrics = + GDTCOREncodeArchive(corruptedMetricsLogSourceMetrics, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedMetricsLogSourceMetrics); + + // When + NSError *decodeError; + GDTCORLogSourceMetrics *decodedMetricsLogSourceMetrics = + (GDTCORLogSourceMetrics *)GDTCORDecodeArchive(GDTCORLogSourceMetrics.class, + encodedMetricsLogSourceMetrics, &decodeError); + // Then + XCTAssertNotNil(decodeError); + XCTAssertNil(decodedMetricsLogSourceMetrics); +} + +- (void) + assertMergedLogSourceMetricsFromLogSourceMetrics1:(GDTCORLogSourceMetrics *)logSourceMetrics1 + logSourceMetrics2:(GDTCORLogSourceMetrics *)logSourceMetrics2 { + // There are three ways to merge the given log source metrics, and all three ways + // should result in the same merged log source metrics. + GDTCORLogSourceMetrics *mergedLogSourceMetrics1 = + [logSourceMetrics1 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + + GDTCORLogSourceMetrics *mergedLogSourceMetrics2 = + [logSourceMetrics2 logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics1]; + + GDTCORLogSourceMetrics *mergedLogSourceMetrics3 = [[GDTCORLogSourceMetrics.metrics + logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics1] + logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics2]; + + XCTAssertEqualObjects(mergedLogSourceMetrics1, mergedLogSourceMetrics2); + XCTAssertEqualObjects(mergedLogSourceMetrics2, mergedLogSourceMetrics3); +} + +@end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m index eeca15f6..d16ffd7f 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsControllerTest.m @@ -20,7 +20,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h" @@ -58,9 +58,9 @@ - (void)testLoggingEvents_WhenNoMetricsAreStored_StoresEventsAsMetrics { [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], ]]; - GDTCOREventMetricsCounter *expectedCounter = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; // When [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; @@ -69,7 +69,7 @@ - (void)testLoggingEvents_WhenNoMetricsAreStored_StoresEventsAsMetrics { __auto_type metricsPromise = [metricsController getAndResetMetrics]; FBLWaitForPromisesWithTimeout(0.5); - XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounter); + XCTAssertEqualObjects([metricsPromise.value logSourceMetrics], expectedLogSourceMetrics); } - (void)testLoggingEvents_WhenMetricsAreStored_StoresEventsAsMetrics { @@ -98,18 +98,18 @@ - (void)testLoggingEvents_WhenMetricsAreStored_StoresEventsAsMetrics { __auto_type metricsPromise = [metricsController getAndResetMetrics]; FBLWaitForPromisesWithTimeout(0.5); - GDTCOREventMetricsCounter *expectedCounter1 = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents1 allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *expectedLogSourceMetrics1 = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents1 allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; - GDTCOREventMetricsCounter *expectedCounter2 = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents2 allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *expectedLogSourceMetrics2 = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents2 allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; - GDTCOREventMetricsCounter *expectedCounterCombined = - [expectedCounter1 counterByMergingWithCounter:expectedCounter2]; + GDTCORLogSourceMetrics *expectedLogSourceMetricsCombined = [expectedLogSourceMetrics1 + logSourceMetricsByMergingWithLogSourceMetrics:expectedLogSourceMetrics2]; - XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounterCombined); + XCTAssertEqualObjects([metricsPromise.value logSourceMetrics], expectedLogSourceMetricsCombined); } - (void)testGetAndResetMetrics_WhenNoMetricsDataIsStored_ReturnsRejectedPromise { @@ -133,9 +133,8 @@ - (void)testGetAndResetMetrics_WhenNoMetricsDataIsStored_ReturnsRejectedPromise // Dates should be roughly equal (within one second of each other). XCTAssertEqualWithAccuracy(metricsPromise2.value.collectionStartDate.timeIntervalSince1970, NSDate.date.timeIntervalSince1970, 1); - // The dropped event counter should be empty. - XCTAssertEqualObjects([metricsPromise2.value droppedEventCounter], - [GDTCOREventMetricsCounter counter]); + // The log source metrics should be empty. + XCTAssertEqualObjects([metricsPromise2.value logSourceMetrics], [GDTCORLogSourceMetrics metrics]); } - (void)testGetAndResetMetrics_WhenMetricsDataIsStored_ReturnsMetrics { @@ -149,19 +148,19 @@ - (void)testGetAndResetMetrics_WhenMetricsDataIsStored_ReturnsMetrics { [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], ]]; - GDTCOREventMetricsCounter *expectedCounter = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; // When [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; // Then - // - Assert that the retrieved metrics have the expected dropped event counter. + // - Assert that the retrieved metrics have the expected log source metrics. __auto_type metricsPromise1 = [metricsController getAndResetMetrics]; FBLWaitForPromisesWithTimeout(0.5); - XCTAssertEqualObjects([metricsPromise1.value droppedEventCounter], expectedCounter); + XCTAssertEqualObjects([metricsPromise1.value logSourceMetrics], expectedLogSourceMetrics); // - Assert that metrics metadata was reset to start tracking metrics data // from this point forward. @@ -171,9 +170,8 @@ - (void)testGetAndResetMetrics_WhenMetricsDataIsStored_ReturnsMetrics { // Dates should be roughly equal (within one second of each other). XCTAssertEqualWithAccuracy(metricsPromise2.value.collectionStartDate.timeIntervalSince1970, NSDate.date.timeIntervalSince1970, 1); - // The dropped event counter should be empty. - XCTAssertEqualObjects([metricsPromise2.value droppedEventCounter], - [GDTCOREventMetricsCounter counter]); + // The log source metrics should be empty. + XCTAssertEqualObjects([metricsPromise2.value logSourceMetrics], [GDTCORLogSourceMetrics metrics]); } - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreValid_ThenAcceptMetrics { @@ -184,7 +182,7 @@ - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreValid_ThenAcceptMet // - Create valid metrics to offer. GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; GDTCORStorageMetadata *storageMetadata = [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB @@ -207,8 +205,7 @@ - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreValid_ThenAcceptMet FBLWaitForPromisesWithTimeout(0.5); XCTAssertEqualObjects([metricsPromise1.value collectionStartDate], metricsMetadata.collectionStartDate); - XCTAssertEqualObjects([metricsPromise1.value droppedEventCounter], - metricsMetadata.droppedEventCounter); + XCTAssertEqualObjects([metricsPromise1.value logSourceMetrics], metricsMetadata.logSourceMetrics); } - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreInvalid_ThenRejectMetrics { @@ -219,7 +216,7 @@ - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreInvalid_ThenRejectM // - Create invalid metrics to offer. GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; GDTCORStorageMetadata *storageMetadata = [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB @@ -243,9 +240,8 @@ - (void)testOfferMetrics_WhenStorageErrorAndOfferedMetricsAreInvalid_ThenRejectM // Dates should be roughly equal (within one second of each other). XCTAssertEqualWithAccuracy(metricsPromise.value.collectionStartDate.timeIntervalSince1970, NSDate.date.timeIntervalSince1970, 1); - // The dropped event counter should be empty. - XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], - [GDTCOREventMetricsCounter counter]); + // The log source metrics should be empty. + XCTAssertEqualObjects([metricsPromise.value logSourceMetrics], [GDTCORLogSourceMetrics metrics]); } - (void)testOfferMetrics_WhenOfferedMetricsAreInvalid_ThenRejectMetrics { @@ -260,16 +256,16 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreInvalid_ThenRejectMetrics { [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], ]]; - GDTCOREventMetricsCounter *expectedCounter = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *expectedLogSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; // - Create invalid metrics to offer. GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; GDTCORStorageMetadata *storageMetadata = [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB @@ -289,7 +285,7 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreInvalid_ThenRejectMetrics { // - Assert that retrieving the metrics contains the metadata from the original metrics. __auto_type metricsPromise = [metricsController getAndResetMetrics]; FBLWaitForPromisesWithTimeout(0.5); - XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCounter); + XCTAssertEqualObjects([metricsPromise.value logSourceMetrics], expectedLogSourceMetrics); } - (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { @@ -304,9 +300,9 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { [[GDTCOREvent alloc] initWithMappingID:@"log_source_2" target:kGDTCORTargetTest], ]]; - GDTCOREventMetricsCounter *originalCounter = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEvents allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *originalLogSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEvents allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; [metricsController logEventsDroppedForReason:GDTCOREventDropReasonUnknown events:droppedEvents]; @@ -317,13 +313,13 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { [[GDTCOREvent alloc] initWithMappingID:@"log_source_3" target:kGDTCORTargetTest], ]]; - GDTCOREventMetricsCounter *counterToOffer = - [GDTCOREventMetricsCounter counterWithEvents:[droppedEventsToOffer allObjects] - droppedForReason:GDTCOREventDropReasonUnknown]; + GDTCORLogSourceMetrics *counterToOffer = + [GDTCORLogSourceMetrics metricsWithEvents:[droppedEventsToOffer allObjects] + droppedForReason:GDTCOREventDropReasonUnknown]; GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] - eventMetricsCounter:counterToOffer]; + logSourceMetrics:counterToOffer]; GDTCORStorageMetadata *storageMetadata = [GDTCORStorageMetadata metadataWithCurrentCacheSize:15 * 1000 * 1000 // 15 MB @@ -345,9 +341,9 @@ - (void)testOfferMetrics_WhenOfferedMetricsAreValid_ThenAcceptMetrics { __auto_type metricsPromise = [metricsController getAndResetMetrics]; FBLWaitForPromisesWithTimeout(0.5); XCTAssertEqualObjects([metricsPromise.value collectionStartDate], [NSDate distantPast]); - GDTCOREventMetricsCounter *expectedCombinedCounter = - [originalCounter counterByMergingWithCounter:counterToOffer]; - XCTAssertEqualObjects([metricsPromise.value droppedEventCounter], expectedCombinedCounter); + GDTCORLogSourceMetrics *expectedCombinedLogSourceMetrics = + [originalLogSourceMetrics logSourceMetricsByMergingWithLogSourceMetrics:counterToOffer]; + XCTAssertEqualObjects([metricsPromise.value logSourceMetrics], expectedCombinedLogSourceMetrics); } @end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m index 0156870d..07a3f99d 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m @@ -18,7 +18,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" -#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREventMetricsCounter.h" +#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" @interface GDTCORMetricsMetadataTest : XCTestCase @@ -30,7 +30,7 @@ @implementation GDTCORMetricsMetadataTest - (void)testEqualityEdgeCases { GDTCORMetricsMetadata *metricsMetadata1 = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; GDTCORMetricsMetadata *metricsMetadata2 = metricsMetadata1; XCTAssert([metricsMetadata1 isEqual:metricsMetadata2]); XCTAssertFalse([metricsMetadata1 isEqual:@"some string"]); @@ -45,21 +45,21 @@ - (void)testEqualObjectsHaveSameHash { [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], ]; - GDTCOREventMetricsCounter *eventMetricsCounter = - [GDTCOREventMetricsCounter counterWithEvents:events - droppedForReason:GDTCOREventDropReasonStorageFull]; + GDTCORLogSourceMetrics *logSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; GDTCORMetricsMetadata *metricsMetadata1 = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] - eventMetricsCounter:eventMetricsCounter]; + logSourceMetrics:logSourceMetrics]; GDTCORMetricsMetadata *metricsMetadata2 = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantPast] - eventMetricsCounter:eventMetricsCounter]; + logSourceMetrics:logSourceMetrics]; GDTCORMetricsMetadata *metricsMetadata3 = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate distantFuture] - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; // Then XCTAssertEqualObjects(metricsMetadata1, metricsMetadata2); XCTAssertEqual(metricsMetadata1.hash, metricsMetadata2.hash); @@ -75,13 +75,13 @@ - (void)testSecureCoding { [[GDTCOREvent alloc] initWithMappingID:@"log_src_2" target:kGDTCORTargetTest], ]; - GDTCOREventMetricsCounter *eventMetricsCounter = - [GDTCOREventMetricsCounter counterWithEvents:events - droppedForReason:GDTCOREventDropReasonStorageFull]; + GDTCORLogSourceMetrics *logSourceMetrics = + [GDTCORLogSourceMetrics metricsWithEvents:events + droppedForReason:GDTCOREventDropReasonStorageFull]; GDTCORMetricsMetadata *metricsMetadata = [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date] - eventMetricsCounter:eventMetricsCounter]; + logSourceMetrics:logSourceMetrics]; // When // - Encode the metrics metadata. @@ -116,7 +116,7 @@ - (void)testSecureCoding_WhenEncodingIsCorrupted { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wincompatible-pointer-types" [GDTCORMetricsMetadata metadataWithCollectionStartDate:@"date" - eventMetricsCounter:[GDTCOREventMetricsCounter counter]]; + logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; #pragma clang diagnostic pop NSError *encodeError; From 5d6dc693d57eefbbe8e274f03e02370eda24057d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 7 Jun 2022 22:27:59 -0400 Subject: [PATCH 056/131] [v9.2.0] Update podspec and CHANGELOG for 9.2.0 (#78) --- CHANGELOG.md | 3 +++ GoogleDataTransport.podspec | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f74ea97d..125f1300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.2.0 +- Add metrics component to measure SDK performance. (#77) + # v9.1.4 - Rollback logging level changes from v9.1.3. (#60) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index bc66401d..a93099ce 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.1.4' + s.version = '9.2.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From 8b5aca3b85f4439c259b6f143c28efad51119ecd Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 8 Jun 2022 17:07:59 -0400 Subject: [PATCH 057/131] [Metrics] Add `Foundation` import to header (#79) --- .../GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h index ce4ce066..4b24f959 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h @@ -12,5 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import + /// The data type to represent storage size. typedef uint64_t GDTCORStorageSizeBytes; From b3de3ad3a8725e6b2afa28b95fc50e9105c497e7 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 9 Jun 2022 10:36:04 -0400 Subject: [PATCH 058/131] [Infra] Fix source of flake in test helper (#80) --- .../GDTCORTests/Unit/GDTCORFlatFileStorageTest.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index 3aa492d7..1e35db35 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -1450,6 +1450,10 @@ - (void)testStoreEvent_WhenSizeLimitReached_ThenNewEventIsSkipped { [eventStoredExpectation fulfill]; }]; + dispatch_sync([GDTCORFlatFileStorage sharedInstance].storageQueue, ^{ + // Drain queue to allow event to be stored before proceeding. + }); + [self waitForExpectations:@[ eventStoredExpectation ] timeout:1]; [generatedEvents addObject:generatedEvent]; From 5056b15c5acbb90cd214fe4d6138bdf5a740e5a8 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 15 Jun 2022 20:13:42 -0400 Subject: [PATCH 059/131] [Proto] Add and integrate `MacClientInfo` proto message (#57) --- CHANGELOG.md | 1 + .../GDTCCTLibrary/GDTCCTNanopbHelpers.m | 31 ++++++++++++++++++- .../Private/GDTCCTNanopbHelpers.h | 7 +++++ .../Protogen/nanopb/cct.nanopb.c | 15 +++++++-- .../Protogen/nanopb/cct.nanopb.h | 25 +++++++++++++-- .../Unit/GDTCCTNanopbHelpersTest.m | 20 ++++++++++++ .../ProtoSupport/Protos/cct.options | 5 +++ .../ProtoSupport/Protos/cct.proto | 17 ++++++++++ 8 files changed, 114 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 125f1300..7b02f240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # v9.2.0 +- Add and integrate `MacClientInfo` proto message. (#57) - Add metrics component to measure SDK performance. (#77) # v9.1.4 diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m index ece3baeb..c89ade8b 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m @@ -171,7 +171,8 @@ gdt_cct_ClientInfo GDTCCTConstructClientInfo() { clientInfo.ios_client_info = GDTCCTConstructiOSClientInfo(); clientInfo.has_ios_client_info = 1; #elif TARGET_OS_OSX - // TODO(mikehaney24): Expand the proto to include macOS client info. + clientInfo.mac_client_info = GDTCCTConstructMacClientInfo(); + clientInfo.has_mac_client_info = 1; #endif return clientInfo; } @@ -202,6 +203,34 @@ gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo() { return iOSClientInfo; } +gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo() { + gdt_cct_MacClientInfo macOSClientInfo = gdt_cct_MacClientInfo_init_default; + + NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; + NSString *majorVersion = [@(osVersion.majorVersion) stringValue]; + NSString *minorVersion = [@(osVersion.minorVersion) stringValue]; + NSString *majorAndMinorString = [NSString stringWithFormat:@"%@.%@", majorVersion, minorVersion]; + macOSClientInfo.os_major_version = GDTCCTEncodeString(majorAndMinorString); + + NSString *patchVersion = [@(osVersion.patchVersion) stringValue]; + NSString *majorMinorPatchString = + [NSString stringWithFormat:@"%@.%@", majorAndMinorString, patchVersion]; + macOSClientInfo.os_full_version = GDTCCTEncodeString(majorMinorPatchString); + + NSBundle *bundle = [NSBundle mainBundle]; + NSString *version = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey]; + if (version) { + macOSClientInfo.application_build = GDTCCTEncodeString(version); + } + + NSString *bundleID = bundle.bundleIdentifier; + if (bundleID) { + macOSClientInfo.application_bundle_id = GDTCCTEncodeString(bundleID); + } + + return macOSClientInfo; +} + NSData *GDTCCTConstructNetworkConnectionInfoData() { gdt_cct_NetworkConnectionInfo networkConnectionInfo = gdt_cct_NetworkConnectionInfo_init_default; NSInteger currentNetworkType = GDTCORNetworkTypeMessage(); diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h index 8372cf90..d3fe2848 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h @@ -97,6 +97,13 @@ gdt_cct_ClientInfo GDTCCTConstructClientInfo(void); FOUNDATION_EXPORT gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void); +/** Constructs a gdt_cct_MacClientInfo representing the client device. + * + * @return The new gdt_cct_MacClientInfo object. + */ +FOUNDATION_EXPORT +gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo(void); + /** Constructs the data of a gdt_cct_NetworkConnectionInfo representing the client nework connection * information. * diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c index 7c5f1e7c..40d39354 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.c @@ -46,6 +46,14 @@ const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3] = { PB_LAST_FIELD }; +const pb_field_t gdt_cct_MacClientInfo_fields[5] = { + PB_FIELD( 1, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_MacClientInfo, os_major_version, os_major_version, 0), + PB_FIELD( 2, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, os_full_version, os_major_version, 0), + PB_FIELD( 3, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, application_build, os_full_version, 0), + PB_FIELD( 7, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, application_bundle_id, application_build, 0), + PB_LAST_FIELD +}; + const pb_field_t gdt_cct_IosClientInfo_fields[8] = { PB_FIELD( 3, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_IosClientInfo, os_major_version, os_major_version, 0), PB_FIELD( 4, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, os_full_version, os_major_version, 0), @@ -57,9 +65,10 @@ const pb_field_t gdt_cct_IosClientInfo_fields[8] = { PB_LAST_FIELD }; -const pb_field_t gdt_cct_ClientInfo_fields[3] = { +const pb_field_t gdt_cct_ClientInfo_fields[4] = { PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_ClientInfo, client_type, client_type, 0), PB_FIELD( 4, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, ios_client_info, client_type, &gdt_cct_IosClientInfo_fields), + PB_FIELD( 13, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, mac_client_info, ios_client_info, &gdt_cct_MacClientInfo_fields), PB_LAST_FIELD }; @@ -110,7 +119,7 @@ const pb_field_t gdt_cct_LogResponse_fields[3] = { * numbers or field sizes that are larger than what can fit in 8 or 16 bit * field descriptors. */ -PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 65536 && pb_membersize(gdt_cct_LogRequest, client_info) < 65536 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) +PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, mac_client_info) < 65536 && pb_membersize(gdt_cct_LogRequest, client_info) < 65536 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_MacClientInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) #endif #if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) @@ -121,7 +130,7 @@ PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 655 * numbers or field sizes that are larger than what can fit in the default * 8 bit descriptors. */ -PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 256 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 256 && pb_membersize(gdt_cct_LogRequest, client_info) < 256 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) +PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 256 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 256 && pb_membersize(gdt_cct_ClientInfo, mac_client_info) < 256 && pb_membersize(gdt_cct_LogRequest, client_info) < 256 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_MacClientInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse) #endif diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h index 334ddfe6..14e68665 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h @@ -117,11 +117,21 @@ typedef struct _gdt_cct_IosClientInfo { /* @@protoc_insertion_point(struct:gdt_cct_IosClientInfo) */ } gdt_cct_IosClientInfo; +typedef struct _gdt_cct_MacClientInfo { + pb_bytes_array_t *os_major_version; + pb_bytes_array_t *os_full_version; + pb_bytes_array_t *application_build; + pb_bytes_array_t *application_bundle_id; +/* @@protoc_insertion_point(struct:gdt_cct_MacClientInfo) */ +} gdt_cct_MacClientInfo; + typedef struct _gdt_cct_ClientInfo { bool has_client_type; gdt_cct_ClientInfo_ClientType client_type; bool has_ios_client_info; gdt_cct_IosClientInfo ios_client_info; + bool has_mac_client_info; + gdt_cct_MacClientInfo mac_client_info; /* @@protoc_insertion_point(struct:gdt_cct_ClientInfo) */ } gdt_cct_ClientInfo; @@ -197,8 +207,9 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; /* Initializer values for message structs */ #define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default} #define gdt_cct_NetworkConnectionInfo_init_default {false, gdt_cct_NetworkConnectionInfo_NetworkType_NONE, false, gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE} +#define gdt_cct_MacClientInfo_init_default {NULL, NULL, NULL, NULL} #define gdt_cct_IosClientInfo_init_default {NULL, NULL, NULL, NULL, NULL, NULL, NULL} -#define gdt_cct_ClientInfo_init_default {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_default} +#define gdt_cct_ClientInfo_init_default {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_default, false, gdt_cct_MacClientInfo_init_default} #define gdt_cct_BatchedLogRequest_init_default {0, NULL} #define gdt_cct_LogRequest_init_default {false, gdt_cct_ClientInfo_init_default, false, 0, 0, NULL, false, 0, false, 0, false, gdt_cct_QosTierConfiguration_QosTier_DEFAULT} #define gdt_cct_QosTierConfiguration_init_default {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} @@ -206,8 +217,9 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; #define gdt_cct_LogResponse_init_default {false, 0, false, gdt_cct_QosTiersOverride_init_default} #define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero} #define gdt_cct_NetworkConnectionInfo_init_zero {false, _gdt_cct_NetworkConnectionInfo_NetworkType_MIN, false, _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN} +#define gdt_cct_MacClientInfo_init_zero {NULL, NULL, NULL, NULL} #define gdt_cct_IosClientInfo_init_zero {NULL, NULL, NULL, NULL, NULL, NULL, NULL} -#define gdt_cct_ClientInfo_init_zero {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_zero} +#define gdt_cct_ClientInfo_init_zero {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_zero, false, gdt_cct_MacClientInfo_init_zero} #define gdt_cct_BatchedLogRequest_init_zero {0, NULL} #define gdt_cct_LogRequest_init_zero {false, gdt_cct_ClientInfo_init_zero, false, 0, 0, NULL, false, 0, false, 0, false, _gdt_cct_QosTierConfiguration_QosTier_MIN} #define gdt_cct_QosTierConfiguration_init_zero {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} @@ -223,8 +235,13 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; #define gdt_cct_IosClientInfo_model_tag 7 #define gdt_cct_IosClientInfo_language_code_tag 8 #define gdt_cct_IosClientInfo_application_bundle_id_tag 11 +#define gdt_cct_MacClientInfo_os_major_version_tag 1 +#define gdt_cct_MacClientInfo_os_full_version_tag 2 +#define gdt_cct_MacClientInfo_application_build_tag 3 +#define gdt_cct_MacClientInfo_application_bundle_id_tag 7 #define gdt_cct_ClientInfo_client_type_tag 1 #define gdt_cct_ClientInfo_ios_client_info_tag 4 +#define gdt_cct_ClientInfo_mac_client_info_tag 13 #define gdt_cct_NetworkConnectionInfo_network_type_tag 1 #define gdt_cct_NetworkConnectionInfo_mobile_subtype_tag 2 #define gdt_cct_QosTierConfiguration_qos_tier_tag 2 @@ -249,8 +266,9 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; /* Struct field encoding specification for nanopb */ extern const pb_field_t gdt_cct_LogEvent_fields[7]; extern const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3]; +extern const pb_field_t gdt_cct_MacClientInfo_fields[5]; extern const pb_field_t gdt_cct_IosClientInfo_fields[8]; -extern const pb_field_t gdt_cct_ClientInfo_fields[3]; +extern const pb_field_t gdt_cct_ClientInfo_fields[4]; extern const pb_field_t gdt_cct_BatchedLogRequest_fields[2]; extern const pb_field_t gdt_cct_LogRequest_fields[7]; extern const pb_field_t gdt_cct_QosTierConfiguration_fields[3]; @@ -260,6 +278,7 @@ extern const pb_field_t gdt_cct_LogResponse_fields[3]; /* Maximum encoded size of messages (where known) */ /* gdt_cct_LogEvent_size depends on runtime parameters */ #define gdt_cct_NetworkConnectionInfo_size 13 +/* gdt_cct_MacClientInfo_size depends on runtime parameters */ /* gdt_cct_IosClientInfo_size depends on runtime parameters */ /* gdt_cct_ClientInfo_size depends on runtime parameters */ /* gdt_cct_BatchedLogRequest_size depends on runtime parameters */ diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m index a63e70d7..1845c74f 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m @@ -73,6 +73,26 @@ - (void)testConstructBatchedLogRequest { pb_release(gdt_cct_BatchedLogRequest_fields, &batch); } +/** Tests batched log requests include platform-specific client info. */ +- (void)testBatchedLogRequestsIncludePlatformSpecificClientInfo { + // Given + gdt_cct_BatchedLogRequest batch = gdt_cct_BatchedLogRequest_init_default; + NSSet *events = + [NSSet setWithArray:[self.generator generateTheFiveConsistentEvents]]; + // When + XCTAssertNoThrow(batch = GDTCCTConstructBatchedLogRequest(@{@"1018" : events})); + // Then + XCTAssertTrue(batch.log_request->has_client_info); +#if TARGET_OS_IOS || TARGET_OS_TV + XCTAssertTrue(batch.log_request->client_info.has_ios_client_info); + XCTAssertFalse(batch.log_request->client_info.has_mac_client_info); +#elif TARGET_OS_OSX + XCTAssertTrue(batch.log_request->client_info.has_mac_client_info); + XCTAssertFalse(batch.log_request->client_info.has_ios_client_info); +#endif + pb_release(gdt_cct_BatchedLogRequest_fields, &batch); +} + /** Tests encoding a batched log request generates bytes equivalent to canonical protobuf. */ - (void)testEncodeBatchedLogRequest { NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; diff --git a/GoogleDataTransport/ProtoSupport/Protos/cct.options b/GoogleDataTransport/ProtoSupport/Protos/cct.options index c3989642..88ccdfac 100644 --- a/GoogleDataTransport/ProtoSupport/Protos/cct.options +++ b/GoogleDataTransport/ProtoSupport/Protos/cct.options @@ -23,6 +23,11 @@ gdt_cct.IosClientInfo.model type:FT_POINTER gdt_cct.IosClientInfo.language_code type:FT_POINTER gdt_cct.IosClientInfo.application_bundle_id type:FT_POINTER +gdt_cct.MacClientInfo.os_major_version type:FT_POINTER +gdt_cct.MacClientInfo.os_full_version type:FT_POINTER +gdt_cct.MacClientInfo.application_build type:FT_POINTER +gdt_cct.MacClientInfo.application_bundle_id type:FT_POINTER + gdt_cct.LogRequest.log_event type:FT_POINTER gdt_cct.BatchedLogRequest.log_request type:FT_POINTER diff --git a/GoogleDataTransport/ProtoSupport/Protos/cct.proto b/GoogleDataTransport/ProtoSupport/Protos/cct.proto index 8b32dced..54dab8bb 100644 --- a/GoogleDataTransport/ProtoSupport/Protos/cct.proto +++ b/GoogleDataTransport/ProtoSupport/Protos/cct.proto @@ -88,6 +88,21 @@ message NetworkConnectionInfo { optional MobileSubtype mobile_subtype = 2 [default = UNKNOWN_MOBILE_SUBTYPE]; } +message MacClientInfo { + + // The major OS version of the macOS client. Eg: "10.7". + optional string os_major_version = 1; + + // The complete OS version of a macOS client. Eg: "10.7.3". + optional string os_full_version = 2; + + // The client application version. + optional string application_build = 3; + + // The bundle name of the application. + optional string application_bundle_id = 7; +} + message IosClientInfo { // The major OS version of an iOS client. Eg: "8", "9". @@ -131,6 +146,8 @@ message ClientInfo { optional ClientType client_type = 1; optional IosClientInfo ios_client_info = 4; + + optional MacClientInfo mac_client_info = 13; } message BatchedLogRequest { From 55afa37dd6640a10b0d933dc1bc094868ba4e8c9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 8 Aug 2022 14:40:27 -0400 Subject: [PATCH 060/131] [Infra] Add `.ruby-version` file to pin Ruby version for GHA (#83) * [Infra] Add `.ruby-version` file to pin Ruby version for GHA * [skip-ci] Add newline --- .ruby-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..fa376edc --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.7 From eab69ef0b753d93eb1e8c32ecd7cc1bf22d76452 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 8 Aug 2022 21:19:33 -0400 Subject: [PATCH 061/131] Remove Xcode 12 constraint for `datatransport / watchos-test` job (#82) * Remove Xcode 12 constraint * Use macos-latest image * macos-latest is not macos-12! * Update gemfile * Use global Ruby pinning * Trigger CI * Add xcpretty to Gemfile * Try manually installing xcpretty * Add newline to Gemfile --- .github/workflows/datatransport.yml | 8 +- .github/workflows/spm.yml | 1 + .github/workflows/test_coverage.yml | 2 + Gemfile | 5 +- Gemfile.lock | 148 +++++++++++++++++----------- setup-scripts.sh | 2 + 6 files changed, 103 insertions(+), 63 deletions(-) diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index dba5fef1..614283d6 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -18,6 +18,7 @@ jobs: target: [ios, tvos, macos, watchos] steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -30,6 +31,7 @@ jobs: runs-on: macOS-latest steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -38,11 +40,10 @@ jobs: run: scripts/third_party/travis/retry.sh scripts/test_catalyst.sh GoogleDataTransport build watchos-testapp: - runs-on: macOS-10.15 + runs-on: macos-12 steps: - uses: actions/checkout@v2 - - name: Xcode 12 - run: sudo xcode-select -s /Applications/Xcode_12.app/Contents/Developer + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -64,6 +65,7 @@ jobs: needs: pod_lib_lint steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 2fe8365a..de60495b 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -14,6 +14,7 @@ jobs: target: [iOS, tvOS, macOS, catalyst] steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Initialize xcodebuild diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index daafa796..91ea1ddc 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -21,6 +21,7 @@ jobs: target: [ios] steps: - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -40,6 +41,7 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 + - uses: ruby/setup-ruby@v1 - uses: actions/download-artifact@v2 id: download with: diff --git a/Gemfile b/Gemfile index c0e56b84..a6c65895 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,8 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.10.1' +gem 'cocoapods', '1.11.3' gem 'cocoapods-generate', '2.0.1' -gem 'danger', '6.1.0' +gem 'danger', '8.4.5' +gem 'xcpretty' diff --git a/Gemfile.lock b/Gemfile.lock index ac83864b..a62d7a30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,29 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - activesupport (5.2.4.4) + CFPropertyList (3.0.5) + rexml + activesupport (6.1.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.7.0) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) - claide (1.0.3) + claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.10.1) - addressable (~> 2.6) + cocoapods (1.11.3) + addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.10.1) + cocoapods-core (= 1.11.3) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -32,107 +34,137 @@ GEM escape (~> 0.0.4) fourflusher (>= 2.3.0, < 3.0) gh_inspector (~> 1.0) - molinillo (~> 0.6.6) + molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (~> 1.4) - xcodeproj (>= 1.19.0, < 2.0) - cocoapods-core (1.10.1) - activesupport (> 5.0, < 6) - addressable (~> 2.6) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-core (1.11.3) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) fuzzy_match (~> 2.0.4) nap (~> 1.0) netrc (~> 0.11) - public_suffix + public_suffix (~> 4.0) typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.4) + cocoapods-deintegrate (1.0.5) cocoapods-disable-podfile-validations (0.1.1) - cocoapods-downloader (1.4.0) + cocoapods-downloader (1.6.3) cocoapods-generate (2.0.1) cocoapods-disable-podfile-validations (~> 0.1.1) cocoapods-plugins (1.0.0) nap - cocoapods-search (1.0.0) - cocoapods-trunk (1.5.0) + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.7) + concurrent-ruby (1.1.10) cork (0.3.0) colored2 (~> 3.1) - danger (6.1.0) + danger (8.4.5) claide (~> 1.0) claide-plugins (>= 0.9.2) colored2 (~> 3.1) cork (~> 0.1) - faraday (~> 0.9) + faraday (>= 0.9.0, < 2.0) faraday-http-cache (~> 2.0) - git (~> 1.5) - kramdown (~> 2.0) + git (~> 1.7) + kramdown (~> 2.3) kramdown-parser-gfm (~> 1.0) no_proxy_fix octokit (~> 4.7) - terminal-table (~> 1) + terminal-table (>= 1, < 4) escape (0.0.4) - ethon (0.12.0) - ffi (>= 1.3.0) - faraday (0.17.0) - multipart-post (>= 1.2, < 3) - faraday-http-cache (2.0.0) - faraday (~> 0.8) - ffi (1.14.2) + ethon (0.15.0) + ffi (>= 1.15.0) + faraday (1.10.1) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-http-cache (2.4.1) + faraday (>= 0.8) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + ffi (1.15.5) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - git (1.5.0) + git (1.11.0) + rchardet (~> 1.8) httpclient (2.8.3) - i18n (1.8.7) + i18n (1.12.0) concurrent-ruby (~> 1.0) - json (2.5.1) - kramdown (2.3.0) + json (2.6.2) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.14.3) - molinillo (0.6.6) - multipart-post (2.1.1) + minitest (5.16.2) + molinillo (0.8.0) + multipart-post (2.2.3) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) no_proxy_fix (0.1.2) - octokit (4.14.0) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) open4 (1.3.4) - public_suffix (4.0.1) - rexml (3.2.4) - ruby-macho (1.4.0) - sawyer (0.8.2) + public_suffix (4.0.7) + rchardet (1.8.0) + rexml (3.2.5) + rouge (2.0.7) + ruby-macho (2.5.1) + ruby2_keywords (0.0.5) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) + faraday (>= 0.17.3, < 3) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) - unicode-display_width (1.6.0) - xcodeproj (1.19.0) + tzinfo (2.0.5) + concurrent-ruby (~> 1.0) + unicode-display_width (2.2.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + zeitwerk (2.6.0) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.10.1) + cocoapods (= 1.11.3) cocoapods-generate (= 2.0.1) - danger (= 6.1.0) + danger (= 8.4.5) + xcpretty BUNDLED WITH - 2.1.2 + 2.1.4 diff --git a/setup-scripts.sh b/setup-scripts.sh index 423b82ad..3112c185 100755 --- a/setup-scripts.sh +++ b/setup-scripts.sh @@ -20,3 +20,5 @@ cd firebase-ios-sdk git checkout master -- scripts cd .. ln -s firebase-ios-sdk/scripts scripts + +gem install xcpretty From 2d7834cc96695c7d6c0b09f94393d32558bf5001 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 8 Aug 2022 22:19:53 -0400 Subject: [PATCH 062/131] [Infra] Add `cancel-in-progress` feature to GHA (#84) --- .github/workflows/check.yml | 4 ++++ .github/workflows/datatransport.yml | 4 ++++ .github/workflows/spm.yml | 4 ++++ .github/workflows/test_coverage.yml | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b4be8890..4a597c12 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,6 +7,10 @@ on: push: branches: main +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: check: runs-on: macos-latest diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index 614283d6..c48457fa 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -10,6 +10,10 @@ env: # Used to compile test-specific code in the SDK. GDT_TEST: 1 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: pod_lib_lint: runs-on: macos-latest diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index de60495b..0f90cc47 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -6,6 +6,10 @@ on: # Run every day at 11pm (PST) - cron uses UTC times - cron: '0 7 * * *' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: swift-build-run: runs-on: macOS-latest diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 91ea1ddc..05c5b2cd 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -12,6 +12,10 @@ env: # Used to compile test-specific code in the SDK. GDT_TEST: 1 +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + jobs: pod_lib_lint: if: github.repository == 'google/GoogleDataTransport' && (github.event.action == 'synchronize' || github.event.action == 'opened' || github.event.pull_request.merged) From 52ed906ce70f50af1aba0718b72843e326cde9d8 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 8 Aug 2022 22:21:31 -0400 Subject: [PATCH 063/131] [Infra] Remove `xcpretty` from `Gemfile` and `Gemfile.lock` (#85) * [Infra] Rely on in Gemfile for GHA * Update Gemfile and undo previous commit --- Gemfile | 1 - Gemfile.lock | 4 ---- 2 files changed, 5 deletions(-) diff --git a/Gemfile b/Gemfile index a6c65895..59187bfe 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,3 @@ gem 'cocoapods', '1.11.3' gem 'cocoapods-generate', '2.0.1' gem 'danger', '8.4.5' -gem 'xcpretty' diff --git a/Gemfile.lock b/Gemfile.lock index a62d7a30..0ab09ae4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -133,7 +133,6 @@ GEM public_suffix (4.0.7) rchardet (1.8.0) rexml (3.2.5) - rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) @@ -153,8 +152,6 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) - xcpretty (0.3.0) - rouge (~> 2.0.7) zeitwerk (2.6.0) PLATFORMS @@ -164,7 +161,6 @@ DEPENDENCIES cocoapods (= 1.11.3) cocoapods-generate (= 2.0.1) danger (= 8.4.5) - xcpretty BUNDLED WITH 2.1.4 From 72f385de06190c832c85f8707f062237417749d2 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 9 Aug 2022 00:55:04 -0400 Subject: [PATCH 064/131] [Cleanup] Synthesize ivars instead of manual declaration (#81) --- .../GDTCORLibrary/GDTCORRegistrar.m | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m index cc2e31ef..5aefd6c8 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORRegistrar.m @@ -38,18 +38,12 @@ return [GDTCORRegistrar sharedInstance].targetToMetricsController[@(target)]; } -@implementation GDTCORRegistrar { - // TODO(ncooke3): Replace ivar declarations with @synthesize attributes. +@implementation GDTCORRegistrar - /** Backing ivar for targetToUploader property. */ - NSMutableDictionary> *_targetToUploader; - - /** Backing ivar for targetToStorage property. */ - NSMutableDictionary> *_targetToStorage; - - /** Backing ivar for targetToMetricsController property. */ - NSMutableDictionary> *_targetToMetricsController; -} +// Manaully synthesize properties declared in `GDTCORRegistrar_Private.h` category. +@synthesize targetToUploader = _targetToUploader; +@synthesize targetToStorage = _targetToStorage; +@synthesize targetToMetricsController = _targetToMetricsController; + (instancetype)sharedInstance { static GDTCORRegistrar *sharedInstance; From 8f02d12c264cc0c1d2e48ae362adb1f5a82cb679 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Sat, 29 Oct 2022 14:06:18 -0700 Subject: [PATCH 065/131] Update CI for Xcode 14 (#86) --- .github/workflows/check.yml | 6 +++--- .github/workflows/datatransport.yml | 12 ++++++------ .github/workflows/spm.yml | 2 +- .github/workflows/test_coverage.yml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4a597c12..81ff4403 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,14 +17,14 @@ jobs: env: MINT_PATH: ${{ github.workspace }}/mint steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v3 with: python-version: 3.6 - name: Cache Mint packages - uses: actions/cache@v1 + uses: actions/cache@v3 with: path: ${{ env.MINT_PATH }} key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }} diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index c48457fa..dc10e5cf 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -19,9 +19,9 @@ jobs: runs-on: macos-latest strategy: matrix: - target: [ios, tvos, macos, watchos] + target: [ios, tvos, macos] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh @@ -34,7 +34,7 @@ jobs: catalyst: runs-on: macOS-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh @@ -46,7 +46,7 @@ jobs: watchos-testapp: runs-on: macos-12 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh @@ -61,14 +61,14 @@ jobs: runs-on: macos-latest strategy: matrix: - target: [ios, tvos, macos, watchos] + target: [ios, tvos, macos] flags: [ '--use-static-frameworks', '--use-libraries' ] needs: pod_lib_lint steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 0f90cc47..5b347416 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -17,7 +17,7 @@ jobs: matrix: target: [iOS, tvOS, macOS, catalyst] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 05c5b2cd..44c32066 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -24,7 +24,7 @@ jobs: matrix: target: [ios] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh @@ -42,7 +42,7 @@ jobs: needs: pod_lib_lint runs-on: macOS-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: ruby/setup-ruby@v1 From 35616cdf59779938ac629a051085b09abae62d25 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 3 Nov 2022 12:15:37 -0400 Subject: [PATCH 066/131] [Infra] Bump clang-format to v15 (#88) --- .github/workflows/check.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 81ff4403..b7a52e4a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,7 +36,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@14 + brew install clang-format@15 brew install mint mint bootstrap diff --git a/README.md b/README.md index 73bcde5a..6d2b3ade 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@14 +brew install clang-format@15 brew install mint ``` From 0dd8cc878442ab50c05281f57172cef0aee07890 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 5 Dec 2022 14:59:57 -0500 Subject: [PATCH 067/131] [Infra] Improve docs and use google domain for testing URL (#89) --- .../GDTCCTTests/Unit/GDTCCTUploaderTest.m | 2 +- .../GDTCORLibrary/GDTCOREndpoints.m | 2 +- .../Public/GoogleDataTransport/GDTCORTargets.h | 17 ++++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m index d4ac0ab3..e4d2a580 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTUploaderTest.m @@ -1237,7 +1237,7 @@ - (nullable NSURL *)serverURLForTarget:(GDTCORTarget)target { // fool github URL scanning bots. static NSURL *CCTServerURL; static NSString *const kINTServerURL = - @"https://dummyapiverylong-dummy.dummy.com/dummy/api/very/long"; + @"https://dummyapiverylong-dummy.google.com/dummy/api/very/long"; static dispatch_once_t CCTOnceToken; dispatch_once(&CCTOnceToken, ^{ const char *p1 = "hts/frbslgiggolai.o/0clgbth"; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m index eacc7b4e..f16db13d 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREndpoints.m @@ -17,7 +17,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h" static NSString *const kINTServerURL = - @"https://dummyapiverylong-dummy.dummy.com/dummy/api/very/long"; + @"https://dummyapiverylong-dummy.google.com/dummy/api/very/long"; @implementation GDTCOREndpoints diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h index 0b83ab9e..3163b550 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h @@ -16,25 +16,24 @@ #import -/** The list of targets supported by the shared transport infrastructure. If adding a new target, - * please use the previous value +1. +/** The list of targets supported by the shared transport infrastructure. + * These targets map to a specific backend designed to accept GDT payloads. If + * adding a new target, please use the previous value +1. */ typedef NS_ENUM(NSInteger, GDTCORTarget) { - /** A target only used in testing. */ + /** Target used for testing purposes. */ kGDTCORTargetTest = 999, - /** The CCT target. */ + /** Target used by internal clients. See go/firelog for more information. */ kGDTCORTargetCCT = 1000, - /** The FLL target. */ + /** Target mapping to the Firelog backend. See go/firelog for more information. */ kGDTCORTargetFLL = 1001, - /** The CSH target. The CSH target is a special-purpose backend. Please do not use it without - * permission. - */ + /** Special-purpose Crashlytics target. Please do not use it without permission. */ kGDTCORTargetCSH = 1002, - /** The INT target. */ + /** Target used for integration testing. */ kGDTCORTargetINT = 1003, }; From f6b558e3f801f2cac336b04f615ce111fa9ddaa0 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 19 Jan 2023 15:01:33 -0500 Subject: [PATCH 068/131] [Release] Bump spec to 9.2.1 and add CHANGELOG entry (#90) --- CHANGELOG.md | 3 +++ GoogleDataTransport.podspec | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b02f240..19b420ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.2.1 +- Add `GDTCORTarget` documentation and use Google domain in testing URL. (#89) + # v9.2.0 - Add and integrate `MacClientInfo` proto message. (#57) - Add metrics component to measure SDK performance. (#77) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index a93099ce..6fabdce9 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.2.0' + s.version = '9.2.1' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From fe7ebfd3b97927b0f1ce02bc05e6626b0e62794d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 20 Mar 2023 13:00:06 -0400 Subject: [PATCH 069/131] [Infra] Upgrade to clang-format16 (#91) --- .github/workflows/check.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b7a52e4a..e209c563 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,7 +36,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@15 + brew install clang-format@16 brew install mint mint bootstrap diff --git a/README.md b/README.md index 6d2b3ade..db467ae2 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@15 +brew install clang-format@16 brew install mint ``` From cc7265b8e3906304e6e81f32c1662a94bbae2357 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 3 Apr 2023 08:40:14 -0700 Subject: [PATCH 070/131] Fix new warnings shown by Xcode 14.3 (#92) --- CHANGELOG.md | 3 +++ GoogleDataTransport.podspec | 2 +- .../GDTCCTLibrary/GDTCCTNanopbHelpers.m | 10 +++++----- GoogleDataTransport/GDTCORLibrary/GDTCORClock.m | 4 ++-- GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m | 6 +++--- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19b420ba..c6270702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.2.2 +- Fix new warnings shown by Xcode 14.3. + # v9.2.1 - Add `GDTCORTarget` documentation and use Google domain in testing URL. (#89) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 6fabdce9..004ec925 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.2.1' + s.version = '9.2.2' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m index c89ade8b..c5ced06b 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m @@ -163,7 +163,7 @@ gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event) { return logEvent; } -gdt_cct_ClientInfo GDTCCTConstructClientInfo() { +gdt_cct_ClientInfo GDTCCTConstructClientInfo(void) { gdt_cct_ClientInfo clientInfo = gdt_cct_ClientInfo_init_default; clientInfo.client_type = gdt_cct_ClientInfo_ClientType_IOS_FIREBASE; clientInfo.has_client_type = 1; @@ -177,7 +177,7 @@ gdt_cct_ClientInfo GDTCCTConstructClientInfo() { return clientInfo; } -gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo() { +gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void) { gdt_cct_IosClientInfo iOSClientInfo = gdt_cct_IosClientInfo_init_default; #if TARGET_OS_IOS || TARGET_OS_TV UIDevice *device = [UIDevice currentDevice]; @@ -203,7 +203,7 @@ gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo() { return iOSClientInfo; } -gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo() { +gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo(void) { gdt_cct_MacClientInfo macOSClientInfo = gdt_cct_MacClientInfo_init_default; NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; @@ -231,7 +231,7 @@ gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo() { return macOSClientInfo; } -NSData *GDTCCTConstructNetworkConnectionInfoData() { +NSData *GDTCCTConstructNetworkConnectionInfoData(void) { gdt_cct_NetworkConnectionInfo networkConnectionInfo = gdt_cct_NetworkConnectionInfo_init_default; NSInteger currentNetworkType = GDTCORNetworkTypeMessage(); if (currentNetworkType) { @@ -252,7 +252,7 @@ gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo() { return networkConnectionInfoData; } -gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype() { +gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype(void) { NSNumber *networkMobileSubtypeMessage = @(GDTCORNetworkMobileSubTypeMessage()); if (!networkMobileSubtypeMessage.intValue) { return gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m index 85afebae..0487ccd2 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m @@ -41,7 +41,7 @@ * * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. */ -static int64_t KernelBootTimeInNanoseconds() { +static int64_t KernelBootTimeInNanoseconds(void) { // Caching the result is not possible because clock drift would not be accounted for. struct timeval boottime; int mib[2] = {CTL_KERN, KERN_BOOTTIME}; @@ -59,7 +59,7 @@ static int64_t KernelBootTimeInNanoseconds() { * * @return The value of gettimeofday, in nanoseconds. */ -static int64_t UptimeInNanoseconds() { +static int64_t UptimeInNanoseconds(void) { int64_t before_now_nsec; int64_t after_now_nsec; struct timeval now; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index a82fd511..a93f03a4 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -84,7 +84,7 @@ BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) { #endif // TARGET_OS_IOS } -GDTCORNetworkType GDTCORNetworkTypeMessage() { +GDTCORNetworkType GDTCORNetworkTypeMessage(void) { #if !TARGET_OS_WATCH SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags]; if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) == @@ -99,7 +99,7 @@ GDTCORNetworkType GDTCORNetworkTypeMessage() { return GDTCORNetworkTypeUNKNOWN; } -GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() { +GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { #if TARGET_OS_IOS static NSDictionary *CTRadioAccessTechnologyToNetworkSubTypeMessage; static CTTelephonyNetworkInfo *networkInfo; @@ -154,7 +154,7 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() { #endif // TARGET_OS_IOS } -NSString *_Nonnull GDTCORDeviceModel() { +NSString *_Nonnull GDTCORDeviceModel(void) { static NSString *deviceModel = @"Unknown"; #if TARGET_OS_IOS || TARGET_OS_TV From 3e5156ae7da2214f1ad6eaa4dd2fc06e4a89479a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 4 May 2023 15:12:20 -0400 Subject: [PATCH 071/131] [Fix] Address Xcode 14.3 Analyzer issue (#95) * [Fix] Address Xcode 14.3 Analyzer issue * Add changelog entry * Fix *all* warnings * check in Gemfile.lock * Style --- CHANGELOG.md | 3 ++ Gemfile | 2 +- Gemfile.lock | 34 +++++++++---------- .../GDTCORLibrary/GDTCORPlatform.m | 8 +++-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6270702..c38486c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.2.3 +- Fix analyzer issues shown by Xcode 14.3. ([firebase-ios-sdk/#11228](https://github.com/firebase/firebase-ios-sdk/issues/11228)) + # v9.2.2 - Fix new warnings shown by Xcode 14.3. diff --git a/Gemfile b/Gemfile index 59187bfe..c0a8695c 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.11.3' +gem 'cocoapods', '1.12.1' gem 'cocoapods-generate', '2.0.1' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index 0ab09ae4..c204008c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,13 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml - activesupport (6.1.6.1) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) algoliasearch (1.27.5) @@ -20,15 +19,15 @@ GEM cork nap open4 (~> 1.3) - cocoapods (1.11.3) + cocoapods (1.12.1) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.12.1) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 1.6.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -36,10 +35,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) + ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + cocoapods-core (1.12.1) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -61,7 +60,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) cork (0.3.0) colored2 (~> 3.1) danger (8.4.5) @@ -78,7 +77,7 @@ GEM octokit (~> 4.7) terminal-table (>= 1, < 4) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) faraday (1.10.1) faraday-em_http (~> 1.0) @@ -112,14 +111,14 @@ GEM git (1.11.0) rchardet (~> 1.8) httpclient (2.8.3) - i18n (1.12.0) + i18n (1.13.0) concurrent-ruby (~> 1.0) - json (2.6.2) + json (2.6.3) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.16.2) + minitest (5.18.0) molinillo (0.8.0) multipart-post (2.2.3) nanaimo (0.3.0) @@ -142,7 +141,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (2.0.5) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.2.0) xcodeproj (1.22.0) @@ -152,13 +151,12 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) - zeitwerk (2.6.0) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.11.3) + cocoapods (= 1.12.1) cocoapods-generate (= 2.0.1) danger (= 8.4.5) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index a93f03a4..cfc65037 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -205,8 +205,12 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { } if (filePath.length > 0) { result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; - if (result == NO || *error) { - GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error); + if (result == NO || (error != NULL && *error != nil)) { + if (error != NULL && *error != nil) { + GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error); + } else { + GDTCORLogDebug(@"Attempt to write archive failed: path:%@", filePath); + } } else { GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); } From 7874c1b48cbffd086ce8a052c4be873a78613775 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 4 May 2023 15:41:12 -0400 Subject: [PATCH 072/131] =?UTF-8?q?[Release]=20Bump=20'GoogleDataTransport?= =?UTF-8?q?.podspec'=20version=20from=20'9.2.2'=20=E2=86=92=20'9.2.3'=20(#?= =?UTF-8?q?96)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 004ec925..0c8a85ec 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.2.2' + s.version = '9.2.3' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From 98a00258d4518b7521253a70b7f70bb76d2120fe Mon Sep 17 00:00:00 2001 From: Ryan Wilson Date: Fri, 23 Jun 2023 18:22:37 -0400 Subject: [PATCH 073/131] [visionOS] Fix visionOS build errors on Xcode 15.0 Beta 2 (#97) * visionOS checks * Fixed the comment too * Attempt to fix CI issue * [skip-ci] Add changelog entry --------- Co-authored-by: Nick Cooke --- CHANGELOG.md | 3 +++ GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m | 8 +++++--- .../GDTCORLibrary/Internal/GDTCORPlatform.h | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c38486c2..23ee87e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.2.4 (SwiftPM Only) +- Fix build errors on the visionOS platform. (#97) + # v9.2.3 - Fix analyzer issues shown by Xcode 14.3. ([firebase-ios-sdk/#11228](https://github.com/firebase/firebase-ios-sdk/issues/11228)) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index cfc65037..5efeae18 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -100,7 +100,9 @@ GDTCORNetworkType GDTCORNetworkTypeMessage(void) { } GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { -#if TARGET_OS_IOS +// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version, +// it will be unnecessary to check if `TARGET_OS_XR` is defined. +#if TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) static NSDictionary *CTRadioAccessTechnologyToNetworkSubTypeMessage; static CTTelephonyNetworkInfo *networkInfo; static dispatch_once_t onceToken; @@ -149,9 +151,9 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { } else { return GDTCORNetworkMobileSubtypeUNKNOWN; } -#else // TARGET_OS_IOS +#else // TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) return GDTCORNetworkMobileSubtypeUNKNOWN; -#endif // TARGET_OS_IOS +#endif // TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) } NSString *_Nonnull GDTCORDeviceModel(void) { diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h index d7ee32ba..e4f292d1 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h @@ -28,7 +28,9 @@ #import #endif // TARGET_OS_IOS || TARGET_OS_TV -#if TARGET_OS_IOS +// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version, +// it will be unnecessary to check if `TARGET_OS_XR` is defined. +#if TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) #import #endif From a9962643dc519968e6b2c41614ca579270d6e02d Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:38:23 -0400 Subject: [PATCH 074/131] [visionOS] Replace 'TARGET_OS_XR' with 'TARGET_OS_VISION' (#99) --- GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m | 8 ++++---- .../GDTCORLibrary/Internal/GDTCORPlatform.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index 5efeae18..1d895282 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -101,8 +101,8 @@ GDTCORNetworkType GDTCORNetworkTypeMessage(void) { GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { // TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version, -// it will be unnecessary to check if `TARGET_OS_XR` is defined. -#if TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) +// it will be unnecessary to check if `TARGET_OS_VISION` is defined. +#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) static NSDictionary *CTRadioAccessTechnologyToNetworkSubTypeMessage; static CTTelephonyNetworkInfo *networkInfo; static dispatch_once_t onceToken; @@ -151,9 +151,9 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { } else { return GDTCORNetworkMobileSubtypeUNKNOWN; } -#else // TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) +#else // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) return GDTCORNetworkMobileSubtypeUNKNOWN; -#endif // TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) +#endif // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) } NSString *_Nonnull GDTCORDeviceModel(void) { diff --git a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h index e4f292d1..89df0bcc 100644 --- a/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h +++ b/GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h @@ -29,8 +29,8 @@ #endif // TARGET_OS_IOS || TARGET_OS_TV // TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version, -// it will be unnecessary to check if `TARGET_OS_XR` is defined. -#if TARGET_OS_IOS && (!defined(TARGET_OS_XR) || !TARGET_OS_XR) +// it will be unnecessary to check if `TARGET_OS_VISION` is defined. +#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION) #import #endif From aae45a320fd0d11811820335b1eabc8753902a40 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 31 Jul 2023 14:52:31 -0400 Subject: [PATCH 075/131] [Release] Update podspec and changelog for 9.2.5 release (#100) --- CHANGELOG.md | 4 ++++ GoogleDataTransport.podspec | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ee87e2..2ceb2dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v9.2.5 +- Replace 'TARGET_OS_XR' with 'TARGET_OS_VISION' for compatibility with + Xcode 15 Beta 5. + # v9.2.4 (SwiftPM Only) - Fix build errors on the visionOS platform. (#97) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 0c8a85ec..889436fa 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.2.3' + s.version = '9.2.5' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From e7175cfff250667414e6d58f7836b4a513f50a66 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 10 Aug 2023 17:18:35 -0400 Subject: [PATCH 076/131] [Infra] Update licensing version in podspec (#101) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 889436fa..f4e156b7 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -8,7 +8,7 @@ Shared library for iOS SDK data transport needs. DESC s.homepage = 'https://developers.google.com/' - s.license = { :type => 'Apache', :file => 'LICENSE' } + s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } s.authors = 'Google, Inc.' s.source = { :git => 'https://github.com/google/GoogleDataTransport.git', From 78c44ad53d41c84d7d04ceab9e8773327befbf2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:13:48 -0700 Subject: [PATCH 077/131] Bump activesupport from 7.0.4.3 to 7.0.7.2 (#103) Bumps [activesupport](https://github.com/rails/rails) from 7.0.4.3 to 7.0.7.2. - [Release notes](https://github.com/rails/rails/releases) - [Changelog](https://github.com/rails/rails/blob/v7.0.7.2/activesupport/CHANGELOG.md) - [Commits](https://github.com/rails/rails/compare/v7.0.4.3...v7.0.7.2) --- updated-dependencies: - dependency-name: activesupport dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c204008c..7aa6ef67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.4.3) + activesupport (7.0.7.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -111,14 +111,14 @@ GEM git (1.11.0) rchardet (~> 1.8) httpclient (2.8.3) - i18n (1.13.0) + i18n (1.14.1) concurrent-ruby (~> 1.0) json (2.6.3) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.18.0) + minitest (5.19.0) molinillo (0.8.0) multipart-post (2.2.3) nanaimo (0.3.0) From aa7a2619a867b106fce165abfac1e346348ffe01 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:02:04 -0400 Subject: [PATCH 078/131] =?UTF-8?q?[Infra]=20Bump=20clang-format@16=20?= =?UTF-8?q?=E2=86=92=2017=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check.yml | 2 +- GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift | 2 +- GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift | 3 ++- GoogleDataTransport/GDTTestApp/viewcontroller.swift | 2 +- README.md | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index e209c563..8671307d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,7 +36,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@16 + brew install clang-format@17 brew install mint mint bootstrap diff --git a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift index b92ef44a..4254ccf4 100644 --- a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift @@ -138,7 +138,7 @@ public extension ViewController { self.generateWifiOnlyEvent, self.generateDailyEvent, ] - let randomIndex: Int = Int.random(in: 0 ..< generationFunctions.count) + let randomIndex: Int = .random(in: 0 ..< generationFunctions.count) generationFunctions[randomIndex](self) } RunLoop.current.run(until: Date(timeIntervalSinceNow: Double.random(in: 0 ..< 1.5))) diff --git a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift index e80f5bb4..df141ed2 100644 --- a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift +++ b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift @@ -20,7 +20,8 @@ import GoogleDataTransport import os.signpost -/// The test actions to run under the profiler to measure performance of `GDTCORFlatFileStorage.checkForExpirations()` method. +/// The test actions to run under the profiler to measure performance of +/// `GDTCORFlatFileStorage.checkForExpirations()` method. @available(iOS 12.0, *) enum EventCleanupPerfTest { static let log = OSLog(subsystem: "GoogleDataTransport-TestApp", category: "EventCleanupPerfTest") diff --git a/GoogleDataTransport/GDTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTTestApp/viewcontroller.swift index b0b264fa..db184e97 100644 --- a/GoogleDataTransport/GDTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTTestApp/viewcontroller.swift @@ -111,7 +111,7 @@ public extension ViewController { self.generateWifiOnlyEvent, self.generateDailyEvent, ] - let randomIndex: Int = Int.random(in: 0 ..< generationFunctions.count) + let randomIndex: Int = .random(in: 0 ..< generationFunctions.count) generationFunctions[randomIndex](self) } RunLoop.current.run(until: Date(timeIntervalSinceNow: Double.random(in: 0 ..< 1.5))) diff --git a/README.md b/README.md index db467ae2..0eecc02b 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@16 +brew install clang-format@17 brew install mint ``` From f70451051e2565dd97250e3cf2816bca72ce7f14 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 26 Oct 2023 15:09:28 -0400 Subject: [PATCH 079/131] Update CocoaPods to v1.14.0 (#106) --- Gemfile | 2 +- Gemfile.lock | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index c0a8695c..90a7184d 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.12.1' +gem 'cocoapods', '1.14.0' gem 'cocoapods-generate', '2.0.1' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index 7aa6ef67..0cb13574 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,10 +3,15 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.0.7.2) + activesupport (7.1.1) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) @@ -14,17 +19,19 @@ GEM httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) + base64 (0.1.1) + bigdecimal (3.1.4) claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.12.1) + cocoapods (1.14.0) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.12.1) + cocoapods-core (= 1.14.0) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.6.0, < 2.0) + cocoapods-downloader (>= 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -36,8 +43,8 @@ GEM molinillo (~> 0.8.0) nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.12.1) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.14.0) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -49,7 +56,7 @@ GEM typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) cocoapods-disable-podfile-validations (0.1.1) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.0) cocoapods-generate (2.0.1) cocoapods-disable-podfile-validations (~> 0.1.1) cocoapods-plugins (1.0.0) @@ -61,6 +68,7 @@ GEM cocoapods-try (1.2.0) colored2 (3.1.2) concurrent-ruby (1.2.2) + connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) danger (8.4.5) @@ -76,6 +84,8 @@ GEM no_proxy_fix octokit (~> 4.7) terminal-table (>= 1, < 4) + drb (2.1.1) + ruby2_keywords escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -104,7 +114,7 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - ffi (1.15.5) + ffi (1.16.3) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -118,9 +128,10 @@ GEM rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.19.0) + minitest (5.20.0) molinillo (0.8.0) multipart-post (2.2.3) + mutex_m (0.1.2) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) @@ -144,7 +155,7 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.2.0) - xcodeproj (1.22.0) + xcodeproj (1.23.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -156,7 +167,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.12.1) + cocoapods (= 1.14.0) cocoapods-generate (= 2.0.1) danger (= 8.4.5) From be0ee94867355be85addcf15106e29b7052a4a8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:55:12 -0700 Subject: [PATCH 080/131] Bump git from 1.11.0 to 1.18.0 (#104) Bumps [git](https://github.com/ruby-git/ruby-git) from 1.11.0 to 1.18.0. - [Release notes](https://github.com/ruby-git/ruby-git/releases) - [Changelog](https://github.com/ruby-git/ruby-git/blob/master/CHANGELOG.md) - [Commits](https://github.com/ruby-git/ruby-git/compare/v1.11.0...v1.18.0) --- updated-dependencies: - dependency-name: git dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0cb13574..40dd5163 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,7 +118,8 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - git (1.11.0) + git (1.18.0) + addressable (~> 2.8) rchardet (~> 1.8) httpclient (2.8.3) i18n (1.14.1) From d47c3689ab868703e0b9f9c7cb2ab99443207e02 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 27 Oct 2023 20:46:36 -0400 Subject: [PATCH 081/131] Add support for transporting product ID in events (#107) * Add support for transporting product ID in events * Add unit tests * Adjust generated files * Add changelog entry * Review * Set env var in project generation script --- CHANGELOG.md | 3 + .../GDTCCTLibrary/GDTCCTNanopbHelpers.m | 23 +++ .../Private/GDTCCTNanopbHelpers.h | 9 ++ .../Protogen/nanopb/cct.nanopb.c | 7 +- .../Protogen/nanopb/cct.nanopb.h | 11 +- .../Protogen/nanopb/compliance.nanopb.c | 62 ++++++++ .../Protogen/nanopb/compliance.nanopb.h | 77 +++++++++ .../nanopb/external_prequest_context.nanopb.c | 35 ++++ .../nanopb/external_prequest_context.nanopb.h | 62 ++++++++ .../nanopb/external_privacy_context.nanopb.c | 59 +++++++ .../nanopb/external_privacy_context.nanopb.h | 64 ++++++++ .../Unit/GDTCCTNanopbHelpersTest.m | 14 ++ .../Unit/Helpers/GDTCCTEventGenerator.m | 13 +- .../GDTCORLibrary/GDTCOREvent.m | 23 ++- .../GDTCORLibrary/GDTCORProductData.m | 77 +++++++++ .../Public/GoogleDataTransport/GDTCOREvent.h | 15 ++ .../GoogleDataTransport/GDTCORProductData.h | 29 ++++ .../GoogleDataTransport/GoogleDataTransport.h | 1 + .../GDTCORTests/Unit/GDTCOREventTest.m | 149 ++++++++++++------ .../Unit/GDTCORFlatFileStorageTest.m | 9 +- .../GDTCORTests/Unit/GDTCORProductDataTest.m | 67 ++++++++ .../ProtoSupport/Protos/cct.proto | 4 + .../ProtoSupport/Protos/compliance.options | 15 ++ .../ProtoSupport/Protos/compliance.proto | 30 ++++ .../Protos/external_prequest_context.options | 15 ++ .../Protos/external_prequest_context.proto | 21 +++ .../Protos/external_privacy_context.options | 15 ++ .../Protos/external_privacy_context.proto | 23 +++ .../ProtoSupport/proto_generator.py | 2 +- GoogleDataTransport/generate_project.sh | 2 +- 30 files changed, 871 insertions(+), 65 deletions(-) create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.c create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.c create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.c create mode 100644 GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h create mode 100644 GoogleDataTransport/GDTCORLibrary/GDTCORProductData.m create mode 100644 GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h create mode 100644 GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m create mode 100644 GoogleDataTransport/ProtoSupport/Protos/compliance.options create mode 100644 GoogleDataTransport/ProtoSupport/Protos/compliance.proto create mode 100644 GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.options create mode 100644 GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.proto create mode 100644 GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.options create mode 100644 GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.proto diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ceb2dc0..b5be3420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Added new `GDTCOREvent` initializer to support attaching a product ID to an event. + # v9.2.5 - Replace 'TARGET_OS_XR' with 'TARGET_OS_VISION' for compatibility with Xcode 15 Beta 5. diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m index c5ced06b..450fda8d 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m @@ -160,9 +160,32 @@ gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event) { return logEvent; } logEvent.source_extension = GDTCCTEncodeData(extensionBytes); // read bytes from the file. + if (event.productData) { + logEvent.compliance_data = GDTCCTConstructComplianceData(event.productData); + logEvent.has_compliance_data = 1; + } return logEvent; } +gdt_cct_ComplianceData GDTCCTConstructComplianceData(GDTCORProductData *productData) { + privacy_context_external_ExternalPRequestContext prequest = + privacy_context_external_ExternalPRequestContext_init_default; + prequest.origin_associated_product_id = productData.productID; + prequest.has_origin_associated_product_id = 1; + + privacy_context_external_ExternalPrivacyContext privacy_context = + privacy_context_external_ExternalPrivacyContext_init_default; + privacy_context.prequest = prequest; + privacy_context.has_prequest = 1; + + gdt_cct_ComplianceData complianceData = gdt_cct_ComplianceData_init_default; + complianceData.privacy_context = privacy_context; + complianceData.has_privacy_context = 1; + complianceData.product_id_origin = gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE; + complianceData.has_product_id_origin = 1; + return complianceData; +} + gdt_cct_ClientInfo GDTCCTConstructClientInfo(void) { gdt_cct_ClientInfo clientInfo = gdt_cct_ClientInfo_init_default; clientInfo.client_type = gdt_cct_ClientInfo_ClientType_IOS_FIREBASE; diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h index d3fe2848..2f0ccd0f 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h @@ -18,8 +18,10 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" +#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h" NS_ASSUME_NONNULL_BEGIN @@ -83,6 +85,13 @@ gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, NSSet +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h" + /* @@protoc_insertion_point(includes) */ #if PB_PROTO_HEADER_VERSION != 30 #error Regenerate this file with the current version of nanopb generator. @@ -171,6 +173,8 @@ typedef struct _gdt_cct_LogEvent { int64_t event_uptime_ms; bool has_network_connection_info; gdt_cct_NetworkConnectionInfo network_connection_info; + bool has_compliance_data; + gdt_cct_ComplianceData compliance_data; /* @@protoc_insertion_point(struct:gdt_cct_LogEvent) */ } gdt_cct_LogEvent; @@ -205,7 +209,7 @@ extern const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_de extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; /* Initializer values for message structs */ -#define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default} +#define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default, false, gdt_cct_ComplianceData_init_default} #define gdt_cct_NetworkConnectionInfo_init_default {false, gdt_cct_NetworkConnectionInfo_NetworkType_NONE, false, gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE} #define gdt_cct_MacClientInfo_init_default {NULL, NULL, NULL, NULL} #define gdt_cct_IosClientInfo_init_default {NULL, NULL, NULL, NULL, NULL, NULL, NULL} @@ -215,7 +219,7 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; #define gdt_cct_QosTierConfiguration_init_default {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0} #define gdt_cct_QosTiersOverride_init_default {0, NULL, false, 0} #define gdt_cct_LogResponse_init_default {false, 0, false, gdt_cct_QosTiersOverride_init_default} -#define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero} +#define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero, false, gdt_cct_ComplianceData_init_zero} #define gdt_cct_NetworkConnectionInfo_init_zero {false, _gdt_cct_NetworkConnectionInfo_NetworkType_MIN, false, _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN} #define gdt_cct_MacClientInfo_init_zero {NULL, NULL, NULL, NULL} #define gdt_cct_IosClientInfo_init_zero {NULL, NULL, NULL, NULL, NULL, NULL, NULL} @@ -254,6 +258,7 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; #define gdt_cct_LogEvent_source_extension_tag 6 #define gdt_cct_LogEvent_timezone_offset_seconds_tag 15 #define gdt_cct_LogEvent_network_connection_info_tag 23 +#define gdt_cct_LogEvent_compliance_data_tag 33 #define gdt_cct_LogRequest_request_time_ms_tag 4 #define gdt_cct_LogRequest_request_uptime_ms_tag 8 #define gdt_cct_LogRequest_client_info_tag 1 @@ -264,7 +269,7 @@ extern const int32_t gdt_cct_QosTierConfiguration_log_source_default; #define gdt_cct_LogResponse_qos_tier_tag 3 /* Struct field encoding specification for nanopb */ -extern const pb_field_t gdt_cct_LogEvent_fields[7]; +extern const pb_field_t gdt_cct_LogEvent_fields[8]; extern const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3]; extern const pb_field_t gdt_cct_MacClientInfo_fields[5]; extern const pb_field_t gdt_cct_IosClientInfo_fields[8]; diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.c new file mode 100644 index 00000000..f3552f72 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.c @@ -0,0 +1,62 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.9 */ + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + +const gdt_cct_ComplianceData_ProductIdOrigin gdt_cct_ComplianceData_product_id_origin_default = gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET; + + +const pb_field_t gdt_cct_ComplianceData_fields[3] = { + PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, gdt_cct_ComplianceData, privacy_context, privacy_context, &privacy_context_external_ExternalPrivacyContext_fields), + PB_FIELD( 2, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_ComplianceData, product_id_origin, privacy_context, &gdt_cct_ComplianceData_product_id_origin_default), + PB_LAST_FIELD +}; + + + +/* Check that field information fits in pb_field_t */ +#if !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_32BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in 8 or 16 bit + * field descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_cct_ComplianceData, privacy_context) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_ComplianceData) +#endif + +#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_16BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in the default + * 8 bit descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(gdt_cct_ComplianceData, privacy_context) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_ComplianceData) +#endif + + +/* @@protoc_insertion_point(eof) */ diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h new file mode 100644 index 00000000..a143c76e --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h @@ -0,0 +1,77 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.9 */ + +#ifndef PB_GDT_CCT_COMPLIANCE_NANOPB_H_INCLUDED +#define PB_GDT_CCT_COMPLIANCE_NANOPB_H_INCLUDED +#include + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Enum definitions */ +typedef enum _gdt_cct_ComplianceData_ProductIdOrigin { + gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET = 0, + gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE = 5 +} gdt_cct_ComplianceData_ProductIdOrigin; +#define _gdt_cct_ComplianceData_ProductIdOrigin_MIN gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET +#define _gdt_cct_ComplianceData_ProductIdOrigin_MAX gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE +#define _gdt_cct_ComplianceData_ProductIdOrigin_ARRAYSIZE ((gdt_cct_ComplianceData_ProductIdOrigin)(gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE+1)) + +/* Struct definitions */ +typedef struct _gdt_cct_ComplianceData { + bool has_privacy_context; + privacy_context_external_ExternalPrivacyContext privacy_context; + bool has_product_id_origin; + gdt_cct_ComplianceData_ProductIdOrigin product_id_origin; +/* @@protoc_insertion_point(struct:gdt_cct_ComplianceData) */ +} gdt_cct_ComplianceData; + +/* Default values for struct fields */ +extern const gdt_cct_ComplianceData_ProductIdOrigin gdt_cct_ComplianceData_product_id_origin_default; + +/* Initializer values for message structs */ +#define gdt_cct_ComplianceData_init_default {false, privacy_context_external_ExternalPrivacyContext_init_default, false, gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET} +#define gdt_cct_ComplianceData_init_zero {false, privacy_context_external_ExternalPrivacyContext_init_zero, false, _gdt_cct_ComplianceData_ProductIdOrigin_MIN} + +/* Field tags (for use in manual encoding/decoding) */ +#define gdt_cct_ComplianceData_privacy_context_tag 1 +#define gdt_cct_ComplianceData_product_id_origin_tag 2 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t gdt_cct_ComplianceData_fields[3]; + +/* Maximum encoded size of messages (where known) */ +#define gdt_cct_ComplianceData_size (14 + privacy_context_external_ExternalPRequestContext_size) + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define COMPLIANCE_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.c new file mode 100644 index 00000000..9eb1d167 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.c @@ -0,0 +1,35 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.9 */ + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + +const pb_field_t privacy_context_external_ExternalPRequestContext_fields[2] = { + PB_FIELD( 13, INT32 , OPTIONAL, STATIC , FIRST, privacy_context_external_ExternalPRequestContext, origin_associated_product_id, origin_associated_product_id, 0), + PB_LAST_FIELD +}; + + +/* @@protoc_insertion_point(eof) */ diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h new file mode 100644 index 00000000..03f0f124 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h @@ -0,0 +1,62 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.9 */ + +#ifndef PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PREQUEST_CONTEXT_NANOPB_H_INCLUDED +#define PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PREQUEST_CONTEXT_NANOPB_H_INCLUDED +#include + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Struct definitions */ +typedef struct _privacy_context_external_ExternalPRequestContext { + bool has_origin_associated_product_id; + int32_t origin_associated_product_id; +/* @@protoc_insertion_point(struct:privacy_context_external_ExternalPRequestContext) */ +} privacy_context_external_ExternalPRequestContext; + +/* Default values for struct fields */ + +/* Initializer values for message structs */ +#define privacy_context_external_ExternalPRequestContext_init_default {false, 0} +#define privacy_context_external_ExternalPRequestContext_init_zero {false, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define privacy_context_external_ExternalPRequestContext_origin_associated_product_id_tag 13 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t privacy_context_external_ExternalPRequestContext_fields[2]; + +/* Maximum encoded size of messages (where known) */ +#define privacy_context_external_ExternalPRequestContext_size 11 + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define EXTERNAL_PREQUEST_CONTEXT_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.c b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.c new file mode 100644 index 00000000..8b816057 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.c @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.3.9.9 */ + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + + +const pb_field_t privacy_context_external_ExternalPrivacyContext_fields[2] = { + PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , FIRST, privacy_context_external_ExternalPrivacyContext, prequest, prequest, &privacy_context_external_ExternalPRequestContext_fields), + PB_LAST_FIELD +}; + + +/* Check that field information fits in pb_field_t */ +#if !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_32BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in 8 or 16 bit + * field descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(privacy_context_external_ExternalPrivacyContext, prequest) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_privacy_context_external_ExternalPrivacyContext) +#endif + +#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT) +/* If you get an error here, it means that you need to define PB_FIELD_16BIT + * compile-time option. You can do that in pb.h or on compiler command line. + * + * The reason you need to do this is that some of your messages contain tag + * numbers or field sizes that are larger than what can fit in the default + * 8 bit descriptors. + */ +PB_STATIC_ASSERT((pb_membersize(privacy_context_external_ExternalPrivacyContext, prequest) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_privacy_context_external_ExternalPrivacyContext) +#endif + + +/* @@protoc_insertion_point(eof) */ diff --git a/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h new file mode 100644 index 00000000..dc6981a5 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h @@ -0,0 +1,64 @@ +/* + * Copyright 2023 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. + */ + +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.3.9.9 */ + +#ifndef PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PRIVACY_CONTEXT_NANOPB_H_INCLUDED +#define PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PRIVACY_CONTEXT_NANOPB_H_INCLUDED +#include + +#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h" + +/* @@protoc_insertion_point(includes) */ +#if PB_PROTO_HEADER_VERSION != 30 +#error Regenerate this file with the current version of nanopb generator. +#endif + + +/* Struct definitions */ +typedef struct _privacy_context_external_ExternalPrivacyContext { + bool has_prequest; + privacy_context_external_ExternalPRequestContext prequest; +/* @@protoc_insertion_point(struct:privacy_context_external_ExternalPrivacyContext) */ +} privacy_context_external_ExternalPrivacyContext; + +/* Default values for struct fields */ + +/* Initializer values for message structs */ +#define privacy_context_external_ExternalPrivacyContext_init_default {false, privacy_context_external_ExternalPRequestContext_init_default} +#define privacy_context_external_ExternalPrivacyContext_init_zero {false, privacy_context_external_ExternalPRequestContext_init_zero} + +/* Field tags (for use in manual encoding/decoding) */ +#define privacy_context_external_ExternalPrivacyContext_prequest_tag 2 + +/* Struct field encoding specification for nanopb */ +extern const pb_field_t privacy_context_external_ExternalPrivacyContext_fields[2]; + +/* Maximum encoded size of messages (where known) */ +#define privacy_context_external_ExternalPrivacyContext_size 13 + +/* Message IDs (where set with "msgid" option) */ +#ifdef PB_MSGID + +#define EXTERNAL_PRIVACY_CONTEXT_MESSAGES \ + + +#endif + +/* @@protoc_insertion_point(eof) */ + +#endif diff --git a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m index 1845c74f..9e4e2ca8 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/GDTCCTNanopbHelpersTest.m @@ -139,6 +139,20 @@ - (void)testBytesAreDecodable { pb_release(gdt_cct_BatchedLogRequest_fields, &decodedBatch); } +- (void)testLogEventsPopulateComplianceFieldWhenGDTEventHasProductData { + // Two of the generated events have product data. + NSSet *storedEvents = + [NSSet setWithArray:[self.generator generateTheFiveConsistentEvents]]; + gdt_cct_BatchedLogRequest batch = GDTCCTConstructBatchedLogRequest(@{@"1018" : storedEvents}); + int eventsThatContainProductData = 0; + for (int i = 0; i < batch.log_request->log_event_count; i++) { + gdt_cct_LogEvent logEvent = batch.log_request->log_event[i]; + eventsThatContainProductData += logEvent.has_compliance_data; + } + XCTAssertEqual(eventsThatContainProductData, 2, + @"Only two of the five events should have compliance data."); +} + - (void)testDecodedEventTimestampMatchToBatchContent { GDTCOREvent *storedEvent = [self.generator generateEvent:GDTCOREventQoSDaily]; NSSet *storedEvents = [NSSet setWithObject:storedEvent]; diff --git a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m index 932bc0a0..ee045fbe 100644 --- a/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m +++ b/GoogleDataTransport/GDTCCTTests/Unit/Helpers/GDTCCTEventGenerator.m @@ -20,6 +20,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" #import "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h" @@ -110,10 +111,15 @@ - (NSURL *)writeConsistentMessageToDisk:(NSString *)messageResource { return [NSURL fileURLWithPath:filePath]; } +/// Consistently generates the same five events. The events slightly differ in their attributes. The +/// first two out of the five events have non-`nil` `productData`. - (NSArray *)generateTheFiveConsistentEvents { NSMutableArray *events = [[NSMutableArray alloc] init]; { - GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"1018" target:_target]; + GDTCOREvent *event = + [[GDTCOREvent alloc] initWithMappingID:@"1018" + productData:[[GDTCORProductData alloc] initWithProductID:98765] + target:_target]; event.clockSnapshot = [GDTCORClock snapshot]; [event.clockSnapshot setValue:@(1111111111111) forKeyPath:@"timeMillis"]; [event.clockSnapshot setValue:@(-25200) forKeyPath:@"timezoneOffsetSeconds"]; @@ -143,7 +149,10 @@ - (NSURL *)writeConsistentMessageToDisk:(NSString *)messageResource { } { - GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"1018" target:_target]; + GDTCOREvent *event = + [[GDTCOREvent alloc] initWithMappingID:@"1018" + productData:[[GDTCORProductData alloc] initWithProductID:98765] + target:_target]; event.clockSnapshot = [GDTCORClock snapshot]; [event.clockSnapshot setValue:@(1111111111111) forKeyPath:@"timeMillis"]; [event.clockSnapshot setValue:@(-25200) forKeyPath:@"timezoneOffsetSeconds"]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m b/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m index 5a8d3243..98db41ac 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m @@ -21,6 +21,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h" @@ -31,7 +32,9 @@ + (NSString *)nextEventID { return [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""]; } -- (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target { +- (nullable instancetype)initWithMappingID:(NSString *)mappingID + productData:(nullable GDTCORProductData *)productData + target:(GDTCORTarget)target { GDTCORAssert(mappingID.length > 0, @"Please give a valid mapping ID"); GDTCORAssert(target > 0, @"A target cannot be negative or 0"); if (mappingID.length == 0 || target <= 0) { @@ -41,6 +44,7 @@ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORT if (self) { _eventID = [GDTCOREvent nextEventID]; _mappingID = mappingID; + _productData = productData; _target = target; _qosTier = GDTCOREventQosDefault; _expirationDate = [NSDate dateWithTimeIntervalSinceNow:604800]; // 7 days. @@ -52,8 +56,14 @@ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORT return self; } +- (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target { + return [self initWithMappingID:mappingID productData:nil target:target]; +} + - (instancetype)copy { - GDTCOREvent *copy = [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; + GDTCOREvent *copy = [[GDTCOREvent alloc] initWithMappingID:_mappingID + productData:_productData + target:_target]; copy->_eventID = _eventID; copy.dataObject = _dataObject; copy.qosTier = _qosTier; @@ -67,10 +77,12 @@ - (NSUInteger)hash { // This loses some precision, but it's probably fine. NSUInteger eventIDHash = [_eventID hash]; NSUInteger mappingIDHash = [_mappingID hash]; + NSUInteger productDataHash = [_productData hash]; NSUInteger timeHash = [_clockSnapshot hash]; NSInteger serializedBytesHash = [_serializedDataObjectBytes hash]; - return eventIDHash ^ mappingIDHash ^ _target ^ _qosTier ^ timeHash ^ serializedBytesHash; + return eventIDHash ^ mappingIDHash ^ productDataHash ^ _target ^ _qosTier ^ timeHash ^ + serializedBytesHash; } - (BOOL)isEqual:(id)object { @@ -115,6 +127,9 @@ - (void)setDataObject:(id)dataObject { /** NSCoding key for customData property. */ static NSString *kCustomDataKey = @"GDTCOREventCustomDataKey"; +/** NSCoding key for productData property. */ +static NSString *kProductDataKey = @"GDTCOREventProductDataKey"; + + (BOOL)supportsSecureCoding { return YES; } @@ -123,6 +138,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder { self = [self init]; if (self) { _mappingID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kMappingIDKey]; + _productData = [aDecoder decodeObjectOfClass:[GDTCORProductData class] forKey:kProductDataKey]; _target = [aDecoder decodeIntegerForKey:kTargetKey]; _eventID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kEventIDKey] ?: [GDTCOREvent nextEventID]; @@ -142,6 +158,7 @@ - (id)initWithCoder:(NSCoder *)aDecoder { - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_eventID forKey:kEventIDKey]; [aCoder encodeObject:_mappingID forKey:kMappingIDKey]; + [aCoder encodeObject:_productData forKey:kProductDataKey]; [aCoder encodeInteger:_target forKey:kTargetKey]; [aCoder encodeInteger:_qosTier forKey:kQoSTierKey]; [aCoder encodeObject:_clockSnapshot forKey:kClockSnapshotKey]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORProductData.m b/GoogleDataTransport/GDTCORLibrary/GDTCORProductData.m new file mode 100644 index 00000000..5e15e8fb --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORProductData.m @@ -0,0 +1,77 @@ +// Copyright 2023 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" + +@implementation GDTCORProductData + +- (instancetype)initWithProductID:(int32_t)productID { + self = [super init]; + if (self) { + _productID = productID; + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + return [[[self class] alloc] initWithProductID:self.productID]; +} + +#pragma mark - Equality + +- (BOOL)isEqualToProductData:(GDTCORProductData *)otherProductData { + return self.productID == otherProductData.productID; +} + +- (BOOL)isEqual:(nullable id)object { + if (object == nil) { + return NO; + } + + if (self == object) { + return YES; + } + + if (![object isKindOfClass:[self class]]) { + return NO; + } + + return [self isEqualToProductData:(GDTCORProductData *)object]; +} + +- (NSUInteger)hash { + return self.productID; +} + +#pragma mark - NSSecureCoding + +/// NSCoding key for `productID` property. +static NSString *kProductIDKey = @"GDTCORProductDataProductIDKey"; + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + int32_t productID = [coder decodeInt32ForKey:kProductIDKey]; + return [self initWithProductID:productID]; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeInt32:self.productID forKey:kProductIDKey]; +} + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h index 52c2384e..f053d3c6 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h @@ -20,6 +20,7 @@ #import "GDTCORTargets.h" @class GDTCORClock; +@class GDTCORProductData; NS_ASSUME_NONNULL_BEGIN @@ -74,6 +75,9 @@ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { /** Bytes that can be used by an uploader later on. */ @property(nullable, nonatomic) NSData *customBytes; +/** The product data that the event is associated with, if any. */ +@property(nullable, readonly, nonatomic) GDTCORProductData *productData; + /** Initializes an instance using the given mappingID. * * @param mappingID The mapping identifier. @@ -82,6 +86,17 @@ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target; +/** Initializes an instance using the given mappingID. + * + * @param mappingID The mapping identifier. + * @param productData The product data to associate this event with. + * @param target The event's target identifier. + * @return An instance of this class. + */ +- (nullable instancetype)initWithMappingID:(NSString *)mappingID + productData:(nullable GDTCORProductData *)productData + target:(GDTCORTarget)target; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h new file mode 100644 index 00000000..ff99822e --- /dev/null +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h @@ -0,0 +1,29 @@ +// Copyright 2023 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 + +@interface GDTCORProductData : NSObject + +/// The product ID. +@property(nonatomic, readonly) int32_t productID; + +/// Initializes an instance using the given product ID. +/// - Parameter productID: A 32-bit integer. +- (instancetype)initWithProductID:(int32_t)productID; + +/// This API is unavailable. +- (instancetype)init NS_UNAVAILABLE; + +@end diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h index 0bd39dc0..b5e83d1d 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GoogleDataTransport.h @@ -20,5 +20,6 @@ #import "GDTCOREvent.h" #import "GDTCOREventDataObject.h" #import "GDTCOREventTransformer.h" +#import "GDTCORProductData.h" #import "GDTCORTargets.h" #import "GDTCORTransport.h" diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m index 77a39089..09bbaa97 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m @@ -15,6 +15,7 @@ */ #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" @@ -34,42 +35,42 @@ @interface GDTCOREventTest : GDTCORTestCase @implementation GDTCOREventTest -/** Tests the designated initializer. */ +/** Tests initializers. */ - (void)testInit { + // Test `- [GDTCOREvent initWithMappingID:target:]` XCTAssertNotNil([[GDTCOREvent alloc] initWithMappingID:@"1" target:kGDTCORTargetTest].eventID); XCTAssertNotNil([[GDTCOREvent alloc] initWithMappingID:@"1" target:kGDTCORTargetTest]); XCTAssertNil([[GDTCOREvent alloc] initWithMappingID:@"" target:kGDTCORTargetTest]); + // Test `- [GDTCOREvent initWithMappingID:productData:target:]` + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:123456]; + GDTCOREvent *event1 = [[GDTCOREvent alloc] initWithMappingID:@"1" + productData:productData + target:kGDTCORTargetTest]; + XCTAssertNotNil(event1); + XCTAssertNotNil(event1.productData); + XCTAssertEqual(event1.productData.productID, 123456); + GDTCOREvent *event2 = [[GDTCOREvent alloc] initWithMappingID:@"1" + productData:nil + target:kGDTCORTargetTest]; + XCTAssertNotNil(event2); + XCTAssertNil(event2.productData); } /** Tests NSKeyedArchiver encoding and decoding. */ - (void)testArchiving { - XCTAssertTrue([GDTCOREvent supportsSecureCoding]); - GDTCORClock *clockSnapshot = [GDTCORClock snapshot]; - int64_t timeMillis = clockSnapshot.timeMillis; - int64_t timezoneOffsetSeconds = clockSnapshot.timezoneOffsetSeconds; + // Test round trip archive of event with `nil` product data. GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"testID" target:kGDTCORTargetTest]; - event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; - event.qosTier = GDTCOREventQoSTelemetry; - event.clockSnapshot = clockSnapshot; - - NSError *error; - NSData *archiveData = GDTCOREncodeArchive(event, nil, &error); - XCTAssertNil(error); - XCTAssertNotNil(archiveData); - - // To ensure that all the objects being retained by the original event are dealloc'd. - event = nil; - error = nil; - GDTCOREvent *decodedEvent = - (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], archiveData, &error); - XCTAssertNil(error); - XCTAssertNotNil(decodedEvent); - XCTAssertEqualObjects(decodedEvent.mappingID, @"testID"); - XCTAssertEqual(decodedEvent.target, kGDTCORTargetTest); - event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; - XCTAssertEqual(decodedEvent.qosTier, GDTCOREventQoSTelemetry); - XCTAssertEqual(decodedEvent.clockSnapshot.timeMillis, timeMillis); - XCTAssertEqual(decodedEvent.clockSnapshot.timezoneOffsetSeconds, timezoneOffsetSeconds); + GDTCOREvent *roundTripEvent = [self assertArchiveUnarchiveRoundTripForEvent:event]; + XCTAssertEqualObjects(event, roundTripEvent); + + // Test round trip archive of event with non-`nil` product data. + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:123456]; + GDTCOREvent *eventWithProductData = [[GDTCOREvent alloc] initWithMappingID:@"testID" + productData:productData + target:kGDTCORTargetTest]; + GDTCOREvent *roundTripEventWithProductData = + [self assertArchiveUnarchiveRoundTripForEvent:eventWithProductData]; + XCTAssertEqualObjects(eventWithProductData, roundTripEventWithProductData); } /** Tests setting variables on a GDTCOREvent instance.*/ @@ -88,34 +89,18 @@ - (void)testSettingVariables { /** Tests equality between GDTCOREvents. */ - (void)testIsEqualAndHash { - GDTCOREvent *event1 = [[GDTCOREvent alloc] initWithMappingID:@"1018" target:kGDTCORTargetTest]; - event1.eventID = @"123"; - event1.clockSnapshot = [GDTCORClock snapshot]; - [event1.clockSnapshot setValue:@(1553534573010) forKeyPath:@"timeMillis"]; - [event1.clockSnapshot setValue:@(-25200) forKeyPath:@"timezoneOffsetSeconds"]; - [event1.clockSnapshot setValue:@(1552576634359451) forKeyPath:@"kernelBootTimeNanoseconds"]; - [event1.clockSnapshot setValue:@(961141365197) forKeyPath:@"uptimeNanoseconds"]; - event1.qosTier = GDTCOREventQosDefault; - event1.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; NSError *error1; - event1.customBytes = [NSJSONSerialization dataWithJSONObject:@{@"customParam1" : @"aValue1"} - options:0 - error:&error1]; + GDTCOREvent *event1 = [self eventWithMappingID:@"1018" + productData:nil + target:kGDTCORTargetTest + error:&error1]; XCTAssertNil(error1); - GDTCOREvent *event2 = [[GDTCOREvent alloc] initWithMappingID:@"1018" target:kGDTCORTargetTest]; - event2.eventID = @"123"; - event2.clockSnapshot = [GDTCORClock snapshot]; - [event2.clockSnapshot setValue:@(1553534573010) forKeyPath:@"timeMillis"]; - [event2.clockSnapshot setValue:@(-25200) forKeyPath:@"timezoneOffsetSeconds"]; - [event2.clockSnapshot setValue:@(1552576634359451) forKeyPath:@"kernelBootTimeNanoseconds"]; - [event2.clockSnapshot setValue:@(961141365197) forKeyPath:@"uptimeNanoseconds"]; - event2.qosTier = GDTCOREventQosDefault; - event2.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; NSError *error2; - event2.customBytes = [NSJSONSerialization dataWithJSONObject:@{@"customParam1" : @"aValue1"} - options:0 - error:&error2]; + GDTCOREvent *event2 = [self eventWithMappingID:@"1018" + productData:nil + target:kGDTCORTargetTest + error:&error2]; XCTAssertNil(error2); XCTAssertEqual([event1 hash], [event2 hash]); XCTAssertEqualObjects(event1, event2); @@ -125,6 +110,15 @@ - (void)testIsEqualAndHash { XCTAssertNotEqual([event1 hash], [event2 hash]); XCTAssertNotEqualObjects(event1, event2); + + // This only really tests that changing the `productData` value causes a change in hash. + NSError *error3; + GDTCOREvent *event3 = [self eventWithMappingID:@"1018" + productData:[[GDTCORProductData alloc] initWithProductID:98765] + target:kGDTCORTargetTest + error:&error3]; + XCTAssertNil(error3); + XCTAssertNotEqual([event1 hash], [event3 hash]); } /** Tests generating event IDs. */ @@ -141,4 +135,57 @@ - (void)testGenerateEventIDs { self.continueAfterFailure = originalContinueAfterFailureValue; } +/** Archives the given event and returns its unarchived form. */ +- (GDTCOREvent *)assertArchiveUnarchiveRoundTripForEvent:(GDTCOREvent *)event { + XCTAssertTrue([GDTCOREvent supportsSecureCoding]); + GDTCORClock *clockSnapshot = [GDTCORClock snapshot]; + int64_t timeMillis = clockSnapshot.timeMillis; + int64_t timezoneOffsetSeconds = clockSnapshot.timezoneOffsetSeconds; + event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; + event.qosTier = GDTCOREventQoSTelemetry; + event.clockSnapshot = clockSnapshot; + + NSError *error; + NSData *archiveData = GDTCOREncodeArchive(event, nil, &error); + XCTAssertNil(error); + XCTAssertNotNil(archiveData); + + // To ensure that all the objects being retained by the original event are dealloc'd. + event = nil; + error = nil; + GDTCOREvent *decodedEvent = + (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], archiveData, &error); + XCTAssertNil(error); + XCTAssertNotNil(decodedEvent); + XCTAssertEqualObjects(decodedEvent.mappingID, @"testID"); + XCTAssertEqual(decodedEvent.target, kGDTCORTargetTest); + event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; + XCTAssertEqual(decodedEvent.qosTier, GDTCOREventQoSTelemetry); + XCTAssertEqual(decodedEvent.clockSnapshot.timeMillis, timeMillis); + XCTAssertEqual(decodedEvent.clockSnapshot.timezoneOffsetSeconds, timezoneOffsetSeconds); + return decodedEvent; +} + +/** Creates an event with a set snapshot time. */ +- (GDTCOREvent *)eventWithMappingID:(NSString *)mappingID + productData:(nullable GDTCORProductData *)productData + target:(GDTCORTarget)target + error:(NSError **)outError { + GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"1018" + productData:productData + target:kGDTCORTargetTest]; + event.eventID = @"123"; + event.clockSnapshot = [GDTCORClock snapshot]; + [event.clockSnapshot setValue:@(1553534573010) forKeyPath:@"timeMillis"]; + [event.clockSnapshot setValue:@(-25200) forKeyPath:@"timezoneOffsetSeconds"]; + [event.clockSnapshot setValue:@(1552576634359451) forKeyPath:@"kernelBootTimeNanoseconds"]; + [event.clockSnapshot setValue:@(961141365197) forKeyPath:@"uptimeNanoseconds"]; + event.qosTier = GDTCOREventQosDefault; + event.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"someData"]; + event.customBytes = [NSJSONSerialization dataWithJSONObject:@{@"customParam1" : @"aValue1"} + options:0 + error:outError]; + return event; +} + @end diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m index 1e35db35..561f3dc1 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m @@ -27,6 +27,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORAssertHelper.h" #import "GoogleDataTransport/GDTCORTests/Unit/Helpers/GDTCORDataObjectTesterClasses.h" @@ -213,7 +214,10 @@ - (void)testStoreMultipleEvents { }]); [self waitForExpectations:@[ writtenExpectation ] timeout:10.0]; - GDTCOREvent *event3 = [[GDTCOREvent alloc] initWithMappingID:@"404" target:kGDTCORTargetTest]; + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:98765]; + GDTCOREvent *event3 = [[GDTCOREvent alloc] initWithMappingID:@"404" + productData:productData + target:kGDTCORTargetTest]; event3.dataObject = [[GDTCORDataObjectTesterSimple alloc] initWithString:@"testString3"]; writtenExpectation = [self expectationWithDescription:@"event written"]; XCTAssertNoThrow([storage storeEvent:event3 @@ -232,6 +236,9 @@ - (void)testStoreMultipleEvents { onComplete:^(NSNumber *_Nullable batchID, NSSet *_Nullable events) { XCTAssertEqual(events.count, 3); + XCTAssert([events containsObject:event1]); + XCTAssert([events containsObject:event2]); + XCTAssert([events containsObject:event3]); [expectation fulfill]; }]; [self waitForExpectations:@[ expectation ] timeout:10]; diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m new file mode 100644 index 00000000..2a38a9e4 --- /dev/null +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m @@ -0,0 +1,67 @@ +// Copyright 2023 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 "GoogleDataTransport/GDTCORTests/Unit/GDTCORTestCase.h" + +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" + +#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h" + +@interface GDTCORProductDataTest : GDTCORTestCase + +@end + +@implementation GDTCORProductDataTest + +static int32_t kTestProductID = 123456; + +- (void)testInit { + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:kTestProductID]; + XCTAssertEqual(productData.productID, kTestProductID); +} + +- (void)testCopy { + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:kTestProductID]; + XCTAssertEqualObjects([productData copy], productData); +} + +- (void)testSecureCoding { + // Given + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:kTestProductID]; + + // When + // - Encode. + NSError *encodeError; + NSData *encodedProductData = GDTCOREncodeArchive(productData, nil, &encodeError); + XCTAssertNil(encodeError); + XCTAssertNotNil(encodedProductData); + + // - Write to disk. + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"productdata.dat"]; + NSError *writeError; + BOOL writeResult = GDTCORWriteDataToFile(encodedProductData, filePath, &writeError); + XCTAssertNil(writeError); + XCTAssertTrue(writeResult); + + // Then + // - Decode from disk. + NSError *decodeError; + GDTCORProductData *decodedProductData = (GDTCORProductData *)GDTCORDecodeArchiveAtPath( + GDTCORProductData.class, filePath, &decodeError); + XCTAssertNil(decodeError); + XCTAssertNotNil(decodedProductData); + XCTAssertEqualObjects(decodedProductData, productData); +} + +@end diff --git a/GoogleDataTransport/ProtoSupport/Protos/cct.proto b/GoogleDataTransport/ProtoSupport/Protos/cct.proto index 54dab8bb..8d644269 100644 --- a/GoogleDataTransport/ProtoSupport/Protos/cct.proto +++ b/GoogleDataTransport/ProtoSupport/Protos/cct.proto @@ -18,6 +18,8 @@ syntax = "proto2"; package gdt_cct; +import "compliance.proto"; + message LogEvent { optional int64 event_time_ms = 1; @@ -30,6 +32,8 @@ message LogEvent { optional sint64 timezone_offset_seconds = 15; optional NetworkConnectionInfo network_connection_info = 23; + + optional ComplianceData compliance_data = 33; } message NetworkConnectionInfo { diff --git a/GoogleDataTransport/ProtoSupport/Protos/compliance.options b/GoogleDataTransport/ProtoSupport/Protos/compliance.options new file mode 100644 index 00000000..ecb0dbaa --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/compliance.options @@ -0,0 +1,15 @@ +# Copyright 2023 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. + +# Options for compliance.proto diff --git a/GoogleDataTransport/ProtoSupport/Protos/compliance.proto b/GoogleDataTransport/ProtoSupport/Protos/compliance.proto new file mode 100644 index 00000000..c5fae8f6 --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/compliance.proto @@ -0,0 +1,30 @@ +// Copyright 2023 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. + +syntax = "proto2"; + +package gdt_cct; + +import "external_privacy_context.proto"; + +message ComplianceData { + optional privacy.context.external.ExternalPrivacyContext privacy_context = 1; + + enum ProductIdOrigin { + NOT_SET = 0; + EVENT_OVERRIDE = 5; + } + + optional ProductIdOrigin product_id_origin = 2 [default = NOT_SET]; +} diff --git a/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.options b/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.options new file mode 100644 index 00000000..7a4cff62 --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.options @@ -0,0 +1,15 @@ +# Copyright 2023 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. + +# Options for external_prequest_context.proto diff --git a/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.proto b/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.proto new file mode 100644 index 00000000..11a5b50e --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/external_prequest_context.proto @@ -0,0 +1,21 @@ +// Copyright 2023 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. + +syntax = "proto2"; + +package privacy.context.external; + +message ExternalPRequestContext { + optional int32 origin_associated_product_id = 13; +} diff --git a/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.options b/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.options new file mode 100644 index 00000000..74960867 --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.options @@ -0,0 +1,15 @@ +# Copyright 2023 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. + +# Options for external_privacy_context.proto diff --git a/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.proto b/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.proto new file mode 100644 index 00000000..ba0dbf21 --- /dev/null +++ b/GoogleDataTransport/ProtoSupport/Protos/external_privacy_context.proto @@ -0,0 +1,23 @@ +// Copyright 2023 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. + +syntax = "proto2"; + +package privacy.context.external; + +import "external_prequest_context.proto"; + +message ExternalPrivacyContext { + optional ExternalPRequestContext prequest = 2; +} diff --git a/GoogleDataTransport/ProtoSupport/proto_generator.py b/GoogleDataTransport/ProtoSupport/proto_generator.py index fab4b22b..8a73a2b2 100755 --- a/GoogleDataTransport/ProtoSupport/proto_generator.py +++ b/GoogleDataTransport/ProtoSupport/proto_generator.py @@ -39,7 +39,7 @@ COPYRIGHT_NOTICE = ''' /* - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/GoogleDataTransport/generate_project.sh b/GoogleDataTransport/generate_project.sh index 9d31e7dc..63bd6a3a 100755 --- a/GoogleDataTransport/generate_project.sh +++ b/GoogleDataTransport/generate_project.sh @@ -31,4 +31,4 @@ readonly DIR="$(git rev-parse --show-toplevel)" "$DIR/GoogleDataTransport/ProtoSupport/generate_cct_protos.sh" || echo "Something went wrong generating protos."; -pod gen "$DIR/GoogleDataTransport.podspec" --auto-open --local-sources=./ --gen-directory="$DIR/gen" --platforms=${platform} --clean +GDT_TEST=1 pod gen "$DIR/GoogleDataTransport.podspec" --auto-open --local-sources=./ --gen-directory="$DIR/gen" --platforms=${platform} --clean From 4e5cbb187ece1acc59c1f4d101709d88f68c0683 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sat, 28 Oct 2023 00:29:06 -0400 Subject: [PATCH 082/131] Add public convenience API for generating a transport event with product data (#108) * Add public convenience API for generating a transport event with product data * Add changelog entry * Update CHANGELOG.md --- CHANGELOG.md | 2 +- .../GDTCORLibrary/GDTCORTransport.m | 4 + .../Public/GoogleDataTransport/GDTCOREvent.h | 4 +- .../GoogleDataTransport/GDTCORTransport.h | 9 ++ .../GDTCORTests/Unit/GDTCORProductDataTest.m | 12 +++ .../GDTCORTests/Unit/GDTCORTransportTest.m | 89 ++++++++++++++----- 6 files changed, 97 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5be3420..e92287f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Unreleased -- Added new `GDTCOREvent` initializer to support attaching a product ID to an event. +- Added new APIs for attaching product ID data to an event. # v9.2.5 - Replace 'TARGET_OS_XR' with 'TARGET_OS_VISION' for compatibility with diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m b/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m index 1db4d525..6efff4c1 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m @@ -71,6 +71,10 @@ - (GDTCOREvent *)eventForTransport { return [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target]; } +- (GDTCOREvent *)eventForTransportWithProductData:(GDTCORProductData *)productData { + return [[GDTCOREvent alloc] initWithMappingID:_mappingID productData:productData target:_target]; +} + #pragma mark - Private helper methods /** Sends the given event through the transport pipeline. diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h index f053d3c6..8ab36014 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h @@ -78,7 +78,7 @@ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { /** The product data that the event is associated with, if any. */ @property(nullable, readonly, nonatomic) GDTCORProductData *productData; -/** Initializes an instance using the given mappingID. +/** Initializes an instance using the given mapping ID and target. * * @param mappingID The mapping identifier. * @param target The event's target identifier. @@ -86,7 +86,7 @@ typedef NS_ENUM(NSInteger, GDTCOREventQoS) { */ - (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target; -/** Initializes an instance using the given mappingID. +/** Initializes an instance using the given mapping ID, product data, and target. * * @param mappingID The mapping identifier. * @param productData The product data to associate this event with. diff --git a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h index e58248da..33d1ba25 100644 --- a/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h +++ b/GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h @@ -20,6 +20,7 @@ #import "GDTCORTargets.h" @class GDTCOREvent; +@class GDTCORProductData; NS_ASSUME_NONNULL_BEGIN @@ -87,6 +88,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (GDTCOREvent *)eventForTransport; +/** + * Creates an event with the given product data for use by this transport. + * + * @param productData The given product data to associate with the created event. + * @return An event that is suited for use by this transport. + */ +- (GDTCOREvent *)eventForTransportWithProductData:(nonnull GDTCORProductData *)productData; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m index 2a38a9e4..2fa4702a 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORProductDataTest.m @@ -36,6 +36,18 @@ - (void)testCopy { XCTAssertEqualObjects([productData copy], productData); } +- (void)testIsEqualAndHash { + GDTCORProductData *productData1 = [[GDTCORProductData alloc] initWithProductID:0000]; + GDTCORProductData *productData2 = [[GDTCORProductData alloc] initWithProductID:0000]; + GDTCORProductData *productData3 = [[GDTCORProductData alloc] initWithProductID:1111]; + + XCTAssertEqualObjects(productData1, productData2); + XCTAssertNotEqualObjects(productData1, productData3); + + XCTAssertEqual(productData1.hash, productData2.hash); + XCTAssertNotEqual(productData1.hash, productData3.hash); +} + - (void)testSecureCoding { // Given GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:kTestProductID]; diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransportTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransportTest.m index b418b6bb..3eed7006 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransportTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORTransportTest.m @@ -18,6 +18,7 @@ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h" #import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h" #import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h" @@ -35,6 +36,8 @@ @interface GDTCORTransportTest : GDTCORTestCase @implementation GDTCORTransportTest +static int32_t kTestProductID = 123456; + - (void)setUp { [super setUp]; [[GDTCORRegistrar sharedInstance] registerStorage:[[GDTCORStorageFake alloc] init] @@ -56,22 +59,61 @@ - (void)testInit { target:kGDTCORTargetTest]); } +- (void)testEventForTransport { + // Given + NSString *mappingID = @"1"; + GDTCORTarget target = kGDTCORTargetTest; + + GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:mappingID + transformers:nil + target:target]; + // When + GDTCOREvent *event = [transport eventForTransport]; + // Then + XCTAssertEqualObjects(event.mappingID, mappingID); + XCTAssertEqual(event.target, target); +} + +- (void)testEventForTransportWithProductData { + // Given + NSString *mappingID = @"1"; + GDTCORTarget target = kGDTCORTargetTest; + + GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:mappingID + transformers:nil + target:target]; + GDTCORProductData *productData = [[GDTCORProductData alloc] initWithProductID:kTestProductID]; + // When + GDTCOREvent *event = [transport eventForTransportWithProductData:productData]; + // Then + XCTAssertEqualObjects(event.mappingID, mappingID); + XCTAssertEqualObjects(event.productData, productData); + XCTAssertEqual(event.target, target); +} + /** Tests sending a telemetry event. */ - (void)testSendTelemetryEvent { GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"1" transformers:nil target:kGDTCORTargetTest]; transport.transformerInstance = [[GDTCORTransformerFake alloc] init]; - GDTCOREvent *event = [transport eventForTransport]; - event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init]; - XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"]; - XCTAssertNoThrow([transport sendTelemetryEvent:event - onComplete:^(BOOL wasWritten, NSError *_Nullable error) { - XCTAssertTrue(wasWritten); - XCTAssertNil(error); - [writtenExpectation fulfill]; - }]); - [self waitForExpectations:@[ writtenExpectation ] timeout:10.0]; + NSArray *events = @[ + [transport eventForTransport], + [transport eventForTransportWithProductData:[[GDTCORProductData alloc] + initWithProductID:kTestProductID]] + ]; + for (GDTCOREvent *event in events) { + event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init]; + + XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"]; + XCTAssertNoThrow([transport sendTelemetryEvent:event + onComplete:^(BOOL wasWritten, NSError *_Nullable error) { + XCTAssertTrue(wasWritten); + XCTAssertNil(error); + [writtenExpectation fulfill]; + }]); + [self waitForExpectations:@[ writtenExpectation ] timeout:10.0]; + } } /** Tests sending a data event. */ @@ -80,16 +122,23 @@ - (void)testSendDataEvent { transformers:nil target:kGDTCORTargetTest]; transport.transformerInstance = [[GDTCORTransformerFake alloc] init]; - GDTCOREvent *event = [transport eventForTransport]; - event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init]; - XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"]; - XCTAssertNoThrow([transport sendDataEvent:event - onComplete:^(BOOL wasWritten, NSError *_Nullable error) { - XCTAssertTrue(wasWritten); - XCTAssertNil(error); - [writtenExpectation fulfill]; - }]); - [self waitForExpectations:@[ writtenExpectation ] timeout:10.0]; + NSArray *events = @[ + [transport eventForTransport], + [transport eventForTransportWithProductData:[[GDTCORProductData alloc] + initWithProductID:kTestProductID]] + ]; + for (GDTCOREvent *event in events) { + event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init]; + + XCTestExpectation *writtenExpectation = [self expectationWithDescription:@"event written"]; + XCTAssertNoThrow([transport sendDataEvent:event + onComplete:^(BOOL wasWritten, NSError *_Nullable error) { + XCTAssertTrue(wasWritten); + XCTAssertNil(error); + [writtenExpectation fulfill]; + }]); + [self waitForExpectations:@[ writtenExpectation ] timeout:10.0]; + } } @end From 3bb97f731190fd2cd8e4b1ea08b0b412d33a3bce Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sat, 28 Oct 2023 00:30:23 -0400 Subject: [PATCH 083/131] [Release] Update podspec and changelog for 9.3.0 release (#109) --- CHANGELOG.md | 2 +- GoogleDataTransport.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e92287f1..64ca86fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Unreleased +# v9.3.0 - Added new APIs for attaching product ID data to an event. # v9.2.5 diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index f4e156b7..34acf67a 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.2.5' + s.version = '9.3.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From e370dd5f4acecd1b6c91f52534f3d73c0de2a9dd Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:08:39 -0400 Subject: [PATCH 084/131] [Infra] Bump Gemfile to use CocoaPods 1.14.2 (#110) --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 90a7184d..9ba15e66 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.14.0' +gem 'cocoapods', '1.14.2' gem 'cocoapods-generate', '2.0.1' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index 40dd5163..c8e8607c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,10 +26,10 @@ GEM cork nap open4 (~> 1.3) - cocoapods (1.14.0) + cocoapods (1.14.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.0) + cocoapods-core (= 1.14.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -44,7 +44,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.0) + cocoapods-core (1.14.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -168,7 +168,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.14.0) + cocoapods (= 1.14.2) cocoapods-generate (= 2.0.1) danger (= 8.4.5) From f9e0ffea8158eaad3a0e692adc7d10e3d1b0343f Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Tue, 28 Nov 2023 10:12:16 -0800 Subject: [PATCH 085/131] Fix badfood crashes and add diagnostic (#111) * Fix badfood crashes and add diagnostic * One more diagnostic --- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 4 ++++ .../GDTCORLibrary/GDTCORFlatFileStorage.m | 5 ----- .../GDTCORLibrary/GDTCORTransformer.m | 17 +++++++---------- .../GDTCORLibrary/GDTCORUploadCoordinator.m | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 04fede58..acf9ac99 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -141,6 +141,10 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions // End the background task. backgroundTaskCompletion(); + } else { + GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelWarnings, + @"Attempted to cancel invalid background task in " + "GDTCCTUploadOperation."); } }]; diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m index 22e84670..fe36a198 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m @@ -844,11 +844,6 @@ - (void)appWillBackground:(GDTCORApplication *)app { }); } -- (void)appWillTerminate:(GDTCORApplication *)application { - dispatch_sync(_storageQueue, ^{ - }); -} - @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m b/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m index b6d02497..ece93681 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m @@ -70,8 +70,13 @@ - (void)transformEvent:(GDTCOREvent *)event completion(wasWritten, error); } - // The work is done, cancel the background task if it's valid. - [weakApplication endBackgroundTask:bgID]; + if (bgID != GDTCORBackgroundIdentifierInvalid) { + // The work is done, cancel the background task if it's valid. + [weakApplication endBackgroundTask:bgID]; + } else { + GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelWarnings, + @"Attempted to cancel invalid background task in GDTCORTransformer."); + } bgID = GDTCORBackgroundIdentifierInvalid; }; @@ -100,12 +105,4 @@ - (void)transformEvent:(GDTCOREvent *)event }); } -#pragma mark - GDTCORLifecycleProtocol - -- (void)appWillTerminate:(GDTCORApplication *)application { - // Flush the queue immediately. - dispatch_sync(_eventWritingQueue, ^{ - }); -} - @end diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m b/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m index 3357fbe3..cdc108ae 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORUploadCoordinator.m @@ -168,7 +168,7 @@ - (void)appWillBackground:(GDTCORApplication *)app { } - (void)appWillTerminate:(GDTCORApplication *)application { - dispatch_sync(_coordinationQueue, ^{ + dispatch_async(_coordinationQueue, ^{ [self stopTimer]; }); } From a732a4b47f59e4f725a2ea10f0c77e93a7131117 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:24:17 -0500 Subject: [PATCH 086/131] [Release] Update changelog entry for 9.3 (#112) * [Release] Update changelog entry for 9.3 * style --- CHANGELOG.md | 1 + GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ca86fa..773d2657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v9.3.0 - Added new APIs for attaching product ID data to an event. +- Fix `0x8BADF00D` crashes and add diagnostics. ([firebase-ios-sdk/#9859](https://github.com/firebase/firebase-ios-sdk/issues/9859)) # v9.2.5 - Replace 'TARGET_OS_XR' with 'TARGET_OS_VISION' for compatibility with diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index acf9ac99..05937ea4 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -144,7 +144,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions } else { GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelWarnings, @"Attempted to cancel invalid background task in " - "GDTCCTUploadOperation."); + "GDTCCTUploadOperation."); } }]; From 4b272370cd4e2483c08ba4edd1882b2982bd8c79 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 18 Dec 2023 07:32:33 -0800 Subject: [PATCH 087/131] Fix CI for Firebase repo now using main (#113) --- README.md | 2 +- setup-scripts.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0eecc02b..97df3a13 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ Alternatively disable signing in each target: ### Code Formatting To ensure that the code is formatted consistently, run the script -[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/check.sh) +[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh) before creating a PR. GitHub Actions will verify that any code changes are done in a style compliant diff --git a/setup-scripts.sh b/setup-scripts.sh index 3112c185..f4480149 100755 --- a/setup-scripts.sh +++ b/setup-scripts.sh @@ -17,7 +17,7 @@ git clone \ https://github.com/firebase/firebase-ios-sdk.git \ ; cd firebase-ios-sdk -git checkout master -- scripts +git checkout main -- scripts cd .. ln -s firebase-ios-sdk/scripts scripts From dcb89bbdf237009b661094347e5c6c2b46dee2a0 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:57:39 -0500 Subject: [PATCH 088/131] [GoogleDataTransport] Add privacy manifest for SPM / CocoaPods (#115) --- GoogleDataTransport.podspec | 4 +++ .../Resources/PrivacyInfo.xcprivacy | 30 +++++++++++++++++++ Package.swift | 1 + 3 files changed, 35 insertions(+) create mode 100644 GoogleDataTransport/Resources/PrivacyInfo.xcprivacy diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 34acf67a..9ee709c1 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -47,6 +47,10 @@ Shared library for iOS SDK data transport needs. 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/"' } + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'GoogleDataTransport/Resources/PrivacyInfo.xcprivacy' + } + # The nanopb pod sets these defs, so we must too. (We *do* require 16bit # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT # anyways.) diff --git a/GoogleDataTransport/Resources/PrivacyInfo.xcprivacy b/GoogleDataTransport/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..1e83fa67 --- /dev/null +++ b/GoogleDataTransport/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/Package.swift b/Package.swift index 7665088b..a2efac68 100644 --- a/Package.swift +++ b/Package.swift @@ -67,6 +67,7 @@ let package = Package( "GDTCORLibrary", "GDTCCTLibrary", ], + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "GDTCORLibrary/Public", cSettings: [ .headerSearchPath("../"), From d3fdb60478befdddf34acb8fe841b7f7bbd37f53 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:58:05 -0500 Subject: [PATCH 089/131] [Release] Bump spec to 9.4.0 and add changelog entry (#116) --- CHANGELOG.md | 3 +++ GoogleDataTransport.podspec | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 773d2657..5e019dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v9.4.0 +- Add privacy manifest. (#114) + # v9.3.0 - Added new APIs for attaching product ID data to an event. - Fix `0x8BADF00D` crashes and add diagnostics. ([firebase-ios-sdk/#9859](https://github.com/firebase/firebase-ios-sdk/issues/9859)) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 9ee709c1..d16e2655 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.3.0' + s.version = '9.4.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC From 211caa3ac6eded2959f82fa4c47fb72846c1b033 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 22 Feb 2024 11:05:39 -0800 Subject: [PATCH 090/131] Enable nanopb privacy manifest update (#117) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index d16e2655..600d85d9 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -40,7 +40,7 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] s.dependency 'GoogleUtilities/Environment', '~> 7.7' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' header_search_paths = { From a637d318ae7ae246b02d7305121275bc75ed5565 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 27 Feb 2024 08:54:55 -0700 Subject: [PATCH 091/131] [nanopb] Bump nanopb version in Package.swift (#118) --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a2efac68..b875d18a 100644 --- a/Package.swift +++ b/Package.swift @@ -30,7 +30,7 @@ let package = Package( .package( name: "nanopb", url: "https://github.com/firebase/nanopb.git", - "2.30908.0" ..< "2.30910.0" + "2.30908.0" ..< "2.30911.0" ), .package( name: "Promises", From 4075c48acce8ba5ad05f4869420258746e9d3891 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:52:18 -0500 Subject: [PATCH 092/131] [CocoaPods] Prep for 9.4.1 patch release (#120) * [CocoaPods] Prep for 9.4.1 patch release * Update instructions to hopefully prevent this in future --- CHANGELOG.md | 4 ++++ GoogleDataTransport.podspec | 2 +- README.md | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e019dc3..5c5cd3a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v9.4.1 (CocoaPods Only) +- The v9.4.0 podspec did not include the intended dependency range for nanopb + ([firebase-ios-sdk/#12477](https://github.com/firebase/firebase-ios-sdk/issues/12477)) + # v9.4.0 - Add privacy manifest. (#114) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 600d85d9..00156ba7 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.4.0' + s.version = '9.4.1' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC diff --git a/README.md b/README.md index 97df3a13..f1628cb2 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,11 @@ The release process is as follows: * Publish the newly versioned pod to CocoaPods It's recommended to point to the `GoogleDataTransport.podspec` in `staging` to make sure the correct spec is being published. + + > [!WARNING] + > Manually update your local SpecsStaging clone or run `pod repo update` + > before proceeding. + ```console pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport/{version}/GoogleDataTransport.podspec --skip-tests ``` From 7698f4f4f8fa97a4638fce029d18ab796ca98ca4 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:25:38 -0400 Subject: [PATCH 093/131] [Infra] clang-format@18 (#121) --- .github/workflows/check.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 8671307d..9f2d0fa2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -36,7 +36,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@17 + brew install clang-format@18 brew install mint mint bootstrap diff --git a/README.md b/README.md index f1628cb2..59227d95 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@17 +brew install clang-format@18 brew install mint ``` From 95c7a018d32bb2071a9b76feb16ab1c96ce94a0e Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 29 Apr 2024 08:17:00 -0700 Subject: [PATCH 094/131] Update to CocoaPods 1.15.2 (#122) --- Gemfile | 2 +- Gemfile.lock | 40 +++++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Gemfile b/Gemfile index 9ba15e66..cfff186c 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.14.2' +gem 'cocoapods', '1.15.2' gem 'cocoapods-generate', '2.0.1' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index c8e8607c..cae4ac70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,11 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.6) + CFPropertyList (3.0.7) + base64 + nkf rexml - activesupport (7.1.1) + activesupport (7.1.3.2) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -19,19 +21,19 @@ GEM httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) - base64 (0.1.1) - bigdecimal (3.1.4) + base64 (0.2.0) + bigdecimal (3.1.7) claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.14.2) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.2) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) cocoapods-trunk (>= 1.6.0, < 2.0) @@ -44,7 +46,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.2) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -56,7 +58,7 @@ GEM typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) cocoapods-disable-podfile-validations (0.1.1) - cocoapods-downloader (2.0) + cocoapods-downloader (2.1) cocoapods-generate (2.0.1) cocoapods-disable-podfile-validations (~> 0.1.1) cocoapods-plugins (1.0.0) @@ -67,7 +69,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) @@ -84,8 +86,7 @@ GEM no_proxy_fix octokit (~> 4.7) terminal-table (>= 1, < 4) - drb (2.1.1) - ruby2_keywords + drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) @@ -122,20 +123,21 @@ GEM addressable (~> 2.8) rchardet (~> 1.8) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) - json (2.6.3) + json (2.7.2) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.20.0) + minitest (5.22.3) molinillo (0.8.0) multipart-post (2.2.3) - mutex_m (0.1.2) + mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) no_proxy_fix (0.1.2) octokit (4.25.1) faraday (>= 1, < 3) @@ -151,12 +153,12 @@ GEM faraday (>= 0.17.3, < 3) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.2.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -168,7 +170,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.14.2) + cocoapods (= 1.15.2) cocoapods-generate (= 2.0.1) danger (= 8.4.5) From 6e27aa51abc4847ba748f3ea11bb0a1f3d09f5d3 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 29 Apr 2024 11:21:53 -0700 Subject: [PATCH 095/131] Run check on pull requests and on macos-14 CI (#123) --- .github/workflows/check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9f2d0fa2..ea343afa 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -4,6 +4,7 @@ on: pull_request: paths: - '.github/workflows/check.yml' + - 'GoogleDataTransport' push: branches: main @@ -21,7 +22,7 @@ jobs: - uses: actions/setup-python@v3 with: - python-version: 3.6 + python-version: '3.10' - name: Cache Mint packages uses: actions/cache@v3 From d31430281a53e5d43b20ee8fa2c2999ef39862e4 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 13 May 2024 13:19:12 -0400 Subject: [PATCH 096/131] [NFC] Fix null pointer profiler warning (#125) --- CHANGELOG.md | 3 +++ GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5cd3a0..4b75224f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Fix null pointer warning from profiler. (#124) + # v9.4.1 (CocoaPods Only) - The v9.4.0 podspec did not include the intended dependency range for nanopb ([firebase-ios-sdk/#12477](https://github.com/firebase/firebase-ios-sdk/issues/12477)) diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m index aaabb476..9910bf0d 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m @@ -52,7 +52,7 @@ + (instancetype)metricsWithEvents:(NSArray *)events // If the dropped event counter for this event's mapping ID is `nil`, // an empty mutable counter is returned. NSMutableDictionary *eventCounter = - [NSMutableDictionary dictionaryWithDictionary:eventCounterByLogSource[event.mappingID]]; + [NSMutableDictionary dictionaryWithDictionary:eventCounterByLogSource[event.mappingID] ?: @{}]; // Increment the log source metrics for the given reason. NSInteger currentEventCountForReason = [eventCounter[@(reason)] integerValue]; From 23fb0d945d96da4e8111629d6a7092d5ee688071 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 13 May 2024 15:16:42 -0400 Subject: [PATCH 097/131] [NFC] Fix check workflow and style file (#126) * [NFC] Fix check workflow and style file * Adjust GHA file path --- .github/workflows/check.yml | 2 +- GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ea343afa..7be943a7 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -4,7 +4,7 @@ on: pull_request: paths: - '.github/workflows/check.yml' - - 'GoogleDataTransport' + - 'GoogleDataTransport**' push: branches: main diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m index 9910bf0d..9da40dbb 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORLogSourceMetrics.m @@ -51,8 +51,8 @@ + (instancetype)metricsWithEvents:(NSArray *)events // Get the dropped event counter for the event's mapping ID (log source). // If the dropped event counter for this event's mapping ID is `nil`, // an empty mutable counter is returned. - NSMutableDictionary *eventCounter = - [NSMutableDictionary dictionaryWithDictionary:eventCounterByLogSource[event.mappingID] ?: @{}]; + NSMutableDictionary *eventCounter = [NSMutableDictionary + dictionaryWithDictionary:eventCounterByLogSource[event.mappingID] ?: @{}]; // Increment the log source metrics for the given reason. NSInteger currentEventCountForReason = [eventCounter[@(reason)] integerValue]; From 63935d04ecc48d6b5db216da67777253e1c7ffbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 May 2024 17:16:35 -0400 Subject: [PATCH 098/131] Bump rexml from 3.2.5 to 3.2.8 in the bundler group across 1 directory (#128) Bumps the bundler group with 1 update in the / directory: [rexml](https://github.com/ruby/rexml). Updates `rexml` from 3.2.5 to 3.2.8 - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.2.5...v3.2.8) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cae4ac70..87be12d1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,12 +145,14 @@ GEM open4 (1.3.4) public_suffix (4.0.7) rchardet (1.8.0) - rexml (3.2.5) + rexml (3.2.8) + strscan (>= 3.0.9) ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.1) From eb1aaf00fdc6d805c3abcb3f7bc401dce3153987 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 20 May 2024 12:33:32 -0700 Subject: [PATCH 099/131] Disable failing test in CI (#129) --- .github/workflows/datatransport.yml | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index dc10e5cf..6c98d553 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -43,19 +43,21 @@ jobs: - name: Setup project and Test Catalyst run: scripts/third_party/travis/retry.sh scripts/test_catalyst.sh GoogleDataTransport build - watchos-testapp: - runs-on: macos-12 - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - - name: Setup Scripts Directory - run: ./setup-scripts.sh - - name: Setup Bundler - run: scripts/setup_bundler.sh - - name: Prereqs - run: scripts/install_prereqs.sh GoogleDataTransport watchOS xcodebuild - - name: Setup project and build watchOS test app - run: scripts/third_party/travis/retry.sh scripts/build.sh GoogleDataTransport watchOS xcodebuild +# TODO: Investigate how to build correctly after build failure introduced by +# https://github.com/firebase/firebase-ios-sdk/pull/12966 + # watchos-testapp: + # runs-on: macos-12 + # steps: + # - uses: actions/checkout@v3 + # - uses: ruby/setup-ruby@v1 + # - name: Setup Scripts Directory + # run: ./setup-scripts.sh + # - name: Setup Bundler + # run: scripts/setup_bundler.sh + # - name: Prereqs + # run: scripts/install_prereqs.sh GoogleDataTransport watchOS xcodebuild + # - name: Setup project and build watchOS test app + # run: scripts/third_party/travis/retry.sh scripts/build.sh GoogleDataTransport watchOS xcodebuild datatransport-options-matrix: runs-on: macos-latest From b7bd781b343f8c630553076544d7c0bd6e7fbddf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 22 May 2024 15:14:59 -0400 Subject: [PATCH 100/131] [Infra] Refactor out clock utils (#130) * [Infra] Refactor out clock utils * License adjustments --- GoogleDataTransport.podspec | 3 +- .../GDTCORLibrary/GDTCORClock.m | 45 +-- LICENSE | 363 ++++++++++++++++++ Package.swift | 10 + .../GDTCORClockUtils/GDTCORClockUtils.m | 59 +++ third_party/GDTCORClockUtils/LICENSE | 361 +++++++++++++++++ .../Public/GDTCORClockUtils.h | 17 + 7 files changed, 816 insertions(+), 42 deletions(-) create mode 100644 third_party/GDTCORClockUtils/GDTCORClockUtils.m create mode 100644 third_party/GDTCORClockUtils/LICENSE create mode 100644 third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 00156ba7..41df84b9 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -31,7 +31,8 @@ Shared library for iOS SDK data transport needs. s.prefix_header_file = false s.source_files = ['GoogleDataTransport/GDTCORLibrary/**/*', - 'GoogleDataTransport/GDTCCTLibrary/**/*'] + 'GoogleDataTransport/GDTCCTLibrary/**/*', + 'third_party/GDTCORClockUtils/**/*.[mh]'] s.public_header_files = 'GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/*.h' s.ios.frameworks = 'SystemConfiguration', 'CoreTelephony' s.osx.frameworks = 'SystemConfiguration', 'CoreTelephony' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m index 0487ccd2..dce1c023 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m @@ -18,6 +18,8 @@ #import +#import "third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h" + // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a // time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external @@ -35,53 +37,14 @@ // Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be // difficult to unit test. -/** Returns the kernel boottime property from sysctl. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. - */ -static int64_t KernelBootTimeInNanoseconds(void) { - // Caching the result is not possible because clock drift would not be accounted for. - struct timeval boottime; - int mib[2] = {CTL_KERN, KERN_BOOTTIME}; - size_t size = sizeof(boottime); - int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); - if (rc != 0) { - return 0; - } - return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; -} - -/** Returns value of gettimeofday, in nanoseconds. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The value of gettimeofday, in nanoseconds. - */ -static int64_t UptimeInNanoseconds(void) { - int64_t before_now_nsec; - int64_t after_now_nsec; - struct timeval now; - - before_now_nsec = KernelBootTimeInNanoseconds(); - // Addresses a race condition in which the system time has updated, but the boottime has not. - do { - gettimeofday(&now, NULL); - after_now_nsec = KernelBootTimeInNanoseconds(); - } while (after_now_nsec != before_now_nsec); - return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - - before_now_nsec; -} - // TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. @implementation GDTCORClock - (instancetype)init { self = [super init]; if (self) { - _kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds(); - _uptimeNanoseconds = UptimeInNanoseconds(); + _kernelBootTimeNanoseconds = GDTCORKernelBootTimeInNanoseconds(); + _uptimeNanoseconds = GDTCORUptimeInNanoseconds(); _timeMillis = (int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC); _timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT]; diff --git a/LICENSE b/LICENSE index d6456956..605b1aae 100644 --- a/LICENSE +++ b/LICENSE @@ -200,3 +200,366 @@ 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. + +================================================================================ + +License: CC BY-SA 3.0 +License url: http://creativecommons.org/licenses/by-sa/3.0/ + +Creative Commons Legal Code + +Attribution-ShareAlike 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR + DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and + other pre-existing works, such as a translation, adaptation, + derivative work, arrangement of music or other alterations of a + literary or artistic work, or phonogram or performance and includes + cinematographic adaptations or any other form in which the Work may be + recast, transformed, or adapted including in any form recognizably + derived from the original, except that a work that constitutes a + Collection will not be considered an Adaptation for the purpose of + this License. For the avoidance of doubt, where the Work is a musical + work, performance or phonogram, the synchronization of the Work in + timed-relation with a moving image ("synching") will be considered an + Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as + encyclopedias and anthologies, or performances, phonograms or + broadcasts, or other works or subject matter other than works listed + in Section 1(f) below, which, by reason of the selection and + arrangement of their contents, constitute intellectual creations, in + which the Work is included in its entirety in unmodified form along + with one or more other contributions, each constituting separate and + independent works in themselves, which together are assembled into a + collective whole. A work that constitutes a Collection will not be + considered an Adaptation (as defined below) for the purposes of this + License. + c. "Creative Commons Compatible License" means a license that is listed + at https://creativecommons.org/compatiblelicenses that has been + approved by Creative Commons as being essentially equivalent to this + License, including, at a minimum, because that license: (i) contains + terms that have the same purpose, meaning and effect as the License + Elements of this License; and, (ii) explicitly permits the relicensing + of adaptations of works made available under that license under this + License or a Creative Commons jurisdiction license with the same + License Elements as this License. + d. "Distribute" means to make available to the public the original and + copies of the Work or Adaptation, as appropriate, through sale or + other transfer of ownership. + e. "License Elements" means the following high-level license attributes + as selected by Licensor and indicated in the title of this License: + Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that + offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, + the individual, individuals, entity or entities who created the Work + or if no individual or entity can be identified, the publisher; and in + addition (i) in the case of a performance the actors, singers, + musicians, dancers, and other persons who act, sing, deliver, declaim, + play in, interpret or otherwise perform literary or artistic works or + expressions of folklore; (ii) in the case of a phonogram the producer + being the person or legal entity who first fixes the sounds of a + performance or other sounds; and, (iii) in the case of broadcasts, the + organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms + of this License including without limitation any production in the + literary, scientific and artistic domain, whatever may be the mode or + form of its expression including digital form, such as a book, + pamphlet and other writing; a lecture, address, sermon or other work + of the same nature; a dramatic or dramatico-musical work; a + choreographic work or entertainment in dumb show; a musical + composition with or without words; a cinematographic work to which are + assimilated works expressed by a process analogous to cinematography; + a work of drawing, painting, architecture, sculpture, engraving or + lithography; a photographic work to which are assimilated works + expressed by a process analogous to photography; a work of applied + art; an illustration, map, plan, sketch or three-dimensional work + relative to geography, topography, architecture or science; a + performance; a broadcast; a phonogram; a compilation of data to the + extent it is protected as a copyrightable work; or a work performed by + a variety or circus performer to the extent it is not otherwise + considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with + respect to the Work, or who has received express permission from the + Licensor to exercise rights under this License despite a previous + violation. + j. "Publicly Perform" means to perform public recitations of the Work and + to communicate to the public those public recitations, by any means or + process, including by wire or wireless means or public digital + performances; to make available to the public Works in such a way that + members of the public may access these Works from a place and at a + place individually chosen by them; to perform the Work to the public + by any means or process and the communication to the public of the + performances of the Work, including by public digital performance; to + broadcast and rebroadcast the Work by any means including signs, + sounds or images. + k. "Reproduce" means to make copies of the Work by any means including + without limitation by sound or visual recordings and the right of + fixation and reproducing fixations of the Work, including storage of a + protected performance or phonogram in digital form or other electronic + medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more + Collections, and to Reproduce the Work as incorporated in the + Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, + including any translation in any medium, takes reasonable steps to + clearly label, demarcate or otherwise identify that changes were made + to the original Work. For example, a translation could be marked "The + original work was translated from English to Spanish," or a + modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated + in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor + reserves the exclusive right to collect such royalties for any + exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme can be waived, the Licensor waives the + exclusive right to collect such royalties for any exercise by You + of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to + collect royalties, whether individually or, in the event that the + Licensor is a member of a collecting society that administers + voluntary licensing schemes, via that society, from any exercise + by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms + of this License. You must include a copy of, or the Uniform Resource + Identifier (URI) for, this License with every copy of the Work You + Distribute or Publicly Perform. You may not offer or impose any terms + on the Work that restrict the terms of this License or the ability of + the recipient of the Work to exercise the rights granted to that + recipient under the terms of the License. You may not sublicense the + Work. You must keep intact all notices that refer to this License and + to the disclaimer of warranties with every copy of the Work You + Distribute or Publicly Perform. When You Distribute or Publicly + Perform the Work, You may not impose any effective technological + measures on the Work that restrict the ability of a recipient of the + Work from You to exercise the rights granted to that recipient under + the terms of the License. This Section 4(a) applies to the Work as + incorporated in a Collection, but this does not require the Collection + apart from the Work itself to be made subject to the terms of this + License. If You create a Collection, upon notice from any Licensor You + must, to the extent practicable, remove from the Collection any credit + as required by Section 4(c), as requested. If You create an + Adaptation, upon notice from any Licensor You must, to the extent + practicable, remove from the Adaptation any credit as required by + Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the + terms of: (i) this License; (ii) a later version of this License with + the same License Elements as this License; (iii) a Creative Commons + jurisdiction license (either this or a later license version) that + contains the same License Elements as this License (e.g., + Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible + License. If you license the Adaptation under one of the licenses + mentioned in (iv), you must comply with the terms of that license. If + you license the Adaptation under the terms of any of the licenses + mentioned in (i), (ii) or (iii) (the "Applicable License"), you must + comply with the terms of the Applicable License generally and the + following provisions: (I) You must include a copy of, or the URI for, + the Applicable License with every copy of each Adaptation You + Distribute or Publicly Perform; (II) You may not offer or impose any + terms on the Adaptation that restrict the terms of the Applicable + License or the ability of the recipient of the Adaptation to exercise + the rights granted to that recipient under the terms of the Applicable + License; (III) You must keep intact all notices that refer to the + Applicable License and to the disclaimer of warranties with every copy + of the Work as included in the Adaptation You Distribute or Publicly + Perform; (IV) when You Distribute or Publicly Perform the Adaptation, + You may not impose any effective technological measures on the + Adaptation that restrict the ability of a recipient of the Adaptation + from You to exercise the rights granted to that recipient under the + terms of the Applicable License. This Section 4(b) applies to the + Adaptation as incorporated in a Collection, but this does not require + the Collection apart from the Adaptation itself to be made subject to + the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or + Collections, You must, unless a request has been made pursuant to + Section 4(a), keep intact all copyright notices for the Work and + provide, reasonable to the medium or means You are utilizing: (i) the + name of the Original Author (or pseudonym, if applicable) if supplied, + and/or if the Original Author and/or Licensor designate another party + or parties (e.g., a sponsor institute, publishing entity, journal) for + attribution ("Attribution Parties") in Licensor's copyright notice, + terms of service or by other reasonable means, the name of such party + or parties; (ii) the title of the Work if supplied; (iii) to the + extent reasonably practicable, the URI, if any, that Licensor + specifies to be associated with the Work, unless such URI does not + refer to the copyright notice or licensing information for the Work; + and (iv) , consistent with Ssection 3(b), in the case of an + Adaptation, a credit identifying the use of the Work in the Adaptation + (e.g., "French translation of the Work by Original Author," or + "Screenplay based on original Work by Original Author"). The credit + required by this Section 4(c) may be implemented in any reasonable + manner; provided, however, that in the case of a Adaptation or + Collection, at a minimum such credit will appear, if a credit for all + contributing authors of the Adaptation or Collection appears, then as + part of these credits and in a manner at least as prominent as the + credits for the other contributing authors. For the avoidance of + doubt, You may only use the credit required by this Section for the + purpose of attribution in the manner set out above and, by exercising + Your rights under this License, You may not implicitly or explicitly + assert or imply any connection with, sponsorship or endorsement by the + Original Author, Licensor and/or Attribution Parties, as appropriate, + of You or Your use of the Work, without the separate, express prior + written permission of the Original Author, Licensor and/or Attribution + Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be + otherwise permitted by applicable law, if You Reproduce, Distribute or + Publicly Perform the Work either by itself or as part of any + Adaptations or Collections, You must not distort, mutilate, modify or + take other derogatory action in relation to the Work which would be + prejudicial to the Original Author's honor or reputation. Licensor + agrees that in those jurisdictions (e.g. Japan), in which any exercise + of the right granted in Section 3(b) of this License (the right to + make Adaptations) would be deemed to be a distortion, mutilation, + modification or other derogatory action prejudicial to the Original + Author's honor and reputation, the Licensor will waive or not assert, + as appropriate, this Section, to the fullest extent permitted by the + applicable national law, to enable You to reasonably exercise Your + right under Section 3(b) of this License (right to make Adaptations) + but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate + automatically upon any breach by You of the terms of this License. + Individuals or entities who have received Adaptations or Collections + from You under this License, however, will not have their licenses + terminated provided such individuals or entities remain in full + compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will + survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the + Work under different license terms or to stop distributing the Work at + any time; provided, however that any such election will not serve to + withdraw this License (or any other license that has been, or is + required to be, granted under the terms of this License), and this + License will continue in full force and effect unless terminated as + stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, + the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor + offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this License, and without further action + by the parties to this agreement, such provision shall be reformed to + the minimum extent necessary to make such provision valid and + enforceable. + d. No term or provision of this License shall be deemed waived and no + breach consented to unless such waiver or consent shall be in writing + and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with + respect to the Work licensed here. There are no understandings, + agreements or representations with respect to the Work not specified + here. Licensor shall not be bound by any additional provisions that + may appear in any communication from You. This License may not be + modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this + License were drafted utilizing the terminology of the Berne Convention + for the Protection of Literary and Artistic Works (as amended on + September 28, 1979), the Rome Convention of 1961, the WIPO Copyright + Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 + and the Universal Copyright Convention (as revised on July 24, 1971). + These rights and subject matter take effect in the relevant + jurisdiction in which the License terms are sought to be enforced + according to the corresponding provisions of the implementation of + those treaty provisions in the applicable national law. If the + standard suite of rights granted under applicable copyright law + includes additional rights not granted under this License, such + additional rights are deemed to be included in the License; this + License is not intended to restrict the license of any rights under + applicable law. + + +Creative Commons Notice + + Creative Commons is not a party to this License, and makes no warranty + whatsoever in connection with the Work. Creative Commons will not be + liable to You or any party on any legal theory for any damages + whatsoever, including without limitation any general, special, + incidental or consequential damages arising in connection to this + license. Notwithstanding the foregoing two (2) sentences, if Creative + Commons has expressly identified itself as the Licensor hereunder, it + shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the + Work is licensed under the CCPL, Creative Commons does not authorize + the use by either party of the trademark "Creative Commons" or any + related trademark or logo of Creative Commons without the prior + written consent of Creative Commons. Any permitted use will be in + compliance with Creative Commons' then-current trademark usage + guidelines, as may be published on its website or otherwise made + available upon request from time to time. For the avoidance of doubt, + this trademark restriction does not form part of the License. diff --git a/Package.swift b/Package.swift index b875d18a..cae8ca02 100644 --- a/Package.swift +++ b/Package.swift @@ -51,6 +51,7 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), + "third-party-GDTCORClockUtils", ], path: "GoogleDataTransport", exclude: [ @@ -81,6 +82,15 @@ let package = Package( .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .catalyst])), ] ), + .target( + name: "third-party-GDTCORClockUtils", + path: "third_party/GDTCORClockUtils", + exclude: ["LICENSE"], + publicHeadersPath: "Public", + cSettings: [ + .headerSearchPath("../../"), + ] + ), .testTarget( name: "swift-test", dependencies: [ diff --git a/third_party/GDTCORClockUtils/GDTCORClockUtils.m b/third_party/GDTCORClockUtils/GDTCORClockUtils.m new file mode 100644 index 00000000..1731a2e9 --- /dev/null +++ b/third_party/GDTCORClockUtils/GDTCORClockUtils.m @@ -0,0 +1,59 @@ +#import + +#include + +// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all +// are subject to drift. That it to say, multiple consecutive calls do not always result in a +// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external +// factors. This class attempts to determine the wall-clock time at the time of the event by +// capturing the kernel start and time since boot to determine a wallclock time in UTC. +// +// Timezone offsets at the time of a snapshot are also captured in order to provide local-time +// details. Other classes in this library depend on comparing times at some time in the future to +// a time captured in the past, and this class needs to provide a mechanism to do that. +// +// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide +// a monotonic clock mechanism to accurately check if some clock snapshot was before or after +// by using a shared reference point (kernel boot time). +// +// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be +// difficult to unit test. + +/** Returns the kernel boottime property from sysctl. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. + */ +int64_t GDTCORKernelBootTimeInNanoseconds(void) { + // Caching the result is not possible because clock drift would not be accounted for. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) { + return 0; + } + return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; +} + +/** Returns value of gettimeofday, in nanoseconds. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The value of gettimeofday, in nanoseconds. + */ +int64_t GDTCORUptimeInNanoseconds(void) { + int64_t before_now_nsec; + int64_t after_now_nsec; + struct timeval now; + + before_now_nsec = GDTCORKernelBootTimeInNanoseconds(); + // Addresses a race condition in which the system time has updated, but the boottime has not. + do { + gettimeofday(&now, NULL); + after_now_nsec = GDTCORKernelBootTimeInNanoseconds(); + } while (after_now_nsec != before_now_nsec); + return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - + before_now_nsec; +} diff --git a/third_party/GDTCORClockUtils/LICENSE b/third_party/GDTCORClockUtils/LICENSE new file mode 100644 index 00000000..f181cc96 --- /dev/null +++ b/third_party/GDTCORClockUtils/LICENSE @@ -0,0 +1,361 @@ +License: CC BY-SA 3.0 +License url: http://creativecommons.org/licenses/by-sa/3.0/ + +Creative Commons Legal Code + +Attribution-ShareAlike 3.0 Unported + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR + DAMAGES RESULTING FROM ITS USE. + +License + +THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE +COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY +COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS +AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. + +BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE +TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY +BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS +CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND +CONDITIONS. + +1. Definitions + + a. "Adaptation" means a work based upon the Work, or upon the Work and + other pre-existing works, such as a translation, adaptation, + derivative work, arrangement of music or other alterations of a + literary or artistic work, or phonogram or performance and includes + cinematographic adaptations or any other form in which the Work may be + recast, transformed, or adapted including in any form recognizably + derived from the original, except that a work that constitutes a + Collection will not be considered an Adaptation for the purpose of + this License. For the avoidance of doubt, where the Work is a musical + work, performance or phonogram, the synchronization of the Work in + timed-relation with a moving image ("synching") will be considered an + Adaptation for the purpose of this License. + b. "Collection" means a collection of literary or artistic works, such as + encyclopedias and anthologies, or performances, phonograms or + broadcasts, or other works or subject matter other than works listed + in Section 1(f) below, which, by reason of the selection and + arrangement of their contents, constitute intellectual creations, in + which the Work is included in its entirety in unmodified form along + with one or more other contributions, each constituting separate and + independent works in themselves, which together are assembled into a + collective whole. A work that constitutes a Collection will not be + considered an Adaptation (as defined below) for the purposes of this + License. + c. "Creative Commons Compatible License" means a license that is listed + at https://creativecommons.org/compatiblelicenses that has been + approved by Creative Commons as being essentially equivalent to this + License, including, at a minimum, because that license: (i) contains + terms that have the same purpose, meaning and effect as the License + Elements of this License; and, (ii) explicitly permits the relicensing + of adaptations of works made available under that license under this + License or a Creative Commons jurisdiction license with the same + License Elements as this License. + d. "Distribute" means to make available to the public the original and + copies of the Work or Adaptation, as appropriate, through sale or + other transfer of ownership. + e. "License Elements" means the following high-level license attributes + as selected by Licensor and indicated in the title of this License: + Attribution, ShareAlike. + f. "Licensor" means the individual, individuals, entity or entities that + offer(s) the Work under the terms of this License. + g. "Original Author" means, in the case of a literary or artistic work, + the individual, individuals, entity or entities who created the Work + or if no individual or entity can be identified, the publisher; and in + addition (i) in the case of a performance the actors, singers, + musicians, dancers, and other persons who act, sing, deliver, declaim, + play in, interpret or otherwise perform literary or artistic works or + expressions of folklore; (ii) in the case of a phonogram the producer + being the person or legal entity who first fixes the sounds of a + performance or other sounds; and, (iii) in the case of broadcasts, the + organization that transmits the broadcast. + h. "Work" means the literary and/or artistic work offered under the terms + of this License including without limitation any production in the + literary, scientific and artistic domain, whatever may be the mode or + form of its expression including digital form, such as a book, + pamphlet and other writing; a lecture, address, sermon or other work + of the same nature; a dramatic or dramatico-musical work; a + choreographic work or entertainment in dumb show; a musical + composition with or without words; a cinematographic work to which are + assimilated works expressed by a process analogous to cinematography; + a work of drawing, painting, architecture, sculpture, engraving or + lithography; a photographic work to which are assimilated works + expressed by a process analogous to photography; a work of applied + art; an illustration, map, plan, sketch or three-dimensional work + relative to geography, topography, architecture or science; a + performance; a broadcast; a phonogram; a compilation of data to the + extent it is protected as a copyrightable work; or a work performed by + a variety or circus performer to the extent it is not otherwise + considered a literary or artistic work. + i. "You" means an individual or entity exercising rights under this + License who has not previously violated the terms of this License with + respect to the Work, or who has received express permission from the + Licensor to exercise rights under this License despite a previous + violation. + j. "Publicly Perform" means to perform public recitations of the Work and + to communicate to the public those public recitations, by any means or + process, including by wire or wireless means or public digital + performances; to make available to the public Works in such a way that + members of the public may access these Works from a place and at a + place individually chosen by them; to perform the Work to the public + by any means or process and the communication to the public of the + performances of the Work, including by public digital performance; to + broadcast and rebroadcast the Work by any means including signs, + sounds or images. + k. "Reproduce" means to make copies of the Work by any means including + without limitation by sound or visual recordings and the right of + fixation and reproducing fixations of the Work, including storage of a + protected performance or phonogram in digital form or other electronic + medium. + +2. Fair Dealing Rights. Nothing in this License is intended to reduce, +limit, or restrict any uses free from copyright or rights arising from +limitations or exceptions that are provided for in connection with the +copyright protection under copyright law or other applicable laws. + +3. License Grant. Subject to the terms and conditions of this License, +Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +perpetual (for the duration of the applicable copyright) license to +exercise the rights in the Work as stated below: + + a. to Reproduce the Work, to incorporate the Work into one or more + Collections, and to Reproduce the Work as incorporated in the + Collections; + b. to create and Reproduce Adaptations provided that any such Adaptation, + including any translation in any medium, takes reasonable steps to + clearly label, demarcate or otherwise identify that changes were made + to the original Work. For example, a translation could be marked "The + original work was translated from English to Spanish," or a + modification could indicate "The original work has been modified."; + c. to Distribute and Publicly Perform the Work including as incorporated + in Collections; and, + d. to Distribute and Publicly Perform Adaptations. + e. For the avoidance of doubt: + + i. Non-waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme cannot be waived, the Licensor + reserves the exclusive right to collect such royalties for any + exercise by You of the rights granted under this License; + ii. Waivable Compulsory License Schemes. In those jurisdictions in + which the right to collect royalties through any statutory or + compulsory licensing scheme can be waived, the Licensor waives the + exclusive right to collect such royalties for any exercise by You + of the rights granted under this License; and, + iii. Voluntary License Schemes. The Licensor waives the right to + collect royalties, whether individually or, in the event that the + Licensor is a member of a collecting society that administers + voluntary licensing schemes, via that society, from any exercise + by You of the rights granted under this License. + +The above rights may be exercised in all media and formats whether now +known or hereafter devised. The above rights include the right to make +such modifications as are technically necessary to exercise the rights in +other media and formats. Subject to Section 8(f), all rights not expressly +granted by Licensor are hereby reserved. + +4. Restrictions. The license granted in Section 3 above is expressly made +subject to and limited by the following restrictions: + + a. You may Distribute or Publicly Perform the Work only under the terms + of this License. You must include a copy of, or the Uniform Resource + Identifier (URI) for, this License with every copy of the Work You + Distribute or Publicly Perform. You may not offer or impose any terms + on the Work that restrict the terms of this License or the ability of + the recipient of the Work to exercise the rights granted to that + recipient under the terms of the License. You may not sublicense the + Work. You must keep intact all notices that refer to this License and + to the disclaimer of warranties with every copy of the Work You + Distribute or Publicly Perform. When You Distribute or Publicly + Perform the Work, You may not impose any effective technological + measures on the Work that restrict the ability of a recipient of the + Work from You to exercise the rights granted to that recipient under + the terms of the License. This Section 4(a) applies to the Work as + incorporated in a Collection, but this does not require the Collection + apart from the Work itself to be made subject to the terms of this + License. If You create a Collection, upon notice from any Licensor You + must, to the extent practicable, remove from the Collection any credit + as required by Section 4(c), as requested. If You create an + Adaptation, upon notice from any Licensor You must, to the extent + practicable, remove from the Adaptation any credit as required by + Section 4(c), as requested. + b. You may Distribute or Publicly Perform an Adaptation only under the + terms of: (i) this License; (ii) a later version of this License with + the same License Elements as this License; (iii) a Creative Commons + jurisdiction license (either this or a later license version) that + contains the same License Elements as this License (e.g., + Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible + License. If you license the Adaptation under one of the licenses + mentioned in (iv), you must comply with the terms of that license. If + you license the Adaptation under the terms of any of the licenses + mentioned in (i), (ii) or (iii) (the "Applicable License"), you must + comply with the terms of the Applicable License generally and the + following provisions: (I) You must include a copy of, or the URI for, + the Applicable License with every copy of each Adaptation You + Distribute or Publicly Perform; (II) You may not offer or impose any + terms on the Adaptation that restrict the terms of the Applicable + License or the ability of the recipient of the Adaptation to exercise + the rights granted to that recipient under the terms of the Applicable + License; (III) You must keep intact all notices that refer to the + Applicable License and to the disclaimer of warranties with every copy + of the Work as included in the Adaptation You Distribute or Publicly + Perform; (IV) when You Distribute or Publicly Perform the Adaptation, + You may not impose any effective technological measures on the + Adaptation that restrict the ability of a recipient of the Adaptation + from You to exercise the rights granted to that recipient under the + terms of the Applicable License. This Section 4(b) applies to the + Adaptation as incorporated in a Collection, but this does not require + the Collection apart from the Adaptation itself to be made subject to + the terms of the Applicable License. + c. If You Distribute, or Publicly Perform the Work or any Adaptations or + Collections, You must, unless a request has been made pursuant to + Section 4(a), keep intact all copyright notices for the Work and + provide, reasonable to the medium or means You are utilizing: (i) the + name of the Original Author (or pseudonym, if applicable) if supplied, + and/or if the Original Author and/or Licensor designate another party + or parties (e.g., a sponsor institute, publishing entity, journal) for + attribution ("Attribution Parties") in Licensor's copyright notice, + terms of service or by other reasonable means, the name of such party + or parties; (ii) the title of the Work if supplied; (iii) to the + extent reasonably practicable, the URI, if any, that Licensor + specifies to be associated with the Work, unless such URI does not + refer to the copyright notice or licensing information for the Work; + and (iv) , consistent with Ssection 3(b), in the case of an + Adaptation, a credit identifying the use of the Work in the Adaptation + (e.g., "French translation of the Work by Original Author," or + "Screenplay based on original Work by Original Author"). The credit + required by this Section 4(c) may be implemented in any reasonable + manner; provided, however, that in the case of a Adaptation or + Collection, at a minimum such credit will appear, if a credit for all + contributing authors of the Adaptation or Collection appears, then as + part of these credits and in a manner at least as prominent as the + credits for the other contributing authors. For the avoidance of + doubt, You may only use the credit required by this Section for the + purpose of attribution in the manner set out above and, by exercising + Your rights under this License, You may not implicitly or explicitly + assert or imply any connection with, sponsorship or endorsement by the + Original Author, Licensor and/or Attribution Parties, as appropriate, + of You or Your use of the Work, without the separate, express prior + written permission of the Original Author, Licensor and/or Attribution + Parties. + d. Except as otherwise agreed in writing by the Licensor or as may be + otherwise permitted by applicable law, if You Reproduce, Distribute or + Publicly Perform the Work either by itself or as part of any + Adaptations or Collections, You must not distort, mutilate, modify or + take other derogatory action in relation to the Work which would be + prejudicial to the Original Author's honor or reputation. Licensor + agrees that in those jurisdictions (e.g. Japan), in which any exercise + of the right granted in Section 3(b) of this License (the right to + make Adaptations) would be deemed to be a distortion, mutilation, + modification or other derogatory action prejudicial to the Original + Author's honor and reputation, the Licensor will waive or not assert, + as appropriate, this Section, to the fullest extent permitted by the + applicable national law, to enable You to reasonably exercise Your + right under Section 3(b) of this License (right to make Adaptations) + but not otherwise. + +5. Representations, Warranties and Disclaimer + +UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR +OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY +KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, +INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, +FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF +LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, +WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION +OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. + +6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE +LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR +ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES +ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS +BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. Termination + + a. This License and the rights granted hereunder will terminate + automatically upon any breach by You of the terms of this License. + Individuals or entities who have received Adaptations or Collections + from You under this License, however, will not have their licenses + terminated provided such individuals or entities remain in full + compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will + survive any termination of this License. + b. Subject to the above terms and conditions, the license granted here is + perpetual (for the duration of the applicable copyright in the Work). + Notwithstanding the above, Licensor reserves the right to release the + Work under different license terms or to stop distributing the Work at + any time; provided, however that any such election will not serve to + withdraw this License (or any other license that has been, or is + required to be, granted under the terms of this License), and this + License will continue in full force and effect unless terminated as + stated above. + +8. Miscellaneous + + a. Each time You Distribute or Publicly Perform the Work or a Collection, + the Licensor offers to the recipient a license to the Work on the same + terms and conditions as the license granted to You under this License. + b. Each time You Distribute or Publicly Perform an Adaptation, Licensor + offers to the recipient a license to the original Work on the same + terms and conditions as the license granted to You under this License. + c. If any provision of this License is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of + the remainder of the terms of this License, and without further action + by the parties to this agreement, such provision shall be reformed to + the minimum extent necessary to make such provision valid and + enforceable. + d. No term or provision of this License shall be deemed waived and no + breach consented to unless such waiver or consent shall be in writing + and signed by the party to be charged with such waiver or consent. + e. This License constitutes the entire agreement between the parties with + respect to the Work licensed here. There are no understandings, + agreements or representations with respect to the Work not specified + here. Licensor shall not be bound by any additional provisions that + may appear in any communication from You. This License may not be + modified without the mutual written agreement of the Licensor and You. + f. The rights granted under, and the subject matter referenced, in this + License were drafted utilizing the terminology of the Berne Convention + for the Protection of Literary and Artistic Works (as amended on + September 28, 1979), the Rome Convention of 1961, the WIPO Copyright + Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 + and the Universal Copyright Convention (as revised on July 24, 1971). + These rights and subject matter take effect in the relevant + jurisdiction in which the License terms are sought to be enforced + according to the corresponding provisions of the implementation of + those treaty provisions in the applicable national law. If the + standard suite of rights granted under applicable copyright law + includes additional rights not granted under this License, such + additional rights are deemed to be included in the License; this + License is not intended to restrict the license of any rights under + applicable law. + + +Creative Commons Notice + + Creative Commons is not a party to this License, and makes no warranty + whatsoever in connection with the Work. Creative Commons will not be + liable to You or any party on any legal theory for any damages + whatsoever, including without limitation any general, special, + incidental or consequential damages arising in connection to this + license. Notwithstanding the foregoing two (2) sentences, if Creative + Commons has expressly identified itself as the Licensor hereunder, it + shall have all rights and obligations of Licensor. + + Except for the limited purpose of indicating to the public that the + Work is licensed under the CCPL, Creative Commons does not authorize + the use by either party of the trademark "Creative Commons" or any + related trademark or logo of Creative Commons without the prior + written consent of Creative Commons. Any permitted use will be in + compliance with Creative Commons' then-current trademark usage + guidelines, as may be published on its website or otherwise made + available upon request from time to time. For the avoidance of doubt, + this trademark restriction does not form part of the License. + diff --git a/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h b/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h new file mode 100644 index 00000000..f8c6c3d4 --- /dev/null +++ b/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h @@ -0,0 +1,17 @@ +#include + +/** Returns the kernel boottime property from sysctl. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. + */ +int64_t GDTCORKernelBootTimeInNanoseconds(void); + +/** Returns value of gettimeofday, in nanoseconds. + * + * Inspired by https://stackoverflow.com/a/40497811 + * + * @return The value of gettimeofday, in nanoseconds. + */ +int64_t GDTCORUptimeInNanoseconds(void); From d4932cd78106741ed38ddcd83f1207fc9ff79a3a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 27 May 2024 11:37:22 -0400 Subject: [PATCH 101/131] [NFC] Prefer over (#131) --- third_party/GDTCORClockUtils/GDTCORClockUtils.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/GDTCORClockUtils/GDTCORClockUtils.m b/third_party/GDTCORClockUtils/GDTCORClockUtils.m index 1731a2e9..a618603d 100644 --- a/third_party/GDTCORClockUtils/GDTCORClockUtils.m +++ b/third_party/GDTCORClockUtils/GDTCORClockUtils.m @@ -1,6 +1,6 @@ #import -#include +#include // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a From f97952e7ace747c2a6ba6948fe455fd375af4145 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Mon, 27 May 2024 11:40:29 -0400 Subject: [PATCH 102/131] [Docs] Update README.md to recommend `--use-json` (#132) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59227d95..084c2c04 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ After the CI is green: Push to SpecsStaging ```console - pod repo push --skip-tests staging GoogleDataTransport.podspec + pod repo push --skip-tests --use-json staging GoogleDataTransport.podspec ``` If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with: @@ -49,7 +49,7 @@ After the CI is green: Push to SpecsDev ```console - pod repo push --skip-tests dev GoogleDataTransport.podspec + pod repo push --skip-tests --use-json dev GoogleDataTransport.podspec ``` If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with: @@ -94,7 +94,7 @@ The release process is as follows: > before proceeding. ```console - pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport/{version}/GoogleDataTransport.podspec --skip-tests + pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport/{version}/GoogleDataTransport.podspec.json --skip-tests ``` The pod push was successful if the above command logs: `🚀 GoogleDataTransport ({version}) successfully published`. From 0ce7c117cc7f0439efc7c1eb5c5db8f7151947d0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 30 May 2024 13:38:41 -0400 Subject: [PATCH 103/131] Bump podspec to 10.0.0 (#133) --- GoogleDataTransport.podspec | 4 ++-- Package.swift | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 41df84b9..43d5bc98 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '9.4.1' + s.version = '10.0.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC @@ -40,7 +40,7 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] - s.dependency 'GoogleUtilities/Environment', '~> 7.7' + s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' diff --git a/Package.swift b/Package.swift index cae8ca02..3c6ae866 100644 --- a/Package.swift +++ b/Package.swift @@ -40,7 +40,8 @@ let package = Package( .package( name: "GoogleUtilities", url: "https://github.com/google/GoogleUtilities.git", - "7.2.1" ..< "8.0.0" + // TODO: Update to '"8.0.0" ..< "9.0.0"' when ready. + .branch("release-8.0") ), ], // TODO: Restructure directory structure to simplify the excludes here. From e6d2576c9d1910bf92739c907aa544e1cdb29982 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:04:02 -0400 Subject: [PATCH 104/131] [Infra] Refactor recent third_party refactors (#134) * Revert "[NFC] Prefer over (#131)" This reverts commit d4932cd78106741ed38ddcd83f1207fc9ff79a3a. * Revert "[Infra] Refactor out clock utils (#130)" This reverts commit b7bd781b343f8c630553076544d7c0bd6e7fbddf. * [Infra] Refactor recent third_party refactors --- GoogleDataTransport.podspec | 3 +- .../GDTCORLibrary/GDTCORClock.m | 41 +- LICENSE | 363 ------------------ Package.swift | 10 - .../GDTCORClockUtils/GDTCORClockUtils.m | 59 --- third_party/GDTCORClockUtils/LICENSE | 361 ----------------- .../Public/GDTCORClockUtils.h | 17 - 7 files changed, 38 insertions(+), 816 deletions(-) delete mode 100644 third_party/GDTCORClockUtils/GDTCORClockUtils.m delete mode 100644 third_party/GDTCORClockUtils/LICENSE delete mode 100644 third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 41df84b9..00156ba7 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -31,8 +31,7 @@ Shared library for iOS SDK data transport needs. s.prefix_header_file = false s.source_files = ['GoogleDataTransport/GDTCORLibrary/**/*', - 'GoogleDataTransport/GDTCCTLibrary/**/*', - 'third_party/GDTCORClockUtils/**/*.[mh]'] + 'GoogleDataTransport/GDTCCTLibrary/**/*'] s.public_header_files = 'GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/*.h' s.ios.frameworks = 'SystemConfiguration', 'CoreTelephony' s.osx.frameworks = 'SystemConfiguration', 'CoreTelephony' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m index dce1c023..97f96fa9 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m @@ -18,8 +18,6 @@ #import -#import "third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h" - // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a // time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external @@ -37,14 +35,49 @@ // Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be // difficult to unit test. +/** Returns the kernel boottime property from sysctl. + * + * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. + */ +static int64_t KernelBootTimeInNanoseconds(void) { + // Caching the result is not possible because clock drift would not be accounted for. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) { + return 0; + } + return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; +} + +/** Returns value of gettimeofday, in nanoseconds. + * + * @return The value of gettimeofday, in nanoseconds. + */ +static int64_t UptimeInNanoseconds(void) { + int64_t before_now_nsec; + int64_t after_now_nsec; + struct timeval now; + + before_now_nsec = KernelBootTimeInNanoseconds(); + // Addresses a race condition in which the system time has updated, but the boottime has not. + do { + gettimeofday(&now, NULL); + after_now_nsec = KernelBootTimeInNanoseconds(); + } while (after_now_nsec != before_now_nsec); + return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - + before_now_nsec; +} + // TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. @implementation GDTCORClock - (instancetype)init { self = [super init]; if (self) { - _kernelBootTimeNanoseconds = GDTCORKernelBootTimeInNanoseconds(); - _uptimeNanoseconds = GDTCORUptimeInNanoseconds(); + _kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds(); + _uptimeNanoseconds = UptimeInNanoseconds(); _timeMillis = (int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC); _timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT]; diff --git a/LICENSE b/LICENSE index 605b1aae..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -200,366 +200,3 @@ 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. - -================================================================================ - -License: CC BY-SA 3.0 -License url: http://creativecommons.org/licenses/by-sa/3.0/ - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. diff --git a/Package.swift b/Package.swift index cae8ca02..b875d18a 100644 --- a/Package.swift +++ b/Package.swift @@ -51,7 +51,6 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), - "third-party-GDTCORClockUtils", ], path: "GoogleDataTransport", exclude: [ @@ -82,15 +81,6 @@ let package = Package( .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .catalyst])), ] ), - .target( - name: "third-party-GDTCORClockUtils", - path: "third_party/GDTCORClockUtils", - exclude: ["LICENSE"], - publicHeadersPath: "Public", - cSettings: [ - .headerSearchPath("../../"), - ] - ), .testTarget( name: "swift-test", dependencies: [ diff --git a/third_party/GDTCORClockUtils/GDTCORClockUtils.m b/third_party/GDTCORClockUtils/GDTCORClockUtils.m deleted file mode 100644 index a618603d..00000000 --- a/third_party/GDTCORClockUtils/GDTCORClockUtils.m +++ /dev/null @@ -1,59 +0,0 @@ -#import - -#include - -// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all -// are subject to drift. That it to say, multiple consecutive calls do not always result in a -// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external -// factors. This class attempts to determine the wall-clock time at the time of the event by -// capturing the kernel start and time since boot to determine a wallclock time in UTC. -// -// Timezone offsets at the time of a snapshot are also captured in order to provide local-time -// details. Other classes in this library depend on comparing times at some time in the future to -// a time captured in the past, and this class needs to provide a mechanism to do that. -// -// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide -// a monotonic clock mechanism to accurately check if some clock snapshot was before or after -// by using a shared reference point (kernel boot time). -// -// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be -// difficult to unit test. - -/** Returns the kernel boottime property from sysctl. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. - */ -int64_t GDTCORKernelBootTimeInNanoseconds(void) { - // Caching the result is not possible because clock drift would not be accounted for. - struct timeval boottime; - int mib[2] = {CTL_KERN, KERN_BOOTTIME}; - size_t size = sizeof(boottime); - int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); - if (rc != 0) { - return 0; - } - return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; -} - -/** Returns value of gettimeofday, in nanoseconds. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The value of gettimeofday, in nanoseconds. - */ -int64_t GDTCORUptimeInNanoseconds(void) { - int64_t before_now_nsec; - int64_t after_now_nsec; - struct timeval now; - - before_now_nsec = GDTCORKernelBootTimeInNanoseconds(); - // Addresses a race condition in which the system time has updated, but the boottime has not. - do { - gettimeofday(&now, NULL); - after_now_nsec = GDTCORKernelBootTimeInNanoseconds(); - } while (after_now_nsec != before_now_nsec); - return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - - before_now_nsec; -} diff --git a/third_party/GDTCORClockUtils/LICENSE b/third_party/GDTCORClockUtils/LICENSE deleted file mode 100644 index f181cc96..00000000 --- a/third_party/GDTCORClockUtils/LICENSE +++ /dev/null @@ -1,361 +0,0 @@ -License: CC BY-SA 3.0 -License url: http://creativecommons.org/licenses/by-sa/3.0/ - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. - diff --git a/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h b/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h deleted file mode 100644 index f8c6c3d4..00000000 --- a/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -/** Returns the kernel boottime property from sysctl. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. - */ -int64_t GDTCORKernelBootTimeInNanoseconds(void); - -/** Returns value of gettimeofday, in nanoseconds. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The value of gettimeofday, in nanoseconds. - */ -int64_t GDTCORUptimeInNanoseconds(void); From 4df4854bd27e5f0c52e33720220b612a71824ee3 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 5 Jun 2024 14:53:59 -0400 Subject: [PATCH 105/131] [Infra] Refactor recent third_party refactors (#134) (#135) * Revert "[NFC] Prefer over (#131)" This reverts commit d4932cd78106741ed38ddcd83f1207fc9ff79a3a. * Revert "[Infra] Refactor out clock utils (#130)" This reverts commit b7bd781b343f8c630553076544d7c0bd6e7fbddf. * [Infra] Refactor recent third_party refactors --- GoogleDataTransport.podspec | 3 +- .../GDTCORLibrary/GDTCORClock.m | 41 +- LICENSE | 363 ------------------ Package.swift | 10 - .../GDTCORClockUtils/GDTCORClockUtils.m | 59 --- third_party/GDTCORClockUtils/LICENSE | 361 ----------------- .../Public/GDTCORClockUtils.h | 17 - 7 files changed, 38 insertions(+), 816 deletions(-) delete mode 100644 third_party/GDTCORClockUtils/GDTCORClockUtils.m delete mode 100644 third_party/GDTCORClockUtils/LICENSE delete mode 100644 third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 43d5bc98..c0853bfb 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -31,8 +31,7 @@ Shared library for iOS SDK data transport needs. s.prefix_header_file = false s.source_files = ['GoogleDataTransport/GDTCORLibrary/**/*', - 'GoogleDataTransport/GDTCCTLibrary/**/*', - 'third_party/GDTCORClockUtils/**/*.[mh]'] + 'GoogleDataTransport/GDTCCTLibrary/**/*'] s.public_header_files = 'GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/*.h' s.ios.frameworks = 'SystemConfiguration', 'CoreTelephony' s.osx.frameworks = 'SystemConfiguration', 'CoreTelephony' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m index dce1c023..97f96fa9 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORClock.m @@ -18,8 +18,6 @@ #import -#import "third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h" - // Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all // are subject to drift. That it to say, multiple consecutive calls do not always result in a // time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external @@ -37,14 +35,49 @@ // Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be // difficult to unit test. +/** Returns the kernel boottime property from sysctl. + * + * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. + */ +static int64_t KernelBootTimeInNanoseconds(void) { + // Caching the result is not possible because clock drift would not be accounted for. + struct timeval boottime; + int mib[2] = {CTL_KERN, KERN_BOOTTIME}; + size_t size = sizeof(boottime); + int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); + if (rc != 0) { + return 0; + } + return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; +} + +/** Returns value of gettimeofday, in nanoseconds. + * + * @return The value of gettimeofday, in nanoseconds. + */ +static int64_t UptimeInNanoseconds(void) { + int64_t before_now_nsec; + int64_t after_now_nsec; + struct timeval now; + + before_now_nsec = KernelBootTimeInNanoseconds(); + // Addresses a race condition in which the system time has updated, but the boottime has not. + do { + gettimeofday(&now, NULL); + after_now_nsec = KernelBootTimeInNanoseconds(); + } while (after_now_nsec != before_now_nsec); + return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - + before_now_nsec; +} + // TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE. @implementation GDTCORClock - (instancetype)init { self = [super init]; if (self) { - _kernelBootTimeNanoseconds = GDTCORKernelBootTimeInNanoseconds(); - _uptimeNanoseconds = GDTCORUptimeInNanoseconds(); + _kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds(); + _uptimeNanoseconds = UptimeInNanoseconds(); _timeMillis = (int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC); _timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT]; diff --git a/LICENSE b/LICENSE index 605b1aae..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -200,366 +200,3 @@ 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. - -================================================================================ - -License: CC BY-SA 3.0 -License url: http://creativecommons.org/licenses/by-sa/3.0/ - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. diff --git a/Package.swift b/Package.swift index 3c6ae866..029bac59 100644 --- a/Package.swift +++ b/Package.swift @@ -52,7 +52,6 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), .product(name: "GULEnvironment", package: "GoogleUtilities"), - "third-party-GDTCORClockUtils", ], path: "GoogleDataTransport", exclude: [ @@ -83,15 +82,6 @@ let package = Package( .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .catalyst])), ] ), - .target( - name: "third-party-GDTCORClockUtils", - path: "third_party/GDTCORClockUtils", - exclude: ["LICENSE"], - publicHeadersPath: "Public", - cSettings: [ - .headerSearchPath("../../"), - ] - ), .testTarget( name: "swift-test", dependencies: [ diff --git a/third_party/GDTCORClockUtils/GDTCORClockUtils.m b/third_party/GDTCORClockUtils/GDTCORClockUtils.m deleted file mode 100644 index a618603d..00000000 --- a/third_party/GDTCORClockUtils/GDTCORClockUtils.m +++ /dev/null @@ -1,59 +0,0 @@ -#import - -#include - -// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all -// are subject to drift. That it to say, multiple consecutive calls do not always result in a -// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external -// factors. This class attempts to determine the wall-clock time at the time of the event by -// capturing the kernel start and time since boot to determine a wallclock time in UTC. -// -// Timezone offsets at the time of a snapshot are also captured in order to provide local-time -// details. Other classes in this library depend on comparing times at some time in the future to -// a time captured in the past, and this class needs to provide a mechanism to do that. -// -// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide -// a monotonic clock mechanism to accurately check if some clock snapshot was before or after -// by using a shared reference point (kernel boot time). -// -// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be -// difficult to unit test. - -/** Returns the kernel boottime property from sysctl. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. - */ -int64_t GDTCORKernelBootTimeInNanoseconds(void) { - // Caching the result is not possible because clock drift would not be accounted for. - struct timeval boottime; - int mib[2] = {CTL_KERN, KERN_BOOTTIME}; - size_t size = sizeof(boottime); - int rc = sysctl(mib, 2, &boottime, &size, NULL, 0); - if (rc != 0) { - return 0; - } - return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC; -} - -/** Returns value of gettimeofday, in nanoseconds. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The value of gettimeofday, in nanoseconds. - */ -int64_t GDTCORUptimeInNanoseconds(void) { - int64_t before_now_nsec; - int64_t after_now_nsec; - struct timeval now; - - before_now_nsec = GDTCORKernelBootTimeInNanoseconds(); - // Addresses a race condition in which the system time has updated, but the boottime has not. - do { - gettimeofday(&now, NULL); - after_now_nsec = GDTCORKernelBootTimeInNanoseconds(); - } while (after_now_nsec != before_now_nsec); - return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC - - before_now_nsec; -} diff --git a/third_party/GDTCORClockUtils/LICENSE b/third_party/GDTCORClockUtils/LICENSE deleted file mode 100644 index f181cc96..00000000 --- a/third_party/GDTCORClockUtils/LICENSE +++ /dev/null @@ -1,361 +0,0 @@ -License: CC BY-SA 3.0 -License url: http://creativecommons.org/licenses/by-sa/3.0/ - -Creative Commons Legal Code - -Attribution-ShareAlike 3.0 Unported - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR - DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE -COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY -COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS -AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE -TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY -BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS -CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND -CONDITIONS. - -1. Definitions - - a. "Adaptation" means a work based upon the Work, or upon the Work and - other pre-existing works, such as a translation, adaptation, - derivative work, arrangement of music or other alterations of a - literary or artistic work, or phonogram or performance and includes - cinematographic adaptations or any other form in which the Work may be - recast, transformed, or adapted including in any form recognizably - derived from the original, except that a work that constitutes a - Collection will not be considered an Adaptation for the purpose of - this License. For the avoidance of doubt, where the Work is a musical - work, performance or phonogram, the synchronization of the Work in - timed-relation with a moving image ("synching") will be considered an - Adaptation for the purpose of this License. - b. "Collection" means a collection of literary or artistic works, such as - encyclopedias and anthologies, or performances, phonograms or - broadcasts, or other works or subject matter other than works listed - in Section 1(f) below, which, by reason of the selection and - arrangement of their contents, constitute intellectual creations, in - which the Work is included in its entirety in unmodified form along - with one or more other contributions, each constituting separate and - independent works in themselves, which together are assembled into a - collective whole. A work that constitutes a Collection will not be - considered an Adaptation (as defined below) for the purposes of this - License. - c. "Creative Commons Compatible License" means a license that is listed - at https://creativecommons.org/compatiblelicenses that has been - approved by Creative Commons as being essentially equivalent to this - License, including, at a minimum, because that license: (i) contains - terms that have the same purpose, meaning and effect as the License - Elements of this License; and, (ii) explicitly permits the relicensing - of adaptations of works made available under that license under this - License or a Creative Commons jurisdiction license with the same - License Elements as this License. - d. "Distribute" means to make available to the public the original and - copies of the Work or Adaptation, as appropriate, through sale or - other transfer of ownership. - e. "License Elements" means the following high-level license attributes - as selected by Licensor and indicated in the title of this License: - Attribution, ShareAlike. - f. "Licensor" means the individual, individuals, entity or entities that - offer(s) the Work under the terms of this License. - g. "Original Author" means, in the case of a literary or artistic work, - the individual, individuals, entity or entities who created the Work - or if no individual or entity can be identified, the publisher; and in - addition (i) in the case of a performance the actors, singers, - musicians, dancers, and other persons who act, sing, deliver, declaim, - play in, interpret or otherwise perform literary or artistic works or - expressions of folklore; (ii) in the case of a phonogram the producer - being the person or legal entity who first fixes the sounds of a - performance or other sounds; and, (iii) in the case of broadcasts, the - organization that transmits the broadcast. - h. "Work" means the literary and/or artistic work offered under the terms - of this License including without limitation any production in the - literary, scientific and artistic domain, whatever may be the mode or - form of its expression including digital form, such as a book, - pamphlet and other writing; a lecture, address, sermon or other work - of the same nature; a dramatic or dramatico-musical work; a - choreographic work or entertainment in dumb show; a musical - composition with or without words; a cinematographic work to which are - assimilated works expressed by a process analogous to cinematography; - a work of drawing, painting, architecture, sculpture, engraving or - lithography; a photographic work to which are assimilated works - expressed by a process analogous to photography; a work of applied - art; an illustration, map, plan, sketch or three-dimensional work - relative to geography, topography, architecture or science; a - performance; a broadcast; a phonogram; a compilation of data to the - extent it is protected as a copyrightable work; or a work performed by - a variety or circus performer to the extent it is not otherwise - considered a literary or artistic work. - i. "You" means an individual or entity exercising rights under this - License who has not previously violated the terms of this License with - respect to the Work, or who has received express permission from the - Licensor to exercise rights under this License despite a previous - violation. - j. "Publicly Perform" means to perform public recitations of the Work and - to communicate to the public those public recitations, by any means or - process, including by wire or wireless means or public digital - performances; to make available to the public Works in such a way that - members of the public may access these Works from a place and at a - place individually chosen by them; to perform the Work to the public - by any means or process and the communication to the public of the - performances of the Work, including by public digital performance; to - broadcast and rebroadcast the Work by any means including signs, - sounds or images. - k. "Reproduce" means to make copies of the Work by any means including - without limitation by sound or visual recordings and the right of - fixation and reproducing fixations of the Work, including storage of a - protected performance or phonogram in digital form or other electronic - medium. - -2. Fair Dealing Rights. Nothing in this License is intended to reduce, -limit, or restrict any uses free from copyright or rights arising from -limitations or exceptions that are provided for in connection with the -copyright protection under copyright law or other applicable laws. - -3. License Grant. Subject to the terms and conditions of this License, -Licensor hereby grants You a worldwide, royalty-free, non-exclusive, -perpetual (for the duration of the applicable copyright) license to -exercise the rights in the Work as stated below: - - a. to Reproduce the Work, to incorporate the Work into one or more - Collections, and to Reproduce the Work as incorporated in the - Collections; - b. to create and Reproduce Adaptations provided that any such Adaptation, - including any translation in any medium, takes reasonable steps to - clearly label, demarcate or otherwise identify that changes were made - to the original Work. For example, a translation could be marked "The - original work was translated from English to Spanish," or a - modification could indicate "The original work has been modified."; - c. to Distribute and Publicly Perform the Work including as incorporated - in Collections; and, - d. to Distribute and Publicly Perform Adaptations. - e. For the avoidance of doubt: - - i. Non-waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme cannot be waived, the Licensor - reserves the exclusive right to collect such royalties for any - exercise by You of the rights granted under this License; - ii. Waivable Compulsory License Schemes. In those jurisdictions in - which the right to collect royalties through any statutory or - compulsory licensing scheme can be waived, the Licensor waives the - exclusive right to collect such royalties for any exercise by You - of the rights granted under this License; and, - iii. Voluntary License Schemes. The Licensor waives the right to - collect royalties, whether individually or, in the event that the - Licensor is a member of a collecting society that administers - voluntary licensing schemes, via that society, from any exercise - by You of the rights granted under this License. - -The above rights may be exercised in all media and formats whether now -known or hereafter devised. The above rights include the right to make -such modifications as are technically necessary to exercise the rights in -other media and formats. Subject to Section 8(f), all rights not expressly -granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made -subject to and limited by the following restrictions: - - a. You may Distribute or Publicly Perform the Work only under the terms - of this License. You must include a copy of, or the Uniform Resource - Identifier (URI) for, this License with every copy of the Work You - Distribute or Publicly Perform. You may not offer or impose any terms - on the Work that restrict the terms of this License or the ability of - the recipient of the Work to exercise the rights granted to that - recipient under the terms of the License. You may not sublicense the - Work. You must keep intact all notices that refer to this License and - to the disclaimer of warranties with every copy of the Work You - Distribute or Publicly Perform. When You Distribute or Publicly - Perform the Work, You may not impose any effective technological - measures on the Work that restrict the ability of a recipient of the - Work from You to exercise the rights granted to that recipient under - the terms of the License. This Section 4(a) applies to the Work as - incorporated in a Collection, but this does not require the Collection - apart from the Work itself to be made subject to the terms of this - License. If You create a Collection, upon notice from any Licensor You - must, to the extent practicable, remove from the Collection any credit - as required by Section 4(c), as requested. If You create an - Adaptation, upon notice from any Licensor You must, to the extent - practicable, remove from the Adaptation any credit as required by - Section 4(c), as requested. - b. You may Distribute or Publicly Perform an Adaptation only under the - terms of: (i) this License; (ii) a later version of this License with - the same License Elements as this License; (iii) a Creative Commons - jurisdiction license (either this or a later license version) that - contains the same License Elements as this License (e.g., - Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible - License. If you license the Adaptation under one of the licenses - mentioned in (iv), you must comply with the terms of that license. If - you license the Adaptation under the terms of any of the licenses - mentioned in (i), (ii) or (iii) (the "Applicable License"), you must - comply with the terms of the Applicable License generally and the - following provisions: (I) You must include a copy of, or the URI for, - the Applicable License with every copy of each Adaptation You - Distribute or Publicly Perform; (II) You may not offer or impose any - terms on the Adaptation that restrict the terms of the Applicable - License or the ability of the recipient of the Adaptation to exercise - the rights granted to that recipient under the terms of the Applicable - License; (III) You must keep intact all notices that refer to the - Applicable License and to the disclaimer of warranties with every copy - of the Work as included in the Adaptation You Distribute or Publicly - Perform; (IV) when You Distribute or Publicly Perform the Adaptation, - You may not impose any effective technological measures on the - Adaptation that restrict the ability of a recipient of the Adaptation - from You to exercise the rights granted to that recipient under the - terms of the Applicable License. This Section 4(b) applies to the - Adaptation as incorporated in a Collection, but this does not require - the Collection apart from the Adaptation itself to be made subject to - the terms of the Applicable License. - c. If You Distribute, or Publicly Perform the Work or any Adaptations or - Collections, You must, unless a request has been made pursuant to - Section 4(a), keep intact all copyright notices for the Work and - provide, reasonable to the medium or means You are utilizing: (i) the - name of the Original Author (or pseudonym, if applicable) if supplied, - and/or if the Original Author and/or Licensor designate another party - or parties (e.g., a sponsor institute, publishing entity, journal) for - attribution ("Attribution Parties") in Licensor's copyright notice, - terms of service or by other reasonable means, the name of such party - or parties; (ii) the title of the Work if supplied; (iii) to the - extent reasonably practicable, the URI, if any, that Licensor - specifies to be associated with the Work, unless such URI does not - refer to the copyright notice or licensing information for the Work; - and (iv) , consistent with Ssection 3(b), in the case of an - Adaptation, a credit identifying the use of the Work in the Adaptation - (e.g., "French translation of the Work by Original Author," or - "Screenplay based on original Work by Original Author"). The credit - required by this Section 4(c) may be implemented in any reasonable - manner; provided, however, that in the case of a Adaptation or - Collection, at a minimum such credit will appear, if a credit for all - contributing authors of the Adaptation or Collection appears, then as - part of these credits and in a manner at least as prominent as the - credits for the other contributing authors. For the avoidance of - doubt, You may only use the credit required by this Section for the - purpose of attribution in the manner set out above and, by exercising - Your rights under this License, You may not implicitly or explicitly - assert or imply any connection with, sponsorship or endorsement by the - Original Author, Licensor and/or Attribution Parties, as appropriate, - of You or Your use of the Work, without the separate, express prior - written permission of the Original Author, Licensor and/or Attribution - Parties. - d. Except as otherwise agreed in writing by the Licensor or as may be - otherwise permitted by applicable law, if You Reproduce, Distribute or - Publicly Perform the Work either by itself or as part of any - Adaptations or Collections, You must not distort, mutilate, modify or - take other derogatory action in relation to the Work which would be - prejudicial to the Original Author's honor or reputation. Licensor - agrees that in those jurisdictions (e.g. Japan), in which any exercise - of the right granted in Section 3(b) of this License (the right to - make Adaptations) would be deemed to be a distortion, mutilation, - modification or other derogatory action prejudicial to the Original - Author's honor and reputation, the Licensor will waive or not assert, - as appropriate, this Section, to the fullest extent permitted by the - applicable national law, to enable You to reasonably exercise Your - right under Section 3(b) of this License (right to make Adaptations) - but not otherwise. - -5. Representations, Warranties and Disclaimer - -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR -OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY -KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, -INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, -FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF -LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, -WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION -OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE -LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR -ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES -ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS -BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. Termination - - a. This License and the rights granted hereunder will terminate - automatically upon any breach by You of the terms of this License. - Individuals or entities who have received Adaptations or Collections - from You under this License, however, will not have their licenses - terminated provided such individuals or entities remain in full - compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will - survive any termination of this License. - b. Subject to the above terms and conditions, the license granted here is - perpetual (for the duration of the applicable copyright in the Work). - Notwithstanding the above, Licensor reserves the right to release the - Work under different license terms or to stop distributing the Work at - any time; provided, however that any such election will not serve to - withdraw this License (or any other license that has been, or is - required to be, granted under the terms of this License), and this - License will continue in full force and effect unless terminated as - stated above. - -8. Miscellaneous - - a. Each time You Distribute or Publicly Perform the Work or a Collection, - the Licensor offers to the recipient a license to the Work on the same - terms and conditions as the license granted to You under this License. - b. Each time You Distribute or Publicly Perform an Adaptation, Licensor - offers to the recipient a license to the original Work on the same - terms and conditions as the license granted to You under this License. - c. If any provision of this License is invalid or unenforceable under - applicable law, it shall not affect the validity or enforceability of - the remainder of the terms of this License, and without further action - by the parties to this agreement, such provision shall be reformed to - the minimum extent necessary to make such provision valid and - enforceable. - d. No term or provision of this License shall be deemed waived and no - breach consented to unless such waiver or consent shall be in writing - and signed by the party to be charged with such waiver or consent. - e. This License constitutes the entire agreement between the parties with - respect to the Work licensed here. There are no understandings, - agreements or representations with respect to the Work not specified - here. Licensor shall not be bound by any additional provisions that - may appear in any communication from You. This License may not be - modified without the mutual written agreement of the Licensor and You. - f. The rights granted under, and the subject matter referenced, in this - License were drafted utilizing the terminology of the Berne Convention - for the Protection of Literary and Artistic Works (as amended on - September 28, 1979), the Rome Convention of 1961, the WIPO Copyright - Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 - and the Universal Copyright Convention (as revised on July 24, 1971). - These rights and subject matter take effect in the relevant - jurisdiction in which the License terms are sought to be enforced - according to the corresponding provisions of the implementation of - those treaty provisions in the applicable national law. If the - standard suite of rights granted under applicable copyright law - includes additional rights not granted under this License, such - additional rights are deemed to be included in the License; this - License is not intended to restrict the license of any rights under - applicable law. - - -Creative Commons Notice - - Creative Commons is not a party to this License, and makes no warranty - whatsoever in connection with the Work. Creative Commons will not be - liable to You or any party on any legal theory for any damages - whatsoever, including without limitation any general, special, - incidental or consequential damages arising in connection to this - license. Notwithstanding the foregoing two (2) sentences, if Creative - Commons has expressly identified itself as the Licensor hereunder, it - shall have all rights and obligations of Licensor. - - Except for the limited purpose of indicating to the public that the - Work is licensed under the CCPL, Creative Commons does not authorize - the use by either party of the trademark "Creative Commons" or any - related trademark or logo of Creative Commons without the prior - written consent of Creative Commons. Any permitted use will be in - compliance with Creative Commons' then-current trademark usage - guidelines, as may be published on its website or otherwise made - available upon request from time to time. For the avoidance of doubt, - this trademark restriction does not form part of the License. - diff --git a/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h b/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h deleted file mode 100644 index f8c6c3d4..00000000 --- a/third_party/GDTCORClockUtils/Public/GDTCORClockUtils.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -/** Returns the kernel boottime property from sysctl. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The KERN_BOOTTIME property from sysctl, in nanoseconds. - */ -int64_t GDTCORKernelBootTimeInNanoseconds(void); - -/** Returns value of gettimeofday, in nanoseconds. - * - * Inspired by https://stackoverflow.com/a/40497811 - * - * @return The value of gettimeofday, in nanoseconds. - */ -int64_t GDTCORUptimeInNanoseconds(void); From eb303a185cf30443436705f8d94a02753f83921a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 6 Jun 2024 15:42:20 -0700 Subject: [PATCH 106/131] [v10] Minimum platform updates (#136) --- CHANGELOG.md | 9 +- GoogleDataTransport.podspec | 12 +-- .../GDTCORLibrary/GDTCORPlatform.m | 98 +++++-------------- .../GDTTestApp/EventCleanupPerfTest.swift | 1 - .../GDTTestApp/viewcontroller.swift | 24 ++--- Package.swift | 11 +-- 6 files changed, 49 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b75224f..3f99690b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ -# Unreleased +# v10.0.0 - Fix null pointer warning from profiler. (#124) +- [changed] **Breaking change**: Platform Minimum supported version updates: + - | Platform | GoogleUtilities 8.0| + | ------------- | ------------- | + | iOS | **12.0** | + | tvOS | **13.0** | + | macOS | **10.15** | + | watchOS | **7.0** | # v9.4.1 (CocoaPods Only) - The v9.4.0 podspec did not include the intended dependency range for nanopb diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index c0853bfb..44d7e938 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -15,10 +15,10 @@ Shared library for iOS SDK data transport needs. :tag => 'CocoaPods-' + s.version.to_s } - ios_deployment_target = '9.0' - osx_deployment_target = '10.12' - tvos_deployment_target = '10.0' - watchos_deployment_target = '6.0' + ios_deployment_target = '12.0' + osx_deployment_target = '10.15' + tvos_deployment_target = '13.0' + watchos_deployment_target = '7.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target @@ -40,8 +40,8 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] s.dependency 'GoogleUtilities/Environment', '~> 8.0' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' - s.dependency 'PromisesObjC', '>= 1.2', '< 3.0' + s.dependency 'nanopb', '~> 3.30910.0' + s.dependency 'PromisesObjC', '~> 2.4' header_search_paths = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/"' diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m index 1d895282..0729bde9 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m @@ -129,19 +129,13 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { if (networkCurrentRadioAccessTechnologyDict.count) { networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0]; } -#else // TARGET_OS_MACCATALYST - if (@available(iOS 12.0, *)) { - NSDictionary *networkCurrentRadioAccessTechnologyDict = - networkInfo.serviceCurrentRadioAccessTechnology; - if (networkCurrentRadioAccessTechnologyDict.count) { - // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio - // tech to another, so we'll just return the first value in the dictionary. - networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0]; - } - } else { -#if TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000 - networkCurrentRadioAccessTechnology = networkInfo.currentRadioAccessTechnology; -#endif // TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED < 120000 +#else // TARGET_OS_MACCATALYST + NSDictionary *networkCurrentRadioAccessTechnologyDict = + networkInfo.serviceCurrentRadioAccessTechnology; + if (networkCurrentRadioAccessTechnologyDict.count) { + // In iOS 12, multiple radio technologies can be captured. We prefer not particular radio + // tech to another, so we'll just return the first value in the dictionary. + networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0]; } #endif // TARGET_OS_MACCATALYST if (networkCurrentRadioAccessTechnology) { @@ -197,50 +191,23 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { } } NSData *resultData; - if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) { - resultData = [NSKeyedArchiver archivedDataWithRootObject:obj - requiringSecureCoding:YES - error:error]; - if (resultData == nil || (error != NULL && *error != nil)) { - GDTCORLogDebug(@"Encoding an object failed: %@", *error); - return nil; - } - if (filePath.length > 0) { - result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; - if (result == NO || (error != NULL && *error != nil)) { - if (error != NULL && *error != nil) { - GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error); - } else { - GDTCORLogDebug(@"Attempt to write archive failed: path:%@", filePath); - } + resultData = [NSKeyedArchiver archivedDataWithRootObject:obj + requiringSecureCoding:YES + error:error]; + if (resultData == nil || (error != NULL && *error != nil)) { + GDTCORLogDebug(@"Encoding an object failed: %@", *error); + return nil; + } + if (filePath.length > 0) { + result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; + if (result == NO || (error != NULL && *error != nil)) { + if (error != NULL && *error != nil) { + GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error); } else { - GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); + GDTCORLogDebug(@"Attempt to write archive failed: path:%@", filePath); } - } - } else { - @try { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - resultData = [NSKeyedArchiver archivedDataWithRootObject:obj]; -#pragma clang diagnostic pop - if (filePath.length > 0) { - result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error]; - if (result == NO || *error) { - GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", filePath, *error); - } else { - GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); - } - } - } @catch (NSException *exception) { - NSString *errorString = - [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception]; - *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:-1 - userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}]; - } - if (filePath.length > 0) { - GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@", - result ? @"YES" : @"NO", filePath, *error); + } else { + GDTCORLogDebug(@"Writing archive succeeded: %@", filePath); } } return resultData; @@ -261,26 +228,7 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) { id _Nullable GDTCORDecodeArchive(Class archiveClass, NSData *_Nonnull archiveData, NSError **_Nonnull error) { - id unarchivedObject = nil; - if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4, *)) { - unarchivedObject = [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass - fromData:archiveData - error:error]; - } else { - @try { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; -#pragma clang diagnostic pop - } @catch (NSException *exception) { - NSString *errorString = - [NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception]; - *error = [NSError errorWithDomain:NSCocoaErrorDomain - code:-1 - userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}]; - } - } - return unarchivedObject; + return [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass fromData:archiveData error:error]; } BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) { diff --git a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift index df141ed2..d8164bf8 100644 --- a/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift +++ b/GoogleDataTransport/GDTTestApp/EventCleanupPerfTest.swift @@ -22,7 +22,6 @@ import os.signpost /// The test actions to run under the profiler to measure performance of /// `GDTCORFlatFileStorage.checkForExpirations()` method. -@available(iOS 12.0, *) enum EventCleanupPerfTest { static let log = OSLog(subsystem: "GoogleDataTransport-TestApp", category: "EventCleanupPerfTest") diff --git a/GoogleDataTransport/GDTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTTestApp/viewcontroller.swift index db184e97..18db9488 100644 --- a/GoogleDataTransport/GDTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTTestApp/viewcontroller.swift @@ -64,28 +64,20 @@ public extension ViewController { } @IBAction func generateTestEvents(button: UIButton) { - if #available(iOS 12.0, *) { - let numberOfEvents = 10000 - set(status: "Generating \(numberOfEvents) events") + let numberOfEvents = 10000 + set(status: "Generating \(numberOfEvents) events") - EventCleanupPerfTest.generateTestEvents(count: numberOfEvents) { - self.set(status: "Generated \(numberOfEvents) events") - } - } else { - print("Performance testing set up for iOS 12.0 and later") + EventCleanupPerfTest.generateTestEvents(count: numberOfEvents) { + self.set(status: "Generated \(numberOfEvents) events") } } @IBAction func runEventCleanupPerformanceTest(button: UIButton) { - if #available(iOS 12.0, *) { - set(status: "Event Cleanup Performance Test started") - EventCleanupPerfTest.run { - DispatchQueue.main.async { - self.set(status: "Event Cleanup Performance Test finished") - } + set(status: "Event Cleanup Performance Test started") + EventCleanupPerfTest.run { + DispatchQueue.main.async { + self.set(status: "Event Cleanup Performance Test finished") } - } else { - print("Performance testing set up for iOS 12.0 and later") } } diff --git a/Package.swift b/Package.swift index 029bac59..43f45b15 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. // Copyright 2021 Google LLC @@ -19,7 +19,7 @@ import PackageDescription let package = Package( name: "GoogleDataTransport", - platforms: [.iOS(.v10), .macOS(.v10_12), .tvOS(.v10), .watchOS(.v6)], + platforms: [.iOS(.v12), .macOS(.v10_15), .tvOS(.v13), .watchOS(.v7)], products: [ .library( name: "GoogleDataTransport", @@ -28,20 +28,17 @@ let package = Package( ], dependencies: [ .package( - name: "nanopb", url: "https://github.com/firebase/nanopb.git", "2.30908.0" ..< "2.30911.0" ), .package( - name: "Promises", url: "https://github.com/google/promises.git", - "1.2.8" ..< "3.0.0" + "2.4.0" ..< "3.0.0" ), .package( - name: "GoogleUtilities", url: "https://github.com/google/GoogleUtilities.git", // TODO: Update to '"8.0.0" ..< "9.0.0"' when ready. - .branch("release-8.0") + branch: "release-8.0" ), ], // TODO: Restructure directory structure to simplify the excludes here. From 0e418b8367b866db57b88615e056bebbab7bdcc1 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:18:47 -0400 Subject: [PATCH 107/131] [v10] Remove GoogleUtilities dependency (#137) * [v10] Add new files to replace GULs promise APIs * Add renames * Remove GoogleUtilities dependency * Style --- CHANGELOG.md | 2 +- GoogleDataTransport.podspec | 1 - .../GDTCCTURLSessionDataResponse.m | 28 ++++++++++++ .../GDTCCTLibrary/GDTCCTUploadOperation.m | 22 +++++----- .../NSURLSession+GDTCCTPromises.m | 44 +++++++++++++++++++ .../Private/GDTCCTURLSessionDataResponse.h | 29 ++++++++++++ .../Private/NSURLSession+GDTCCTPromises.h | 35 +++++++++++++++ Package.swift | 6 --- 8 files changed, 148 insertions(+), 19 deletions(-) create mode 100644 GoogleDataTransport/GDTCCTLibrary/GDTCCTURLSessionDataResponse.m create mode 100644 GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m create mode 100644 GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h create mode 100644 GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f99690b..29be7673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # v10.0.0 - Fix null pointer warning from profiler. (#124) - [changed] **Breaking change**: Platform Minimum supported version updates: - - | Platform | GoogleUtilities 8.0| + - | Platform | GoogleDataTransport 10.0| | ------------- | ------------- | | iOS | **12.0** | | tvOS | **13.0** | diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 44d7e938..fdbf96a9 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -39,7 +39,6 @@ Shared library for iOS SDK data transport needs. s.libraries = ['z'] - s.dependency 'GoogleUtilities/Environment', '~> 8.0' s.dependency 'nanopb', '~> 3.30910.0' s.dependency 'PromisesObjC', '~> 2.4' diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTURLSessionDataResponse.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTURLSessionDataResponse.m new file mode 100644 index 00000000..669e93fe --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTURLSessionDataResponse.m @@ -0,0 +1,28 @@ +// Copyright 2024 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h" + +@implementation GDTCCTURLSessionDataResponse + +- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body { + self = [super init]; + if (self) { + _HTTPResponse = response; + _HTTPBody = body; + } + return self; +} + +@end diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 05937ea4..3e87f0f8 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -34,11 +34,11 @@ #import #import -#import -#import #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h" +#import "GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" @@ -227,7 +227,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions // 1. Send URL request. return [self sendURLRequestWithBatch:batch target:target] .thenOn(self.uploaderQueue, - ^FBLPromise *(GULURLSessionDataResponse *response) { + ^FBLPromise *(GDTCCTURLSessionDataResponse *response) { // 2. Update the next upload time and process response. [self updateNextUploadTimeWithResponse:response forTarget:target]; @@ -246,7 +246,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions } /** Processes a URL session response for a given batch from storage. */ -- (FBLPromise *)processResponse:(GULURLSessionDataResponse *)response +- (FBLPromise *)processResponse:(GDTCCTURLSessionDataResponse *)response forBatch:(GDTCORUploadBatch *)batch storage:(id)storage { // Cleanup batch based on the response's status code. @@ -286,8 +286,8 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions } /** Composes and sends URL request. */ -- (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch - target:(GDTCORTarget)target { +- (FBLPromise *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch + target:(GDTCORTarget)target { return [FBLPromise onQueue:self.uploaderQueue do:^NSURLRequest * { @@ -307,13 +307,13 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions return request; }] .thenOn(self.uploaderQueue, - ^FBLPromise *(NSURLRequest *request) { + ^FBLPromise *(NSURLRequest *request) { // 2. Send URL request. - return - [[self uploaderSessionCreateIfNeeded] gul_dataTaskPromiseWithRequest:request]; + return [[self uploaderSessionCreateIfNeeded] + gdtcct_dataTaskPromiseWithRequest:request]; }) .thenOn(self.uploaderQueue, - ^GULURLSessionDataResponse *(GULURLSessionDataResponse *response) { + ^GDTCCTURLSessionDataResponse *(GDTCCTURLSessionDataResponse *response) { // Invalidate session to release the delegate (which is `self`) to break the retain // cycle. [self.uploaderSession finishTasksAndInvalidate]; @@ -328,7 +328,7 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions } /** Parses server response and update next upload time for the specified target based on it. */ -- (void)updateNextUploadTimeWithResponse:(GULURLSessionDataResponse *)response +- (void)updateNextUploadTimeWithResponse:(GDTCCTURLSessionDataResponse *)response forTarget:(GDTCORTarget)target { GDTCORClock *futureUploadTime; if (response.HTTPBody) { diff --git a/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m b/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m new file mode 100644 index 00000000..9c81736d --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m @@ -0,0 +1,44 @@ +// Copyright 2024 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 "GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h" + +#if __has_include() +#import +#else +#import "FBLPromises.h" +#endif + +#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h" + +@implementation NSURLSession (GDTCCTPromises) + +- (FBLPromise *)gdtcct_dataTaskPromiseWithRequest: + (NSURLRequest *)URLRequest { + return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { + [[self dataTaskWithRequest:URLRequest + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + reject(error); + } else { + fulfill([[GDTCCTURLSessionDataResponse alloc] + initWithResponse:(NSHTTPURLResponse *)response + HTTPBody:data]); + } + }] resume]; + }]; +} + +@end diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h new file mode 100644 index 00000000..30ec2031 --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h @@ -0,0 +1,29 @@ +// Copyright 2024 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 + +NS_ASSUME_NONNULL_BEGIN + +/** The class represents HTTP response received from `NSURLSession`. */ +@interface GDTCCTURLSessionDataResponse : NSObject + +@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse; +@property(nonatomic, nullable, readonly) NSData *HTTPBody; + +- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h b/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h new file mode 100644 index 00000000..aba0641c --- /dev/null +++ b/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h @@ -0,0 +1,35 @@ +// Copyright 2024 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 + +@class FBLPromise; +@class GDTCCTURLSessionDataResponse; + +NS_ASSUME_NONNULL_BEGIN + +/** Promise based API for `NSURLSession`. */ +@interface NSURLSession (GDTCCTPromises) + +/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method. + * @param URLRequest The request to create a data task with. + * @return A promise that is fulfilled when an HTTP response is received (with any response code), + * or is rejected with the error passed to the task completion. + */ +- (FBLPromise *)gdtcct_dataTaskPromiseWithRequest: + (NSURLRequest *)URLRequest; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Package.swift b/Package.swift index 43f45b15..c30ac0b5 100644 --- a/Package.swift +++ b/Package.swift @@ -35,11 +35,6 @@ let package = Package( url: "https://github.com/google/promises.git", "2.4.0" ..< "3.0.0" ), - .package( - url: "https://github.com/google/GoogleUtilities.git", - // TODO: Update to '"8.0.0" ..< "9.0.0"' when ready. - branch: "release-8.0" - ), ], // TODO: Restructure directory structure to simplify the excludes here. targets: [ @@ -48,7 +43,6 @@ let package = Package( dependencies: [ .product(name: "nanopb", package: "nanopb"), .product(name: "FBLPromises", package: "Promises"), - .product(name: "GULEnvironment", package: "GoogleUtilities"), ], path: "GoogleDataTransport", exclude: [ From 8aa98a183009632844b5afb7bb541fb557488065 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:04:51 -0400 Subject: [PATCH 108/131] [Infra] Update GoogleDataTransport.podspec (#139) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 00156ba7..414e728b 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -55,7 +55,7 @@ Shared library for iOS SDK data transport needs. # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT # anyways.) preprocessor_definitions = - 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1'\ + 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1' \ 'GDTCOR_VERSION=' + s.version.to_s if ENV['GDT_TEST'] && ENV['GDT_TEST'] == '1' then From 471d369736b336588a7dd8d475a47982efc259cc Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 8 Jul 2024 19:06:10 -0400 Subject: [PATCH 109/131] [v10] Merge `main` into `release-10.0` (#140) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index fdbf96a9..a3e88515 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -54,7 +54,7 @@ Shared library for iOS SDK data transport needs. # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT # anyways.) preprocessor_definitions = - 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1'\ + 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1' \ 'GDTCOR_VERSION=' + s.version.to_s if ENV['GDT_TEST'] && ENV['GDT_TEST'] == '1' then From 2e1ede3e3bb21634a743ad79b786a41044ccb270 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 9 Jul 2024 10:27:29 -0400 Subject: [PATCH 110/131] Bump minimum nanopb to 2.30910.0 in Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index c30ac0b5..f760ad6c 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/firebase/nanopb.git", - "2.30908.0" ..< "2.30911.0" + "2.30910.0" ..< "2.30911.0" ), .package( url: "https://github.com/google/promises.git", From f3b3de89388b68aa00b039e286d6e94a9d986044 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:34:32 -0400 Subject: [PATCH 111/131] [Cleanup] Remove unneeded Platform extension in Package.swift (#143) --- Package.swift | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Package.swift b/Package.swift index f760ad6c..36c89238 100644 --- a/Package.swift +++ b/Package.swift @@ -69,8 +69,8 @@ let package = Package( .define("PB_ENABLE_MALLOC", to: "1"), ], linkerSettings: [ - .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS, .catalyst])), - .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .catalyst])), + .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS, .macCatalyst])), + .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .macCatalyst])), ] ), .testTarget( @@ -93,13 +93,3 @@ let package = Package( cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 ) - -extension Platform { - static var catalyst: Self { - #if swift(>=5.5) - return Self.macCatalyst - #else - return Self.macOS - #endif // swift(>=5.5) - } -} From dc65bb4d771f34f4917701128f9d8a2795d9fed1 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 16 Jul 2024 13:19:29 -0400 Subject: [PATCH 112/131] [NFC] Style Package.swift (#144) --- Package.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 36c89238..d49077b3 100644 --- a/Package.swift +++ b/Package.swift @@ -69,7 +69,10 @@ let package = Package( .define("PB_ENABLE_MALLOC", to: "1"), ], linkerSettings: [ - .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS, .macCatalyst])), + .linkedFramework( + "SystemConfiguration", + .when(platforms: [.iOS, .macOS, .tvOS, .macCatalyst]) + ), .linkedFramework("CoreTelephony", .when(platforms: [.macOS, .iOS, .macCatalyst])), ] ), From 617af071af9aa1d6a091d59a202910ac482128f9 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:49:20 -0400 Subject: [PATCH 113/131] [v10] Fix '[FBLPromise HTTPBody]' SwiftUI Preview crash] (#146) * [v10] Fix '[FBLPromise HTTPBody]' SwiftUI Preview crash * Add CHANGELOG entry * Review * Force link other categories * Fix changelog * Fix podspec --- CHANGELOG.md | 5 +++ GoogleDataTransport.podspec | 2 +- .../GDTCCTLibrary/GDTCCTUploadOperation.m | 36 +++++++++++++-- .../GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m | 4 ++ .../GDTCOREvent+GDTMetricsSupport.m | 4 ++ .../GDTCORMetrics+GDTCCTSupport.m | 4 ++ .../NSURLSession+GDTCCTPromises.m | 44 ------------------- .../Private/NSURLSession+GDTCCTPromises.h | 35 --------------- .../GDTCORFlatFileStorage+Promises.m | 4 ++ .../GDTCORLibrary/GDTCORTransport.m | 12 +++++ 10 files changed, 67 insertions(+), 83 deletions(-) delete mode 100644 GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m delete mode 100644 GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 29be7673..3b25a4bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v10.1.0 +- Fix `[FBLPromise HTTPBody]` SwiftUI Previews crash when using binary + distribution. ([firebase-ios-sdk/#13318](https://github.com/firebase/firebase-ios-sdk/issues/13318), + [firebase-ios-sdk/#11733](https://github.com/firebase/firebase-ios-sdk/issues/11733)) + # v10.0.0 - Fix null pointer warning from profiler. (#124) - [changed] **Breaking change**: Platform Minimum supported version updates: diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index a3e88515..870fea9b 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleDataTransport' - s.version = '10.0.0' + s.version = '10.1.0' s.summary = 'Google iOS SDK data transport.' s.description = <<-DESC diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 3e87f0f8..354d14cc 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -38,7 +38,6 @@ #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h" #import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h" -#import "GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h" #import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h" @@ -309,8 +308,23 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions .thenOn(self.uploaderQueue, ^FBLPromise *(NSURLRequest *request) { // 2. Send URL request. - return [[self uploaderSessionCreateIfNeeded] - gdtcct_dataTaskPromiseWithRequest:request]; + NSURLSession *session = [self uploaderSessionCreateIfNeeded]; + return [FBLPromise wrapObjectOrErrorCompletion:^( + FBLPromiseObjectOrErrorCompletion _Nonnull handler) { + [[session dataTaskWithRequest:request + completionHandler:^(NSData *_Nullable data, + NSURLResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + handler(nil, error); + } else { + handler([[GDTCCTURLSessionDataResponse alloc] + initWithResponse:(NSHTTPURLResponse *)response + HTTPBody:data], + nil); + } + }] resume]; + }]; }) .thenOn(self.uploaderQueue, ^GDTCCTURLSessionDataResponse *(GDTCCTURLSessionDataResponse *response) { @@ -633,6 +647,22 @@ - (void)cancel { } } +#pragma mark - Force Category Linking + +extern void GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category(void); +extern void GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category(void); +extern void GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category(void); + +/// Does nothing when called, and not meant to be called. +/// +/// This method forces the linker to include categories even if +/// users do not include the '-ObjC' linker flag in their project. ++ (void)noop { + GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category(); + GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category(); + GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category(); +} + @end NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m index 1c77b4cb..f102c81e 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTCCTSupport.m @@ -238,3 +238,7 @@ - (void)setEventCode:(NSNumber *)eventCode { } @end + +/// Stub used to force the linker to include the categories in this file. +void GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category(void) { +} diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m index aa5dabe0..6386835c 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCOREvent+GDTMetricsSupport.m @@ -31,3 +31,7 @@ + (GDTCOREvent *)eventWithMetrics:(GDTCORMetrics *)metrics forTarget:(GDTCORTarg } @end + +/// Stub used to force the linker to include the categories in this file. +void GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category(void) { +} diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m index f83902ba..044b85a1 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCORMetrics+GDTCCTSupport.m @@ -205,3 +205,7 @@ gdt_client_metrics_LogEventDropped_Reason GDTCCTConvertEventDropReasonToProtoRea } @end + +/// Stub used to force the linker to include the categories in this file. +void GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category(void) { +} diff --git a/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m b/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m deleted file mode 100644 index 9c81736d..00000000 --- a/GoogleDataTransport/GDTCCTLibrary/NSURLSession+GDTCCTPromises.m +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 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 "GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h" - -#if __has_include() -#import -#else -#import "FBLPromises.h" -#endif - -#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h" - -@implementation NSURLSession (GDTCCTPromises) - -- (FBLPromise *)gdtcct_dataTaskPromiseWithRequest: - (NSURLRequest *)URLRequest { - return [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { - [[self dataTaskWithRequest:URLRequest - completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - reject(error); - } else { - fulfill([[GDTCCTURLSessionDataResponse alloc] - initWithResponse:(NSHTTPURLResponse *)response - HTTPBody:data]); - } - }] resume]; - }]; -} - -@end diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h b/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h deleted file mode 100644 index aba0641c..00000000 --- a/GoogleDataTransport/GDTCCTLibrary/Private/NSURLSession+GDTCCTPromises.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2024 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 - -@class FBLPromise; -@class GDTCCTURLSessionDataResponse; - -NS_ASSUME_NONNULL_BEGIN - -/** Promise based API for `NSURLSession`. */ -@interface NSURLSession (GDTCCTPromises) - -/** Creates a promise wrapping `-[NSURLSession dataTaskWithRequest:completionHandler:]` method. - * @param URLRequest The request to create a data task with. - * @return A promise that is fulfilled when an HTTP response is received (with any response code), - * or is rejected with the error passed to the task completion. - */ -- (FBLPromise *)gdtcct_dataTaskPromiseWithRequest: - (NSURLRequest *)URLRequest; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m index 2f018b2c..4ff937c6 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage+Promises.m @@ -155,3 +155,7 @@ - (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason { } @end + +/// Stub used to force the linker to include the categories in this file. +void GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category(void) { +} diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m b/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m index 6efff4c1..870ad710 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORTransport.m @@ -93,4 +93,16 @@ - (void)sendEvent:(GDTCOREvent *)event onComplete:completion]; } +#pragma mark - Force Category Linking + +extern void GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category(void); + +/// Does nothing when called, and not meant to be called. +/// +/// This method forces the linker to include categories even if +/// users do not include the '-ObjC' linker flag in their project. ++ (void)noop { + GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category(); +} + @end From fa012de3b44d0092a1082fa9816b834a96d1f994 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:35:41 -0700 Subject: [PATCH 114/131] Bump rexml from 3.2.8 to 3.3.6 in the bundler group (#147) Bumps the bundler group with 1 update: [rexml](https://github.com/ruby/rexml). Updates `rexml` from 3.2.8 to 3.3.6 - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.2.8...v3.3.6) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 87be12d1..a79e4778 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,8 +145,8 @@ GEM open4 (1.3.4) public_suffix (4.0.7) rchardet (1.8.0) - rexml (3.2.8) - strscan (>= 3.0.9) + rexml (3.3.6) + strscan ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) @@ -160,13 +160,13 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.2.0) - xcodeproj (1.24.0) + xcodeproj (1.25.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (~> 3.2.4) + rexml (>= 3.3.2, < 4.0) PLATFORMS ruby From 1352cc07aae5a7c7199974ac300d6a7c06518a77 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 23 Aug 2024 07:23:08 -0700 Subject: [PATCH 115/131] Resync swiftformat (#148) --- .github/workflows/check.yml | 3 --- .swiftformat | 14 ++++++++++++++ .../GDTCCTTestApp/viewcontroller.swift | 2 +- .../NotificationService.swift | 2 +- .../InterfaceController.swift | 2 +- .../NotificationController.swift | 2 +- .../InterfaceController.swift | 2 +- .../NotificationController.swift | 2 +- .../GDTTestApp/viewcontroller.swift | 2 +- .../InterfaceController.swift | 4 ++-- .../NotificationController.swift | 2 +- Mintfile | 2 +- 12 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 .swiftformat diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7be943a7..79c5d632 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -2,9 +2,6 @@ name: check on: pull_request: - paths: - - '.github/workflows/check.yml' - - 'GoogleDataTransport**' push: branches: main diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 00000000..7def2b71 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,14 @@ +# Formatting Options - Mimic Google style +--indent 2 +--maxwidth 100 +--wrapparameters afterfirst + +# Disabled Rules + +# Too many of our swift files have simplistic examples. While technically +# it's correct to remove the unused argument labels, it makes our examples +# look wrong. +--disable unusedArguments + +# We prefer trailing braces. +--disable wrapMultilineStatementBraces diff --git a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift index 4254ccf4..7b5355c3 100644 --- a/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTCCTTestApp/viewcontroller.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation import Dispatch +import Foundation import GoogleDataTransport public extension ViewController { diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift index a811aa6c..7d89d840 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTServiceExtension/NotificationService.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import UserNotifications import FirebaseMessaging import GoogleDataTransport +import UserNotifications class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift index 85d0a9bc..62824a36 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/InterfaceController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit import Foundation import GoogleDataTransport +import WatchKit class InterfaceController: WKInterfaceController { var transport = GDTCORTransport(mappingID: "1018", transformers: nil, target: GDTCORTarget.FLL)! diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/NotificationController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/NotificationController.swift index 1212a170..924cb24f 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/NotificationController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSCompanionTestAppExtension/NotificationController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit import Foundation import UserNotifications +import WatchKit class NotificationController: WKUserNotificationInterfaceController { override init() { diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift index 26784d16..faf6c044 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/InterfaceController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit import Foundation import GoogleDataTransport +import WatchKit class InterfaceController: WKInterfaceController { var transport = GDTCORTransport(mappingID: "1018", transformers: nil, target: GDTCORTarget.FLL)! diff --git a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/NotificationController.swift b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/NotificationController.swift index 03387acb..a57ecd16 100644 --- a/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/NotificationController.swift +++ b/GoogleDataTransport/GDTCCTWatchOSTestApp/GDTCCTWatchOSIndependentTestAppWatchKitExtension/NotificationController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit import Foundation import UserNotifications +import WatchKit class NotificationController: WKUserNotificationInterfaceController { override init() { diff --git a/GoogleDataTransport/GDTTestApp/viewcontroller.swift b/GoogleDataTransport/GDTTestApp/viewcontroller.swift index 18db9488..df65d89f 100644 --- a/GoogleDataTransport/GDTTestApp/viewcontroller.swift +++ b/GoogleDataTransport/GDTTestApp/viewcontroller.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import Foundation import Dispatch +import Foundation import GoogleDataTransport public extension ViewController { diff --git a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift index 0544ef58..c9ed9d65 100644 --- a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift +++ b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/InterfaceController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit -import Foundation import Dispatch +import Foundation +import WatchKit import GoogleDataTransport diff --git a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/NotificationController.swift b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/NotificationController.swift index 03387acb..a57ecd16 100644 --- a/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/NotificationController.swift +++ b/GoogleDataTransport/GDTWatchOSTestApp/GDTWatchOSTestAppWatchKitExtension/NotificationController.swift @@ -14,9 +14,9 @@ * limitations under the License. */ -import WatchKit import Foundation import UserNotifications +import WatchKit class NotificationController: WKUserNotificationInterfaceController { override init() { diff --git a/Mintfile b/Mintfile index 4e60f1d0..640a5ca2 100644 --- a/Mintfile +++ b/Mintfile @@ -1 +1 @@ -nicklockwood/SwiftFormat@0.49.2 +nicklockwood/SwiftFormat@0.54.0 From d6b23f74b7d970fea5c0d4e187cc415eacd83ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:57:52 -0700 Subject: [PATCH 116/131] Bump actions/download-artifact (#150) Bumps the github_actions group with 1 update in the /.github/workflows directory: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 2 to 4.1.7 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production dependency-group: github_actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 44c32066..64235cb6 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -46,7 +46,7 @@ jobs: with: fetch-depth: 0 - uses: ruby/setup-ruby@v1 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4.1.7 id: download with: path: /Users/runner/test From 677effa02099e039b9604754e5c0f5c1404b507b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:22:03 -0400 Subject: [PATCH 117/131] Bump rexml from 3.3.6 to 3.3.9 in the bundler group (#151) Bumps the bundler group with 1 update: [rexml](https://github.com/ruby/rexml). Updates `rexml` from 3.3.6 to 3.3.9 - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9) --- updated-dependencies: - dependency-name: rexml dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a79e4778..9d3ebbf5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,14 +145,12 @@ GEM open4 (1.3.4) public_suffix (4.0.7) rchardet (1.8.0) - rexml (3.3.6) - strscan + rexml (3.3.9) ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.1) From a7a170a8ed6d54acee8648f32c52d0985783875f Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:30:11 -0400 Subject: [PATCH 118/131] [Infra] Bump clang-format to 19 (#152) * [Infra] Bump clang-format to 19 * Bump upload-artifact to v4 --- .github/workflows/check.yml | 2 +- .github/workflows/test_coverage.yml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 79c5d632..dc54d8e5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -34,7 +34,7 @@ jobs: - name: Setup check run: | brew update - brew install clang-format@18 + brew install clang-format@19 brew install mint mint bootstrap diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml index 64235cb6..f138177d 100644 --- a/.github/workflows/test_coverage.yml +++ b/.github/workflows/test_coverage.yml @@ -33,7 +33,7 @@ jobs: - name: PodLibLint DataTransport run: | scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec=Tests-Unit,CCT-Tests-Unit - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: codecoverage path: /Users/runner/*.xcresult diff --git a/README.md b/README.md index 084c2c04..daa99139 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ GitHub Actions will verify that any code changes are done in a style compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@18 +brew install clang-format@19 brew install mint ``` From 43b992a30405c5af6c837570c87648b6a315d42c Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:29:00 -0500 Subject: [PATCH 119/131] [Infra] Update GoogleDataTransport.podspec (#154) --- GoogleDataTransport.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleDataTransport.podspec b/GoogleDataTransport.podspec index 870fea9b..fdbba71e 100644 --- a/GoogleDataTransport.podspec +++ b/GoogleDataTransport.podspec @@ -54,7 +54,7 @@ Shared library for iOS SDK data transport needs. # (or larger) fields, so we'd have to set at least PB_FIELD_16BIT # anyways.) preprocessor_definitions = - 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1' \ + 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1 ' \ 'GDTCOR_VERSION=' + s.version.to_s if ENV['GDT_TEST'] && ENV['GDT_TEST'] == '1' then From c6afba69ca5a5dd956de23303c6a77599c404324 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Fri, 27 Dec 2024 17:46:38 -0800 Subject: [PATCH 120/131] add analyze to ci (#155) * add analyze to ci * fix bad flag * fix bad flag x2 --- .github/workflows/datatransport.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index 6c98d553..220c25bd 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -29,7 +29,7 @@ jobs: run: ./scripts/setup_bundler.sh - name: PodLibLint DataTransport run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb GoogleDataTransport.podspec --platforms=${{ matrix.target }} + scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb GoogleDataTransport.podspec --platforms=${{ matrix.target }} --analyze catalyst: runs-on: macOS-latest @@ -78,4 +78,4 @@ jobs: run: ./scripts/setup_bundler.sh - name: PodLibLint DataTransport Cron run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb GoogleDataTransport.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} + scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb GoogleDataTransport.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} --analyze From 19d440e888a91d95452f50f250b602e94514a482 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:39:35 -0500 Subject: [PATCH 121/131] [Infra] Prefer `setup_check.sh` over manual config (#157) * [Infra] Prefer `setup_check.sh` over manual config * Style --- .github/workflows/check.yml | 6 +----- GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index dc54d8e5..c1de3e87 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -32,11 +32,7 @@ jobs: run: ./setup-scripts.sh - name: Setup check - run: | - brew update - brew install clang-format@19 - brew install mint - mint bootstrap + run: scripts/setup_check.sh - name: Style run: scripts/style.sh test-only diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m index 14462aea..4ab1aeb1 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORAssert.m @@ -24,8 +24,9 @@ GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) { if (assertionBlockSEL) { IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL]; if (assertionBlockIMP) { - GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)( - GDTCORAssertClass, assertionBlockSEL); + GDTCORAssertionBlock assertionBlock = + ((GDTCORAssertionBlock (*)(id, SEL))assertionBlockIMP)(GDTCORAssertClass, + assertionBlockSEL); if (assertionBlock) { return assertionBlock; } From 45c5b1b31b1ac93c4b02402f1a9d42c780fd19cf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:45:26 -0500 Subject: [PATCH 122/131] [Infra] Update README.md code formatting docs (#158) * [Infra] Update README.md code formatting docs * Update README.md --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index daa99139..067dafa8 100644 --- a/README.md +++ b/README.md @@ -206,17 +206,7 @@ Alternatively disable signing in each target: ### Code Formatting -To ensure that the code is formatted consistently, run the script -[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh) -before creating a PR. - -GitHub Actions will verify that any code changes are done in a style compliant -way. Install `clang-format` and `mint`: - -```console -brew install clang-format@19 -brew install mint -``` +See [firebase-ios-sdk's code formatting docs][firebase-ios-sdk-formatting]. ### Running Unit Tests @@ -234,3 +224,4 @@ The contents of this repository is licensed under the [gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions [gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg +[firebase-ios-sdk-formatting]: https://github.com/firebase/firebase-ios-sdk?tab=readme-ov-file#code-formatting From 398227941b1b83179d3241f72842e2b1e23663f4 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Wed, 16 Apr 2025 09:22:49 -0700 Subject: [PATCH 123/131] Use Xcode 16 in CI (#159) * Use Xcode 16 in CI * update cocoapods version and other gems --- .github/workflows/spm.yml | 2 ++ Gemfile | 5 ++-- Gemfile.lock | 53 ++++++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 5b347416..543b4537 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -21,6 +21,8 @@ jobs: - uses: ruby/setup-ruby@v1 - name: Setup Scripts Directory run: ./setup-scripts.sh + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer - name: Initialize xcodebuild run: xcodebuild -list - name: iOS Unit Tests diff --git a/Gemfile b/Gemfile index cfff186c..5b693ac2 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,6 @@ # commit Gemfile and Gemfile.lock. source 'https://rubygems.org' -gem 'cocoapods', '1.15.2' - -gem 'cocoapods-generate', '2.0.1' +gem 'cocoapods', '1.16.2' +gem 'cocoapods-generate', '2.2.5' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index 9d3ebbf5..cc2c8a30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,15 +5,18 @@ GEM base64 nkf rexml - activesupport (7.1.3.2) + activesupport (7.1.5.1) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) @@ -22,16 +25,17 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.7) + benchmark (0.4.0) + bigdecimal (3.1.9) claide (1.1.0) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.15.2) + cocoapods (1.16.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) + cocoapods-core (= 1.16.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -45,8 +49,8 @@ GEM molinillo (~> 0.8.0) nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -57,10 +61,10 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-disable-podfile-validations (0.1.1) + cocoapods-disable-podfile-validations (0.2.0) cocoapods-downloader (2.1) - cocoapods-generate (2.0.1) - cocoapods-disable-podfile-validations (~> 0.1.1) + cocoapods-generate (2.2.5) + cocoapods-disable-podfile-validations (>= 0.1.1, < 0.3.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -69,8 +73,8 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.3) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) cork (0.3.0) colored2 (~> 3.1) danger (8.4.5) @@ -115,26 +119,28 @@ GEM faraday-patron (1.0.0) faraday-rack (1.0.0) faraday-retry (1.0.3) - ffi (1.16.3) + ffi (1.17.2) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) git (1.18.0) addressable (~> 2.8) rchardet (~> 1.8) - httpclient (2.8.3) - i18n (1.14.4) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.7.2) + json (2.10.2) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.22.3) + logger (1.7.0) + minitest (5.25.5) molinillo (0.8.0) multipart-post (2.2.3) - mutex_m (0.2.0) - nanaimo (0.3.0) + mutex_m (0.3.0) + nanaimo (0.4.0) nap (1.1.0) netrc (0.11.0) nkf (0.2.0) @@ -151,6 +157,7 @@ GEM sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + securerandom (0.3.2) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) typhoeus (1.4.1) @@ -158,20 +165,20 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.2.0) - xcodeproj (1.25.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.15.2) - cocoapods-generate (= 2.0.1) + cocoapods (= 1.16.2) + cocoapods-generate (= 2.2.5) danger (= 8.4.5) BUNDLED WITH From 5a9a019961a288b311284cb708542e4891f96215 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:32:48 -0700 Subject: [PATCH 124/131] Bump rexml from 3.3.9 to 3.4.2 in the bundler group across 1 directory (#161) Bumps the bundler group with 1 update in the / directory: [rexml](https://github.com/ruby/rexml). Updates `rexml` from 3.3.9 to 3.4.2 - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.9...v3.4.2) --- updated-dependencies: - dependency-name: rexml dependency-version: 3.4.2 dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cc2c8a30..ba408811 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -151,7 +151,7 @@ GEM open4 (1.3.4) public_suffix (4.0.7) rchardet (1.8.0) - rexml (3.3.9) + rexml (3.4.2) ruby-macho (2.5.1) ruby2_keywords (0.0.5) sawyer (0.9.2) From 17eb3cb40bcff538636be87676f8ad10e8bbf108 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Mon, 8 Dec 2025 14:53:31 -0800 Subject: [PATCH 125/131] Fix GDTCCTUploader-upload Crash (#164) --- CHANGELOG.md | 3 +++ GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m | 5 +++-- .../GDTCCTLibrary/Private/GDTCCTUploadOperation.h | 3 --- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b25a4bc..186ba259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- Cancel upload operation when background task expires. + # v10.1.0 - Fix `[FBLPromise HTTPBody]` SwiftUI Previews crash when using binary distribution. ([firebase-ios-sdk/#13318](https://github.com/firebase/firebase-ios-sdk/issues/13318), diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m index 354d14cc..0b85840a 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTUploadOperation.m @@ -135,8 +135,9 @@ - (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions beginBackgroundTaskWithName:@"GDTCCTUploader-upload" expirationHandler:^{ if (backgroundTaskID != GDTCORBackgroundIdentifierInvalid) { - // Cancel the upload and complete delivery. - [self.currentTask cancel]; + // Cancel the upload. The outgoing upload network request + // is not cancelled and may or may not complete delivery. + [self cancel]; // End the background task. backgroundTaskCompletion(); diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h index cfc2c485..de234d5a 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h @@ -70,9 +70,6 @@ NS_ASSUME_NONNULL_BEGIN /** The queue on which all CCT uploading will occur. */ @property(nonatomic, readonly) dispatch_queue_t uploaderQueue; -/** The current upload task. */ -@property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask; - @end NS_ASSUME_NONNULL_END From 559c9c61b50f47ca0c1a10a71868c008542e8768 Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 9 Dec 2025 06:38:37 -0800 Subject: [PATCH 126/131] Fix GDTCORDirectorySizeTracker crash (#163) * Fix GDTCORDirectorySizeTracker crash * style * Apply suggestions from code review Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> * Update GoogleDataTransport/GDTCORTests/Unit/GDTCORDirectorySizeTrackerTests.m Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> * Add missing import * Fix changelog merge mistake --------- Co-authored-by: Nick Cooke <36927374+ncooke3@users.noreply.github.com> --- CHANGELOG.md | 1 + .../GDTCORLibrary/GDTCORDirectorySizeTracker.m | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 186ba259..5da55242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased - Cancel upload operation when background task expires. +- Log error when handling directory enumeration. # v10.1.0 - Fix `[FBLPromise HTTPBody]` SwiftUI Previews crash when using binary diff --git a/GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m b/GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m index 0bc85150..07dd958d 100644 --- a/GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m +++ b/GoogleDataTransport/GDTCORLibrary/GDTCORDirectorySizeTracker.m @@ -15,6 +15,7 @@ */ #import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h" +#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h" @interface GDTCORDirectorySizeTracker () @@ -76,6 +77,9 @@ - (GDTCORStorageSizeBytes)calculateDirectoryContentSize { includingPropertiesForKeys:prefetchedProperties options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:^BOOL(NSURL *_Nonnull url, NSError *_Nonnull error) { + GDTCORLogWarning(GDTCORMCWFileReadError, + @"Error enumerating content size for URL %@: %@", url, + error); return YES; }]; From 1a68efabf59ab3414ab6e0c1f83a59a2269b90a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 17:21:31 -0500 Subject: [PATCH 127/131] Bump faraday in the bundler group across 1 directory (#165) Bumps the bundler group with 1 update in the / directory: [faraday](https://github.com/lostisland/faraday). Updates `faraday` from 1.10.1 to 1.10.5 - [Release notes](https://github.com/lostisland/faraday/releases) - [Changelog](https://github.com/lostisland/faraday/blob/main/CHANGELOG.md) - [Commits](https://github.com/lostisland/faraday/compare/v1.10.1...v1.10.5) --- updated-dependencies: - dependency-name: faraday dependency-version: 1.10.5 dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ba408811..e7d6f42a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,7 @@ GEM escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - faraday (1.10.1) + faraday (1.10.5) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -107,14 +107,14 @@ GEM faraday-retry (~> 1.0) ruby2_keywords (>= 0.0.4) faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) + faraday-em_synchrony (1.0.1) faraday-excon (1.1.0) faraday-http-cache (2.4.1) faraday (>= 0.8) faraday-httpclient (1.0.1) - faraday-multipart (1.0.4) - multipart-post (~> 2) - faraday-net_http (1.0.1) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) faraday-rack (1.0.0) @@ -138,7 +138,7 @@ GEM logger (1.7.0) minitest (5.25.5) molinillo (0.8.0) - multipart-post (2.2.3) + multipart-post (2.4.1) mutex_m (0.3.0) nanaimo (0.4.0) nap (1.1.0) From 3e812fb60c4649720cfca4941676b6ca7fd9317a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Thu, 5 Mar 2026 09:32:37 -0800 Subject: [PATCH 128/131] Remove problematic decoding unit test (#166) --- .../Unit/GDTCORMetricsMetadataTest.m | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m index 07a3f99d..40e7c2ca 100644 --- a/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m +++ b/GoogleDataTransport/GDTCORTests/Unit/GDTCORMetricsMetadataTest.m @@ -108,36 +108,4 @@ - (void)testSecureCoding { XCTAssertEqualObjects(decodedMetricsMetadata, metricsMetadata); } -- (void)testSecureCoding_WhenEncodingIsCorrupted { - // Given - // - Create an invalid instance and write its encoding to a file. When - // decoding, the invalid encoding should be treated as a corrupt encoding. - GDTCORMetricsMetadata *corruptedMetadata = -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wincompatible-pointer-types" - [GDTCORMetricsMetadata metadataWithCollectionStartDate:@"date" - logSourceMetrics:[GDTCORLogSourceMetrics metrics]]; -#pragma clang diagnostic pop - - NSError *encodeError; - NSData *encodedMetricsMetadata = GDTCOREncodeArchive(corruptedMetadata, nil, &encodeError); - XCTAssertNil(encodeError); - XCTAssertNotNil(encodedMetricsMetadata); - - NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"metadata.dat"]; - NSError *writeError; - BOOL writeResult = GDTCORWriteDataToFile(encodedMetricsMetadata, filePath, &writeError); - XCTAssertNil(writeError); - XCTAssertTrue(writeResult); - - // When - NSError *decodeError; - GDTCORMetricsMetadata *decodedMetricsMetadata = - (GDTCORMetricsMetadata *)GDTCORDecodeArchiveAtPath(GDTCORMetricsMetadata.class, filePath, - &decodeError); - // Then - XCTAssertNotNil(decodeError); - XCTAssertNil(decodedMetricsMetadata); -} - @end From 38f9d4c6432ad77c56f9675dff5507f4014bc08a Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:43:45 -0400 Subject: [PATCH 129/131] chore(gemfile): bump addressable and concurrent-ruby gems (#169) --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e7d6f42a..e347e462 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,8 +18,8 @@ GEM mutex_m securerandom (>= 0.3) tzinfo (~> 2.0) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) @@ -73,7 +73,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.3.5) + concurrent-ruby (1.3.7) connection_pool (2.5.0) cork (0.3.0) colored2 (~> 3.1) From 645f71baab42c05c6ab55f702764390df304f4bf Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 23 Jun 2026 19:46:24 -0400 Subject: [PATCH 130/131] chore: delete .github/workflows/test_coverage.yml (#170) --- .github/workflows/test_coverage.yml | 88 ----------------------------- 1 file changed, 88 deletions(-) delete mode 100644 .github/workflows/test_coverage.yml diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml deleted file mode 100644 index f138177d..00000000 --- a/.github/workflows/test_coverage.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: test_coverage - -on: - pull_request: - # open will be triggered when a pull request is created. - # synchronize will be triggered when a pull request has new commits. - # closed will be triggered when a pull request is closed. - types: [opened, synchronize, closed] - -env: - METRICS_SERVICE_SECRET: ${{ secrets.GHASecretsGPGPassphrase1 }} - # Used to compile test-specific code in the SDK. - GDT_TEST: 1 - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - pod_lib_lint: - if: github.repository == 'google/GoogleDataTransport' && (github.event.action == 'synchronize' || github.event.action == 'opened' || github.event.pull_request.merged) - runs-on: macos-latest - strategy: - matrix: - target: [ios] - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - - name: Setup Scripts Directory - run: ./setup-scripts.sh - - name: Setup Bundler - run: ./scripts/setup_bundler.sh - - name: PodLibLint DataTransport - run: | - scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=GoogleDataTransport --platform=${{ matrix.target }} --test_spec=Tests-Unit,CCT-Tests-Unit - - uses: actions/upload-artifact@v4 - with: - name: codecoverage - path: /Users/runner/*.xcresult - manage_test_results: - if: github.repository == 'google/GoogleDataTransport' && (github.event.action == 'synchronize' || github.event.action == 'opened' || github.event.pull_request.merged) - needs: pod_lib_lint - runs-on: macOS-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: ruby/setup-ruby@v1 - - uses: actions/download-artifact@v4.1.7 - id: download - with: - path: /Users/runner/test - - name: Setup Scripts Directory - run: ./setup-scripts.sh - - name: Access to Metrics Service - if: ${{ env.METRICS_SERVICE_SECRET }} - run: | - # Install gcloud sdk - curl https://sdk.cloud.google.com > install.sh - bash install.sh --disable-prompts - echo "${HOME}/google-cloud-sdk/bin/" >> $GITHUB_PATH - export PATH="${HOME}/google-cloud-sdk/bin/:${PATH}" - - # Activate the service account for Metrics Service. - scripts/decrypt_gha_secret.sh scripts/gha-encrypted/metrics_service_access.json.gpg \ - metrics-access.json "${{ env.METRICS_SERVICE_SECRET }}" - gcloud auth activate-service-account --key-file metrics-access.json - - name: Build code coverage tool - run: | - swift build --package-path firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/ - cat $GITHUB_EVENT_PATH - - name: Generate report - if: github.event.pull_request.merged != true && env.METRICS_SERVICE_SECRET - env: - pr_branch: ${{ github.event.pull_request.head.ref }} - run: | - echo $(git merge-base remotes/origin/${pr_branch} remotes/origin/main) - common_commit=$(git merge-base remotes/origin/${pr_branch} remotes/origin/main) - GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) - if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/.build/debug/CoverageReportGenerator --presubmit "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --pull-request-num ${{github.event.pull_request.number}} --base-commit "$common_commit" - fi - - name: Update New Coverage Data - if: github.event.pull_request.merged == true && env.METRICS_SERVICE_SECRET - run: | - if [ -d "${{steps.download.outputs.download-path}}" ]; then - firebase-ios-sdk/scripts/health_metrics/generate_code_coverage_report/.build/debug/CoverageReportGenerator --merge "google/GoogleDataTransport" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleDataTransport/actions/runs/${GITHUB_RUN_ID}" --source-branch "${{ github.base_ref }}" - fi From 555ff25407470d369dfdd9d5e6a93c315aa85661 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:20:31 -0400 Subject: [PATCH 131/131] chore: pin actions, fix warning, and update gha perms, xcode, ruby, and gems (#171) * chore(gemfile): bump addressable and concurrent-ruby gems * chore: update faraday, activesupport, and danger gems * ci: pin github actions to specific commit hashes * update .ruby-version * spm fixes * perms * warning fix --- .github/workflows/check.yml | 7 +- .github/workflows/datatransport.yml | 19 +-- .github/workflows/spm.yml | 11 +- .ruby-version | 2 +- Gemfile | 2 +- Gemfile.lock | 130 ++++++++---------- .../GDTCCTLibrary/GDTCCTNanopbHelpers.m | 4 +- .../Private/GDTCCTNanopbHelpers.h | 4 +- 8 files changed, 88 insertions(+), 91 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index c1de3e87..ffbe144d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -9,15 +9,18 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: check: runs-on: macos-latest env: MINT_PATH: ${{ github.workspace }}/mint steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 with: python-version: '3.10' diff --git a/.github/workflows/datatransport.yml b/.github/workflows/datatransport.yml index 220c25bd..d704680d 100644 --- a/.github/workflows/datatransport.yml +++ b/.github/workflows/datatransport.yml @@ -14,6 +14,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: pod_lib_lint: runs-on: macos-latest @@ -21,8 +24,8 @@ jobs: matrix: target: [ios, tvos, macos] steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -34,8 +37,8 @@ jobs: catalyst: runs-on: macOS-latest steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler @@ -48,8 +51,8 @@ jobs: # watchos-testapp: # runs-on: macos-12 # steps: - # - uses: actions/checkout@v3 - # - uses: ruby/setup-ruby@v1 + # - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + # - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314 # - name: Setup Scripts Directory # run: ./setup-scripts.sh # - name: Setup Bundler @@ -70,8 +73,8 @@ jobs: ] needs: pod_lib_lint steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Setup Bundler diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 543b4537..7c0e2f69 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -6,23 +6,26 @@ on: # Run every day at 11pm (PST) - cron uses UTC times - cron: '0 7 * * *' +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: swift-build-run: - runs-on: macOS-latest + runs-on: macOS-26 strategy: matrix: target: [iOS, tvOS, macOS, catalyst] steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314 - name: Setup Scripts Directory run: ./setup-scripts.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.4.app/Contents/Developer - name: Initialize xcodebuild run: xcodebuild -list - name: iOS Unit Tests diff --git a/.ruby-version b/.ruby-version index fa376edc..47b322c9 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7 +3.4.1 diff --git a/Gemfile b/Gemfile index 5b693ac2..7dfba34c 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,4 @@ source 'https://rubygems.org' gem 'cocoapods', '1.16.2' gem 'cocoapods-generate', '2.2.5' -gem 'danger', '8.4.5' +gem 'danger', '>= 9.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index e347e462..9a821ff6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,32 +1,28 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - activesupport (7.1.5.1) + CFPropertyList (3.0.8) + activesupport (7.2.3.1) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) - minitest (>= 5.1) - mutex_m + minitest (>= 5.1, < 6) securerandom (>= 0.3) - tzinfo (~> 2.0) + tzinfo (~> 2.0, >= 2.0.5) addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) - base64 (0.2.0) - benchmark (0.4.0) - bigdecimal (3.1.9) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) claide (1.1.0) claide-plugins (0.9.2) cork @@ -74,97 +70,89 @@ GEM cocoapods-try (1.2.0) colored2 (3.1.2) concurrent-ruby (1.3.7) - connection_pool (2.5.0) + connection_pool (3.0.2) cork (0.3.0) colored2 (~> 3.1) - danger (8.4.5) + danger (9.6.0) + base64 (~> 0.2) claide (~> 1.0) claide-plugins (>= 0.9.2) - colored2 (~> 3.1) + colored2 (>= 3.1, < 5) cork (~> 0.1) - faraday (>= 0.9.0, < 2.0) + faraday (>= 0.9.0, < 3.0) faraday-http-cache (~> 2.0) - git (~> 1.7) - kramdown (~> 2.3) + git (>= 1.13) + kramdown (>= 2.5.1, < 3.0) kramdown-parser-gfm (~> 1.0) - no_proxy_fix - octokit (~> 4.7) - terminal-table (>= 1, < 4) - drb (2.2.1) + octokit (>= 4.0) + pstore (~> 0.1) + terminal-table (>= 1, < 5) + drb (2.2.3) escape (0.0.4) - ethon (0.16.0) + ethon (0.18.0) ffi (>= 1.15.0) - faraday (1.10.5) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) - ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.1) - faraday-excon (1.1.0) - faraday-http-cache (2.4.1) + logger + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-http-cache (2.7.0) faraday (>= 0.8) - faraday-httpclient (1.0.1) - faraday-multipart (1.2.0) - multipart-post (~> 2.0) - faraday-net_http (1.0.2) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - ffi (1.17.2) + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.4) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - git (1.18.0) + git (4.3.2) + activesupport (>= 5.0) addressable (~> 2.8) - rchardet (~> 1.8) + process_executer (~> 4.0) + rchardet (~> 1.9) httpclient (2.9.0) mutex_m - i18n (1.14.7) + i18n (1.15.2) concurrent-ruby (~> 1.0) - json (2.10.2) - kramdown (2.4.0) - rexml + json (2.20.0) + kramdown (2.5.2) + rexml (>= 3.4.4) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) logger (1.7.0) - minitest (5.25.5) + minitest (5.27.0) molinillo (0.8.0) - multipart-post (2.4.1) mutex_m (0.3.0) nanaimo (0.4.0) nap (1.1.0) + net-http (0.9.1) + uri (>= 0.11.1) netrc (0.11.0) - nkf (0.2.0) - no_proxy_fix (0.1.2) - octokit (4.25.1) + octokit (10.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) + process_executer (4.0.4) + track_open_instances (~> 0.1) + pstore (0.2.1) public_suffix (4.0.7) - rchardet (1.8.0) - rexml (3.4.2) + rchardet (1.10.2) + rexml (3.4.4) ruby-macho (2.5.1) - ruby2_keywords (0.0.5) - sawyer (0.9.2) + sawyer (0.9.3) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - securerandom (0.3.2) - terminal-table (3.0.2) - unicode-display_width (>= 1.1.1, < 3) - typhoeus (1.4.1) - ethon (>= 0.9.0) + securerandom (0.4.1) + terminal-table (4.0.0) + unicode-display_width (>= 1.1.1, < 4) + track_open_instances (0.1.15) + typhoeus (1.6.0) + ethon (>= 0.18.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.2.0) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) @@ -179,7 +167,7 @@ PLATFORMS DEPENDENCIES cocoapods (= 1.16.2) cocoapods-generate (= 2.2.5) - danger (= 8.4.5) + danger (>= 9.3.1) BUNDLED WITH - 2.1.4 + 2.6.2 diff --git a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m index 450fda8d..84a93264 100644 --- a/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m +++ b/GoogleDataTransport/GDTCCTLibrary/GDTCCTNanopbHelpers.m @@ -35,12 +35,12 @@ #pragma mark - General purpose encoders -pb_bytes_array_t *GDTCCTEncodeString(NSString *string) { +pb_bytes_array_t *_Nullable GDTCCTEncodeString(NSString *string) { NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding]; return GDTCCTEncodeData(stringBytes); } -pb_bytes_array_t *GDTCCTEncodeData(NSData *data) { +pb_bytes_array_t *_Nullable GDTCCTEncodeData(NSData *data) { pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length)); if (pbBytesArray != NULL) { [data getBytes:pbBytesArray->bytes length:data.length]; diff --git a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h index 2f0ccd0f..a0cd81f3 100644 --- a/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h +++ b/GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h @@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN * @param string The string to convert. * @return A newly allocated array of bytes representing the UTF8 encoding of the string. */ -pb_bytes_array_t *GDTCCTEncodeString(NSString *string); +pb_bytes_array_t *_Nullable GDTCCTEncodeString(NSString *string); /** Converts an NSData to a pb_bytes_array_t*. * @@ -43,7 +43,7 @@ pb_bytes_array_t *GDTCCTEncodeString(NSString *string); * @param data The data to convert. * @return A newly allocated array of bytes with [data bytes] copied into it. */ -pb_bytes_array_t *GDTCCTEncodeData(NSData *data); +pb_bytes_array_t *_Nullable GDTCCTEncodeData(NSData *data); #pragma mark - CCT object constructors