Merge branch 'main' into app-delegate-main

This commit is contained in:
Maksym Malyhin committed 2021-07-15 17:11:54 -04:00
commit 54b221e66e
35 files changed
+1414 -198

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Google Utilities
url: https://github.com/firebase/firebase-ios-sdk/issues/new/choose
about: Google Utilities is not supported for direct usage. Please file with the product library that depends on Google Utilities.
+8
View File
@@ -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)
---
<!-- DO NOT DELETE
validate_template=true
template_path=.github/ISSUE_TEMPLATE/bug_report.md
-->
+1 -1
View File
@@ -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
+79
View File
@@ -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/GoogleUtilities' && (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=GoogleUtilities --platform=${{ matrix.target }} --test_spec=unit,unit-swift --output_path="/Users/runner/GoogleUtilities-ios-unit.xcresult"
- uses: actions/upload-artifact@v2
with:
name: codecoverage
path: /Users/runner/*.xcresult
manage_test_results:
if: github.repository == 'google/GoogleUtilities' && (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.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/GoogleUtilities" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleUtilities/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/GoogleUtilities" --head-commit "${GITHUB_SHA}" --token $(gcloud auth print-identity-token) --xcresult-dir "${{steps.download.outputs.download-path}}/codecoverage" --log-link "https://github.com/google/GoogleUtilities/actions/runs/${GITHUB_RUN_ID}" --source-branch "${{ github.base_ref }}"
fi
+24 -1
View File
@@ -1,3 +1,26 @@
# 7.5.0
- Bump Promises dependency. (#8334)
# 7.4.3
- Include all object classes when using archiver. (#42)
# 7.4.2 (Swift PM)
- Fix warnings in Xcode 13 beta 1. (#41)
# 7.4.1
- Improve heartbeat date storage's version compatability. (#37)
# 7.4.0
- Change heartbeat directory name and refactor file. (#19)
- Introduce user defaults based heartbeat storage. (#23)
- Support iOS 9 with Swift Package Manager.
# 7.3.1
- Add explicit dependency on Security framework to Environment subspec. (#12)
# 7.3.0
- Fix conflicting internal constant name and move it out of the public header. (#7635)
# 7.2.2
- Fix warnings introduced with Xcode 12.5. (#8)
@@ -85,7 +108,7 @@
# 6.0.0
- GULAppDelegateSwizzler - proxy APNS methods separately. (#2835)
- Cocoapods 1.7.0 multiproject support. (#2751)
- Bump minimium iOS version to iOS 8. (#2876)
- Bump minimum iOS version to iOS 8. (#2876)
# 5.7.0
- Restore to 5.5.0 tag after increased App Store warnings. (#2807)
+12 -4
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'GoogleUtilities'
s.version = '7.2.2'
s.version = '7.5.0'
s.summary = 'Google Utilities for Apple platform SDKs'
s.description = <<-DESC
@@ -38,7 +38,10 @@ other Google CocoaPods. They're not intended for direct public usage.
s.subspec 'Environment' do |es|
es.source_files = 'GoogleUtilities/Environment/**/*.[mh]'
es.public_header_files = 'GoogleUtilities/Environment/Public/GoogleUtilities/*.h'
es.dependency 'PromisesObjC', '~> 1.2'
es.dependency 'PromisesObjC', '>= 1.2', '< 3.0'
es.frameworks = [
'Security'
]
end
s.subspec 'Logger' do |ls|
@@ -126,7 +129,7 @@ other Google CocoaPods. They're not intended for direct public usage.
s.test_spec 'unit' do |unit_tests|
unit_tests.scheme = { :code_coverage => true }
# All tests require arc except Tests/Network/third_party/GTMHTTPServer.m
unit_tests.platforms = {
:ios => ios_deployment_target,
:osx => osx_deployment_target,
@@ -135,7 +138,12 @@ other Google CocoaPods. They're not intended for direct public usage.
unit_tests.source_files = [
'GoogleUtilities/Tests/Unit/**/*.[mh]',
]
unit_tests.requires_arc = 'GoogleUtilities/Tests/Unit/*/*.[mh]'
# All tests require arc except Tests/Network/third_party/GTMHTTPServer.m
unit_tests.requires_arc = [
'GoogleUtilities/Tests/Unit/*/*.[mh]',
'GoogleUtilities/Tests/Unit/Environment/**/*.[mh]'
]
unit_tests.requires_app_host = true
unit_tests.dependency 'OCMock'
end
@@ -17,6 +17,8 @@
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
NSString *const kGULHeartbeatStorageDirectory = @"Google/FIRApp";
@interface GULHeartbeatDateStorage ()
/** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator;
@@ -29,9 +31,7 @@
@synthesize fileURL = _fileURL;
- (instancetype)initWithFileName:(NSString *)fileName {
if (fileName == nil) {
return nil;
}
if (fileName == nil) return nil;
self = [super init];
if (self) {
@@ -41,13 +41,13 @@
return self;
}
/** Lazy getter for fileURL
/** Lazy getter for fileURL.
* @return fileURL where heartbeat information is stored.
*/
- (NSURL *)fileURL {
if (!_fileURL) {
NSURL *directoryURL = [[self class] directoryPathURL];
[[self class] checkAndCreateDirectory:directoryURL fileCoordinator:_fileCoordinator];
NSURL *directoryURL = [self directoryPathURL];
[self checkAndCreateDirectory:directoryURL];
_fileURL = [directoryURL URLByAppendingPathComponent:_fileName];
}
return _fileURL;
@@ -56,36 +56,32 @@
/** Returns the URL path of the directory for heartbeat storage data.
* @return the URL path of the directory for heartbeat storage data.
*/
+ (NSURL *)directoryPathURL {
- (NSURL *)directoryPathURL {
NSArray<NSString *> *paths;
#if TARGET_OS_TV
NSArray<NSString *> *paths =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
NSArray<NSString *> *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif
NSArray<NSString *> *components = @[ paths.lastObject, @"Google/FIRApp" ];
NSString *directoryString = [NSString pathWithComponents:components];
NSURL *directoryURL = [NSURL fileURLWithPath:directoryString];
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif // TARGET_OS_TV
NSString *rootPath = [paths lastObject];
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
NSURL *directoryURL = [rootURL URLByAppendingPathComponent:kGULHeartbeatStorageDirectory];
return directoryURL;
}
/** Checks and creates a directory for the directory specified by the
* directory url
* @param directoryPathURL The path to the directory which needs to be created.
* @param fileCoordinator The fileCoordinator object to coordinate writes to the directory.
/** Check for the existence of the directory specified by the URL, and create it if it does not
* exist.
* @param directoryPathURL The path to the directory that needs to exist.
*/
+ (void)checkAndCreateDirectory:(NSURL *)directoryPathURL
fileCoordinator:(NSFileCoordinator *)fileCoordinator {
- (void)checkAndCreateDirectory:(NSURL *)directoryPathURL {
NSError *fileCoordinatorError = nil;
[fileCoordinator
[self.fileCoordinator
coordinateWritingItemAtURL:directoryPathURL
options:0
error:&fileCoordinatorError
byAccessor:^(NSURL *writingDirectoryURL) {
NSError *error;
if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
// If fail creating the Application Support directory, log warning.
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL
withIntermediateDirectories:YES
@@ -95,65 +91,78 @@
}];
}
- (nullable NSMutableDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL {
NSError *error;
NSMutableDictionary *dict;
NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error];
if (objectData == nil || error != nil) {
dict = [NSMutableDictionary dictionary];
} else {
dict = [GULSecureCoding
unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]]
fromData:objectData
error:&error];
if (dict == nil || error != nil) {
dict = [NSMutableDictionary dictionary];
}
}
return dict;
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
__block NSMutableDictionary *dict;
__block NSDictionary *heartbeatDictionary;
NSError *error;
[self.fileCoordinator coordinateReadingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL) {
dict = [self heartbeatDictionaryWithFileURL:readingURL];
heartbeatDictionary =
[self heartbeatDictionaryWithFileURL:readingURL];
}];
return dict[tag];
NSDate *heartbeatDate = heartbeatDictionary[tag];
if (![heartbeatDate isKindOfClass:[NSDate class]]) {
return nil;
}
return heartbeatDate;
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
NSError *error;
__block BOOL isSuccess = false;
[self.fileCoordinator coordinateReadingItemAtURL:self.fileURL
options:0
writingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL, NSURL *writingURL) {
NSMutableDictionary *dictionary =
[self heartbeatDictionaryWithFileURL:readingURL];
dictionary[tag] = date;
NSError *error;
isSuccess = [self writeDictionary:dictionary
forWritingURL:writingURL
error:&error];
}];
[self.fileCoordinator
coordinateReadingItemAtURL:self.fileURL
options:0
writingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL, NSURL *writingURL) {
NSMutableDictionary *heartbeatDictionary =
[[self heartbeatDictionaryWithFileURL:readingURL] mutableCopy];
heartbeatDictionary[tag] = date;
NSError *error;
isSuccess = [self writeDictionary:[heartbeatDictionary copy]
forWritingURL:writingURL
error:&error];
}];
return isSuccess;
}
- (BOOL)writeDictionary:(NSMutableDictionary *)dictionary
- (NSDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL {
NSDictionary *heartbeatDictionary;
NSError *error;
NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error];
if (objectData.length > 0 && error == nil) {
NSSet<Class> *objectClasses =
[NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class ]];
heartbeatDictionary = [GULSecureCoding unarchivedObjectOfClasses:objectClasses
fromData:objectData
error:&error];
}
if (heartbeatDictionary.count == 0 || error != nil) {
heartbeatDictionary = [NSDictionary dictionary];
}
return heartbeatDictionary;
}
- (BOOL)writeDictionary:(NSDictionary *)dictionary
forWritingURL:(NSURL *)writingFileURL
error:(NSError **)outError {
NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError];
if (*outError != nil) {
return false;
} else {
return [data writeToURL:writingFileURL atomically:YES];
// Archive a mutable copy `dictionary` for writing to disk. This is done for
// backwards compatibility. See Google Utilities issue #36 for more context.
// TODO: Remove usage of mutable copy in a future version of Google Utilities.
NSData *data = [GULSecureCoding archivedDataWithRootObject:[dictionary mutableCopy]
error:outError];
if (data.length == 0) {
return NO;
}
return [data writeToURL:writingFileURL atomically:YES];
}
@end
@@ -0,0 +1,68 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h"
@interface GULHeartbeatDateStorageUserDefaults ()
/** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
/** The key for user defaults to store heartbeat information. */
@property(nonatomic, readonly) NSString *key;
@end
@implementation GULHeartbeatDateStorageUserDefaults
- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key {
self = [super init];
if (self) {
_userDefaults = defaults;
_key = key;
}
return self;
}
- (NSMutableDictionary *)heartbeatDictionaryFromDefaults {
NSDictionary *heartbeatDict = [self.userDefaults objectForKey:self.key];
if (heartbeatDict != nil) {
return [heartbeatDict mutableCopy];
} else {
return [NSMutableDictionary dictionary];
}
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
NSDate *date = nil;
@synchronized(self.userDefaults) {
NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults];
date = dict[tag];
}
return date;
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
@synchronized(self.userDefaults) {
NSMutableDictionary *dict = [self heartbeatDictionaryFromDefaults];
dict[tag] = date;
[self.userDefaults setObject:dict forKey:self.key];
}
return true;
}
@end
@@ -0,0 +1,40 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Describes an object that can store and fetch heartbeat dates for given tags.
*/
@protocol GULHeartbeatDateStorable <NSObject>
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END
@@ -16,10 +16,15 @@
#import <Foundation/Foundation.h>
#import "GULHeartbeatDateStorable.h"
NS_ASSUME_NONNULL_BEGIN
/// The name of the directory where the heartbeat data is stored.
extern NSString *const kGULHeartbeatStorageDirectory;
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage : NSObject
@interface GULHeartbeatDateStorage : NSObject <GULHeartbeatDateStorable>
- (instancetype)init NS_UNAVAILABLE;
@@ -0,0 +1,51 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GULHeartbeatDateStorable.h"
NS_ASSUME_NONNULL_BEGIN
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorageUserDefaults : NSObject <GULHeartbeatDateStorable>
/**
* Default initializer. tvOS can only write to the cache directory and
* there are no guarantees that the directory will persist. User defaults will
* be retained, so that should be used instead.
* @param defaults User defaults instance to store the heartbeat information.
* @param key The key to be used with the user defaults instance.
*/
- (instancetype)initWithDefaults:(NSUserDefaults *)defaults key:(NSString *)key;
- (instancetype)init NS_UNAVAILABLE;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END
@@ -14,6 +14,8 @@
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h"
FOUNDATION_EXPORT NSString *kGULSwizzlerAssociatedObjectKey;
@interface GULObjectSwizzler (Internal)
- (void)swizzledObjectHasBeenDeallocatedWithGeneratedSubclass:(BOOL)isInstanceOfGeneratedSubclass;
@@ -16,6 +16,7 @@
#import <objc/runtime.h>
#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
@implementation GULObjectSwizzler {
@@ -80,7 +81,7 @@
}
GULObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:object key:kSwizzlerAssociatedObjectKey];
[[self class] getAssociatedObject:object key:kGULSwizzlerAssociatedObjectKey];
if ([existingSwizzler isKindOfClass:[GULObjectSwizzler class]]) {
// The object has been swizzled already, no need to swizzle again.
return existingSwizzler;
@@ -130,7 +131,7 @@
__strong id swizzledObject = _swizzledObject;
GULObjectSwizzler *existingSwizzler =
[[self class] getAssociatedObject:swizzledObject key:kSwizzlerAssociatedObjectKey];
[[self class] getAssociatedObject:swizzledObject key:kGULSwizzlerAssociatedObjectKey];
if (existingSwizzler != nil) {
NSAssert(existingSwizzler == self, @"The swizzled object has a different swizzler.");
// The object has been swizzled already.
@@ -139,7 +140,7 @@
if (swizzledObject) {
[GULObjectSwizzler setAssociatedObject:swizzledObject
key:kSwizzlerAssociatedObjectKey
key:kGULSwizzlerAssociatedObjectKey
value:self
association:GUL_ASSOCIATION_RETAIN];
@@ -17,7 +17,7 @@
#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
NSString *kSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
NSString *kGULSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
@interface GULSwizzledObject ()
@@ -46,7 +46,7 @@ NSString *kSwizzlerAssociatedObjectKey = @"gul_objectSwizzler";
}
- (GULObjectSwizzler *)gul_objectSwizzler {
return [GULObjectSwizzler getAssociatedObject:self key:kSwizzlerAssociatedObjectKey];
return [GULObjectSwizzler getAssociatedObject:self key:kGULSwizzlerAssociatedObjectKey];
}
#pragma mark - Donor methods
@@ -18,8 +18,6 @@
@class GULObjectSwizzler;
FOUNDATION_EXPORT NSString *kSwizzlerAssociatedObjectKey;
/** This class exists as a method donor. These methods will be added to all objects that are
* swizzled by the object swizzler. This class should not be instantiated.
*/
+1 -1
View File
@@ -65,7 +65,7 @@ dispatch_queue_t GetGULSwizzlingQueue(void) {
[inv setArgument:&(currentImp) atIndex:2];
[inv setArgument:&(newImp) atIndex:3];
[inv setArgument:&(resolvedClass) atIndex:4];
[inv setArgument:(void *_Nonnull) & (selector) atIndex:5];
[inv setArgument:(void *_Nonnull)&(selector) atIndex:5];
[inv invoke];
}
}
-39
View File
@@ -1,39 +0,0 @@
# Google Utilities
## Overview
The GoogleUtilities pod is a set of utilities organized into CocoaPods subspecs.
See the [podspec](../GoogleUtilities.podspec) for a summary of what utilities
are currently included. They're used by Firebase and other Google products.
Direct usage by non-Google products and CocoaPods is **NOT** currently
recommended or supported in general.
**However**, we do specifically recommend that non-Google SDKs can use the App Delegate Swizzler to hook into app delegate methods as this reduces conflicts with multiple SDKs trying to do the same.
Instructions on how to adopt the app delegate swizzler for use by your SDK are available [here](./AppDelegateSwizzler/README.md).
## Development
Follow the subsequent instructions to develop, debug, unit test, and
integration test FirebaseFunctions:
### Prereqs
- At least CocoaPods 1.7.0
### To Develop
- Run `pod gen GoogleUtilities.podspec`
- `open gen/GoogleUtilities/GoogleUtilities.xcworkspace`
OR these two commands can be combined with
- `pod gen GoogleUtilities.podspec --auto-open --gen-directory="gen" --clean`
You're now in an Xcode workspace generate for building, debugging and
testing the GoogleUtilities CocoaPod.
### Running Unit Tests
Choose the one of the Tests* schemes and press Command-u.
@@ -16,7 +16,7 @@
#import <Foundation/Foundation.h>
/** This class handles the caching and retreival of IMPs as we swizzle and unswizzle them. It uses
/** This class handles the caching and retrieval of IMPs as we swizzle and unswizzle them. It uses
* two C++ STL unordered_maps as the underlying data store. This class is NOT thread safe.
*/
@interface GULSwizzlingCache : NSObject
@@ -18,7 +18,7 @@
/** Checks if we've swizzled the currentIMP and returns the original IMP that would be invoked if
* we hadn't swizzled it in the first place. This method is private because consumers don't need it
* to cache or retrive any IMPs. It is used internally and for certain asserts in GULSwizzler.
* to cache or retrieve any IMPs. It is used internally and for certain asserts in GULSwizzler.
*
* @param currentIMP The IMP returned by class_getMethodImplementation.
* @return The original IMP that would be invoked if we hadn't swizzled at all, and in cases where
@@ -16,75 +16,423 @@
#import <XCTest/XCTest.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorage.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
// Import specific version implementations for compatibility testing.
#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h"
#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h"
@interface GULHeartbeatDateStorageTest : XCTestCase
@property(nonatomic) GULHeartbeatDateStorage *storage;
@end
static NSString *const kTestFileName = @"GULStorageHeartbeatTest";
static NSString *const kTestFileName = @"GULStorageHeartbeatTestFile";
@implementation GULHeartbeatDateStorageTest
@implementation GULHeartbeatDateStorageTest {
BOOL _rootDirectoryCreated;
}
- (void)setUp {
#if TARGET_OS_TV
NSArray *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
NSArray *path =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif
NSString *rootPath = [path firstObject];
XCTAssertNotNil(rootPath);
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
- (BOOL)setUpWithError:(NSError *__autoreleasing _Nullable *)error {
[super setUpWithError:error];
NSError *error;
if (![rootURL checkResourceIsReachableAndReturnError:&error]) {
XCTAssert([[NSFileManager defaultManager] createDirectoryAtURL:rootURL
withIntermediateDirectories:YES
attributes:nil
error:&error],
@"Error: %@", error);
NSError *directoryError;
if (!_rootDirectoryCreated) {
_rootDirectoryCreated = [self createRootDirectoryWithError:&directoryError];
}
self.storage = [[GULHeartbeatDateStorage alloc] initWithFileName:kTestFileName];
BOOL success = _rootDirectoryCreated;
if (!success && error) {
*error = [NSError errorWithDomain:@"com.GULHeartbeatDateStorageTest.ErrorDomain"
code:1
userInfo:@{NSUnderlyingErrorKey : directoryError}];
}
return success;
}
- (void)setUp {
[super setUp];
self.storage = [[GULHeartbeatDateStorage alloc] initWithFileName:kTestFileName];
[self assertInitializationDoesNotAccessFileSystem];
}
- (void)tearDown {
[[NSFileManager defaultManager] removeItemAtURL:[self.storage fileURL] error:nil];
[super tearDown];
// Removes the Heartbeat Storage Directory if it exists.
NSURL *directoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
[[NSFileManager defaultManager] removeItemAtURL:directoryURL error:nil];
self.storage = nil;
}
#pragma mark - Public API Tests
- (void)testHeartbeatDateForTag {
NSDate *now = [NSDate date];
[self.storage setHearbeatDate:now forTag:@"fire-iid"];
XCTAssertEqual([now timeIntervalSinceReferenceDate],
[[self.storage heartbeatDateForTag:@"fire-iid"] timeIntervalSinceReferenceDate]);
// 1. Tags and saves heartbeat info, which creates the storage directory & file as side effects.
NSDate *date = [NSDate date];
NSString *tag = @"tag";
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 2. Retrieve saved heartbeat info.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
// 3. Assert that requested heartbeat info matches what was stored.
// This implies the storage directory & file were created, written to, and read from.
XCTAssertEqualObjects(retrievedDate, date);
}
#pragma mark - Private Helpers
/// Heartbeat info is requested when the storage directory already exists (i.e. it was created in a
/// previous app launch).
- (void)testHeartbeatDateForTagWhenHeartbeatStorageDirectoryExists {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with heartbeat info.
// 2.1 Create a dictionary with heartbeat info.
NSDate *storedDate = [NSDate distantPast];
NSString *storedTag = @"stored-tag";
NSDictionary *storedHeartbeatDictionary = @{storedTag : storedDate};
// 2.2 Encode the dictionary.
NSError *archiveError;
NSData *data = [GULSecureCoding archivedDataWithRootObject:storedHeartbeatDictionary
error:&archiveError];
XCTAssertNotNil(data);
XCTAssertNil(archiveError);
// 2.3 Write the encoded dictionary to file.
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[data writeToURL:heartbeatStorageFileURL atomically:YES];
XCTAssertNotNil(data);
// 3. Retrieve the stored heartbeat info and validate that it matches the info that was stored.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:storedTag];
XCTAssertEqualObjects(retrievedDate, storedDate);
}
- (void)testHeartbeatDateForTagWhenReturnedDateIsNil {
NSString *nonexistentTag = @"missing-tag";
NSDate *nilDate = [self.storage heartbeatDateForTag:nonexistentTag];
XCTAssertNil(nilDate);
}
- (void)testSetHeartbeatDateForTagWhenExpectingFailure {
// The `setHearbeatDate: forTag:` API is expected to return NO if it is unable to write the
// heartbeat info to file. To verify the API successfully fails, an invalid string is used to
// create an invalid instance of `GULHeartbeatDateStorage` by supplying an invalid filename.
// This replicates a file system error that prohibits `setHearbeatDate: forTag:` from successfully
// storing the heartbeat info.
NSString *invalidFileName = @"";
GULHeartbeatDateStorage *invalidStorage =
[[GULHeartbeatDateStorage alloc] initWithFileName:invalidFileName];
BOOL success = [invalidStorage setHearbeatDate:NSDate.date forTag:@"tag"];
XCTAssertFalse(success);
}
- (void)testSetHeartbeatDateForTag {
NSDate *date = [NSDate date];
NSString *tag = @"tag";
// 1. Retrieve heartbeat info that is not stored and assert the retrieved info is nil.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNil(retrievedDate);
// 2. Save the heartbeat info and assert the save was successful.
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 3. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate.
retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, date);
}
- (void)testHeartbeatDateForTagWhenHeartbeatFileReturnsInvalidData {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with invalid data.
NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertNotNil(corruptedData);
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[corruptedData writeToURL:heartbeatStorageFileURL atomically:YES];
// 3. Retrieve saved heartbeat info and assert that it is nil.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:@"tag"];
XCTAssertNil(retrievedDate);
}
- (void)testHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedContent {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with unexpected content.
NSArray *array = @[ [NSDate distantPast], [NSDate date], [NSDate distantFuture] ];
NSError *archiveError;
NSData *data = [GULSecureCoding archivedDataWithRootObject:array error:&archiveError];
XCTAssertNotNil(data);
XCTAssert(data.length > 0);
XCTAssertNil(archiveError);
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[data writeToURL:heartbeatStorageFileURL atomically:YES];
// 3. Retrieve saved heartbeat info and assert that it is nil.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:@"tag"];
XCTAssertNil(retrievedDate);
}
- (void)testSetHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedDictionaryContent {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with unexpected content.
NSDictionary *heartbeatDict = @{@"tag" : [NSDate distantPast]};
NSDictionary *nestedHeartbeatDict = @{@"tag" : heartbeatDict};
NSError *archiveError;
NSData *data = [GULSecureCoding archivedDataWithRootObject:nestedHeartbeatDict
error:&archiveError];
XCTAssertNotNil(data);
XCTAssert(data.length > 0);
XCTAssertNil(archiveError);
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[data writeToURL:heartbeatStorageFileURL atomically:YES];
// 3. Create heartbeat info.
NSDate *date = [NSDate date];
NSString *tag = @"tag";
// 4. Retrieve heartbeat info that is not stored and assert the retrieved info is nil.
// This assertion implies type validation in `heartbeatDateForTag:` has worked correctly.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNil(retrievedDate);
// 5. Save the heartbeat info and assert the save was successful.
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 6. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate.
retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, date);
}
- (void)testSetHeartbeatDateForTagWhenHeartbeatFileContainsUnexpectedContent {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with unexpected content.
NSArray *array = @[ [NSDate distantPast], [NSDate date], [NSDate distantFuture] ];
NSError *archiveError;
NSData *data = [GULSecureCoding archivedDataWithRootObject:array error:&archiveError];
XCTAssertNotNil(data);
XCTAssert(data.length > 0);
XCTAssertNil(archiveError);
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[data writeToURL:heartbeatStorageFileURL atomically:YES];
// 3. Create heartbeat info.
NSDate *date = [NSDate date];
NSString *tag = @"tag";
// 4. Retrieve heartbeat info that is not stored and assert the retrieved info is nil.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNil(retrievedDate);
// 5. Save the heartbeat info and assert the save was successful.
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 6. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate.
retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, date);
}
- (void)testSetHeartbeatDateForTagWhenHeartbeatFileReturnsInvalidData {
// 1. Manually create the heartbeat directory.
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSError *error;
BOOL directoryCreated = [self explicitlyCreateDirectoryForURL:heartbeatStorageDirectoryURL
withError:&error];
XCTAssert(directoryCreated);
// 2. Populate the storage file with invalid data.
NSData *corruptedData = [@"abc" dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertNotNil(corruptedData);
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
[corruptedData writeToURL:heartbeatStorageFileURL atomically:YES];
// 3. Tag and save heartbeat info. This should overwrite the invalid data with a
// correctly encoded heartbeat dictionary.
NSDate *date = [NSDate date];
NSString *tag = @"tag";
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 4. Retrieve saved heartbeat info.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
// 5. Assert that requested heartbeat info matches what was stored.
// This implies the storage directory & file were created, written to, and read from.
XCTAssertEqualObjects(retrievedDate, date);
}
- (void)testConformsToHeartbeatStorableProtocol {
XCTAssertTrue([self.storage conformsToProtocol:@protocol(GULHeartbeatDateStorable)]);
}
#pragma mark - Version Compatibility (#36)
- (void)testCompatibility_pre7_4_0 {
NSString *tag = @"tag";
// 1. Store heartbeat using current heartbeat API.
NSDate *distantPast = [NSDate distantPast];
BOOL successfulSave = [self.storage setHearbeatDate:distantPast forTag:tag];
XCTAssert(successfulSave);
[self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]];
// 2. Developer downgrades to below 7.4.0.
// 3. Store heartbeat from pre-7.4.0 API and verify success.
NSDate *storedDate = [NSDate date];
GULHeartbeatDateStorage7_3_1 *storage7_3_1 =
[[GULHeartbeatDateStorage7_3_1 alloc] initWithFileName:kTestFileName];
// The following line caused crashes after persistent storage modifications by 7.4.0.
successfulSave = [storage7_3_1 setHearbeatDate:storedDate forTag:tag];
XCTAssert(successfulSave);
[self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]];
NSDate *retrievedDate = [storage7_3_1 heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, storedDate);
}
- (void)testForwardCompatibility7_4_0 {
NSString *tag = @"tag";
// 1. Store heartbeat using heartbeat API from 7.4.0. (Immutable info is written to disk.)
NSDate *storedDate = [NSDate distantPast];
GULHeartbeatDateStorage7_4_0 *storage7_4_0 =
[[GULHeartbeatDateStorage7_4_0 alloc] initWithFileName:kTestFileName];
// The following line caused crashes after persistent storage modifications by 7.4.0.
BOOL successfulSave = [storage7_4_0 setHearbeatDate:storedDate forTag:tag];
XCTAssert(successfulSave);
[self assertStoredHeartbeatDictionaryIsKindOf:[NSDictionary class]];
// 2. Developer upgrades to current version.
// 3. Retrieve and then store heartbeat from current API and verify success.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNotNil(retrievedDate);
XCTAssertEqualObjects(retrievedDate, storedDate);
NSDate *date = [NSDate date];
successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
[self assertStoredHeartbeatDictionaryIsKindOf:[NSMutableDictionary class]];
retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNotNil(retrievedDate);
XCTAssertEqualObjects(retrievedDate, date);
}
#pragma mark - Testing Utilities
- (BOOL)createRootDirectoryWithError:(NSError *__autoreleasing _Nullable *)outError {
NSArray<NSString *> *paths;
#if TARGET_OS_TV
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif // TARGET_OS_TV
NSString *rootPath = [paths lastObject];
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
BOOL rootDirectoryExists = [rootURL checkResourceIsReachableAndReturnError:nil];
if (!rootDirectoryExists) {
rootDirectoryExists = [[NSFileManager defaultManager] createDirectoryAtURL:rootURL
withIntermediateDirectories:YES
attributes:nil
error:outError];
}
return rootDirectoryExists;
}
- (BOOL)explicitlyCreateDirectoryForURL:(NSURL *)directoryPathURL withError:(NSError **)outError {
BOOL success = false;
if (![directoryPathURL checkResourceIsReachableAndReturnError:outError]) {
success = [[NSFileManager defaultManager] createDirectoryAtURL:directoryPathURL
withIntermediateDirectories:YES
attributes:nil
error:outError];
}
return success;
}
- (void)assertInitializationDoesNotAccessFileSystem {
NSURL *fileURL = [self heartbeatFileURL];
NSError *error;
BOOL fileIsReachable = [fileURL checkResourceIsReachableAndReturnError:&error];
XCTAssertFalse(fileIsReachable,
NSString *directoryURL = [[self pathURLForDirectory:kGULHeartbeatStorageDirectory] path];
BOOL isDir;
BOOL directoryIsReachable =
[[NSFileManager defaultManager] fileExistsAtPath:directoryURL isDirectory:&isDir] && isDir;
XCTAssertFalse(directoryIsReachable,
@"The Heartbeat Storage Directory already exists. "
@"GULHeartbeatDateStorage initialization should not access the file system.");
XCTAssertNotNil(error, @"Error: %@", error);
}
- (NSURL *)heartbeatFileURL {
- (NSURL *)pathURLForDirectory:(NSString *)directory {
NSArray<NSString *> *paths;
#if TARGET_OS_TV
NSArray *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
NSArray *path =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif
NSString *rootPath = [path firstObject];
NSArray<NSString *> *components = @[ rootPath, @"Google/FIRApp", kTestFileName ];
NSString *fileString = [NSString pathWithComponents:components];
NSURL *fileURL = [NSURL fileURLWithPath:fileString];
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif // TARGET_OS_TV
NSString *rootPath = [paths lastObject];
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
NSURL *directoryURL = [rootURL URLByAppendingPathComponent:directory];
return directoryURL;
}
- (NSURL *)fileURLForDirectory:(NSURL *)directoryURL {
NSURL *fileURL = [directoryURL URLByAppendingPathComponent:kTestFileName];
return fileURL;
}
- (void)assertStoredHeartbeatDictionaryIsKindOf:(Class)class {
NSURL *heartbeatStorageDirectoryURL = [self pathURLForDirectory:kGULHeartbeatStorageDirectory];
NSURL *heartbeatStorageFileURL = [self fileURLForDirectory:heartbeatStorageDirectoryURL];
NSData *objectData = [NSData dataWithContentsOfURL:heartbeatStorageFileURL options:0 error:nil];
__auto_type objectClasses =
[NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class ]];
NSMutableDictionary *heartbeatDict = [GULSecureCoding unarchivedObjectOfClasses:objectClasses
fromData:objectData
error:nil];
if (class == [NSDictionary class]) {
XCTAssertFalse([heartbeatDict isKindOfClass:[NSMutableDictionary class]]);
}
XCTAssertTrue([heartbeatDict isKindOfClass:class]);
}
@end
@@ -0,0 +1,89 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <XCTest/XCTest.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorageUserDefaults.h"
@interface GULHeartbeatDateStorageUserDefaultsTest : XCTestCase
@property(nonatomic) GULHeartbeatDateStorageUserDefaults *storage;
@property(nonatomic) NSUserDefaults *defaults;
@end
@implementation GULHeartbeatDateStorageUserDefaultsTest
- (void)setUp {
NSString *suiteName = [self userDefaultsSuiteName];
self.defaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];
self.storage = [[GULHeartbeatDateStorageUserDefaults alloc] initWithDefaults:self.defaults
key:@"test_root"];
}
- (void)tearDown {
[self.defaults removePersistentDomainForName:[self userDefaultsSuiteName]];
self.defaults = nil;
self.storage = nil;
}
#pragma mark - Public API Tests
- (void)testHeartbeatDateForTag {
// 1. Tag and save some heartbeat info.
NSDate *storedDate = [NSDate date];
NSString *tag = @"fire-iid";
[self.storage setHearbeatDate:storedDate forTag:tag];
// 2. Retrieve the stored heartbeat info and assert the retrieved info is accurate.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, storedDate);
}
- (void)testHeartbeatDateForTagWhenReturnedDateIsNil {
NSString *nonexistentTag = @"missing-tag";
NSDate *nilDate = [self.storage heartbeatDateForTag:nonexistentTag];
XCTAssertNil(nilDate);
}
- (void)testSetHeartbeatDateForTag {
NSDate *date = [NSDate date];
NSString *tag = @"tag";
// 1. Retrieve heartbeat info that is not stored and assert the retrieved info is nil.
NSDate *retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertNil(retrievedDate);
// 2. Save the heartbeat info and assert the save was successful.
BOOL successfulSave = [self.storage setHearbeatDate:date forTag:tag];
XCTAssert(successfulSave);
// 3. Retrieve heartbeat info that is now stored and assert the retrieved info is accurate.
retrievedDate = [self.storage heartbeatDateForTag:tag];
XCTAssertEqualObjects(retrievedDate, date);
}
- (void)testConformsToHeartbeatStorableProtocol {
XCTAssertTrue([self.storage conformsToProtocol:@protocol(GULHeartbeatDateStorable)]);
}
#pragma mark - Testing Utilities
- (NSString *)userDefaultsSuiteName {
NSCharacterSet *lettersToTrim = [[NSCharacterSet letterCharacterSet] invertedSet];
NSString *nameWithSpaces = [self.name stringByTrimmingCharactersInSet:lettersToTrim];
return [nameWithSpaces stringByReplacingOccurrencesOfString:@" " withString:@"_"];
}
@end
@@ -87,6 +87,8 @@
// 3. Read existing object which is not present in in-memory cache.
[self.cache removeAllObjects];
// TODO: Evaluate if GULKeychainStorage needs an API that takes set of classes. (#42)
// The following method causes an NSKeyedUnarchiver-related runtime warning log.
[self assertSuccessReadObject:@{@"key" : @"value"}
forKey:@"test-key1"
class:[NSDictionary class]
@@ -0,0 +1,51 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// MARK: This file is strictly for version compatibility testing.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage7_3_1 : NSObject
- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL *fileURL;
/**
* Default initializer.
* @param fileName The name of the file to store the date information.
* exist, it will be created if needed.
*/
- (instancetype)initWithFileName:(NSString *)fileName;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,161 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// MARK: This file is strictly for version compatibility testing.
#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.3.1.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
@interface GULHeartbeatDateStorage7_3_1 ()
/** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator;
/** The name of the file that stores heartbeat information. */
@property(nonatomic, readonly) NSString *fileName;
@end
@implementation GULHeartbeatDateStorage7_3_1
@synthesize fileURL = _fileURL;
- (instancetype)initWithFileName:(NSString *)fileName {
if (fileName == nil) {
return nil;
}
self = [super init];
if (self) {
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
_fileName = fileName;
}
return self;
}
/** Lazy getter for fileURL
* @return fileURL where heartbeat information is stored.
*/
- (NSURL *)fileURL {
if (!_fileURL) {
NSURL *directoryURL = [[self class] directoryPathURL];
[[self class] checkAndCreateDirectory:directoryURL fileCoordinator:_fileCoordinator];
_fileURL = [directoryURL URLByAppendingPathComponent:_fileName];
}
return _fileURL;
}
/** Returns the URL path of the directory for heartbeat storage data.
* @return the URL path of the directory for heartbeat storage data.
*/
+ (NSURL *)directoryPathURL {
#if TARGET_OS_TV
NSArray<NSString *> *paths =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
NSArray<NSString *> *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif
NSArray<NSString *> *components = @[ paths.lastObject, @"Google/FIRApp" ];
NSString *directoryString = [NSString pathWithComponents:components];
NSURL *directoryURL = [NSURL fileURLWithPath:directoryString];
return directoryURL;
}
/** Checks and creates a directory for the directory specified by the
* directory url
* @param directoryPathURL The path to the directory which needs to be created.
* @param fileCoordinator The fileCoordinator object to coordinate writes to the directory.
*/
+ (void)checkAndCreateDirectory:(NSURL *)directoryPathURL
fileCoordinator:(NSFileCoordinator *)fileCoordinator {
NSError *fileCoordinatorError = nil;
[fileCoordinator
coordinateWritingItemAtURL:directoryPathURL
options:0
error:&fileCoordinatorError
byAccessor:^(NSURL *writingDirectoryURL) {
NSError *error;
if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
// If fail creating the Application Support directory, log warning.
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
}];
}
- (nullable NSMutableDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL {
NSError *error;
NSMutableDictionary *dict;
NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error];
if (objectData == nil || error != nil) {
dict = [NSMutableDictionary dictionary];
} else {
dict = [GULSecureCoding
unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]]
fromData:objectData
error:&error];
if (dict == nil || error != nil) {
dict = [NSMutableDictionary dictionary];
}
}
return dict;
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
__block NSMutableDictionary *dict;
NSError *error;
[self.fileCoordinator coordinateReadingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL) {
dict = [self heartbeatDictionaryWithFileURL:readingURL];
}];
return dict[tag];
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
NSError *error;
__block BOOL isSuccess = false;
[self.fileCoordinator coordinateReadingItemAtURL:self.fileURL
options:0
writingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL, NSURL *writingURL) {
NSMutableDictionary *dictionary =
[self heartbeatDictionaryWithFileURL:readingURL];
dictionary[tag] = date;
NSError *error;
isSuccess = [self writeDictionary:dictionary
forWritingURL:writingURL
error:&error];
}];
return isSuccess;
}
- (BOOL)writeDictionary:(NSMutableDictionary *)dictionary
forWritingURL:(NSURL *)writingFileURL
error:(NSError **)outError {
NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError];
if (*outError != nil) {
return false;
} else {
return [data writeToURL:writingFileURL atomically:YES];
}
}
@end
@@ -0,0 +1,55 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// MARK: This file is strictly for version compatibility testing.
#import <Foundation/Foundation.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULHeartbeatDateStorable.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const kGULHeartbeatStorageDirectory7_4_0; // Avoids duplicate linker error.
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage7_4_0 : NSObject <GULHeartbeatDateStorable>
- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL *fileURL;
/**
* Default initializer.
* @param fileName The name of the file to store the date information.
* exist, it will be created if needed.
*/
- (instancetype)initWithFileName:(NSString *)fileName;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,162 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// MARK: This file is strictly for version compatibility testing.
#import "GoogleUtilities/Tests/Unit/Environment/Sources/GULHeartbeatDateStorageV7.4.0.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"
// Add static to avoid linker error.
NSString *const kGULHeartbeatStorageDirectory7_4_0 = @"Google/FIRApp";
@interface GULHeartbeatDateStorage7_4_0 ()
/** The storage to store the date of the last sent heartbeat. */
@property(nonatomic, readonly) NSFileCoordinator *fileCoordinator;
/** The name of the file that stores heartbeat information. */
@property(nonatomic, readonly) NSString *fileName;
@end
@implementation GULHeartbeatDateStorage7_4_0
@synthesize fileURL = _fileURL;
- (instancetype)initWithFileName:(NSString *)fileName {
if (fileName == nil) return nil;
self = [super init];
if (self) {
_fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
_fileName = fileName;
}
return self;
}
/** Lazy getter for fileURL.
* @return fileURL where heartbeat information is stored.
*/
- (NSURL *)fileURL {
if (!_fileURL) {
NSURL *directoryURL = [self directoryPathURL];
[self checkAndCreateDirectory:directoryURL];
_fileURL = [directoryURL URLByAppendingPathComponent:_fileName];
}
return _fileURL;
}
/** Returns the URL path of the directory for heartbeat storage data.
* @return the URL path of the directory for heartbeat storage data.
*/
- (NSURL *)directoryPathURL {
NSArray<NSString *> *paths;
#if TARGET_OS_TV
paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif // TARGET_OS_TV
NSString *rootPath = [paths lastObject];
NSURL *rootURL = [NSURL fileURLWithPath:rootPath];
NSURL *directoryURL = [rootURL URLByAppendingPathComponent:kGULHeartbeatStorageDirectory7_4_0];
return directoryURL;
}
/** Check for the existence of the directory specified by the URL, and create it if it does not
* exist.
* @param directoryPathURL The path to the directory that needs to exist.
*/
- (void)checkAndCreateDirectory:(NSURL *)directoryPathURL {
NSError *fileCoordinatorError = nil;
[self.fileCoordinator
coordinateWritingItemAtURL:directoryPathURL
options:0
error:&fileCoordinatorError
byAccessor:^(NSURL *writingDirectoryURL) {
NSError *error;
if (![writingDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtURL:writingDirectoryURL
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
}];
}
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag {
__block NSDictionary *heartbeatDictionary;
NSError *error;
[self.fileCoordinator coordinateReadingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL) {
heartbeatDictionary =
[self heartbeatDictionaryWithFileURL:readingURL];
}];
return heartbeatDictionary[tag];
}
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag {
NSError *error;
__block BOOL isSuccess = false;
[self.fileCoordinator
coordinateReadingItemAtURL:self.fileURL
options:0
writingItemAtURL:self.fileURL
options:0
error:&error
byAccessor:^(NSURL *readingURL, NSURL *writingURL) {
NSMutableDictionary *heartbeatDictionary =
[[self heartbeatDictionaryWithFileURL:readingURL] mutableCopy];
heartbeatDictionary[tag] = date;
NSError *error;
isSuccess = [self writeDictionary:heartbeatDictionary.copy
forWritingURL:writingURL
error:&error];
}];
return isSuccess;
}
- (NSDictionary *)heartbeatDictionaryWithFileURL:(NSURL *)readingFileURL {
NSDictionary *heartbeatDictionary = nil;
NSError *error;
NSData *objectData = [NSData dataWithContentsOfURL:readingFileURL options:0 error:&error];
if (objectData.length > 0 && error == nil) {
NSSet<Class> *objectClasses = [NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]];
heartbeatDictionary = [GULSecureCoding unarchivedObjectOfClasses:objectClasses
fromData:objectData
error:&error];
}
if (heartbeatDictionary.count == 0 || error != nil) {
heartbeatDictionary = [NSDictionary dictionary];
}
return heartbeatDictionary;
}
- (BOOL)writeDictionary:(NSDictionary *)dictionary
forWritingURL:(NSURL *)writingFileURL
error:(NSError **)outError {
NSData *data = [GULSecureCoding archivedDataWithRootObject:dictionary error:outError];
if (data.length == 0) {
return NO;
}
return [data writeToURL:writingFileURL atomically:YES];
}
@end
@@ -62,7 +62,7 @@
#import <CFNetwork/CFNetwork.h>
#endif // GTM_IPHONE_SDK
// Global contants needed for errors from start
// Global constants needed for errors from start
#undef _EXTERN
#undef _INITIALIZE_AS
@@ -87,7 +87,7 @@ enum {
// ----------------------------------------------------------------------------
// See comment at top of file for the intened use of this class.
// See comment at top of file for the intended use of this class.
@interface GTMHTTPServer : NSObject {
@private
id delegate_; // WEAK
@@ -37,7 +37,7 @@
@implementation GULSecureCodingTests
- (void)testArchiveUnarchiveSingleClass {
NSDictionary *objectToArchive = @{@"key1" : @"value1", @"key2" : @(2)};
NSDictionary *objectToArchive = @{};
NSError *error;
NSData *archiveData = [GULSecureCoding archivedDataWithRootObject:objectToArchive error:&error];
@@ -60,7 +60,8 @@
XCTAssertNotNil(archiveData);
NSDictionary *unarchivedObject = [GULSecureCoding
unarchivedObjectOfClasses:[NSSet setWithArray:@[ NSDictionary.class, NSDate.class ]]
unarchivedObjectOfClasses:
[NSSet setWithArray:@[ NSDictionary.class, NSDate.class, NSString.class, NSNumber.class ]]
fromData:archiveData
error:&error];
XCTAssertNil(error);
@@ -562,7 +562,7 @@ static BOOL gRespondsToHandleBackgroundSession;
XCTAssertEqualObjects(descriptionAfter, descriptionString);
}
/** Tests that methods that are not overriden by the App Delegate Proxy still work as expected. */
/** Tests that methods that are not overridden by the App Delegate Proxy still work as expected. */
- (void)testNotOverriddenMethods {
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
@@ -587,7 +587,7 @@ static BOOL gRespondsToHandleBackgroundSession;
* handles it correctly.
*/
- (void)testAppDelegateInstance {
// The test logic involves using KVC on the UIApplication.delegate propery. This does not really
// The test logic involves using KVC on the UIApplication.delegate property. This does not really
// work well with OCMPartialMock([GULApplication sharedApplication]) and triggers issue
// https://github.com/erikdoe/ocmock/issues/346.
// Let's stop mocking the shared application for this particular test.
@@ -776,12 +776,12 @@ static BOOL gRespondsToHandleBackgroundSession;
[GULAppDelegateSwizzler proxyOriginalDelegate];
NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
BOOL shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
BOOL shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
continueUserActivity:testUserActivity
restorationHandler:^(NSArray *restorableObjects){
}];
// Verify that it is NO when there are no interceptors.
XCTAssertFalse(shouldContinueUserActvitiy);
XCTAssertFalse(shouldContinueUserActivity);
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
OCMExpect([interceptor application:OCMOCK_ANY
@@ -789,12 +789,12 @@ static BOOL gRespondsToHandleBackgroundSession;
restorationHandler:OCMOCK_ANY])
.andReturn(NO);
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
continueUserActivity:testUserActivity
restorationHandler:^(NSArray *restorableObjects){
}];
// Verify that it is NO when the only interceptor returns a NO.
XCTAssertFalse(shouldContinueUserActvitiy);
XCTAssertFalse(shouldContinueUserActivity);
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
OCMExpect([interceptor2 application:OCMOCK_ANY
@@ -807,13 +807,13 @@ static BOOL gRespondsToHandleBackgroundSession;
continueUserActivity:OCMOCK_ANY
restorationHandler:OCMOCK_ANY])
.andReturn(NO);
shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
continueUserActivity:testUserActivity
restorationHandler:^(NSArray *restorableObjects){
}];
// The result is YES if one of the interceptors returns YES.
XCTAssertTrue(shouldContinueUserActvitiy);
XCTAssertTrue(shouldContinueUserActivity);
}
- (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors {
@@ -15,7 +15,7 @@
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULObjectSwizzler.h"
#import "GoogleUtilities/ISASwizzler/GULObjectSwizzler+Internal.h"
#import "GoogleUtilities/ISASwizzler/Public/GoogleUtilities/GULSwizzledObject.h"
#import "GoogleUtilities/SwizzlerTestHelpers/GULProxy.h"
@@ -390,7 +390,7 @@
// Release GULObjectSwizzler
[GULObjectSwizzler setAssociatedObject:object
key:kSwizzlerAssociatedObjectKey
key:kGULSwizzlerAssociatedObjectKey
value:nil
association:GUL_ASSOCIATION_RETAIN];
@@ -434,7 +434,7 @@
// Release GULObjectSwizzler
[GULObjectSwizzler setAssociatedObject:object
key:kSwizzlerAssociatedObjectKey
key:kGULSwizzlerAssociatedObjectKey
value:nil
association:GUL_ASSOCIATION_RETAIN];
@@ -126,7 +126,7 @@
SEL swizzledSelector = @selector(description);
// Observe that swizzling the superclass IMP propogates to its subclasses when we haven't yet
// Observe that swizzling the superclass IMP propagates to its subclasses when we haven't yet
// swizzled and unsiwzzled the subclasses.
[GULSwizzler swizzleClass:[PollutedTestObject class]
selector:swizzledSelector
@@ -410,7 +410,7 @@
selector:testSelector2
isClassSelector:NO];
XCTAssertNotEqual(origImp2, NULL, @"Original IMP is NULL after swizzle.");
XCTAssertNotEqual(origImp, origImp2, @"Implementations are the same when they should't be.");
XCTAssertNotEqual(origImp, origImp2, @"Implementations are the same when they shouldn't be.");
[GULSwizzler unswizzleClass:testClass selector:testSelector isClassSelector:NO];
[GULSwizzler unswizzleClass:testClass2 selector:testSelector2 isClassSelector:NO];
@@ -149,7 +149,7 @@
XCTAssert([[GULSwizzlingCache sharedInstance] cachedIMPForClass:swizzledClass
withSelector:swizzledSelector] != NULL);
XCTAssertEqual([GULSwizzlingCache originalIMPOfCurrentIMP:newIMP], originalIMP,
@"New to original IMP cache was not correctly poplated.");
@"New to original IMP cache was not correctly populated.");
[[GULSwizzlingCache sharedInstance] clearCacheForSwizzledIMP:newIMP
selector:swizzledSelector
+3 -5
View File
@@ -19,7 +19,7 @@ import PackageDescription
let package = Package(
name: "GoogleUtilities",
platforms: [.iOS(.v10), .macOS(.v10_12), .tvOS(.v10), .watchOS(.v6)],
platforms: [.iOS(.v9), .macOS(.v10_12), .tvOS(.v10), .watchOS(.v6)],
products: [
.library(
name: "GULAppDelegateSwizzler",
@@ -63,7 +63,7 @@ let package = Package(
),
],
dependencies: [
.package(name: "Promises", url: "https://github.com/google/promises.git", "1.2.8" ..< "1.3.0"),
.package(name: "Promises", url: "https://github.com/google/promises.git", "1.2.8" ..< "3.0.0"),
.package(
name: "OCMock",
url: "https://github.com/firebase/ocmock.git",
@@ -79,7 +79,6 @@ let package = Package(
"GoogleUtilities_Network"],
path: "GoogleUtilities",
exclude: [
"README.md",
"AppDelegateSwizzler/README.md",
"Environment/",
"Network/",
@@ -94,8 +93,7 @@ let package = Package(
],
sources: [
"AppDelegateSwizzler/",
"SceneDelegateSwizzler/",
"Common/*.h",
"Common/",
],
publicHeadersPath: "AppDelegateSwizzler/Public",
cSettings: [
+106 -15
View File
@@ -16,22 +16,113 @@ 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 GoogleUtilities.podspec`
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`
* On google3, copybara and run a global TAP
* `third_party/firebase/ios/Releases/run_copy_bara.py --directory GoogleUtilities`
* Determine the next version for release by checking the
[tagged releases](https://github.com/google/GoogleUtilities/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
git checkout main
git pull
```
* Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleUtilities.podspec#L3))
```console
git tag CocoaPods-{version}
git push origin CocoaPods-{version}
```
* Push the podspec to the designated repo
* If this version of GoogleUtilities is intended to launch **before or with** the next Firebase release:
<details>
<summary>Push to <b>SpecsStaging</b></summary>
```console
pod repo push --skip-tests staging GoogleUtilities.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
```
</details>
* Otherwise:
<details>
<summary>Push to <b>SpecsDev</b></summary>
```console
pod repo push --skip-tests dev GoogleUtilities.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
```
</details>
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`.
* On google3, run copybara using the command below. Then, start a global TAP on the generated CL. Deflake as needed.
```console
third_party/firebase/ios/Releases/run_copy_bara.py --directory GoogleUtilities --branch main
```
## 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/GoogleUtilities/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 GoogleUtilities.podspec`
* Clean up SpecsStaging
```console
git tag {version}
git push origin {version}
```
*Note: Ensure that any inflight PRs that depend on the new `GoogleUtilities` version are updated to point to the
newly tagged version rather than a checksum.*
### CocoaPods
* Publish the newly versioned pod to CocoaPods
It's recommended to point to the `GoogleUtilities.podspec` in `staging` to make sure the correct spec is being published.
```console
pod trunk push ~/.cocoapods/repos/staging/GoogleUtilities/{version}/GoogleUtilities.podspec
```
*Note: In some cases, it may be acceptable to `pod trunk push` with the `--skip-tests` flag. Please double check with
the maintainers before doing so.*
The pod push was successful if the above command logs: `🚀 GoogleUtilities ({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 [GoogleUtilities's CocoaPods page](https://cocoapods.org/pods/GoogleUtilities).
### [Create GitHub Release](https://github.com/google/GoogleUtilities/releases/new/)
Update the [release template](https://github.com/google/GoogleUtilities/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/GoogleUtilities/releases/edit/9.0.1) for an example.
*Don't forget to perform the [post release cleanup](#post-release-cleanup)!*
### Post Release Cleanup
<details>
<summary>Clean up <b>SpecsStaging</b></summary>
```console
pwd=$(pwd)
mkdir -p /tmp/release-cleanup && cd $_
git clone git@github.com:firebase/SpecsStaging.git
cd SpecsStaging/
git rm -rf GoogleUtilities/
git commit -m "Post publish cleanup"
git push origin master
rm -rf /tmp/release-cleanup
cd $pwd
```
</details>
## Development
@@ -76,8 +167,8 @@ before creating a PR.
GitHub Actions will verify that any code changes are done in a style compliant
way. Install `clang-format` and `mint`:
```
brew install clang-format@11
```console
brew install clang-format@12
brew install mint
```